From dc1bf7bc478f2aebab5466a239341d270966db38 Mon Sep 17 00:00:00 2001 From: TwinDad Date: Wed, 25 Jan 2017 14:58:40 -0500 Subject: [PATCH 01/21] Add WiThrottle class Add Inglenook Game class --- DCCpp_Uno/WiThrottle.cpp | 312 +++++++++++++++++++++++++++++++ DCCpp_Uno/WiThrottle.hpp | 37 ++++ Inglenook.cpp | 386 +++++++++++++++++++++++++++++++++++++++ Inglenook.h | 59 ++++++ 4 files changed, 794 insertions(+) create mode 100644 DCCpp_Uno/WiThrottle.cpp create mode 100644 DCCpp_Uno/WiThrottle.hpp create mode 100644 Inglenook.cpp create mode 100644 Inglenook.h diff --git a/DCCpp_Uno/WiThrottle.cpp b/DCCpp_Uno/WiThrottle.cpp new file mode 100644 index 0000000..449db13 --- /dev/null +++ b/DCCpp_Uno/WiThrottle.cpp @@ -0,0 +1,312 @@ +/********************************************************************** + +WiThrottle.cpp +COPYRIGHT (c) 2017 Mark S. Underwood + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#include +#include +#include "DCCpp_Uno.h" +#include "Comm.h" +#include "SerialCommand.h" +#include "PacketRegister.h" +#include "WiThrottle.hpp" + +#define FORCED_REGISTER_NUMBER 1 + +extern RegisterList mainRegs; + +static char command[MAX_COMMAND_LENGTH+1]; +static int address = 3; + +static const byte byte1FuncOnVals[29] = { 144, 129, 130, 132, 136, + 177, 178, 180, 184, + 161, 162, 164, 168, + 222, 222, 222, 222, + 222, 222, 222, 222, + 223, 223, 223, 223, + 223, 223, 223, 223 }; + +static const byte byte1FuncOffVals[29] = { 128, 128, 128, 128, 128, + 176, 176, 176, 176, + 160, 160, 160, 160, + 222, 222, 222, 222, + 222, 222, 222, 222, + 223, 223, 223, 223, + 223, 223, 223, 223 }; + +static const byte byte2FuncOnVals[29] = { 1, 2, 4, 8, + 16, 32, 64, 128, + 1, 2, 4, 8, + 16, 32, 64, 128 }; + + +static void WiThrottle::readCommand(char c) { + char x; + sprintf(command, "%c", c); + +#if COMM_TYPE == 0 + + // Read all the bytes until we encounter a newline, end-of-string, or + // run out of bytes. + while(INTERFACE.available() > 0) { + x = INTERFACE.read(); + Serial.print(x); + if (x == '\n' || x == '\0') { + sprintf(command,"%s%c",command, x); + Serial.println("Received"); + parseToDCCpp(command); + } else if (strlen(command) < MAX_COMMAND_LENGTH) { + sprintf(command, "%s%c", command, x); + } + } + +#elif COMM_TYPE == 1 + + // Connect to the source + EthernetClient client = INTERFACE.available(); + + // Read all the bytes until we encounter a newline, end-of-string, or + // run out of bytes. + if (client) { + while (client.connected() && client.available()) { + x = client.read(); + if (x == '\n' || x == '\0') { + sprintf(command,"%s%c",command, x); + parseToDCCpp(command); + } else if (strlen(command) < MAX_COMMAND_LENGTH) { + sprintf(command, "%s%c", command, x); + } + } + } +#endif +} + +static void WiThrottle::parseToDCCpp(char *s) { + // Do something :) + Serial.println(s); + switch (s[0]) { + case 'T': // Throttle + case 'S': // Second Throttle + doThrottleCommand(NULL, s+1); + break; + case 'M': // Multi-Throttle + parseMCommand(s); + break; + case 'C': // Old "T" command + if (s[1] == 'T') { + doThrottleCommand(NULL, s+2); + } + break; + case 'N': // Name of throttle + parseNCommand(s); + break; + case 'H': // Hardware (get device UUID) + parseHCommand(s); + break; + case '*': // Heartbeat + case 'D': // Direct hex packet + case 'P': // Panel stuff + case 'R': // Roster stuff + case 'Q': // Quit + break; + default: + return(s); + } +} + +static bool WiThrottle::isWTCommand(char c) { + switch (c) { + case 'T': // Throttle + case 'S': // Second Throttle + case 'M': // Multi-Throttle + case 'D': // Direct hex packet + case '*': // Heartbeat + case 'C': // Old 'T' command + case 'N': // Name + case 'H': // Hardware + case 'P': // Panel + case 'R': // Roster + case 'Q': // Quit + Serial.print(c); + Serial.println(" is a WiThrottle cmd"); + return(true); + default: + return(false); + } +} + +static void WiThrottle::parseHCommand(char *s) { + switch(s[1]) { + case 'U': + // get device UDID and do something with it. + return; + default: + return; + } +} + +static void WiThrottle::parseNCommand(char *s) { + // Get the Name and store it somewhere... if needed. + Serial.print("Name = "); + Serial.println(String(s)); + return; +} + +static void WiThrottle::parseMCommand(char *s) { + char *key, *action; + switch(s[1]) { + case 'A': + key = strtok(s, "<;>"); + action = strtok(NULL, "<;>"); + Serial.println(key); + Serial.println(action); + doThrottleCommand(key, action); + case '+': + case '-': + default: + return; + } +} + +static void WiThrottle::doThrottleCommand(char *key, char *action) { + int reg, dir, spd, f; + byte byte1, byte2; + // TODO: When supporting multiple throttles, KEY will tell us which + // throttle to do the action on. + switch(action[0]) { + case 'V': // Velocity + // DCC++ Format: + // DCC++ Returns: + if (address < 0) { return; } + reg = getRegisterForCab(address); + dir = getDirForCab(address); + sprintf(command, "t %d %d %s %d", reg, address, (action+1), dir ); + SerialCommand::parse(command); + break; + + case 'X': // E-Stop + // DCC++ Format: with SPEED = -1 + // DCC++ Returns: + if (address < 0) { return; } + reg = getRegisterForCab(address); + dir = getDirForCab(address); + sprintf(command, "t %d %d -1 %d", reg, address, (action+1), dir); + SerialCommand::parse(command); + break; + + case 'F': // Function + case 'f': // force function (v>=2.0) + // DCC++ Format: + // DCC++ Returns: (none) + f = strtol((action+2), NULL,10); + if (f == 0 || f > 28) { + // Invalid conversion + break; + } + byte1 = getFuncByte1((action[1] == '1'), f); + byte2 = getFuncByte2((action[1] == '1'), f); + if (byte2 == 255) { + sprintf(command, "f %d %d", address, byte1); + } else { + sprintf(command, "f %d %d %d", address, byte1, byte2); + } + SerialCommand::parse(command); + break; + + case 'R': // Direction + // DCC++ Format: + // DCC++ Returns: + if (address < 0) { return; } + reg = getRegisterForCab(address); + spd = getSpeedForCab(address); + sprintf(command, "t %d %d %d %c", reg, address, spd, + (action[1] == '0' ? '0' : '1')); + Serial.print("cmd = " + String(command)); + SerialCommand::parse(command); + break; + + case 'I': // Idle + // DCC++ Format: + // DCC++ Returns: + if (address < 0) { return; } + reg = getRegisterForCab(address); + dir = getDirForCab(address); + sprintf(command, "t %d %d 0 %d", reg, address, dir); + SerialCommand::parse(command); + break; + + case 'r': // Release + case 'd': // Dispatch + address = 0; + break; + + case 'L': // set long address + case 'S': // set short address + address = strtol((action+1), NULL, 10); + break; + + case 'q': // request (v>=2.0) + handleRequest(action); + break; + + case 'E': // set address from roster (v>=1.7) + case 'C': // consist + case 'c': // consist lead from roster (v>=1.7) + case 's': // speed step mode (v>= 2.0) + case 'm': // momentary (v>=2.0) + case 'Q': // quit + default: + return; + } +} + +byte WiThrottle::getFuncByte1(bool t, int f) { + if (t) { + return(byte1FuncOnVals[f]); + } else { + return(byte1FuncOffVals[f]); + } +} + +byte WiThrottle::getFuncByte2(bool t, int f) { + if (f < 13) { + return(255); + } else if (t) { + return(byte2FuncOnVals[f-13]); + } else { + return(0); + } +} + +void WiThrottle::handleRequest(char *s) { + // TODO: Handle Requests + return; +} + +int WiThrottle::getRegisterForCab(int c) { + return(FORCED_REGISTER_NUMBER); +} + +int WiThrottle::getSpeedForCab(int c) { + int spd = mainRegs.speedTable[FORCED_REGISTER_NUMBER]; + if (spd < 0) { spd = -spd; } + return(spd); +} + +int WiThrottle::getDirForCab(int c) { + int spd = mainRegs.speedTable[FORCED_REGISTER_NUMBER]; + return(spd >= 0 ? 1 : 0); +} + +void WiThrottle::sendIntroMessage(void) { + INTERFACE.println("VN2.0"); + // TODO: Send roster here +#if COMM_TYPE == 1 + INTERFACE.print("PW"); + INTERFACE.println(ETHERNET_PORT); +#endif +} diff --git a/DCCpp_Uno/WiThrottle.hpp b/DCCpp_Uno/WiThrottle.hpp new file mode 100644 index 0000000..d39ca48 --- /dev/null +++ b/DCCpp_Uno/WiThrottle.hpp @@ -0,0 +1,37 @@ +/********************************************************************** + +WiThrottle.h +COPYRIGHT (c) 2017 Mark S. Underwood + +Part of DCC++ BASE STATION for the Arduino + +**********************************************************************/ + +#ifndef WITHROTTLE_H +#define WITHROTTLE_H + + +class WiThrottle { +private: + //static char command[MAX_COMMAND_LENGTH+1]; + //static int address; // someday will be an array? + +protected: + static void parseHCommand(char *s); + static void parseNCommand(char *s); + static void parseMCommand(char *s); + static void doThrottleCommand(char *key, char *action); + static byte getFuncByte1(bool t, int f); + static byte getFuncByte2(bool t, int f); + static int getRegisterForCab(int c); + static int getSpeedForCab(int c); + static int getDirForCab(int c); + static void handleRequest(char *s); +public: + static void parseToDCCpp(char *s); + static bool isWTCommand(char c); + static void readCommand(char c); + static void sendIntroMessage(void); +}; + +#endif // WITHROTTLE_H diff --git a/Inglenook.cpp b/Inglenook.cpp new file mode 100644 index 0000000..f698194 --- /dev/null +++ b/Inglenook.cpp @@ -0,0 +1,386 @@ +#include +#include +#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) +#include +#include +#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) +#include +#include // is this necessary? +#else +#error CANNOT COMPILE -- INVALID LCD LIBRARY SELECTED +#endif + +#include "Inglenook.h" + +// Define generic button names for multi-library compatibility +#define KEYS_NONE -1 +#define KEYS_RIGHT 0 +#define KEYS_UP 1 +#define KEYS_DOWN 2 +#define KEYS_LEFT 3 +#define KEYS_SELECT 4 + +// Game State Machine States +#define STATE_IDLE 0 +#define STATE_MENUS 1 +#define STATE_ACTION 2 +#define STATE_BUILD 3 +int game_state = STATE_MENUS; + +// Index used for displaying the cars in the Build Train state. +int car_index; + +// Array holding the current sorting of the train to be built. +int train[TRAIN_LENGTH]; + +// Menu subsystem "stuff" +void menuUseEvent(MenuUseEvent e); +void menuChangeEvent(MenuChangeEvent e); +MenuBackend *menu = new MenuBackend(menuUseEvent, menuChangeEvent); + +#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) +LCDKeypad lcd = LCDKeypad(); +#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) +Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield(); +#endif + +static InglenookGame *thegame = NULL; + + +static InglenookGame *InglenookGame::getTheGame() { + if (thegame == NULL) { + thegame = new InglenookGame(); + } + return(thegame); +} + +InglenookGame::InglenookGame() { + //lcd = LCDKeypad(); + menuSetup(); +} + +void InglenookGame::menuSetup() { + //Serial.println("Setting up menu..."); + MenuItem *miBuild = new MenuItem("Build Train"); + MenuItem *miList = new MenuItem("List Cars"); + MenuItem *miCarList[7] = { + new MenuItem("Car 0"), + new MenuItem("Car 1"), + new MenuItem("Car 2"), + new MenuItem("Car 3"), + new MenuItem("Car 4"), + new MenuItem("Car 5"), + new MenuItem("Car 6") + }; + menu->getRoot().add(*miBuild); + miBuild->add(*miList); + miList->addRight(*miCarList[0]); + for (int i = 0; i < NUM_CARS-2; i++) { + miCarList[i]->add(*miCarList[i+1]); + } +} + +static void InglenookGame::begin() { + randomSeed(analogRead(44)); + menuSetup(); + lcd.begin(16, 2); + //Serial.println("lcd begun"); + printWelcome(); + game_state = STATE_IDLE; + car_index = -1; +} + +void InglenookGame::printWelcome() { + lcd.clear(); + //Serial.println("lcd clear"); + lcd.setCursor(0, 0); + //Serial.println("lcd setcursor"); + lcd.print("INGLENOOK GAME"); + lcd.setCursor(0,1); + lcd.print("Select to start"); +} + +void InglenookGame::play() { + // Read and respond to the buttons + int buttons = checkButtons(); + switch(game_state) { + case STATE_IDLE: + if (buttons == KEYS_SELECT) { + //Serial.println("Starting game..."); + game_state = STATE_MENUS; + menu->moveDown(); + //menu.moveDown(); + doMenuDisplay(); + } + break; + + + case STATE_MENUS: + switch(buttons) { + case KEYS_UP: + if (menu->getCurrent() == "Build Trains") { + game_state == STATE_IDLE; + printWelcome(); + } else { + menu->moveUp(); + doMenuDisplay(); + } + break; + case KEYS_DOWN: + menu->moveDown(); + doMenuDisplay(); + break; + case KEYS_LEFT: + //menu.moveBack(); + if (menu->getCurrent() == "Build Trains") { + game_state == STATE_IDLE; + printWelcome(); + } else { + menu->moveLeft(); + doMenuDisplay(); + } + break; + case KEYS_RIGHT: + menu->moveRight(); + doMenuDisplay(); + break; + case KEYS_SELECT: + if (menu->getCurrent() == "List Cars") { + menu->moveRight(); // for this one "use" == "move right" + doMenuDisplay(); + } else { + menu->use(); + } + break; + // Add other combinations here. + } // switch(buttons) + break; + + case STATE_BUILD: + switch(buttons) { + case KEYS_NONE: + break; + case KEYS_LEFT: + case KEYS_SELECT: + game_state = STATE_MENUS; + //menu.moveUp(); + doMenuDisplay(); + break; + case KEYS_DOWN: + case KEYS_RIGHT: + if (car_index >= 3) { + car_index = 3; + } else { + car_index++; + } + doListTrain(car_index); + break; + case KEYS_UP: + if (car_index > -1) { + car_index--; + } + if (car_index == -1) { + doDisplayTrain(); + } else { + doListTrain(car_index); + } + break; + } // switch(buttons) + } // switch(game_state) +} + + +int InglenookGame::checkButtons() { +#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) + // OSEPP LCDKeypad + int buttons = lcd.button(); + if (buttons != -1) { + debounceButton(buttons); + //Serial.print("Button Check = "); + //Serial.println(buttons); + return(buttons); + } else { + return(KEYS_NONE); + } +#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) + // ADAFRUIT RGBLCD + uint8_t buttons = lcd.readButtons(); + if (buttons & BUTTON_UP) { + return(KEYS_UP); + } + if (buttons & BUTTON_DOWN) { + return(KEYS_DOWN); + } + if (buttons & BUTTON_LEFT) { + return(KEYS_LEFT); + } + if (buttons & BUTTON_RIGHT) { + return(KEYS_RIGHT); + } + if (buttons & BUTTON_SELECT) { + return(KEYS_SELECT); + } + return(KEYS_NONE); +#endif +} + +int InglenookGame::debounceButton(int button) { + if (button != KEYS_NONE) { + while(lcd.button() != KEYS_NONE) { + ; + } + } +} + +void InglenookGame::buildTrain() { + bool cars_used[NUM_CARS] = { false, false, false, false, false, false, false, false }; + int car = 0; + bool found_one = false; + game_state = STATE_BUILD; + for (int i = 0; i < TRAIN_LENGTH; i++) { + found_one = false; + do { + car = random(0, NUM_CARS) & 0xFFFF; + //Serial.println("found " + String(car)); + if (cars_used[car] == false) { + // Car is not used. Use it. + cars_used[car] = true; + train[i] = car; + found_one = true; + //Serial.println("using " + String(car)); + } // if + } while (!found_one); + //Serial.println("i = " + String(i) + " car = " + String(car)); + } // for +} + +void InglenookGame::doMenuDisplay() { + lcd.clear(); + if (menu->getCurrent() == "Build Train") { + lcd.setCursor(0,1); lcd.print("List Cars"); + lcd.setCursor(0,0); lcd.print("Build Train"); + lcd.setCursor(0,0); + } + if (menu->getCurrent() == "List Cars") { + lcd.setCursor(0,1); lcd.print(""); + lcd.setCursor(0,0); lcd.print("List Cars"); + lcd.setCursor(0,0); + } + if (menu->getCurrent() == "Car 0") { + lcd.setCursor(0,0); lcd.print("1:" + carnames[0]); + lcd.setCursor(0,1); lcd.print("2:" + carnames[1]); + lcd.setCursor(0,0); + } + if (menu->getCurrent() == "Car 1") { + lcd.setCursor(0,0); lcd.print("2:" + carnames[1]); + lcd.setCursor(0,1); lcd.print("3:" + carnames[2]); + lcd.setCursor(0,0); + } + if (menu->getCurrent() == "Car 2") { + lcd.setCursor(0,0); lcd.print("3:" + carnames[2]); + lcd.setCursor(0,1); lcd.print("4:" + carnames[3]); + lcd.setCursor(0,0); + } + if (menu->getCurrent() == "Car 3") { + lcd.setCursor(0,0); lcd.print("4:" + carnames[3]); + lcd.setCursor(0,1); lcd.print("5:" + carnames[4]); + lcd.setCursor(0,0); + } + if (menu->getCurrent() == "Car 4") { + lcd.setCursor(0,0); lcd.print("5:" + carnames[4]); + lcd.setCursor(0,1); lcd.print("6:" + carnames[5]); + lcd.setCursor(0,0); + } + if (menu->getCurrent() == "Car 5") { + lcd.setCursor(0,0); lcd.print("6:" + carnames[5]); + lcd.setCursor(0,1); lcd.print("7:" + carnames[6]); + lcd.setCursor(0,0); + } + if ((menu->getCurrent() == "Car 6") || (menu->getCurrent() == "Car 7")) { + lcd.setCursor(0,0); lcd.print("7:" + carnames[6]); + lcd.setCursor(0,1); lcd.print("8:" + carnames[7]); + lcd.setCursor(0,0); + } + lcd.setCursor(0,0); + lcd.blink(); +} + +void InglenookGame::doDisplayTrain() { + // DEBUG: + //Serial.println("BUILD THIS TRAIN"); + for (int i = 0; i < TRAIN_LENGTH; i++) { + //Serial.print(String(i) + ": " + carnames[train[i]] + "; "); + } + //Serial.println(""); + // END DEBUG + lcd.clear(); + //lcd.autoscroll(); + lcd.setCursor(0,0); + lcd.print("BUILD THIS TRAIN"); + lcd.setCursor(0,1); + for (int i = 0; i < TRAIN_LENGTH; i++) { + lcd.print(String(train[i]) + " "); + } + lcd.noBlink(); +} + +void InglenookGame::doListTrain(int car) { + String s1, s2; + switch(car) { + case 1: + s1 = "2:" + carnames[train[1]]; + s2 = "3:" + carnames[train[2]]; + break; + case 2: + s1 = "3:" + carnames[train[2]]; + s2 = "4:" + carnames[train[3]]; + break; + case 3: + s1 = "4:" + carnames[train[3]]; + s2 = "5:" + carnames[train[4]]; + break; + case 0: + default: + s1 = "1:" + carnames[train[0]]; + s2 = "2:" + carnames[train[1]]; + break; + } // switch(car) + lcd.clear(); + lcd.setCursor(0,0); + lcd.print(s1); + lcd.setCursor(0,1); + lcd.print(s2); +} + +void menuChangeEvent(MenuChangeEvent changed) { + // Update the display to reflect the current menu state + // For now we'll use serial output. + //Serial.print("Menu change "); + //Serial.print(changed.from.getName()); + //Serial.print(" -> "); + //Serial.println(changed.to.getName()); +} + +void menuUseEvent(MenuUseEvent used) { + InglenookGame *thegame = InglenookGame::getTheGame(); + //Serial.print("Menu use "); + //Serial.println(used.item.getName()); + if (used.item == "Build Train") { + //Serial.println("Building Train..."); + thegame->buildTrain(); + thegame->doDisplayTrain(); + /* + for (int i = 0; i < TRAIN_LENGTH; i++) { + Serial.println("Car " + String(i) + ": " + carnames[train[i]]); + } + */ + } + if (used.item == "List Cars") { + //Serial.println("List Cars..."); + for (int i = 0; i < NUM_CARS; i++) { + //Serial.println("Car " + String(i) + ": " + carnames[i]); + } + } +} + + diff --git a/Inglenook.h b/Inglenook.h new file mode 100644 index 0000000..415aec0 --- /dev/null +++ b/Inglenook.h @@ -0,0 +1,59 @@ +#ifndef INGLENOOK_H +#define INGLENOOK_H + +// LCD Display Types: +// +// 0 = OSEPP LCDKeypad +// 1 = Adafruit RGB LCD +#define LCD_DISPLAY_TYPE_OSEPP 0 +#define LCD_DISPLAY_TYPE_ADAFRUIT 1 +#define LCD_DISPLAY_TYPE LCD_DISPLAY_TYPE_OSEPP + +// NOTE: These are actually set by the rules of the game... +// WARNING: There are several places (initialization, etc.) where +// the value of these is assumed fixed at 5 and 8. +#define TRAIN_LENGTH 5 +#define NUM_CARS 8 + +const String carnames[NUM_CARS] = { // String names of cars on the layout (for display) + "Blue Boxcar", + "Red Boxcar", + "Green Boxcar", + "Black Tank Car", + "Black Gondola", + "Grey Hopper", + "Brown Flatcar", + "Brown Boxcar" +}; + +//class MenuBackend; +//class MenuItem; +//class MenuUseEvent; +//class MenuChangeEvent; + +class InglenookGame { + private: +//LCDKeypad lcd; + + public: + static InglenookGame *getTheGame(); + void begin(); + void play(); + void buildTrain(void); + void doDisplayTrain(); + + protected: + InglenookGame(); + //void menuUseEvent(MenuUseEvent e); + //void menuChangeEvent(MenuChangeEvent e); + void menuSetup(); + void printWelcome(); + int checkButtons(); + int debounceButton(int button); + void doMenuDisplay(); + void doListTrain(int car); + +}; + + +#endif From e656241d07937a17b19f3ce31109c383ef575069 Mon Sep 17 00:00:00 2001 From: TwinDad Date: Wed, 25 Jan 2017 15:02:02 -0500 Subject: [PATCH 02/21] The rest of the WiThrottle / Inglenook changes --- DCCpp_Uno/Config.h | 15 +++++++++++++++ DCCpp_Uno/DCCpp_Uno.ino | 16 ++++++++++++++++ Inglenook.cpp => DCCpp_Uno/Inglenook.cpp | 0 Inglenook.h => DCCpp_Uno/Inglenook.h | 0 DCCpp_Uno/SerialCommand.cpp | 17 +++++++++++++++++ 5 files changed, 48 insertions(+) rename Inglenook.cpp => DCCpp_Uno/Inglenook.cpp (100%) rename Inglenook.h => DCCpp_Uno/Inglenook.h (100%) diff --git a/DCCpp_Uno/Config.h b/DCCpp_Uno/Config.h index 126f909..defacba 100644 --- a/DCCpp_Uno/Config.h +++ b/DCCpp_Uno/Config.h @@ -39,6 +39,7 @@ Part of DCC++ BASE STATION for the Arduino // //#define IP_ADDRESS { 192, 168, 1, 200 } +#define IP_ADDRESS { 192, 168, 2, 4 } ///////////////////////////////////////////////////////////////////////////////////// // @@ -55,4 +56,18 @@ Part of DCC++ BASE STATION for the Arduino #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF } ///////////////////////////////////////////////////////////////////////////////////// +// +// ENABLE THE WITHROTTLE INTERFACE +// + +#define WITHROTTLE_SUPPORT 1 + +///////////////////////////////////////////////////////////////////////////////////// +// +// ENABLE THE INGLENOOK SIDINGS GAME ADD-ON. REQUIRES AN LCD WITH 5 BUTTONS. +// + +#define INGLENOOK_GAME 1 + +///////////////////////////////////////////////////////////////////////////////////// diff --git a/DCCpp_Uno/DCCpp_Uno.ino b/DCCpp_Uno/DCCpp_Uno.ino index 9378374..aead7ea 100644 --- a/DCCpp_Uno/DCCpp_Uno.ino +++ b/DCCpp_Uno/DCCpp_Uno.ino @@ -177,6 +177,9 @@ DCC++ BASE STATION is configured through the Config.h file that contains all use #include "EEStore.h" #include "Config.h" #include "Comm.h" +#ifdef INGLENOOK_GAME +#include "Inglenook.h" +#endif void showConfiguration(); @@ -196,6 +199,10 @@ volatile RegisterList progRegs(2); // create a shorter list CurrentMonitor mainMonitor(CURRENT_MONITOR_PIN_MAIN,""); // create monitor for current on Main Track CurrentMonitor progMonitor(CURRENT_MONITOR_PIN_PROG,""); // create monitor for current on Program Track +#ifdef INGLENOOK_GAME +InglenookGame *iGame; +#endif + /////////////////////////////////////////////////////////////////////////////// // MAIN ARDUINO LOOP /////////////////////////////////////////////////////////////////////////////// @@ -210,6 +217,10 @@ void loop(){ } Sensor::check(); // check sensors for activate/de-activate + +#ifdef INGLENOOK_GAME + iGame->play(); +#endif } // loop @@ -222,6 +233,11 @@ void setup(){ Serial.begin(115200); // configure serial interface Serial.flush(); +#ifdef INGLENOOK_GAME + iGame = InglenookGame::getTheGame(); + iGame->begin(); +#endif + #ifdef SDCARD_CS pinMode(SDCARD_CS,OUTPUT); digitalWrite(SDCARD_CS,HIGH); // Deselect the SD card diff --git a/Inglenook.cpp b/DCCpp_Uno/Inglenook.cpp similarity index 100% rename from Inglenook.cpp rename to DCCpp_Uno/Inglenook.cpp diff --git a/Inglenook.h b/DCCpp_Uno/Inglenook.h similarity index 100% rename from Inglenook.h rename to DCCpp_Uno/Inglenook.h diff --git a/DCCpp_Uno/SerialCommand.cpp b/DCCpp_Uno/SerialCommand.cpp index 4d3f9b2..fa22a9f 100644 --- a/DCCpp_Uno/SerialCommand.cpp +++ b/DCCpp_Uno/SerialCommand.cpp @@ -14,6 +14,7 @@ Part of DCC++ BASE STATION for the Arduino // See SerialCommand::parse() below for defined text commands. + #include "SerialCommand.h" #include "DCCpp_Uno.h" #include "Accessories.h" @@ -21,6 +22,9 @@ Part of DCC++ BASE STATION for the Arduino #include "Outputs.h" #include "EEStore.h" #include "Comm.h" +#ifdef WITHROTTLE_SUPPORT +#include "WiThrottle.hpp" +#endif extern int __heap_start, *__brkval; @@ -49,6 +53,11 @@ void SerialCommand::process(){ while(INTERFACE.available()>0){ // while there is data on the serial line c=INTERFACE.read(); +#ifdef WITHROTTLE_SUPPORT + if (WiThrottle::isWTCommand(c)) { + WiThrottle::readCommand(c); + } else +#endif if(c=='<') // start of new command sprintf(commandString,""); else if(c=='>') // end of new command @@ -62,8 +71,16 @@ void SerialCommand::process(){ EthernetClient client=INTERFACE.available(); if(client){ +#ifdef WITHROTTLE_SUPPORT + WiThrottle::sendIntroMessage(); +#endif while(client.connected() && client.available()){ // while there is data on the network c=client.read(); +#ifdef WITHROTTLE_SUPPORT + if (WiThrottle::isWTCommand(c)) { + WiThrottle::readCommand(c); + } else +#endif if(c=='<') // start of new command sprintf(commandString,""); else if(c=='>') // end of new command From e6316e516a3c8a9b39e66d6a175e0df1e5b7d600 Mon Sep 17 00:00:00 2001 From: TwinDad Date: Thu, 26 Jan 2017 23:56:50 -0500 Subject: [PATCH 03/21] Update Inglenook to keep its own static LCD object. Working version of LCDThrottle --- DCCpp_Uno/Config.h | 11 +- DCCpp_Uno/DCCpp_Uno.ino | 23 ++- DCCpp_Uno/Inglenook.cpp | 4 +- DCCpp_Uno/LCDThrottle.cpp | 349 ++++++++++++++++++++++++++++++++++++++ DCCpp_Uno/LCDThrottle.h | 50 ++++++ 5 files changed, 429 insertions(+), 8 deletions(-) create mode 100644 DCCpp_Uno/LCDThrottle.cpp create mode 100644 DCCpp_Uno/LCDThrottle.h diff --git a/DCCpp_Uno/Config.h b/DCCpp_Uno/Config.h index defacba..968113a 100644 --- a/DCCpp_Uno/Config.h +++ b/DCCpp_Uno/Config.h @@ -39,7 +39,7 @@ Part of DCC++ BASE STATION for the Arduino // //#define IP_ADDRESS { 192, 168, 1, 200 } -#define IP_ADDRESS { 192, 168, 2, 4 } +#define IP_ADDRESS { 192, 168, 0, 2 } ///////////////////////////////////////////////////////////////////////////////////// // @@ -67,7 +67,14 @@ Part of DCC++ BASE STATION for the Arduino // ENABLE THE INGLENOOK SIDINGS GAME ADD-ON. REQUIRES AN LCD WITH 5 BUTTONS. // -#define INGLENOOK_GAME 1 +#define INGLENOOK_GAME 0 + +///////////////////////////////////////////////////////////////////////////////////// +// +// ENABLE THE LCD THROTTLE. REQUIRES AN LCD WITH 5 BUTTONS. +// + +#define LCD_THROTTLE 1 ///////////////////////////////////////////////////////////////////////////////////// diff --git a/DCCpp_Uno/DCCpp_Uno.ino b/DCCpp_Uno/DCCpp_Uno.ino index aead7ea..b4099cd 100644 --- a/DCCpp_Uno/DCCpp_Uno.ino +++ b/DCCpp_Uno/DCCpp_Uno.ino @@ -177,9 +177,12 @@ DCC++ BASE STATION is configured through the Config.h file that contains all use #include "EEStore.h" #include "Config.h" #include "Comm.h" -#ifdef INGLENOOK_GAME +#if (INGLENOOK_GAME == 1) #include "Inglenook.h" #endif +#if (LCD_THROTTLE == 1) +#include "LCDThrottle.h" +#endif void showConfiguration(); @@ -199,15 +202,23 @@ volatile RegisterList progRegs(2); // create a shorter list CurrentMonitor mainMonitor(CURRENT_MONITOR_PIN_MAIN,""); // create monitor for current on Main Track CurrentMonitor progMonitor(CURRENT_MONITOR_PIN_PROG,""); // create monitor for current on Program Track -#ifdef INGLENOOK_GAME +#if (INGLENOOK_GAME == 1) InglenookGame *iGame; #endif +#if (LCD_THROTTLE == 1) +LCDThrottle *lcdThrottle; +#endif + /////////////////////////////////////////////////////////////////////////////// // MAIN ARDUINO LOOP /////////////////////////////////////////////////////////////////////////////// void loop(){ + +#if (LCD_THROTTLE == 1) + lcdThrottle->run(); +#endif SerialCommand::process(); // check for, and process, and new serial commands @@ -218,7 +229,7 @@ void loop(){ Sensor::check(); // check sensors for activate/de-activate -#ifdef INGLENOOK_GAME +#if (INGLENOOK_GAME == 1) iGame->play(); #endif @@ -233,7 +244,11 @@ void setup(){ Serial.begin(115200); // configure serial interface Serial.flush(); -#ifdef INGLENOOK_GAME +#if (LCD_THROTTLE == 1) + lcdThrottle = LCDThrottle::getThrottle(1, 32); +#endif + +#if (INGLENOOK_GAME == 1) iGame = InglenookGame::getTheGame(); iGame->begin(); #endif diff --git a/DCCpp_Uno/Inglenook.cpp b/DCCpp_Uno/Inglenook.cpp index f698194..8d64e14 100644 --- a/DCCpp_Uno/Inglenook.cpp +++ b/DCCpp_Uno/Inglenook.cpp @@ -39,9 +39,9 @@ void menuChangeEvent(MenuChangeEvent e); MenuBackend *menu = new MenuBackend(menuUseEvent, menuChangeEvent); #if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) -LCDKeypad lcd = LCDKeypad(); +static LCDKeypad lcd = LCDKeypad(); #elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) -Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield(); +static Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield(); #endif static InglenookGame *thegame = NULL; diff --git a/DCCpp_Uno/LCDThrottle.cpp b/DCCpp_Uno/LCDThrottle.cpp new file mode 100644 index 0000000..039c2b5 --- /dev/null +++ b/DCCpp_Uno/LCDThrottle.cpp @@ -0,0 +1,349 @@ +#include +#include "SerialCommand.h" +#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) +#include +#include +#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) +#include +#include // is this necessary? +#else +#error CANNOT COMPILE -- INVALID LCD LIBRARY SELECTED +#endif + +#include "LCDThrottle.h" + +// Define generic button names for multi-library compatibility +#define KEYS_NONE -1 +#define KEYS_RIGHT 0 +#define KEYS_UP 1 +#define KEYS_DOWN 2 +#define KEYS_LEFT 3 +#define KEYS_SELECT 4 +#define KEYS_LONG_RIGHT 128 +#define KEYS_LONG_UP 129 +#define KEYS_LONG_DOWN 130 +#define KEYS_LONG_LEFT 131 +#define KEYS_LONG_SELECT 132 + +#define SPEED_UP_INCREMENT 13 +#define SPEED_DOWN_INCREMENT 13 +#define MAX_SPEED 126 +#define MAX_NOTCH_NORMAL 15 +#define MAX_NOTCH_SWITCHER 7 + +#define THROTTLE_STATE_RUN 0 +#define THROTTLE_STATE_DEBOUNCE 1 + +// MAX_COMMAND_LENGTH is defined in SerialCommand.h +#define MAX_COMMAND_LENGTH 30 +char command[MAX_COMMAND_LENGTH]; + +#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) +static LCDKeypad lcd = LCDKeypad(); +#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) +static Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield(); +#endif + +//static char display[2][17]; + +static LCDThrottle *lcdThrottle = NULL; + +static LCDThrottle *LCDThrottle::getThrottle(int r, int c, char **d) { + if (d == NULL) { return(NULL); } + if (lcdThrottle == NULL) { + lcdThrottle = new LCDThrottle(r, c, d); + } + + return(lcdThrottle); +} + +LCDThrottle::LCDThrottle(int reg, int cab, char **d) { + //lcd = new LCDKeypad(); + lcd.begin(16,2); + throttleState = THROTTLE_STATE_RUN; + this->reg = reg; + this->cab = cab; + notch = 0; + speed = 0; + dir = FORWARD; + display = d; + displayMode = DISPLAY_MODE; + updateDisplay(); +} + +void LCDThrottle::run() { + int button = debounceButtons(); + switch(throttleState) { + case THROTTLE_STATE_RUN: + switch(button) { + case KEYS_RIGHT: + increaseSpeed(); + sendThrottleCommand(); + updateDisplay(); + break; + case KEYS_LEFT: + decreaseSpeed(); + sendThrottleCommand(); + updateDisplay(); + break; + case KEYS_UP: + case KEYS_DOWN: + // For now, dumbly toggle direction with either left or right key. + // Maybe make this smarter or repurpose later. + dir = (dir == FORWARD ? REVERSE : FORWARD); + sendThrottleCommand(); + updateDisplay(); + break; + case KEYS_SELECT: + // For now, this is emergency stop. + speed = -1; + notch = 0; + sendThrottleCommand(); + // reset speed to Zero after sending "Emergency Stop" special value of -1 + // This won't hurt b/c the Base Station will set the loco's speed to zero as well. + speed = 0; + updateDisplay(); + break; + default: + break; + // Do nothing. + } // switch(button) + break; + + case THROTTLE_STATE_DEBOUNCE: + // ??? + break; + } // switch(throttleState) +} + +void LCDThrottle::increaseSpeed() { + int tmp_notch; + //int maxnotch; + if (displayMode == DISPLAY_MODE_NORMAL) { + // in DISPLAY_MODE_NORMAL, notch is 0->maxnotch. + // since this is the "increase" function we never have to worry about + // flipping the direction bit. + notch = (notch == MAX_NOTCH_NORMAL ? MAX_NOTCH_NORMAL : notch + 1); + speed = notch * (MAX_SPEED / MAX_NOTCH_NORMAL); + } else { + // in DISPLAY_MODE_SWITCHER the direction can change when "increasing" + // the throttle, it depends. Have to deal with absolute value. + // First, get the signed version of "notch" and increment it. + tmp_notch = (dir == REVERSE ? -notch : notch); + tmp_notch = (tmp_notch == MAX_NOTCH_SWITCHER ? MAX_NOTCH_SWITCHER : tmp_notch + 1); + // Now handle the possible sign change by setting the direction and + // storing notch = abs(tmp_notch) + if (tmp_notch >= 0) { + dir = FORWARD; + notch = tmp_notch; + } else { + dir = REVERSE; + notch = -tmp_notch; + } + speed = notch * (MAX_SPEED / MAX_NOTCH_SWITCHER); + } // if(displayMode) + + Serial.println("inc: N= " + String(notch) + " S=" + String(speed)); +} + +void LCDThrottle::decreaseSpeed() { + int tmp_notch; + if (displayMode == DISPLAY_MODE_NORMAL) { + notch = (notch == 0 ? 0 : notch - 1); + speed = notch * (MAX_SPEED / MAX_NOTCH_NORMAL); + } else { + tmp_notch = (dir == REVERSE ? -notch : notch); + tmp_notch = (tmp_notch == -MAX_NOTCH_SWITCHER ? -MAX_NOTCH_SWITCHER : tmp_notch - 1); + if (tmp_notch < 0) { + dir = REVERSE; + notch = -tmp_notch; + } else { + dir = FORWARD; + notch = tmp_notch; + } + speed = notch * (MAX_SPEED / MAX_NOTCH_SWITCHER); + } + Serial.println("dec: N= " + String(notch) + " S=" + String(speed)); +} + +void LCDThrottle::sendThrottleCommand() { + sprintf(command, ""); + sprintf(command, "t%d %d %d %d", reg, cab, speed, dir); + Serial.println(command); + SerialCommand::parse(command); +} +/* +int LCDThrottle::checkButtons() { +#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) + // OSEPP LCDKeypad + int buttons = lcd.button(); + if (buttons != -1) { + debounceButton(buttons); + //Serial.print("Button Check = "); + //Serial.println(buttons); + return(buttons); + } else { + return(KEYS_NONE); + } +#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) + // ADAFRUIT RGBLCD + uint8_t buttons = lcd.readButtons(); + if (buttons & BUTTON_UP) { + return(KEYS_UP); + } + if (buttons & BUTTON_DOWN) { + return(KEYS_DOWN); + } + if (buttons & BUTTON_LEFT) { + return(KEYS_LEFT); + } + if (buttons & BUTTON_RIGHT) { + return(KEYS_RIGHT); + } + if (buttons & BUTTON_SELECT) { + return(KEYS_SELECT); + } + return(KEYS_NONE); +#endif +} +*/ + +/** getButton() + * Translates the different LCD button values + * to a common set + */ +int LCDThrottle::getButton() { +#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) + // OSEPP LCDKeypad + return(lcd.button()); +#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) + // ADAFRUIT RGBLCD + uint8_t buttons = lcd.readButtons(); + if (buttons & BUTTON_UP) { + return(KEYS_UP); + } + if (buttons & BUTTON_DOWN) { + return(KEYS_DOWN); + } + if (buttons & BUTTON_LEFT) { + return(KEYS_LEFT); + } + if (buttons & BUTTON_RIGHT) { + return(KEYS_RIGHT); + } + if (buttons & BUTTON_SELECT) { + return(KEYS_SELECT); + } + return(KEYS_NONE); +#endif +} + +int LCDThrottle::debounceButtons() { + int button = getButton(); + static long startDebounce; + static int keyval; + int retv; + if (throttleState == THROTTLE_STATE_RUN) { + if (button == KEYS_NONE) { + return(KEYS_NONE); + } else { + Serial.println("Raw Key: " + String(button)); + startDebounce = millis(); + keyval = button; + throttleState = THROTTLE_STATE_DEBOUNCE; + } + } else { + // actively debouncing... + if (button == KEYS_NONE) { + Serial.println("Debounced Key: " + String(keyval)); + // Debounce complete. Decide if it's long or not. + throttleState = THROTTLE_STATE_RUN; + retv = keyval; + keyval = KEYS_NONE; + if (millis() - startDebounce > 2000) { + Serial.println("2 second button press! Val = " + String(retv)); + // Longer than 2 second hold + switch(retv) { + case KEYS_RIGHT: + return(KEYS_LONG_RIGHT); + case KEYS_UP: + return(KEYS_LONG_UP); + case KEYS_DOWN: + return(KEYS_LONG_DOWN); + case KEYS_LEFT: + return(KEYS_LONG_LEFT); + case KEYS_SELECT: + return(KEYS_LONG_SELECT); + default: + return(KEYS_NONE); + } + } else { + return(retv); + } // long key + } // keys_none + } // throttle state == RUN +} + +// Display Modes... + +void LCDThrottle::updateDisplay() { + switch(displayMode) { + case DISPLAY_MODE_SWITCHER: + // SWITCHER: Speed/Direction together + // (something useful) + // <------0------> + lcd.clear(); + //lcd.setCursor(0,1); + // Draw the line. + sprintf(display[0], "Throttle: %04d", cab); + sprintf(display[1], "<------0------>"); + //lcd.print("<------0------>"); + // Figure out where to put the cursor + lcd.setCursor(0,0); lcd.print(display[0]); + lcd.setCursor(0,1); lcd.print(display[1]); + Serial.println("D0:" + String(display[0])); + Serial.println("D1:" + String(display[1])); + if (notch == 0) { + lcd.setCursor(7,1); + } else { + int tmp_notch = notch; + if (tmp_notch == 0) { + tmp_notch += 7; + } else { + tmp_notch = (dir == FORWARD ? tmp_notch + 7 : 7 - tmp_notch); + } + Serial.println("S=" + String(speed) + " N=" + String(notch) + " T=" + String(tmp_notch)); + lcd.setCursor(tmp_notch, 1); + } + lcd.blink(); + break; + + case DISPLAY_MODE_NORMAL: + default: + // NORMAL: Speed + Direction + // <-- --> + // 0--------------+ + lcd.clear(); + lcd.setCursor(0,0); + if (dir == REVERSE) { + sprintf(display[0], "<--- Loco: %04d", cab); + //lcd.print("<--- "); + } else { + sprintf(display[0], "Loco: %04d --->", cab); + //lcd.print(" DIR --->"); + } + sprintf(display[1], "0 "); + if (speed > 0) { + for (int i = 0; i < notch-1; i++) { + display[1][i+1] = '-'; + } + display[1][notch] = '|'; + display[1][notch+1] = 0; + } + lcd.setCursor(0,0); lcd.print(display[0]); + lcd.setCursor(0,1); lcd.print(display[1]); + Serial.println("D0:" + String(display[0])); + Serial.println("D1:" + String(display[1])); + break; + } +} diff --git a/DCCpp_Uno/LCDThrottle.h b/DCCpp_Uno/LCDThrottle.h new file mode 100644 index 0000000..3a3f7a1 --- /dev/null +++ b/DCCpp_Uno/LCDThrottle.h @@ -0,0 +1,50 @@ +#ifndef LCD_THROTTLE_H +#define LCD_THROTTLE_H + +// DCC++ Throttle using the buttons on a 16x2 + 5 button Display Shield + +// LCD Display Types: +// +// 0 = OSEPP LCDKeypad +// 1 = Adafruit RGB LCD +#define LCD_DISPLAY_TYPE_OSEPP 0 +#define LCD_DISPLAY_TYPE_ADAFRUIT 1 +#define LCD_DISPLAY_TYPE LCD_DISPLAY_TYPE_OSEPP + +#define DISPLAY_MODE_NORMAL 1 +#define DISPLAY_MODE_SWITCHER 2 +#define DISPLAY_MODE DISPLAY_MODE_SWITCHER + +// Throttle Directions +#define FORWARD 1 +#define REVERSE 0 + +class LCDThrottle { + private: + int throttleState; + int reg; + int cab; + int speed; + int dir; + int displayMode; + int notch; + char **display; + + public: + static LCDThrottle *getThrottle(int r, int c, char** d); + //void begin(LCDKeypad *lcd); + void run(); + + protected: + LCDThrottle(int reg, int cab); + void sendThrottleCommand(); + //int checkButtons(); + int debounceButtons(); + int getButton(); + void increaseSpeed(); + void decreaseSpeed(); + void updateDisplay(); + +}; + +#endif // LCD_THROTTLE_H From 72817cb788b2d8a7940a56c6c3ac0fdd411a8ff1 Mon Sep 17 00:00:00 2001 From: TwinDad Date: Tue, 31 Jan 2017 10:09:14 -0500 Subject: [PATCH 04/21] Update Inglenook to make use of LCD class. Clean up some junk too. --- DCCpp_Uno/Inglenook.cpp | 183 ++++++++++++---------------------------- DCCpp_Uno/Inglenook.h | 18 +--- 2 files changed, 56 insertions(+), 145 deletions(-) diff --git a/DCCpp_Uno/Inglenook.cpp b/DCCpp_Uno/Inglenook.cpp index 8d64e14..5a74fd3 100644 --- a/DCCpp_Uno/Inglenook.cpp +++ b/DCCpp_Uno/Inglenook.cpp @@ -1,25 +1,8 @@ #include #include -#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) -#include -#include -#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) -#include -#include // is this necessary? -#else -#error CANNOT COMPILE -- INVALID LCD LIBRARY SELECTED -#endif #include "Inglenook.h" -// Define generic button names for multi-library compatibility -#define KEYS_NONE -1 -#define KEYS_RIGHT 0 -#define KEYS_UP 1 -#define KEYS_DOWN 2 -#define KEYS_LEFT 3 -#define KEYS_SELECT 4 - // Game State Machine States #define STATE_IDLE 0 #define STATE_MENUS 1 @@ -38,11 +21,7 @@ void menuUseEvent(MenuUseEvent e); void menuChangeEvent(MenuChangeEvent e); MenuBackend *menu = new MenuBackend(menuUseEvent, menuChangeEvent); -#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) -static LCDKeypad lcd = LCDKeypad(); -#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) -static Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield(); -#endif +static char display[2][17]; static InglenookGame *thegame = NULL; @@ -55,7 +34,7 @@ static InglenookGame *InglenookGame::getTheGame() { } InglenookGame::InglenookGame() { - //lcd = LCDKeypad(); + lcd = new LCD(); menuSetup(); } @@ -83,33 +62,30 @@ void InglenookGame::menuSetup() { static void InglenookGame::begin() { randomSeed(analogRead(44)); menuSetup(); - lcd.begin(16, 2); - //Serial.println("lcd begun"); + lcd->begin(); printWelcome(); game_state = STATE_IDLE; car_index = -1; } void InglenookGame::printWelcome() { - lcd.clear(); - //Serial.println("lcd clear"); - lcd.setCursor(0, 0); - //Serial.println("lcd setcursor"); - lcd.print("INGLENOOK GAME"); - lcd.setCursor(0,1); - lcd.print("Select to start"); + lcd->clear(); + sprintf(display[0], "INGLENOOK GAME"); + sprintf(display[1], "Select to start"); + lcd->updateDisplay(display[0], display[1]); } void InglenookGame::play() { // Read and respond to the buttons - int buttons = checkButtons(); + //int buttons = checkButtons(); + lcd->run(); + int buttons = lcd->getButtons(); switch(game_state) { case STATE_IDLE: if (buttons == KEYS_SELECT) { //Serial.println("Starting game..."); game_state = STATE_MENUS; menu->moveDown(); - //menu.moveDown(); doMenuDisplay(); } break; @@ -131,7 +107,6 @@ void InglenookGame::play() { doMenuDisplay(); break; case KEYS_LEFT: - //menu.moveBack(); if (menu->getCurrent() == "Build Trains") { game_state == STATE_IDLE; printWelcome(); @@ -163,7 +138,6 @@ void InglenookGame::play() { case KEYS_LEFT: case KEYS_SELECT: game_state = STATE_MENUS; - //menu.moveUp(); doMenuDisplay(); break; case KEYS_DOWN: @@ -189,49 +163,6 @@ void InglenookGame::play() { } // switch(game_state) } - -int InglenookGame::checkButtons() { -#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) - // OSEPP LCDKeypad - int buttons = lcd.button(); - if (buttons != -1) { - debounceButton(buttons); - //Serial.print("Button Check = "); - //Serial.println(buttons); - return(buttons); - } else { - return(KEYS_NONE); - } -#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) - // ADAFRUIT RGBLCD - uint8_t buttons = lcd.readButtons(); - if (buttons & BUTTON_UP) { - return(KEYS_UP); - } - if (buttons & BUTTON_DOWN) { - return(KEYS_DOWN); - } - if (buttons & BUTTON_LEFT) { - return(KEYS_LEFT); - } - if (buttons & BUTTON_RIGHT) { - return(KEYS_RIGHT); - } - if (buttons & BUTTON_SELECT) { - return(KEYS_SELECT); - } - return(KEYS_NONE); -#endif -} - -int InglenookGame::debounceButton(int button) { - if (button != KEYS_NONE) { - while(lcd.button() != KEYS_NONE) { - ; - } - } -} - void InglenookGame::buildTrain() { bool cars_used[NUM_CARS] = { false, false, false, false, false, false, false, false }; int car = 0; @@ -254,74 +185,72 @@ void InglenookGame::buildTrain() { } // for } +void InglenookGame::updateDisplay(char *row1, char *row2) { + sprintf(display[0], row1); + sprintf(display[1], row2); + lcd->updateDisplay(display[0], display[1]); +} + void InglenookGame::doMenuDisplay() { - lcd.clear(); + lcd->clear(); + if (menu->getCurrent() == "Build Train") { - lcd.setCursor(0,1); lcd.print("List Cars"); - lcd.setCursor(0,0); lcd.print("Build Train"); - lcd.setCursor(0,0); + sprintf(display[0], "Build Train"); + sprintf(display[1], "List Cars"); } if (menu->getCurrent() == "List Cars") { - lcd.setCursor(0,1); lcd.print(""); - lcd.setCursor(0,0); lcd.print("List Cars"); - lcd.setCursor(0,0); + sprintf(display[0], "List Cars"); + sprintf(display[1], ""); } if (menu->getCurrent() == "Car 0") { - lcd.setCursor(0,0); lcd.print("1:" + carnames[0]); - lcd.setCursor(0,1); lcd.print("2:" + carnames[1]); - lcd.setCursor(0,0); + sprintf(display[0],"%s%s", "1:",carnames[0].c_str()); + sprintf(display[1],"%s%s", "2:",carnames[1].c_str()); } if (menu->getCurrent() == "Car 1") { - lcd.setCursor(0,0); lcd.print("2:" + carnames[1]); - lcd.setCursor(0,1); lcd.print("3:" + carnames[2]); - lcd.setCursor(0,0); + sprintf(display[0],"%s%s", "2:",carnames[1].c_str()); + sprintf(display[1],"%s%s", "3:",carnames[2].c_str()); } if (menu->getCurrent() == "Car 2") { - lcd.setCursor(0,0); lcd.print("3:" + carnames[2]); - lcd.setCursor(0,1); lcd.print("4:" + carnames[3]); - lcd.setCursor(0,0); + sprintf(display[0],"%s%s", "3:",carnames[2].c_str()); + sprintf(display[1],"%s%s", "4:",carnames[3].c_str()); } if (menu->getCurrent() == "Car 3") { - lcd.setCursor(0,0); lcd.print("4:" + carnames[3]); - lcd.setCursor(0,1); lcd.print("5:" + carnames[4]); - lcd.setCursor(0,0); + sprintf(display[0],"%s%s", "4:",carnames[3].c_str()); + sprintf(display[1],"%s%s", "5:",carnames[4].c_str()); } if (menu->getCurrent() == "Car 4") { - lcd.setCursor(0,0); lcd.print("5:" + carnames[4]); - lcd.setCursor(0,1); lcd.print("6:" + carnames[5]); - lcd.setCursor(0,0); + sprintf(display[0],"%s%s", "5:",carnames[4].c_str()); + sprintf(display[1],"%s%s", "6:",carnames[5].c_str()); } if (menu->getCurrent() == "Car 5") { - lcd.setCursor(0,0); lcd.print("6:" + carnames[5]); - lcd.setCursor(0,1); lcd.print("7:" + carnames[6]); - lcd.setCursor(0,0); + sprintf(display[0],"%s%s", "6:",carnames[5].c_str()); + sprintf(display[1],"%s%s", "7:",carnames[6].c_str()); } if ((menu->getCurrent() == "Car 6") || (menu->getCurrent() == "Car 7")) { - lcd.setCursor(0,0); lcd.print("7:" + carnames[6]); - lcd.setCursor(0,1); lcd.print("8:" + carnames[7]); - lcd.setCursor(0,0); + sprintf(display[0],"%s%s", "7:",carnames[6].c_str()); + sprintf(display[1],"%s%s", "8:",carnames[7].c_str()); } - lcd.setCursor(0,0); - lcd.blink(); + lcd->updateDisplay(display[0], display[1]); + lcd->setCursor(0,0); + lcd->blink(); } void InglenookGame::doDisplayTrain() { // DEBUG: //Serial.println("BUILD THIS TRAIN"); - for (int i = 0; i < TRAIN_LENGTH; i++) { - //Serial.print(String(i) + ": " + carnames[train[i]] + "; "); - } + //for (int i = 0; i < TRAIN_LENGTH; i++) { + //Serial.print(String(i+1) + ": " + carnames[train[i]] + "; "); + //} //Serial.println(""); // END DEBUG - lcd.clear(); - //lcd.autoscroll(); - lcd.setCursor(0,0); - lcd.print("BUILD THIS TRAIN"); - lcd.setCursor(0,1); + lcd->clear(); + sprintf(display[0], "BUILD THIS TRAIN"); + sprintf(display[1], ""); for (int i = 0; i < TRAIN_LENGTH; i++) { - lcd.print(String(train[i]) + " "); + sprintf(display[1], "%s%d ", display[1], train[i]+1); } - lcd.noBlink(); + updateDisplay(display[0], display[1]); + lcd->noBlink(); } void InglenookGame::doListTrain(int car) { @@ -345,20 +274,14 @@ void InglenookGame::doListTrain(int car) { s2 = "2:" + carnames[train[1]]; break; } // switch(car) - lcd.clear(); - lcd.setCursor(0,0); - lcd.print(s1); - lcd.setCursor(0,1); - lcd.print(s2); + lcd->clear(); + sprintf(display[0], s1.c_str()); + sprintf(display[1], s2.c_str()); + lcd->updateDisplay(display[0], display[1]); } void menuChangeEvent(MenuChangeEvent changed) { - // Update the display to reflect the current menu state - // For now we'll use serial output. - //Serial.print("Menu change "); - //Serial.print(changed.from.getName()); - //Serial.print(" -> "); - //Serial.println(changed.to.getName()); + // Nothing to do here... } void menuUseEvent(MenuUseEvent used) { diff --git a/DCCpp_Uno/Inglenook.h b/DCCpp_Uno/Inglenook.h index 415aec0..b0c78ea 100644 --- a/DCCpp_Uno/Inglenook.h +++ b/DCCpp_Uno/Inglenook.h @@ -1,13 +1,7 @@ #ifndef INGLENOOK_H #define INGLENOOK_H -// LCD Display Types: -// -// 0 = OSEPP LCDKeypad -// 1 = Adafruit RGB LCD -#define LCD_DISPLAY_TYPE_OSEPP 0 -#define LCD_DISPLAY_TYPE_ADAFRUIT 1 -#define LCD_DISPLAY_TYPE LCD_DISPLAY_TYPE_OSEPP +#include "LCD.h" // NOTE: These are actually set by the rules of the game... // WARNING: There are several places (initialization, etc.) where @@ -26,14 +20,9 @@ const String carnames[NUM_CARS] = { // String names of cars on the layout (for d "Brown Boxcar" }; -//class MenuBackend; -//class MenuItem; -//class MenuUseEvent; -//class MenuChangeEvent; - class InglenookGame { private: -//LCDKeypad lcd; + LCD *lcd; public: static InglenookGame *getTheGame(); @@ -44,14 +33,13 @@ class InglenookGame { protected: InglenookGame(); - //void menuUseEvent(MenuUseEvent e); - //void menuChangeEvent(MenuChangeEvent e); void menuSetup(); void printWelcome(); int checkButtons(); int debounceButton(int button); void doMenuDisplay(); void doListTrain(int car); + void updateDisplay(char *row1, char *row2); }; From bc8607ded88bd97448762a4c4de334c19e5d13cf Mon Sep 17 00:00:00 2001 From: TwinDad Date: Tue, 31 Jan 2017 10:12:35 -0500 Subject: [PATCH 05/21] Update LCDThrottle to make use of LCD class. Add power on/off with long press of Select --- DCCpp_Uno/LCDThrottle.cpp | 232 +++++++++----------------------------- DCCpp_Uno/LCDThrottle.h | 23 ++-- 2 files changed, 64 insertions(+), 191 deletions(-) diff --git a/DCCpp_Uno/LCDThrottle.cpp b/DCCpp_Uno/LCDThrottle.cpp index 039c2b5..cf108b1 100644 --- a/DCCpp_Uno/LCDThrottle.cpp +++ b/DCCpp_Uno/LCDThrottle.cpp @@ -1,33 +1,10 @@ #include #include "SerialCommand.h" -#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) -#include -#include -#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) -#include -#include // is this necessary? -#else -#error CANNOT COMPILE -- INVALID LCD LIBRARY SELECTED -#endif - #include "LCDThrottle.h" -// Define generic button names for multi-library compatibility -#define KEYS_NONE -1 -#define KEYS_RIGHT 0 -#define KEYS_UP 1 -#define KEYS_DOWN 2 -#define KEYS_LEFT 3 -#define KEYS_SELECT 4 -#define KEYS_LONG_RIGHT 128 -#define KEYS_LONG_UP 129 -#define KEYS_LONG_DOWN 130 -#define KEYS_LONG_LEFT 131 -#define KEYS_LONG_SELECT 132 - -#define SPEED_UP_INCREMENT 13 -#define SPEED_DOWN_INCREMENT 13 -#define MAX_SPEED 126 +//#define SPEED_UP_INCREMENT 1 /* 13 */ +//#define SPEED_DOWN_INCREMENT 1 /* 13 */ +#define MAX_SPEED 60 /*126 */ #define MAX_NOTCH_NORMAL 15 #define MAX_NOTCH_SWITCHER 7 @@ -38,41 +15,35 @@ #define MAX_COMMAND_LENGTH 30 char command[MAX_COMMAND_LENGTH]; -#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) -static LCDKeypad lcd = LCDKeypad(); -#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) -static Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield(); -#endif - -//static char display[2][17]; +static char display[2][17]; static LCDThrottle *lcdThrottle = NULL; -static LCDThrottle *LCDThrottle::getThrottle(int r, int c, char **d) { - if (d == NULL) { return(NULL); } +static LCDThrottle *LCDThrottle::getThrottle(int r, int c) { if (lcdThrottle == NULL) { - lcdThrottle = new LCDThrottle(r, c, d); + lcdThrottle = new LCDThrottle(r, c); } return(lcdThrottle); } -LCDThrottle::LCDThrottle(int reg, int cab, char **d) { - //lcd = new LCDKeypad(); - lcd.begin(16,2); +LCDThrottle::LCDThrottle(int reg, int cab) { + lcd = new LCD(); + lcd->begin(); throttleState = THROTTLE_STATE_RUN; this->reg = reg; this->cab = cab; notch = 0; speed = 0; dir = FORWARD; - display = d; displayMode = DISPLAY_MODE; + power_state = false; updateDisplay(); } void LCDThrottle::run() { - int button = debounceButtons(); + lcd->run(); + int button = lcd->getButtons(); switch(throttleState) { case THROTTLE_STATE_RUN: switch(button) { @@ -104,6 +75,12 @@ void LCDThrottle::run() { speed = 0; updateDisplay(); break; + case KEYS_LONG_SELECT: + // This will toggle power. + power_state = !power_state; + sendPowerCommand(power_state); + break; + default: break; // Do nothing. @@ -116,9 +93,16 @@ void LCDThrottle::run() { } // switch(throttleState) } +void LCDThrottle::sendPowerCommand(bool on) { + sprintf(command, ""); + sprintf(command, on == true ? "1" : "0"); + Serial.println(command); + SerialCommand::parse(command); + +} + void LCDThrottle::increaseSpeed() { int tmp_notch; - //int maxnotch; if (displayMode == DISPLAY_MODE_NORMAL) { // in DISPLAY_MODE_NORMAL, notch is 0->maxnotch. // since this is the "increase" function we never have to worry about @@ -172,117 +156,6 @@ void LCDThrottle::sendThrottleCommand() { Serial.println(command); SerialCommand::parse(command); } -/* -int LCDThrottle::checkButtons() { -#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) - // OSEPP LCDKeypad - int buttons = lcd.button(); - if (buttons != -1) { - debounceButton(buttons); - //Serial.print("Button Check = "); - //Serial.println(buttons); - return(buttons); - } else { - return(KEYS_NONE); - } -#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) - // ADAFRUIT RGBLCD - uint8_t buttons = lcd.readButtons(); - if (buttons & BUTTON_UP) { - return(KEYS_UP); - } - if (buttons & BUTTON_DOWN) { - return(KEYS_DOWN); - } - if (buttons & BUTTON_LEFT) { - return(KEYS_LEFT); - } - if (buttons & BUTTON_RIGHT) { - return(KEYS_RIGHT); - } - if (buttons & BUTTON_SELECT) { - return(KEYS_SELECT); - } - return(KEYS_NONE); -#endif -} -*/ - -/** getButton() - * Translates the different LCD button values - * to a common set - */ -int LCDThrottle::getButton() { -#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) - // OSEPP LCDKeypad - return(lcd.button()); -#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) - // ADAFRUIT RGBLCD - uint8_t buttons = lcd.readButtons(); - if (buttons & BUTTON_UP) { - return(KEYS_UP); - } - if (buttons & BUTTON_DOWN) { - return(KEYS_DOWN); - } - if (buttons & BUTTON_LEFT) { - return(KEYS_LEFT); - } - if (buttons & BUTTON_RIGHT) { - return(KEYS_RIGHT); - } - if (buttons & BUTTON_SELECT) { - return(KEYS_SELECT); - } - return(KEYS_NONE); -#endif -} - -int LCDThrottle::debounceButtons() { - int button = getButton(); - static long startDebounce; - static int keyval; - int retv; - if (throttleState == THROTTLE_STATE_RUN) { - if (button == KEYS_NONE) { - return(KEYS_NONE); - } else { - Serial.println("Raw Key: " + String(button)); - startDebounce = millis(); - keyval = button; - throttleState = THROTTLE_STATE_DEBOUNCE; - } - } else { - // actively debouncing... - if (button == KEYS_NONE) { - Serial.println("Debounced Key: " + String(keyval)); - // Debounce complete. Decide if it's long or not. - throttleState = THROTTLE_STATE_RUN; - retv = keyval; - keyval = KEYS_NONE; - if (millis() - startDebounce > 2000) { - Serial.println("2 second button press! Val = " + String(retv)); - // Longer than 2 second hold - switch(retv) { - case KEYS_RIGHT: - return(KEYS_LONG_RIGHT); - case KEYS_UP: - return(KEYS_LONG_UP); - case KEYS_DOWN: - return(KEYS_LONG_DOWN); - case KEYS_LEFT: - return(KEYS_LONG_LEFT); - case KEYS_SELECT: - return(KEYS_LONG_SELECT); - default: - return(KEYS_NONE); - } - } else { - return(retv); - } // long key - } // keys_none - } // throttle state == RUN -} // Display Modes... @@ -292,19 +165,20 @@ void LCDThrottle::updateDisplay() { // SWITCHER: Speed/Direction together // (something useful) // <------0------> - lcd.clear(); - //lcd.setCursor(0,1); + lcd->clear(); // Draw the line. - sprintf(display[0], "Throttle: %04d", cab); - sprintf(display[1], "<------0------>"); - //lcd.print("<------0------>"); + sprintf(display[0], "Loco: %04d", cab); + if (power_state == true) { + sprintf(display[1], "<------0------>"); + } else { + sprintf(display[1], "Track Power Off"); + } + lcd->updateDisplay(display[0], display[1]); + Serial.println("D0:" + String(display[0])); + Serial.println("D1:" + String(display[1])); // Figure out where to put the cursor - lcd.setCursor(0,0); lcd.print(display[0]); - lcd.setCursor(0,1); lcd.print(display[1]); - Serial.println("D0:" + String(display[0])); - Serial.println("D1:" + String(display[1])); if (notch == 0) { - lcd.setCursor(7,1); + lcd->setCursor(7,1); } else { int tmp_notch = notch; if (tmp_notch == 0) { @@ -313,9 +187,13 @@ void LCDThrottle::updateDisplay() { tmp_notch = (dir == FORWARD ? tmp_notch + 7 : 7 - tmp_notch); } Serial.println("S=" + String(speed) + " N=" + String(notch) + " T=" + String(tmp_notch)); - lcd.setCursor(tmp_notch, 1); + lcd->setCursor(tmp_notch, 1); + } + if (power_state == true) { + lcd->blink(); + } else { + lcd->noBlink(); } - lcd.blink(); break; case DISPLAY_MODE_NORMAL: @@ -323,25 +201,25 @@ void LCDThrottle::updateDisplay() { // NORMAL: Speed + Direction // <-- --> // 0--------------+ - lcd.clear(); - lcd.setCursor(0,0); + lcd->clear(); if (dir == REVERSE) { sprintf(display[0], "<--- Loco: %04d", cab); - //lcd.print("<--- "); } else { sprintf(display[0], "Loco: %04d --->", cab); - //lcd.print(" DIR --->"); } - sprintf(display[1], "0 "); - if (speed > 0) { - for (int i = 0; i < notch-1; i++) { - display[1][i+1] = '-'; + if (power_state == true) { + sprintf(display[1], "0 "); + if (speed > 0) { + for (int i = 0; i < notch-1; i++) { + display[1][i+1] = '-'; + } + display[1][notch] = '|'; + display[1][notch+1] = 0; } - display[1][notch] = '|'; - display[1][notch+1] = 0; + } else { + sprintf(display[1], "Track Power Off"); } - lcd.setCursor(0,0); lcd.print(display[0]); - lcd.setCursor(0,1); lcd.print(display[1]); + lcd->updateDisplay(display[0], display[1]); Serial.println("D0:" + String(display[0])); Serial.println("D1:" + String(display[1])); break; diff --git a/DCCpp_Uno/LCDThrottle.h b/DCCpp_Uno/LCDThrottle.h index 3a3f7a1..8da4af6 100644 --- a/DCCpp_Uno/LCDThrottle.h +++ b/DCCpp_Uno/LCDThrottle.h @@ -1,19 +1,13 @@ #ifndef LCD_THROTTLE_H #define LCD_THROTTLE_H -// DCC++ Throttle using the buttons on a 16x2 + 5 button Display Shield +#include "LCD.h" -// LCD Display Types: -// -// 0 = OSEPP LCDKeypad -// 1 = Adafruit RGB LCD -#define LCD_DISPLAY_TYPE_OSEPP 0 -#define LCD_DISPLAY_TYPE_ADAFRUIT 1 -#define LCD_DISPLAY_TYPE LCD_DISPLAY_TYPE_OSEPP +// DCC++ Throttle using the buttons on a 16x2 + 5 button Display Shield #define DISPLAY_MODE_NORMAL 1 #define DISPLAY_MODE_SWITCHER 2 -#define DISPLAY_MODE DISPLAY_MODE_SWITCHER +#define DISPLAY_MODE DISPLAY_MODE_NORMAL // Throttle Directions #define FORWARD 1 @@ -28,23 +22,24 @@ class LCDThrottle { int dir; int displayMode; int notch; - char **display; - + LCD *lcd; + bool power_state; + public: - static LCDThrottle *getThrottle(int r, int c, char** d); - //void begin(LCDKeypad *lcd); + static LCDThrottle *getThrottle(int r, int c); void run(); protected: LCDThrottle(int reg, int cab); void sendThrottleCommand(); - //int checkButtons(); + void sendPowerCommand(bool on); int debounceButtons(); int getButton(); void increaseSpeed(); void decreaseSpeed(); void updateDisplay(); + }; #endif // LCD_THROTTLE_H From 5dc2f0469b6cb61ea148aacb7d71c863bc353144 Mon Sep 17 00:00:00 2001 From: TwinDad Date: Tue, 31 Jan 2017 13:50:11 -0500 Subject: [PATCH 06/21] Updating LCD class --- DCCpp_Uno/Config.h | 4 +- DCCpp_Uno/DCCpp_Uno.ino | 2 +- DCCpp_Uno/Inglenook.cpp | 309 -------------------------------------- DCCpp_Uno/LCD.cpp | 174 +++++++++++++++++++++ DCCpp_Uno/LCD.h | 50 ++++++ DCCpp_Uno/LCDThrottle.cpp | 195 +++++++++++++++++++++++- DCCpp_Uno/LCDThrottle.h | 9 ++ DCCpp_Uno/WiThrottle.cpp | 3 + 8 files changed, 428 insertions(+), 318 deletions(-) delete mode 100644 DCCpp_Uno/Inglenook.cpp create mode 100644 DCCpp_Uno/LCD.cpp create mode 100644 DCCpp_Uno/LCD.h diff --git a/DCCpp_Uno/Config.h b/DCCpp_Uno/Config.h index 968113a..bf410b2 100644 --- a/DCCpp_Uno/Config.h +++ b/DCCpp_Uno/Config.h @@ -14,7 +14,7 @@ Part of DCC++ BASE STATION for the Arduino // 0 = ARDUINO MOTOR SHIELD (MAX 18V/2A PER CHANNEL) // 1 = POLOLU MC33926 MOTOR SHIELD (MAX 28V/3A PER CHANNEL) -#define MOTOR_SHIELD_TYPE 0 +#define MOTOR_SHIELD_TYPE 1 ///////////////////////////////////////////////////////////////////////////////////// // @@ -60,7 +60,7 @@ Part of DCC++ BASE STATION for the Arduino // ENABLE THE WITHROTTLE INTERFACE // -#define WITHROTTLE_SUPPORT 1 +#define WITHROTTLE_SUPPORT 0 ///////////////////////////////////////////////////////////////////////////////////// // diff --git a/DCCpp_Uno/DCCpp_Uno.ino b/DCCpp_Uno/DCCpp_Uno.ino index b4099cd..89a00c2 100644 --- a/DCCpp_Uno/DCCpp_Uno.ino +++ b/DCCpp_Uno/DCCpp_Uno.ino @@ -245,7 +245,7 @@ void setup(){ Serial.flush(); #if (LCD_THROTTLE == 1) - lcdThrottle = LCDThrottle::getThrottle(1, 32); + lcdThrottle = LCDThrottle::getThrottle(1, 1141); #endif #if (INGLENOOK_GAME == 1) diff --git a/DCCpp_Uno/Inglenook.cpp b/DCCpp_Uno/Inglenook.cpp deleted file mode 100644 index 5a74fd3..0000000 --- a/DCCpp_Uno/Inglenook.cpp +++ /dev/null @@ -1,309 +0,0 @@ -#include -#include - -#include "Inglenook.h" - -// Game State Machine States -#define STATE_IDLE 0 -#define STATE_MENUS 1 -#define STATE_ACTION 2 -#define STATE_BUILD 3 -int game_state = STATE_MENUS; - -// Index used for displaying the cars in the Build Train state. -int car_index; - -// Array holding the current sorting of the train to be built. -int train[TRAIN_LENGTH]; - -// Menu subsystem "stuff" -void menuUseEvent(MenuUseEvent e); -void menuChangeEvent(MenuChangeEvent e); -MenuBackend *menu = new MenuBackend(menuUseEvent, menuChangeEvent); - -static char display[2][17]; - -static InglenookGame *thegame = NULL; - - -static InglenookGame *InglenookGame::getTheGame() { - if (thegame == NULL) { - thegame = new InglenookGame(); - } - return(thegame); -} - -InglenookGame::InglenookGame() { - lcd = new LCD(); - menuSetup(); -} - -void InglenookGame::menuSetup() { - //Serial.println("Setting up menu..."); - MenuItem *miBuild = new MenuItem("Build Train"); - MenuItem *miList = new MenuItem("List Cars"); - MenuItem *miCarList[7] = { - new MenuItem("Car 0"), - new MenuItem("Car 1"), - new MenuItem("Car 2"), - new MenuItem("Car 3"), - new MenuItem("Car 4"), - new MenuItem("Car 5"), - new MenuItem("Car 6") - }; - menu->getRoot().add(*miBuild); - miBuild->add(*miList); - miList->addRight(*miCarList[0]); - for (int i = 0; i < NUM_CARS-2; i++) { - miCarList[i]->add(*miCarList[i+1]); - } -} - -static void InglenookGame::begin() { - randomSeed(analogRead(44)); - menuSetup(); - lcd->begin(); - printWelcome(); - game_state = STATE_IDLE; - car_index = -1; -} - -void InglenookGame::printWelcome() { - lcd->clear(); - sprintf(display[0], "INGLENOOK GAME"); - sprintf(display[1], "Select to start"); - lcd->updateDisplay(display[0], display[1]); -} - -void InglenookGame::play() { - // Read and respond to the buttons - //int buttons = checkButtons(); - lcd->run(); - int buttons = lcd->getButtons(); - switch(game_state) { - case STATE_IDLE: - if (buttons == KEYS_SELECT) { - //Serial.println("Starting game..."); - game_state = STATE_MENUS; - menu->moveDown(); - doMenuDisplay(); - } - break; - - - case STATE_MENUS: - switch(buttons) { - case KEYS_UP: - if (menu->getCurrent() == "Build Trains") { - game_state == STATE_IDLE; - printWelcome(); - } else { - menu->moveUp(); - doMenuDisplay(); - } - break; - case KEYS_DOWN: - menu->moveDown(); - doMenuDisplay(); - break; - case KEYS_LEFT: - if (menu->getCurrent() == "Build Trains") { - game_state == STATE_IDLE; - printWelcome(); - } else { - menu->moveLeft(); - doMenuDisplay(); - } - break; - case KEYS_RIGHT: - menu->moveRight(); - doMenuDisplay(); - break; - case KEYS_SELECT: - if (menu->getCurrent() == "List Cars") { - menu->moveRight(); // for this one "use" == "move right" - doMenuDisplay(); - } else { - menu->use(); - } - break; - // Add other combinations here. - } // switch(buttons) - break; - - case STATE_BUILD: - switch(buttons) { - case KEYS_NONE: - break; - case KEYS_LEFT: - case KEYS_SELECT: - game_state = STATE_MENUS; - doMenuDisplay(); - break; - case KEYS_DOWN: - case KEYS_RIGHT: - if (car_index >= 3) { - car_index = 3; - } else { - car_index++; - } - doListTrain(car_index); - break; - case KEYS_UP: - if (car_index > -1) { - car_index--; - } - if (car_index == -1) { - doDisplayTrain(); - } else { - doListTrain(car_index); - } - break; - } // switch(buttons) - } // switch(game_state) -} - -void InglenookGame::buildTrain() { - bool cars_used[NUM_CARS] = { false, false, false, false, false, false, false, false }; - int car = 0; - bool found_one = false; - game_state = STATE_BUILD; - for (int i = 0; i < TRAIN_LENGTH; i++) { - found_one = false; - do { - car = random(0, NUM_CARS) & 0xFFFF; - //Serial.println("found " + String(car)); - if (cars_used[car] == false) { - // Car is not used. Use it. - cars_used[car] = true; - train[i] = car; - found_one = true; - //Serial.println("using " + String(car)); - } // if - } while (!found_one); - //Serial.println("i = " + String(i) + " car = " + String(car)); - } // for -} - -void InglenookGame::updateDisplay(char *row1, char *row2) { - sprintf(display[0], row1); - sprintf(display[1], row2); - lcd->updateDisplay(display[0], display[1]); -} - -void InglenookGame::doMenuDisplay() { - lcd->clear(); - - if (menu->getCurrent() == "Build Train") { - sprintf(display[0], "Build Train"); - sprintf(display[1], "List Cars"); - } - if (menu->getCurrent() == "List Cars") { - sprintf(display[0], "List Cars"); - sprintf(display[1], ""); - } - if (menu->getCurrent() == "Car 0") { - sprintf(display[0],"%s%s", "1:",carnames[0].c_str()); - sprintf(display[1],"%s%s", "2:",carnames[1].c_str()); - } - if (menu->getCurrent() == "Car 1") { - sprintf(display[0],"%s%s", "2:",carnames[1].c_str()); - sprintf(display[1],"%s%s", "3:",carnames[2].c_str()); - } - if (menu->getCurrent() == "Car 2") { - sprintf(display[0],"%s%s", "3:",carnames[2].c_str()); - sprintf(display[1],"%s%s", "4:",carnames[3].c_str()); - } - if (menu->getCurrent() == "Car 3") { - sprintf(display[0],"%s%s", "4:",carnames[3].c_str()); - sprintf(display[1],"%s%s", "5:",carnames[4].c_str()); - } - if (menu->getCurrent() == "Car 4") { - sprintf(display[0],"%s%s", "5:",carnames[4].c_str()); - sprintf(display[1],"%s%s", "6:",carnames[5].c_str()); - } - if (menu->getCurrent() == "Car 5") { - sprintf(display[0],"%s%s", "6:",carnames[5].c_str()); - sprintf(display[1],"%s%s", "7:",carnames[6].c_str()); - } - if ((menu->getCurrent() == "Car 6") || (menu->getCurrent() == "Car 7")) { - sprintf(display[0],"%s%s", "7:",carnames[6].c_str()); - sprintf(display[1],"%s%s", "8:",carnames[7].c_str()); - } - lcd->updateDisplay(display[0], display[1]); - lcd->setCursor(0,0); - lcd->blink(); -} - -void InglenookGame::doDisplayTrain() { - // DEBUG: - //Serial.println("BUILD THIS TRAIN"); - //for (int i = 0; i < TRAIN_LENGTH; i++) { - //Serial.print(String(i+1) + ": " + carnames[train[i]] + "; "); - //} - //Serial.println(""); - // END DEBUG - lcd->clear(); - sprintf(display[0], "BUILD THIS TRAIN"); - sprintf(display[1], ""); - for (int i = 0; i < TRAIN_LENGTH; i++) { - sprintf(display[1], "%s%d ", display[1], train[i]+1); - } - updateDisplay(display[0], display[1]); - lcd->noBlink(); -} - -void InglenookGame::doListTrain(int car) { - String s1, s2; - switch(car) { - case 1: - s1 = "2:" + carnames[train[1]]; - s2 = "3:" + carnames[train[2]]; - break; - case 2: - s1 = "3:" + carnames[train[2]]; - s2 = "4:" + carnames[train[3]]; - break; - case 3: - s1 = "4:" + carnames[train[3]]; - s2 = "5:" + carnames[train[4]]; - break; - case 0: - default: - s1 = "1:" + carnames[train[0]]; - s2 = "2:" + carnames[train[1]]; - break; - } // switch(car) - lcd->clear(); - sprintf(display[0], s1.c_str()); - sprintf(display[1], s2.c_str()); - lcd->updateDisplay(display[0], display[1]); -} - -void menuChangeEvent(MenuChangeEvent changed) { - // Nothing to do here... -} - -void menuUseEvent(MenuUseEvent used) { - InglenookGame *thegame = InglenookGame::getTheGame(); - //Serial.print("Menu use "); - //Serial.println(used.item.getName()); - if (used.item == "Build Train") { - //Serial.println("Building Train..."); - thegame->buildTrain(); - thegame->doDisplayTrain(); - /* - for (int i = 0; i < TRAIN_LENGTH; i++) { - Serial.println("Car " + String(i) + ": " + carnames[train[i]]); - } - */ - } - if (used.item == "List Cars") { - //Serial.println("List Cars..."); - for (int i = 0; i < NUM_CARS; i++) { - //Serial.println("Car " + String(i) + ": " + carnames[i]); - } - } -} - - diff --git a/DCCpp_Uno/LCD.cpp b/DCCpp_Uno/LCD.cpp new file mode 100644 index 0000000..3f593de --- /dev/null +++ b/DCCpp_Uno/LCD.cpp @@ -0,0 +1,174 @@ +#include +#include +#include "LCD.h" + +#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) +#include +#include +#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) +#include +#include +#else +#error CANNOT COMPILE -- INVALID LCD LIBRARY SELECTED +#endif + + +#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) +static LCDKeypad lcd = LCDKeypad(); +#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) +static Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield(); +#endif + +#define LCD_NUM_ROWS 2 +#define LCD_NUM_COLS 16 +static char display[LCD_NUM_ROWS][LCD_NUM_COLS+1]; + +#define DISPLAY_STATE_RUN 0 +#define DISPLAY_STATE_DEBOUNCE 1 +#define DISPLAY_STATE_DEBOUNCE_COMPLETE 2 + +#define LONG_PRESS_MS 2000 + +LCD::LCD() { + // Nothing to do ... yet ... + buttonVal = KEYS_NONE; + displayState = DISPLAY_STATE_RUN; +} + +void LCD::begin() { + lcd.begin(LCD_NUM_COLS, LCD_NUM_ROWS); +} + +void LCD::clear() { + lcd.clear(); +} + +void LCD::run() { + int bv = debounceButtons(); + // Not sure yet what needs done here. For now it's optional. + switch(displayState) { + case DISPLAY_STATE_DEBOUNCE_COMPLETE: + buttonVal = bv; + displayState = DISPLAY_STATE_RUN; + break; + case DISPLAY_STATE_RUN: + case DISPLAY_STATE_DEBOUNCE: + default: + break; + } +} + +void LCD::setCursor(int c, int r) { + lcd.setCursor(c, r); +} + +void LCD::blink() { + lcd.blink(); +} + +void LCD::noBlink() { + lcd.noBlink(); +} + +void LCD::cursor() { + lcd.cursor(); +} + +void LCD::noCursor() { + lcd.noCursor(); +} + +void LCD::updateDisplay(char *row1, char *row2) { + lcd.clear(); + sprintf(display[0],"%s", row1); + sprintf(display[1],"%s", row2); + lcd.setCursor(0,0); lcd.print(display[0]); + lcd.setCursor(0,1); lcd.print(display[1]); +} + + +int LCD::getButtons() { + int bv = buttonVal; + buttonVal = KEYS_NONE; + return(bv); +} + +int LCD::getButton() { +#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) + // OSEPP LCDKeypad + int b = lcd.button(); + //Serial.print(String(b) + " "); + return(b); +#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) + // ADAFRUIT RGBLCD + uint8_t buttons = lcd.readButtons(); + if (buttons & BUTTON_UP) { + return(KEYS_UP); + } + if (buttons & BUTTON_DOWN) { + return(KEYS_DOWN); + } + if (buttons & BUTTON_LEFT) { + return(KEYS_LEFT); + } + if (buttons & BUTTON_RIGHT) { + return(KEYS_RIGHT); + } + if (buttons & BUTTON_SELECT) { + return(KEYS_SELECT); + } + return(KEYS_NONE); +#endif +} + +int LCD::debounceButtons() { + int button = getButton(); + static long startDebounce; + static int keyval; + int retv; + if (displayState == DISPLAY_STATE_RUN) { + if (button != KEYS_NONE) { + Serial.println("Raw Key: " + String(button)); + startDebounce = millis(); + keyval = button; + displayState = DISPLAY_STATE_DEBOUNCE; + } + // Always return KEYS_NONE from this state + return(KEYS_NONE); + + } else if (displayState == DISPLAY_STATE_DEBOUNCE) { + // actively debouncing... + if ((button != KEYS_NONE) && ((millis() - startDebounce) > LONG_PRESS_MS)) { + // Longer than 2 second hold + displayState = DISPLAY_STATE_DEBOUNCE_COMPLETE; + retv = keyval; + keyval = KEYS_NONE; + Serial.println("2 second button press! Val = " + String(retv)); + switch(retv) { + case KEYS_RIGHT: + return(KEYS_LONG_RIGHT); + case KEYS_UP: + return(KEYS_LONG_UP); + case KEYS_DOWN: + return(KEYS_LONG_DOWN); + case KEYS_LEFT: + return(KEYS_LONG_LEFT); + case KEYS_SELECT: + return(KEYS_LONG_SELECT); + default: + return(KEYS_NONE); + } + } else if (button == KEYS_NONE) { + // Short press. + Serial.println("Short Press. Debounced Key: " + String(keyval)); + // Debounce complete. Decide if it's long or not. + displayState = DISPLAY_STATE_DEBOUNCE_COMPLETE; + retv = keyval; + keyval = KEYS_NONE; + return(retv); + } else { + // Not finished debouncing yet. + return(KEYS_NONE); + } // KEYS_NONE + } // throttle state == RUN +} diff --git a/DCCpp_Uno/LCD.h b/DCCpp_Uno/LCD.h new file mode 100644 index 0000000..e52e18a --- /dev/null +++ b/DCCpp_Uno/LCD.h @@ -0,0 +1,50 @@ +#ifndef LCD_H +#define LCD_H + +// LCD Display Types: +// +// 0 = OSEPP LCDKeypad +// 1 = Adafruit RGB LCD +#define LCD_DISPLAY_TYPE_OSEPP 0 +#define LCD_DISPLAY_TYPE_ADAFRUIT 1 +#define LCD_DISPLAY_TYPE LCD_DISPLAY_TYPE_ADAFRUIT + + +// Define generic button names for multi-library compatibility +// Return values from getButtons(); +#define KEYS_NONE -1 +#define KEYS_RIGHT 0 +#define KEYS_UP 1 +#define KEYS_DOWN 2 +#define KEYS_LEFT 3 +#define KEYS_SELECT 4 +#define KEYS_LONG_RIGHT 128 +#define KEYS_LONG_UP 129 +#define KEYS_LONG_DOWN 130 +#define KEYS_LONG_LEFT 131 +#define KEYS_LONG_SELECT 132 + + +class LCD { + private: + int displayState; + public: + int buttonVal; + LCD(); + void begin(); + void run(); + int getButtons(); + void updateDisplay(char *row1, char *row2); + void clear(); + void setCursor(int c, int r); + void blink(); + void noBlink(); + void cursor(); + void noCursor(); + protected: + int getButton(); + int debounceButtons(); + +}; + +#endif // LCD_H diff --git a/DCCpp_Uno/LCDThrottle.cpp b/DCCpp_Uno/LCDThrottle.cpp index cf108b1..23811fb 100644 --- a/DCCpp_Uno/LCDThrottle.cpp +++ b/DCCpp_Uno/LCDThrottle.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include "SerialCommand.h" #include "LCDThrottle.h" @@ -8,8 +10,10 @@ #define MAX_NOTCH_NORMAL 15 #define MAX_NOTCH_SWITCHER 7 -#define THROTTLE_STATE_RUN 0 -#define THROTTLE_STATE_DEBOUNCE 1 +#define THROTTLE_STATE_RUN 0 +#define THROTTLE_STATE_DEBOUNCE 1 +#define THROTTLE_STATE_MENUS 2 +#define THROTTLE_STATE_MENU_ACTION 3 // MAX_COMMAND_LENGTH is defined in SerialCommand.h #define MAX_COMMAND_LENGTH 30 @@ -19,25 +23,33 @@ static char display[2][17]; static LCDThrottle *lcdThrottle = NULL; +// Menu subsystem "stuff" +static void menuUseEvent(MenuUseEvent e); +static void menuChangeEvent(MenuChangeEvent e); +static MenuBackend *menu = new MenuBackend(menuUseEvent, menuChangeEvent); + static LCDThrottle *LCDThrottle::getThrottle(int r, int c) { if (lcdThrottle == NULL) { lcdThrottle = new LCDThrottle(r, c); } - return(lcdThrottle); } LCDThrottle::LCDThrottle(int reg, int cab) { lcd = new LCD(); lcd->begin(); - throttleState = THROTTLE_STATE_RUN; + EEPROM_GetAll(); + throttleState = THROTTLE_STATE_MENUS; this->reg = reg; - this->cab = cab; + if (cab != 0) { + this->cab = cab; + } notch = 0; speed = 0; dir = FORWARD; - displayMode = DISPLAY_MODE; + //displayMode = DISPLAY_MODE; power_state = false; + menuSetup(); updateDisplay(); } @@ -79,6 +91,9 @@ void LCDThrottle::run() { // This will toggle power. power_state = !power_state; sendPowerCommand(power_state); + throttleState = THROTTLE_STATE_MENUS; + menu->moveDown(); + doMenuDisplay(); break; default: @@ -90,9 +105,122 @@ void LCDThrottle::run() { case THROTTLE_STATE_DEBOUNCE: // ??? break; + + case THROTTLE_STATE_MENUS: + switch(button) { + case KEYS_UP: + menu->moveUp(); + doMenuDisplay(); + break; + case KEYS_DOWN: + menu->moveUp(); + doMenuDisplay(); + break; + case KEYS_LEFT: + break; + case KEYS_RIGHT: + break; + case KEYS_SELECT: + throttleState = THROTTLE_STATE_MENU_ACTION; + doMenuAction(button); + break; + case KEYS_LONG_SELECT: + throttleState = THROTTLE_STATE_RUN; + break; + + } + break; + + case THROTTLE_STATE_MENU_ACTION: + switch(button) { + case KEYS_SELECT: + EEPROM_StoreAll(); + throttleState = THROTTLE_STATE_MENUS; + doMenuDisplay(); + break; + case KEYS_UP: + case KEYS_DOWN: + case KEYS_LEFT: + case KEYS_RIGHT: + doMenuAction(button); + break; + case KEYS_LONG_SELECT: + EEPROM_StoreAll(); + throttleState = THROTTLE_STATE_RUN; + button = KEYS_NONE; + break; + } + break; + + default: + break; + } // switch(throttleState) } +void LCDThrottle::menuSetup() { + MenuItem *miRun = new MenuItem("Use Throttle"); + MenuItem *miAddr = new MenuItem("Set Address"); + MenuItem *miDisp = new MenuItem("Select Display"); + menu->getRoot().add(*miRun); + miRun->add(*miAddr); + miAddr->add(*miDisp); +} + +void LCDThrottle::doMenuDisplay() { + if (menu->getCurrent() == "Use Throttle") { + lcd->updateDisplay("THROTTLE MENU:", "Use Throttle"); + } + if (menu->getCurrent() == "Set Address") { + lcd->updateDisplay("THROTTLE MENU:", "Set Address"); + } + if (menu->getCurrent() == "Select Display") { + lcd->updateDisplay("THROTTLE MENU:", "Select Display"); + } +} + +void LCDThrottle::doMenuAction(int button) { + static int incval = 1; + static byte incpos = 3; + Serial.println("doMenuAction(" + String(button) + ")"); + + if (menu->getCurrent() == "Select Display") { + if (button == KEYS_UP || button == KEYS_DOWN) { + if (displayMode == DISPLAY_MODE_NORMAL) { + displayMode = DISPLAY_MODE_SWITCHER; + } else { + displayMode = DISPLAY_MODE_NORMAL; + } + } + lcd->updateDisplay("Select Display:", + displayMode == DISPLAY_MODE_NORMAL ? + "Standard" : "Switcher"); + } // Select Display + + if (menu->getCurrent() == "Set Address") { + if (button == KEYS_SELECT) { + incval = 1; + incpos = 3; + } else if (button == KEYS_UP) { + if (cab == 9999) { cab = 0; } + else { cab = cab + incval; } + } else if (button == KEYS_DOWN) { + if (cab == 0) { cab = 9999; } + else { cab = cab - incval; } + } else if (button == KEYS_LEFT) { + if (incval < 1000) { incval *= 10; } + if (incpos > 0) { incpos -= 1; } + } else if (button == KEYS_RIGHT) { + if (incval > 1) { incval /= 10; } + if (incpos < 3) { incpos += 1; } + } + sprintf(display[1], "%04d", cab); + lcd->updateDisplay("Set Address:", display[1]); + lcd->setCursor(incpos, 1); + lcd->cursor(); + } // Set Address +} + void LCDThrottle::sendPowerCommand(bool on) { sprintf(command, ""); sprintf(command, on == true ? "1" : "0"); @@ -225,3 +353,58 @@ void LCDThrottle::updateDisplay() { break; } } + +//--------------------------------------------------------------- +// MenuBackend support functions + +static void menuChangeEvent(MenuChangeEvent changed) { + // Update the display to reflect the current menu state + // For now we'll use serial output. + Serial.print("Menu change "); + Serial.print(changed.from.getName()); + Serial.print(" -> "); + Serial.println(changed.to.getName()); +} + +static void menuUseEvent(MenuUseEvent used) { + //Serial.print("Menu use "); + //Serial.println(used.item.getName()); +} + +//--------------------------------------------------------------- +// EEPROM Interface Functions + +// Memory Locations (byte address) +#define EEPROM_BASE 0 +#define EEPROM_DISPLAY (EEPROM_BASE) +#define EEPROM_ADDRESS (EEPROM_DISPLAY + sizeof(byte)) +#define EEPROM_NEXT (EEPROM_ADDRESS + sizeof(int)) + +void LCDThrottle::EEPROM_StoreAll() { + EEPROM_StoreDisplay(); + EEPROM_StoreAddress(); +} + +void LCDThrottle::EEPROM_GetAll() { + EEPROM_GetDisplay(); + EEPROM_GetAddress(); +} + +void LCDThrottle::EEPROM_StoreDisplay() { + byte oz = (displayMode & 0xFF); + EEPROM.put(EEPROM_DISPLAY, oz); +} + +void LCDThrottle::EEPROM_GetDisplay() { + byte oz; + EEPROM.get(EEPROM_DISPLAY, oz); + displayMode = oz; +} + +void LCDThrottle::EEPROM_StoreAddress() { + EEPROM.put(EEPROM_ADDRESS, cab); +} + +void LCDThrottle::EEPROM_GetAddress() { + EEPROM.get(EEPROM_ADDRESS, cab); +} diff --git a/DCCpp_Uno/LCDThrottle.h b/DCCpp_Uno/LCDThrottle.h index 8da4af6..3ec0dce 100644 --- a/DCCpp_Uno/LCDThrottle.h +++ b/DCCpp_Uno/LCDThrottle.h @@ -38,6 +38,15 @@ class LCDThrottle { void increaseSpeed(); void decreaseSpeed(); void updateDisplay(); + void menuSetup(); + void doMenuDisplay(); + void doMenuAction(int button); + void EEPROM_StoreAll(); + void EEPROM_GetAll(); + void EEPROM_StoreDisplay(); + void EEPROM_GetDisplay(); + void EEPROM_StoreAddress(); + void EEPROM_GetAddress(); }; diff --git a/DCCpp_Uno/WiThrottle.cpp b/DCCpp_Uno/WiThrottle.cpp index 449db13..5863bb1 100644 --- a/DCCpp_Uno/WiThrottle.cpp +++ b/DCCpp_Uno/WiThrottle.cpp @@ -153,6 +153,7 @@ static void WiThrottle::parseNCommand(char *s) { // Get the Name and store it somewhere... if needed. Serial.print("Name = "); Serial.println(String(s)); + // Reply * e.g. *10 for 10 seconds or *0 for no heartbeat expected return; } @@ -304,6 +305,8 @@ int WiThrottle::getDirForCab(int c) { void WiThrottle::sendIntroMessage(void) { INTERFACE.println("VN2.0"); + INTERFACE.println("RL0"); + INTERFACE.println("PPA0"); // PPA0=off PPA1=on PPA2=unknown // TODO: Send roster here #if COMM_TYPE == 1 INTERFACE.print("PW"); From 0be6638ad8587940d6b8811f54a843bea75e974f Mon Sep 17 00:00:00 2001 From: TwinDad Date: Wed, 1 Feb 2017 16:05:41 -0500 Subject: [PATCH 07/21] Working copy. Fixed most things, including getting the LCD to work (duh). --- DCCpp_Uno/Config.h | 7 - DCCpp_Uno/DCCpp_Uno.ino | 23 +- DCCpp_Uno/EEStore.cpp | 16 + DCCpp_Uno/Inglenook.h | 23 +- DCCpp_Uno/LCD.cpp | 115 ++++--- DCCpp_Uno/LCD.h | 21 +- DCCpp_Uno/LCDThrottle.cpp | 621 +++++++++++++++++++++++++++++++------- DCCpp_Uno/LCDThrottle.h | 39 ++- 8 files changed, 652 insertions(+), 213 deletions(-) diff --git a/DCCpp_Uno/Config.h b/DCCpp_Uno/Config.h index bf410b2..19ecb9f 100644 --- a/DCCpp_Uno/Config.h +++ b/DCCpp_Uno/Config.h @@ -62,13 +62,6 @@ Part of DCC++ BASE STATION for the Arduino #define WITHROTTLE_SUPPORT 0 -///////////////////////////////////////////////////////////////////////////////////// -// -// ENABLE THE INGLENOOK SIDINGS GAME ADD-ON. REQUIRES AN LCD WITH 5 BUTTONS. -// - -#define INGLENOOK_GAME 0 - ///////////////////////////////////////////////////////////////////////////////////// // // ENABLE THE LCD THROTTLE. REQUIRES AN LCD WITH 5 BUTTONS. diff --git a/DCCpp_Uno/DCCpp_Uno.ino b/DCCpp_Uno/DCCpp_Uno.ino index 89a00c2..34e16d2 100644 --- a/DCCpp_Uno/DCCpp_Uno.ino +++ b/DCCpp_Uno/DCCpp_Uno.ino @@ -177,9 +177,6 @@ DCC++ BASE STATION is configured through the Config.h file that contains all use #include "EEStore.h" #include "Config.h" #include "Comm.h" -#if (INGLENOOK_GAME == 1) -#include "Inglenook.h" -#endif #if (LCD_THROTTLE == 1) #include "LCDThrottle.h" #endif @@ -202,10 +199,6 @@ volatile RegisterList progRegs(2); // create a shorter list CurrentMonitor mainMonitor(CURRENT_MONITOR_PIN_MAIN,""); // create monitor for current on Main Track CurrentMonitor progMonitor(CURRENT_MONITOR_PIN_PROG,""); // create monitor for current on Program Track -#if (INGLENOOK_GAME == 1) -InglenookGame *iGame; -#endif - #if (LCD_THROTTLE == 1) LCDThrottle *lcdThrottle; #endif @@ -229,10 +222,6 @@ void loop(){ Sensor::check(); // check sensors for activate/de-activate -#if (INGLENOOK_GAME == 1) - iGame->play(); -#endif - } // loop /////////////////////////////////////////////////////////////////////////////// @@ -244,14 +233,10 @@ void setup(){ Serial.begin(115200); // configure serial interface Serial.flush(); -#if (LCD_THROTTLE == 1) - lcdThrottle = LCDThrottle::getThrottle(1, 1141); -#endif - -#if (INGLENOOK_GAME == 1) - iGame = InglenookGame::getTheGame(); - iGame->begin(); -#endif + #if (LCD_THROTTLE == 1) + lcdThrottle = new LCDThrottle(); + lcdThrottle->begin(1); + #endif #ifdef SDCARD_CS pinMode(SDCARD_CS,OUTPUT); diff --git a/DCCpp_Uno/EEStore.cpp b/DCCpp_Uno/EEStore.cpp index a58d431..8c86587 100644 --- a/DCCpp_Uno/EEStore.cpp +++ b/DCCpp_Uno/EEStore.cpp @@ -13,6 +13,16 @@ Part of DCC++ BASE STATION for the Arduino #include "Sensor.h" #include "Outputs.h" #include +#if (LCD_THROTTLE == 1) +// NOTE: Because the turnout/sensor/output data is variable length, the +// LCDThrottle data MUST come first in the list of things to be read/written +// We only allocate space for them (using advance()) and don't actually +// read/write them, but if we don't do that, then their locations could +// move and cause problems. And I don't want to have to re-write all the +// sensor/turnout/output data every time the user changes between +// standard and switcher throttle display. +#include "LCDThrottle.h" +#endif // LCD_THROTTLE /////////////////////////////////////////////////////////////////////////////// @@ -32,6 +42,9 @@ void EEStore::init(){ } reset(); // set memory pointer to first free EEPROM space +#if (LCD_THROTTLE == 1) + advance(sizeof(struct LCDThrottleData)); +#endif Turnout::load(); // load turnout definitions Sensor::load(); // load sensor definitions Output::load(); // load output definitions @@ -54,6 +67,9 @@ void EEStore::clear(){ void EEStore::store(){ reset(); +#if (LCD_THROTTLE == 1) + advance(sizeof(LCDThrottleData)); +#endif Turnout::store(); Sensor::store(); Output::store(); diff --git a/DCCpp_Uno/Inglenook.h b/DCCpp_Uno/Inglenook.h index b0c78ea..04f2c7d 100644 --- a/DCCpp_Uno/Inglenook.h +++ b/DCCpp_Uno/Inglenook.h @@ -1,8 +1,6 @@ #ifndef INGLENOOK_H #define INGLENOOK_H -#include "LCD.h" - // NOTE: These are actually set by the rules of the game... // WARNING: There are several places (initialization, etc.) where // the value of these is assumed fixed at 5 and 8. @@ -22,25 +20,20 @@ const String carnames[NUM_CARS] = { // String names of cars on the layout (for d class InglenookGame { private: - LCD *lcd; + int car_index; public: - static InglenookGame *getTheGame(); + InglenookGame(); void begin(); - void play(); void buildTrain(void); - void doDisplayTrain(); + void doDisplayTrain(LCD *lcd, char *row1, char *row2); + int carIndex(); + void setCarIndex(int c); + void doMenuDisplay(LCD *lcd, char *row1, char *row2); + void doListTrain(LCD *lcd, char *row1, char *row2, int car); protected: - InglenookGame(); - void menuSetup(); - void printWelcome(); - int checkButtons(); - int debounceButton(int button); - void doMenuDisplay(); - void doListTrain(int car); - void updateDisplay(char *row1, char *row2); - + //void printWelcome(LCD *lcd, char *row1, char *row2); }; diff --git a/DCCpp_Uno/LCD.cpp b/DCCpp_Uno/LCD.cpp index 3f593de..12ab9de 100644 --- a/DCCpp_Uno/LCD.cpp +++ b/DCCpp_Uno/LCD.cpp @@ -1,61 +1,62 @@ #include #include + +// Include our own header with definitions and such. #include "LCD.h" +// Include the necessary libraries for the different display shields. #if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) #include #include #elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) #include -#include +#include // is this necessary? #else #error CANNOT COMPILE -- INVALID LCD LIBRARY SELECTED #endif - +// Create the local object of the display we are +// controlling. #if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) static LCDKeypad lcd = LCDKeypad(); #elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) static Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield(); #endif +// Define the size of the display. Pretty much assumes +// 16x2 but could be modded to change that. #define LCD_NUM_ROWS 2 #define LCD_NUM_COLS 16 -static char display[LCD_NUM_ROWS][LCD_NUM_COLS+1]; +// State machine for handling processing of the buttons. #define DISPLAY_STATE_RUN 0 #define DISPLAY_STATE_DEBOUNCE 1 #define DISPLAY_STATE_DEBOUNCE_COMPLETE 2 +#define DISPLAY_STATE_LONG_DEBOUNCE 3 +#define DISPLAY_STATE_LONG_DEBOUNCE_COMPLETE 4 +#define DISPLAY_STATE_LONG_DEBOUNCE_WAIT 5 -#define LONG_PRESS_MS 2000 - +// ctor LCD::LCD() { // Nothing to do ... yet ... - buttonVal = KEYS_NONE; - displayState = DISPLAY_STATE_RUN; } +// Setup function. Call from setup() void LCD::begin() { + buttonVal = KEYS_NONE; + displayState = DISPLAY_STATE_RUN; lcd.begin(LCD_NUM_COLS, LCD_NUM_ROWS); } -void LCD::clear() { - lcd.clear(); +// Run loop for processing the buttons. Call from loop() +void LCD::run() { + debounceButtons(); } -void LCD::run() { - int bv = debounceButtons(); - // Not sure yet what needs done here. For now it's optional. - switch(displayState) { - case DISPLAY_STATE_DEBOUNCE_COMPLETE: - buttonVal = bv; - displayState = DISPLAY_STATE_RUN; - break; - case DISPLAY_STATE_RUN: - case DISPLAY_STATE_DEBOUNCE: - default: - break; - } +//------------------------------------------------ +// Pass-through methods for handling the display +void LCD::clear() { + lcd.clear(); } void LCD::setCursor(int c, int r) { @@ -78,21 +79,29 @@ void LCD::noCursor() { lcd.noCursor(); } +//------------------------------------------------ +// Custom methods + +// Update the two lines of the display with these two strings. void LCD::updateDisplay(char *row1, char *row2) { lcd.clear(); - sprintf(display[0],"%s", row1); - sprintf(display[1],"%s", row2); - lcd.setCursor(0,0); lcd.print(display[0]); - lcd.setCursor(0,1); lcd.print(display[1]); + lcd.setCursor(0,0); lcd.print(row1); + lcd.setCursor(0,1); lcd.print(row2); } +// Get the current button value. int LCD::getButtons() { int bv = buttonVal; buttonVal = KEYS_NONE; return(bv); } +//------------------------------------------------ +// Private / Protected internal methods + +// Retrieve the hardware button value and translate +// it into one of our "standard" button values. int LCD::getButton() { #if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) // OSEPP LCDKeypad @@ -121,12 +130,14 @@ int LCD::getButton() { #endif } -int LCD::debounceButtons() { +// Debounce the button press and figure out if it's a long press. +void LCD::debounceButtons() { int button = getButton(); static long startDebounce; static int keyval; int retv; - if (displayState == DISPLAY_STATE_RUN) { + switch(displayState) { + case DISPLAY_STATE_RUN: if (button != KEYS_NONE) { Serial.println("Raw Key: " + String(button)); startDebounce = millis(); @@ -134,41 +145,51 @@ int LCD::debounceButtons() { displayState = DISPLAY_STATE_DEBOUNCE; } // Always return KEYS_NONE from this state - return(KEYS_NONE); - - } else if (displayState == DISPLAY_STATE_DEBOUNCE) { + break; + + case DISPLAY_STATE_DEBOUNCE: // actively debouncing... if ((button != KEYS_NONE) && ((millis() - startDebounce) > LONG_PRESS_MS)) { // Longer than 2 second hold displayState = DISPLAY_STATE_DEBOUNCE_COMPLETE; - retv = keyval; - keyval = KEYS_NONE; - Serial.println("2 second button press! Val = " + String(retv)); - switch(retv) { + switch(keyval) { case KEYS_RIGHT: - return(KEYS_LONG_RIGHT); + keyval = KEYS_LONG_RIGHT; break; case KEYS_UP: - return(KEYS_LONG_UP); + keyval = KEYS_LONG_UP; break; case KEYS_DOWN: - return(KEYS_LONG_DOWN); + keyval = KEYS_LONG_DOWN; break; case KEYS_LEFT: - return(KEYS_LONG_LEFT); + keyval = KEYS_LONG_LEFT; break; case KEYS_SELECT: - return(KEYS_LONG_SELECT); + keyval = KEYS_LONG_SELECT; break; default: - return(KEYS_NONE); + break; } + buttonVal = keyval; + keyval = KEYS_NONE; + Serial.println("2 second button press! Val = " + String(buttonVal)); + } else if (button == KEYS_NONE) { // Short press. Serial.println("Short Press. Debounced Key: " + String(keyval)); // Debounce complete. Decide if it's long or not. displayState = DISPLAY_STATE_DEBOUNCE_COMPLETE; - retv = keyval; - keyval = KEYS_NONE; - return(retv); + buttonVal = keyval; } else { - // Not finished debouncing yet. - return(KEYS_NONE); + // Not finished debouncing yet. Do nothing. } // KEYS_NONE - } // throttle state == RUN + break; + + case DISPLAY_STATE_LONG_DEBOUNCE_WAIT: + case DISPLAY_STATE_DEBOUNCE_COMPLETE: + // Long press detected, waiting for release before handling more button presses + if (button == KEYS_NONE) { + // User has released button. + displayState = DISPLAY_STATE_RUN; + buttonVal = KEYS_NONE; + } + break; + + } // switch(displayState) } diff --git a/DCCpp_Uno/LCD.h b/DCCpp_Uno/LCD.h index e52e18a..edd6668 100644 --- a/DCCpp_Uno/LCD.h +++ b/DCCpp_Uno/LCD.h @@ -1,6 +1,15 @@ #ifndef LCD_H #define LCD_H +//------------------------------------------------ +// Generic wrapper class for various different +// 16x2 LCD shields with buttons.. Namely: +// * the 4/8-pin OSEPP model +// * the Adafruit I2C version +// +// Allows for long vs. short presses of the buttons +// (but not momentary action -- yet) + // LCD Display Types: // // 0 = OSEPP LCDKeypad @@ -9,6 +18,8 @@ #define LCD_DISPLAY_TYPE_ADAFRUIT 1 #define LCD_DISPLAY_TYPE LCD_DISPLAY_TYPE_ADAFRUIT +// Defines how long a "long press" is in milliseconds. +#define LONG_PRESS_MS 2000 // Define generic button names for multi-library compatibility // Return values from getButtons(); @@ -27,12 +38,12 @@ class LCD { private: - int displayState; - public: + byte displayState; int buttonVal; + public: LCD(); - void begin(); - void run(); + void begin(); // Call from setup() + void run(); // Call from loop() int getButtons(); void updateDisplay(char *row1, char *row2); void clear(); @@ -43,7 +54,7 @@ class LCD { void noCursor(); protected: int getButton(); - int debounceButtons(); + void debounceButtons(); }; diff --git a/DCCpp_Uno/LCDThrottle.cpp b/DCCpp_Uno/LCDThrottle.cpp index 23811fb..7bd98f0 100644 --- a/DCCpp_Uno/LCDThrottle.cpp +++ b/DCCpp_Uno/LCDThrottle.cpp @@ -2,83 +2,152 @@ #include #include #include "SerialCommand.h" +#include "EEStore.h" #include "LCDThrottle.h" - -//#define SPEED_UP_INCREMENT 1 /* 13 */ -//#define SPEED_DOWN_INCREMENT 1 /* 13 */ -#define MAX_SPEED 60 /*126 */ +#include "Inglenook.h" + +//-------------------------------------------------------------------- +/* LCD Throttle + * + * Uses an LCD with buttons to provide a directly-connected throttle + * interface for DCC++. Includes an "Inglenook Sidings" game that will + * randomize a set of cars and tell you a 5-car train to build. + * + * Boots up in the Menu state. Menu options include: + * -- Use Throttle + * -- Set Track Power On/Off + * -- Set Address + * -- Set Display Mode + * -- Set maximum speed (in 128 speed steps) + * -- Play Inglenook Sidings + * + * A long (2-sec+) press on SELECT will toggle between Menu and Throttle view. + * + * Throttle views (Display Modes): + * -- Standard: Separate Direction and Speed indicators + * -- Switcher: Combined bidirectional Direction+Speed indicator + * + * Each click of LEFT/RIGHT will increase/decrease the speed by a fraction + * of the maximum speed set in the menus. The amount depends on the view mode. + * Standard view has 15 steps, so (e.g.) with max speed = 60, each click is + * 4 speed steps. + * Switcher view has +/- 7 steps, so (e.g.) with max speed = 63, each click is + * 9 speed steps. + * + * KNOWN BUGS: + * -- The "spinners" for the highest digit of the Set Address and Set Max Speed + * menu items don't work right. + * -- Some of the menu responses to navigation buttons aren't consistent + */ +//-------------------------------------------------------------------- + +#define MAX_SPEED 126 /*126 */ #define MAX_NOTCH_NORMAL 15 #define MAX_NOTCH_SWITCHER 7 +#define DEFAULT_CAB 3 #define THROTTLE_STATE_RUN 0 #define THROTTLE_STATE_DEBOUNCE 1 #define THROTTLE_STATE_MENUS 2 #define THROTTLE_STATE_MENU_ACTION 3 +#define THROTTLE_STATE_GAME_MENUS 4 +#define THROTTLE_STATE_GAME_BUILD 5 + +struct LCDThrottleData LCDT_EEPROM_Store; -// MAX_COMMAND_LENGTH is defined in SerialCommand.h -#define MAX_COMMAND_LENGTH 30 +// MAX_COMMAND_LENGTH is defined in DCC++ SerialCommand.h +//#define MAX_COMMAND_LENGTH 30 +// Buffer for writing DCC++ commands to the core base station code. char command[MAX_COMMAND_LENGTH]; +// Holder for display string construction static char display[2][17]; -static LCDThrottle *lcdThrottle = NULL; - // Menu subsystem "stuff" static void menuUseEvent(MenuUseEvent e); static void menuChangeEvent(MenuChangeEvent e); -static MenuBackend *menu = new MenuBackend(menuUseEvent, menuChangeEvent); +static MenuBackend *menu; -static LCDThrottle *LCDThrottle::getThrottle(int r, int c) { - if (lcdThrottle == NULL) { - lcdThrottle = new LCDThrottle(r, c); - } - return(lcdThrottle); +/** Constructor + * + */ +LCDThrottle::LCDThrottle() { + ; // do nothing } -LCDThrottle::LCDThrottle(int reg, int cab) { + +void LCDThrottle::begin(int reg) { lcd = new LCD(); lcd->begin(); - EEPROM_GetAll(); - throttleState = THROTTLE_STATE_MENUS; - this->reg = reg; - if (cab != 0) { - this->cab = cab; + //EEPROM_GetAll(); + load(); + if (maxSpeed == 0) { + // EEPROM not yet initialized -> set default. + maxSpeed = MAX_SPEED; + } + if (cab == 0) { + // EEPROM not yet initialized -> set default. + cab = DEFAULT_CAB; } - notch = 0; + throttleState = THROTTLE_STATE_MENUS; // Always start in the menus. + jumpbackState = THROTTLE_STATE_MENUS; + this->reg = reg; // Store the register we should use. + notch = 0; // Idle the loco speed = 0; dir = FORWARD; - //displayMode = DISPLAY_MODE; - power_state = false; + // TODO: Get the actual track power state from the base station. + power_state = false; // Assume track power is off. + sendPowerCommand(power_state); // Don't assume. Force it off. + // Fire up the Inglenook game code. + game = InglenookGame(); + game.begin(); + // Construct the menus. + menu = new MenuBackend(menuUseEvent, menuChangeEvent); menuSetup(); - updateDisplay(); + doMenuDisplay(); } +/** run() + * + * Main Run loop + */ void LCDThrottle::run() { + // Run the underlying LCD stuff lcd->run(); + + // Grab any button presses and process them int button = lcd->getButtons(); + switch(throttleState) { + case THROTTLE_STATE_RUN: switch(button) { case KEYS_RIGHT: + // Speed up increaseSpeed(); sendThrottleCommand(); updateDisplay(); break; + case KEYS_LEFT: + // Slow down decreaseSpeed(); sendThrottleCommand(); updateDisplay(); break; + case KEYS_UP: case KEYS_DOWN: - // For now, dumbly toggle direction with either left or right key. + // For now, dumbly toggle direction with either up or down key. // Maybe make this smarter or repurpose later. + // Possibly use up/down keys for Functions. dir = (dir == FORWARD ? REVERSE : FORWARD); sendThrottleCommand(); updateDisplay(); break; + case KEYS_SELECT: - // For now, this is emergency stop. + // For now, this (Short tap) is emergency stop. speed = -1; notch = 0; sendThrottleCommand(); @@ -87,13 +156,26 @@ void LCDThrottle::run() { speed = 0; updateDisplay(); break; + case KEYS_LONG_SELECT: - // This will toggle power. - power_state = !power_state; - sendPowerCommand(power_state); - throttleState = THROTTLE_STATE_MENUS; - menu->moveDown(); - doMenuDisplay(); + // Idle the Loco and switch to Menus mode. + speed = 0; + notch = 0; + sendThrottleCommand(); + //Serial.println("JumpbackState == " + String(jumpbackState)); + // Jump back to the last non-throttle state we were in. + // This is so when playing the game you can hop back and forth + // directly between the throttle and the built train view. + throttleState = jumpbackState; + // What to display depends on which state we're returning to. + // TODO: Update the updateDisplay() method to handle this instead, if possible. + if (throttleState == THROTTLE_STATE_GAME_BUILD) { + game.doDisplayTrain(lcd, display[0], display[1]); + } else if (throttleState == THROTTLE_STATE_GAME_MENUS) { + doGameMenuDisplay(); + } else { + doMenuDisplay(); + } break; default: @@ -107,67 +189,142 @@ void LCDThrottle::run() { break; case THROTTLE_STATE_MENUS: + // Present the top-level menu. switch(button) { case KEYS_UP: + Serial.println("Up/Left"); + // Don't let the menu system move up to Root. + if (menu->getCurrent() == "Set Track Power") { + // do nothing. + } else { menu->moveUp(); - doMenuDisplay(); + } + doMenuDisplay(); break; + case KEYS_DOWN: - menu->moveUp(); - doMenuDisplay(); - break; - case KEYS_LEFT: + Serial.println("Down/Right"); + menu->moveDown(); + doMenuDisplay(); break; + case KEYS_RIGHT: + //menu->moveRight(); + doMenuDisplay(); + break; + + case KEYS_LEFT: + menu->moveLeft(); + doMenuDisplay(); break; + case KEYS_SELECT: + Serial.println("Select"); throttleState = THROTTLE_STATE_MENU_ACTION; doMenuAction(button); break; + case KEYS_LONG_SELECT: + // Jump to throttle mode. Save Menus as jumpback state. + Serial.println("Long Select"); throttleState = THROTTLE_STATE_RUN; + jumpbackState = THROTTLE_STATE_MENUS; + updateDisplay(); break; - } break; case THROTTLE_STATE_MENU_ACTION: + // Handle top-level menu actions. switch(button) { case KEYS_SELECT: - EEPROM_StoreAll(); + // Do the action and return. + //EEPROM_StoreAll(); + store(); throttleState = THROTTLE_STATE_MENUS; doMenuDisplay(); break; + case KEYS_UP: case KEYS_DOWN: case KEYS_LEFT: case KEYS_RIGHT: doMenuAction(button); break; + case KEYS_LONG_SELECT: - EEPROM_StoreAll(); + // Jump back to throttle state. Don't retun here + // Return to the menu itself instead. + //EEPROM_StoreAll(); + store(); throttleState = THROTTLE_STATE_RUN; + jumpbackState = THROTTLE_STATE_MENUS; button = KEYS_NONE; - break; + updateDisplay(); + break; } break; + case THROTTLE_STATE_GAME_MENUS: + case THROTTLE_STATE_GAME_BUILD: + // Handle the game action. + doGameMenus(button); + break; + default: break; } // switch(throttleState) } +/** menuSetup() + * + * Construct the menu tree + */ void LCDThrottle::menuSetup() { + MenuItem *miPower = new MenuItem("Set Track Power"); MenuItem *miRun = new MenuItem("Use Throttle"); MenuItem *miAddr = new MenuItem("Set Address"); MenuItem *miDisp = new MenuItem("Select Display"); - menu->getRoot().add(*miRun); + MenuItem *miMax = new MenuItem("Max Speed"); + MenuItem *miGame = new MenuItem("Inglenook"); + menu->getRoot().add(*miPower); + miPower->add(*miRun); miRun->add(*miAddr); miAddr->add(*miDisp); + miDisp->add(*miMax); + miMax->add(*miGame); + MenuItem *miBuild = new MenuItem("Build Train"); + MenuItem *miList = new MenuItem("List Cars"); + MenuItem *miCarList[7] = { + new MenuItem("Car 0"), + new MenuItem("Car 1"), + new MenuItem("Car 2"), + new MenuItem("Car 3"), + new MenuItem("Car 4"), + new MenuItem("Car 5"), + new MenuItem("Car 6") + }; + miGame->addRight(*miBuild); + miBuild->add(*miList); + miList->addRight(*miCarList[0]); + for (int i = 0; i < NUM_CARS-2; i++) { + miCarList[i]->add(*miCarList[i+1]); + } + // Get off of the root node. + menu->moveDown(); } +/** doMenuDisplay() + * + * Change the display in response to the current top-level menu selection + */ void LCDThrottle::doMenuDisplay() { + lcd->noCursor(); + + if (menu->getCurrent() == "Set Track Power") { + lcd->updateDisplay("THROTTLE MENU:", (power_state == true ? "Track Power ON" : "Track Power OFF")); + } if (menu->getCurrent() == "Use Throttle") { lcd->updateDisplay("THROTTLE MENU:", "Use Throttle"); } @@ -177,13 +334,49 @@ void LCDThrottle::doMenuDisplay() { if (menu->getCurrent() == "Select Display") { lcd->updateDisplay("THROTTLE MENU:", "Select Display"); } + if (menu->getCurrent() == "Max Speed") { + lcd->updateDisplay("THROTTLE MENU:", "Max Speed Step"); + } + if (menu->getCurrent() == "Inglenook") { + lcd->updateDisplay("THROTTLE MENU:", "Inglenook Game"); + } } +/** doMenuAction() + * + * Take action in response to the user selecting a menu item + */ void LCDThrottle::doMenuAction(int button) { - static int incval = 1; - static byte incpos = 3; Serial.println("doMenuAction(" + String(button) + ")"); - + + // Set Track Power: + // Selecting this menu item toggles the track power on or off. + if (menu->getCurrent() == "Set Track Power") { + power_state = !power_state; + sendPowerCommand(power_state); + doMenuDisplay(); + } + + // Use Throttle + // Selecting this jumps you to the throttle mode. + if(menu->getCurrent() == "Use Throttle") { + throttleState = THROTTLE_STATE_RUN; + jumpbackState = THROTTLE_STATE_MENUS; + updateDisplay(); + } + + // Inglenook + // Selecting this moves you into the game menus. + if (menu->getCurrent() == "Inglenook") { + throttleState = THROTTLE_STATE_GAME_MENUS; + menu->moveRight(); + doGameMenuDisplay(); + } + + // Select Display + // Selecting this allows you to change the throttle display mode + // Up/Down keys toggle the value. + // TODO: Left/Right probably should too if (menu->getCurrent() == "Select Display") { if (button == KEYS_UP || button == KEYS_DOWN) { if (displayMode == DISPLAY_MODE_NORMAL) { @@ -196,39 +389,91 @@ void LCDThrottle::doMenuAction(int button) { displayMode == DISPLAY_MODE_NORMAL ? "Standard" : "Switcher"); } // Select Display - + + // Set Address + // Selecting this allows you to "dial in" the loco address. if (menu->getCurrent() == "Set Address") { - if (button == KEYS_SELECT) { - incval = 1; - incpos = 3; - } else if (button == KEYS_UP) { - if (cab == 9999) { cab = 0; } - else { cab = cab + incval; } - } else if (button == KEYS_DOWN) { - if (cab == 0) { cab = 9999; } - else { cab = cab - incval; } - } else if (button == KEYS_LEFT) { - if (incval < 1000) { incval *= 10; } - if (incpos > 0) { incpos -= 1; } - } else if (button == KEYS_RIGHT) { - if (incval > 1) { incval /= 10; } - if (incpos < 3) { incpos += 1; } - } - sprintf(display[1], "%04d", cab); - lcd->updateDisplay("Set Address:", display[1]); - lcd->setCursor(incpos, 1); - lcd->cursor(); + cab = calcIncValue(button, 3, cab, 9999, "%04d", "Set Address:"); } // Set Address + + // Max Speed + // Selecting this allows you to change the maximum speed setting of the + // loco (effectively setting the speed range of the throttle "knob") + if (menu->getCurrent() == "Max Speed") { + maxSpeed = calcIncValue(button, 2, maxSpeed, 126, "%03d", "Max Speed:"); + } // Set Address + +} + +/** calcIncValue() + * + * Calculate the incremented value to show when "dialing" a settings number. + * TODO: Handling of the most significant digit is broken. + */ +int LCDThrottle::calcIncValue(int button, int maxpos, int val, int maxval, const char *fmt, const char *label) { + static int incval, incpos; + if (button == KEYS_SELECT) { + incval = 1; + incpos = maxpos; + + } else if (button == KEYS_UP) { + if (val + incval > 9999) { + // This will roll over the value. + // For now, don't do anything. + // TODO: Figure out how to roll ONLY the correct digit to zero. + } + else if (val == 9999) { val = 0; } + else { val += incval; } + + } else if (button == KEYS_DOWN) { + if (val - incval < 0) { + // This will roll under the value. + // For now, don't do anything. + // TODO: Figure out how to roll ONLY the correct digit to 9. + } + else if (val == 0) { val = maxval; } + else { val -= incval; } + } else if (button == KEYS_LEFT) { + if (incval < pow(10, maxpos)) { incval *= 10; } + if (incpos > 0) { incpos -= 1; } + } else if (button == KEYS_RIGHT) { + if (incval > 1) { incval /= 10; } + if (incpos < maxpos) { incpos += 1; } + } + Serial.println("val: " + String(val) + " incval: " + String(incval) + " incpos: " + String(incpos)); + sprintf(display[1], fmt, val); + lcd->updateDisplay(label, display[1]); + lcd->setCursor(incpos, 1); + lcd->cursor(); + return(val); } +/** sendPowerCommand() + * + * Send a Power on/off command to the DCC++ Base Station + */ void LCDThrottle::sendPowerCommand(bool on) { sprintf(command, ""); sprintf(command, on == true ? "1" : "0"); - Serial.println(command); + Serial.println("LCD Command: " + String(command)); + SerialCommand::parse(command); +} + +/** sendThrottleCommand() + * + * Send a Throttle command to the DCC++ Base Station + */ +void LCDThrottle::sendThrottleCommand() { + sprintf(command, ""); + sprintf(command, "t%d %d %d %d", reg, cab, speed, dir); + Serial.println("LCD Command: " + String(command)); SerialCommand::parse(command); - } +/** increaseSpeed() + * + * Increment the current speed by one "notch" + */ void LCDThrottle::increaseSpeed() { int tmp_notch; if (displayMode == DISPLAY_MODE_NORMAL) { @@ -236,7 +481,7 @@ void LCDThrottle::increaseSpeed() { // since this is the "increase" function we never have to worry about // flipping the direction bit. notch = (notch == MAX_NOTCH_NORMAL ? MAX_NOTCH_NORMAL : notch + 1); - speed = notch * (MAX_SPEED / MAX_NOTCH_NORMAL); + speed = notch * (maxSpeed / MAX_NOTCH_NORMAL); } else { // in DISPLAY_MODE_SWITCHER the direction can change when "increasing" // the throttle, it depends. Have to deal with absolute value. @@ -252,17 +497,21 @@ void LCDThrottle::increaseSpeed() { dir = REVERSE; notch = -tmp_notch; } - speed = notch * (MAX_SPEED / MAX_NOTCH_SWITCHER); + speed = notch * (maxSpeed / MAX_NOTCH_SWITCHER); } // if(displayMode) Serial.println("inc: N= " + String(notch) + " S=" + String(speed)); } +/** decreaseSpeed() + * + * Decrement the current speed by one "notch" + */ void LCDThrottle::decreaseSpeed() { int tmp_notch; if (displayMode == DISPLAY_MODE_NORMAL) { notch = (notch == 0 ? 0 : notch - 1); - speed = notch * (MAX_SPEED / MAX_NOTCH_NORMAL); + speed = notch * (maxSpeed / MAX_NOTCH_NORMAL); } else { tmp_notch = (dir == REVERSE ? -notch : notch); tmp_notch = (tmp_notch == -MAX_NOTCH_SWITCHER ? -MAX_NOTCH_SWITCHER : tmp_notch - 1); @@ -273,26 +522,22 @@ void LCDThrottle::decreaseSpeed() { dir = FORWARD; notch = tmp_notch; } - speed = notch * (MAX_SPEED / MAX_NOTCH_SWITCHER); + speed = notch * (maxSpeed / MAX_NOTCH_SWITCHER); } Serial.println("dec: N= " + String(notch) + " S=" + String(speed)); } -void LCDThrottle::sendThrottleCommand() { - sprintf(command, ""); - sprintf(command, "t%d %d %d %d", reg, cab, speed, dir); - Serial.println(command); - SerialCommand::parse(command); -} - -// Display Modes... - +/** updateDisplay() + * + * Update the display when in Throttle mode + */ void LCDThrottle::updateDisplay() { switch(displayMode) { case DISPLAY_MODE_SWITCHER: // SWITCHER: Speed/Direction together - // (something useful) + // Loco: // <------0------> + // The blinking cursor shows the current value lcd->clear(); // Draw the line. sprintf(display[0], "Loco: %04d", cab); @@ -327,8 +572,10 @@ void LCDThrottle::updateDisplay() { case DISPLAY_MODE_NORMAL: default: // NORMAL: Speed + Direction - // <-- --> + // <-- --> // 0--------------+ + // The length of the bar shows the speed + // The arrow shows the direction lcd->clear(); if (dir == REVERSE) { sprintf(display[0], "<--- Loco: %04d", cab); @@ -357,6 +604,13 @@ void LCDThrottle::updateDisplay() { //--------------------------------------------------------------- // MenuBackend support functions +// TODO: I could probably use these effectively to clean up a bunch +// of those switch() statements above. + +/** menuChangeEvent() + * + * Callback for change events. + */ static void menuChangeEvent(MenuChangeEvent changed) { // Update the display to reflect the current menu state // For now we'll use serial output. @@ -366,6 +620,10 @@ static void menuChangeEvent(MenuChangeEvent changed) { Serial.println(changed.to.getName()); } +/** menuUseEvent() + * + * Callback for "use" events + */ static void menuUseEvent(MenuUseEvent used) { //Serial.print("Menu use "); //Serial.println(used.item.getName()); @@ -375,36 +633,183 @@ static void menuUseEvent(MenuUseEvent used) { // EEPROM Interface Functions // Memory Locations (byte address) -#define EEPROM_BASE 0 -#define EEPROM_DISPLAY (EEPROM_BASE) -#define EEPROM_ADDRESS (EEPROM_DISPLAY + sizeof(byte)) -#define EEPROM_NEXT (EEPROM_ADDRESS + sizeof(int)) - -void LCDThrottle::EEPROM_StoreAll() { - EEPROM_StoreDisplay(); - EEPROM_StoreAddress(); -} - -void LCDThrottle::EEPROM_GetAll() { - EEPROM_GetDisplay(); - EEPROM_GetAddress(); +// NOTE: Deprecated in favor of struct LCDThrottleData and +// DCC++ EEStore interface + +// DCC++ - compliant EEPROM access. +/** load() + * + * load sticky data from EEStore interface + */ +void LCDThrottle::load() { + EEStore::reset(); + EEPROM.get(EEStore::pointer(), displayMode); + EEStore::advance(sizeof(displayMode)); + EEPROM.get(EEStore::pointer(), cab); + EEStore::advance(sizeof(cab)); + EEPROM.get(EEStore::pointer(), maxSpeed); + EEStore::advance(sizeof(maxSpeed)); } -void LCDThrottle::EEPROM_StoreDisplay() { - byte oz = (displayMode & 0xFF); - EEPROM.put(EEPROM_DISPLAY, oz); +/** load() + * + * store sticky data from EEStore interface + */ +void LCDThrottle::store() { + EEStore::reset(); + EEPROM.put(EEStore::pointer(), displayMode); + EEStore::advance(sizeof(displayMode)); + EEPROM.put(EEStore::pointer(), cab); + EEStore::advance(sizeof(cab)); + EEPROM.put(EEStore::pointer(), maxSpeed); + EEStore::advance(sizeof(maxSpeed)); } -void LCDThrottle::EEPROM_GetDisplay() { - byte oz; - EEPROM.get(EEPROM_DISPLAY, oz); - displayMode = oz; -} +//--------------------------------------------------------------- +// Inglenook Game Functions + +/** doGameMenus(int button) + * + * Handle the Inglenook Game sub-menu + * + * param button: int -> current button value + */ +void LCDThrottle::doGameMenus(int button) { + switch(throttleState) { + case THROTTLE_STATE_GAME_MENUS: + switch(button) { + case KEYS_UP: + menu->moveUp(); + doGameMenuDisplay(); + break; + + case KEYS_DOWN: + menu->moveDown(); + doGameMenuDisplay(); + break; + + case KEYS_LEFT: + menu->moveLeft(); + if (menu->getCurrent() == "Inglenook") { + throttleState = THROTTLE_STATE_MENUS; + doMenuDisplay(); + } else { + doGameMenuDisplay(); + } + break; + + case KEYS_RIGHT: + menu->moveRight(); + doGameMenuDisplay(); + break; + + case KEYS_SELECT: + if (menu->getCurrent() == "List Cars") { + menu->moveRight(); // for this one "use" == "move right" + doGameMenuDisplay(); + } else if (menu->getCurrent() == "Build Train") { + throttleState = THROTTLE_STATE_GAME_BUILD; + game.buildTrain(); + game.doDisplayTrain(lcd, display[0], display[1]); + } + break; + + case KEYS_LONG_SELECT: + throttleState = THROTTLE_STATE_RUN; + jumpbackState = THROTTLE_STATE_GAME_MENUS; + updateDisplay(); + break; + } + break; -void LCDThrottle::EEPROM_StoreAddress() { - EEPROM.put(EEPROM_ADDRESS, cab); + case THROTTLE_STATE_GAME_BUILD: + switch(button) { + case KEYS_NONE: + break; + + case KEYS_LEFT: + case KEYS_SELECT: + throttleState = THROTTLE_STATE_GAME_MENUS; + doGameMenuDisplay(); + break; + + case KEYS_DOWN: + case KEYS_RIGHT: + if (game.carIndex() >= 3) { + game.setCarIndex(3); + } else { + game.setCarIndex(game.carIndex()+1); + } + Serial.println("List Train: Car index " + String(game.carIndex())); + game.doListTrain(lcd, display[0], display[1], game.carIndex()); + break; + + case KEYS_UP: + if (game.carIndex() > -1) { + game.setCarIndex(game.carIndex()-1); + } + if (game.carIndex() == -1) { + game.doDisplayTrain(lcd, display[0], display[1]); + } else { + Serial.println("List Train: Car index " + String(game.carIndex())); + game.doListTrain(lcd, display[0], display[1], game.carIndex()); + } + break; + + case KEYS_LONG_SELECT: + throttleState = THROTTLE_STATE_RUN; + jumpbackState = THROTTLE_STATE_GAME_BUILD; + updateDisplay(); + break; + + } // switch(buttons) + } // switch(state) } -void LCDThrottle::EEPROM_GetAddress() { - EEPROM.get(EEPROM_ADDRESS, cab); +/** doGameMenuDisplay() + * + * Handle display output for the Inglenook Game sub-menu + */ +void LCDThrottle::doGameMenuDisplay() { + lcd->clear(); + + if (menu->getCurrent() == "Build Train") { + sprintf(display[0], "Build Train"); + sprintf(display[1], "List Cars"); + } + if (menu->getCurrent() == "List Cars") { + sprintf(display[0], "List Cars"); + sprintf(display[1], ""); + } + if (menu->getCurrent() == "Car 0") { + sprintf(display[0],"%s%s", "1:",carnames[0].c_str()); + sprintf(display[1],"%s%s", "2:",carnames[1].c_str()); + } + if (menu->getCurrent() == "Car 1") { + sprintf(display[0],"%s%s", "2:",carnames[1].c_str()); + sprintf(display[1],"%s%s", "3:",carnames[2].c_str()); + } + if (menu->getCurrent() == "Car 2") { + sprintf(display[0],"%s%s", "3:",carnames[2].c_str()); + sprintf(display[1],"%s%s", "4:",carnames[3].c_str()); + } + if (menu->getCurrent() == "Car 3") { + sprintf(display[0],"%s%s", "4:",carnames[3].c_str()); + sprintf(display[1],"%s%s", "5:",carnames[4].c_str()); + } + if (menu->getCurrent() == "Car 4") { + sprintf(display[0],"%s%s", "5:",carnames[4].c_str()); + sprintf(display[1],"%s%s", "6:",carnames[5].c_str()); + } + if (menu->getCurrent() == "Car 5") { + sprintf(display[0],"%s%s", "6:",carnames[5].c_str()); + sprintf(display[1],"%s%s", "7:",carnames[6].c_str()); + } + if ((menu->getCurrent() == "Car 6") || (menu->getCurrent() == "Car 7")) { + sprintf(display[0],"%s%s", "7:",carnames[6].c_str()); + sprintf(display[1],"%s%s", "8:",carnames[7].c_str()); + } + lcd->updateDisplay(display[0], display[1]); + lcd->setCursor(0,0); + lcd->blink(); } diff --git a/DCCpp_Uno/LCDThrottle.h b/DCCpp_Uno/LCDThrottle.h index 3ec0dce..ebdd5d5 100644 --- a/DCCpp_Uno/LCDThrottle.h +++ b/DCCpp_Uno/LCDThrottle.h @@ -2,6 +2,7 @@ #define LCD_THROTTLE_H #include "LCD.h" +#include "Inglenook.h" // DCC++ Throttle using the buttons on a 16x2 + 5 button Display Shield @@ -13,24 +14,29 @@ #define FORWARD 1 #define REVERSE 0 +class MenuItem; + class LCDThrottle { - private: - int throttleState; +private: + int throttleState; +int jumpbackState; int reg; int cab; int speed; int dir; - int displayMode; + byte displayMode; int notch; LCD *lcd; + int maxSpeed; bool power_state; + InglenookGame game; public: - static LCDThrottle *getThrottle(int r, int c); + LCDThrottle(); + void begin(int reg); void run(); protected: - LCDThrottle(int reg, int cab); void sendThrottleCommand(); void sendPowerCommand(bool on); int debounceButtons(); @@ -41,14 +47,23 @@ class LCDThrottle { void menuSetup(); void doMenuDisplay(); void doMenuAction(int button); - void EEPROM_StoreAll(); - void EEPROM_GetAll(); - void EEPROM_StoreDisplay(); - void EEPROM_GetDisplay(); - void EEPROM_StoreAddress(); - void EEPROM_GetAddress(); - + void load(); + void store(); + void doGameMenus(int b); + void setupInglenookMenu(MenuItem *m); + void inglenookBegin(); + void doGameMenuDisplay(); + int calcIncValue(int button, int maxpos, int val, int maxval, const char *fmt, const char *label); }; +struct LCDThrottleData { +byte dislay; +byte reserved1; +int address; +int maxspeed; +int reserved[5]; +}; + + #endif // LCD_THROTTLE_H From 935534ed7e24ab0d56fea8dfa72f1bc056d5fd07 Mon Sep 17 00:00:00 2001 From: TwinDad Date: Wed, 1 Feb 2017 16:10:39 -0500 Subject: [PATCH 08/21] Remove WiThrottle from this branch, which should be LCDThrottle-specific. --- DCCpp_Uno/Config.h | 7 - DCCpp_Uno/SerialCommand.cpp | 16 -- DCCpp_Uno/WiThrottle.cpp | 315 ------------------------------------ DCCpp_Uno/WiThrottle.hpp | 37 ----- 4 files changed, 375 deletions(-) delete mode 100644 DCCpp_Uno/WiThrottle.cpp delete mode 100644 DCCpp_Uno/WiThrottle.hpp diff --git a/DCCpp_Uno/Config.h b/DCCpp_Uno/Config.h index 19ecb9f..0b0df70 100644 --- a/DCCpp_Uno/Config.h +++ b/DCCpp_Uno/Config.h @@ -55,13 +55,6 @@ Part of DCC++ BASE STATION for the Arduino #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF } -///////////////////////////////////////////////////////////////////////////////////// -// -// ENABLE THE WITHROTTLE INTERFACE -// - -#define WITHROTTLE_SUPPORT 0 - ///////////////////////////////////////////////////////////////////////////////////// // // ENABLE THE LCD THROTTLE. REQUIRES AN LCD WITH 5 BUTTONS. diff --git a/DCCpp_Uno/SerialCommand.cpp b/DCCpp_Uno/SerialCommand.cpp index fa22a9f..0774fbe 100644 --- a/DCCpp_Uno/SerialCommand.cpp +++ b/DCCpp_Uno/SerialCommand.cpp @@ -22,9 +22,6 @@ Part of DCC++ BASE STATION for the Arduino #include "Outputs.h" #include "EEStore.h" #include "Comm.h" -#ifdef WITHROTTLE_SUPPORT -#include "WiThrottle.hpp" -#endif extern int __heap_start, *__brkval; @@ -53,11 +50,6 @@ void SerialCommand::process(){ while(INTERFACE.available()>0){ // while there is data on the serial line c=INTERFACE.read(); -#ifdef WITHROTTLE_SUPPORT - if (WiThrottle::isWTCommand(c)) { - WiThrottle::readCommand(c); - } else -#endif if(c=='<') // start of new command sprintf(commandString,""); else if(c=='>') // end of new command @@ -71,16 +63,8 @@ void SerialCommand::process(){ EthernetClient client=INTERFACE.available(); if(client){ -#ifdef WITHROTTLE_SUPPORT - WiThrottle::sendIntroMessage(); -#endif while(client.connected() && client.available()){ // while there is data on the network c=client.read(); -#ifdef WITHROTTLE_SUPPORT - if (WiThrottle::isWTCommand(c)) { - WiThrottle::readCommand(c); - } else -#endif if(c=='<') // start of new command sprintf(commandString,""); else if(c=='>') // end of new command diff --git a/DCCpp_Uno/WiThrottle.cpp b/DCCpp_Uno/WiThrottle.cpp deleted file mode 100644 index 5863bb1..0000000 --- a/DCCpp_Uno/WiThrottle.cpp +++ /dev/null @@ -1,315 +0,0 @@ -/********************************************************************** - -WiThrottle.cpp -COPYRIGHT (c) 2017 Mark S. Underwood - -Part of DCC++ BASE STATION for the Arduino - -**********************************************************************/ - -#include -#include -#include "DCCpp_Uno.h" -#include "Comm.h" -#include "SerialCommand.h" -#include "PacketRegister.h" -#include "WiThrottle.hpp" - -#define FORCED_REGISTER_NUMBER 1 - -extern RegisterList mainRegs; - -static char command[MAX_COMMAND_LENGTH+1]; -static int address = 3; - -static const byte byte1FuncOnVals[29] = { 144, 129, 130, 132, 136, - 177, 178, 180, 184, - 161, 162, 164, 168, - 222, 222, 222, 222, - 222, 222, 222, 222, - 223, 223, 223, 223, - 223, 223, 223, 223 }; - -static const byte byte1FuncOffVals[29] = { 128, 128, 128, 128, 128, - 176, 176, 176, 176, - 160, 160, 160, 160, - 222, 222, 222, 222, - 222, 222, 222, 222, - 223, 223, 223, 223, - 223, 223, 223, 223 }; - -static const byte byte2FuncOnVals[29] = { 1, 2, 4, 8, - 16, 32, 64, 128, - 1, 2, 4, 8, - 16, 32, 64, 128 }; - - -static void WiThrottle::readCommand(char c) { - char x; - sprintf(command, "%c", c); - -#if COMM_TYPE == 0 - - // Read all the bytes until we encounter a newline, end-of-string, or - // run out of bytes. - while(INTERFACE.available() > 0) { - x = INTERFACE.read(); - Serial.print(x); - if (x == '\n' || x == '\0') { - sprintf(command,"%s%c",command, x); - Serial.println("Received"); - parseToDCCpp(command); - } else if (strlen(command) < MAX_COMMAND_LENGTH) { - sprintf(command, "%s%c", command, x); - } - } - -#elif COMM_TYPE == 1 - - // Connect to the source - EthernetClient client = INTERFACE.available(); - - // Read all the bytes until we encounter a newline, end-of-string, or - // run out of bytes. - if (client) { - while (client.connected() && client.available()) { - x = client.read(); - if (x == '\n' || x == '\0') { - sprintf(command,"%s%c",command, x); - parseToDCCpp(command); - } else if (strlen(command) < MAX_COMMAND_LENGTH) { - sprintf(command, "%s%c", command, x); - } - } - } -#endif -} - -static void WiThrottle::parseToDCCpp(char *s) { - // Do something :) - Serial.println(s); - switch (s[0]) { - case 'T': // Throttle - case 'S': // Second Throttle - doThrottleCommand(NULL, s+1); - break; - case 'M': // Multi-Throttle - parseMCommand(s); - break; - case 'C': // Old "T" command - if (s[1] == 'T') { - doThrottleCommand(NULL, s+2); - } - break; - case 'N': // Name of throttle - parseNCommand(s); - break; - case 'H': // Hardware (get device UUID) - parseHCommand(s); - break; - case '*': // Heartbeat - case 'D': // Direct hex packet - case 'P': // Panel stuff - case 'R': // Roster stuff - case 'Q': // Quit - break; - default: - return(s); - } -} - -static bool WiThrottle::isWTCommand(char c) { - switch (c) { - case 'T': // Throttle - case 'S': // Second Throttle - case 'M': // Multi-Throttle - case 'D': // Direct hex packet - case '*': // Heartbeat - case 'C': // Old 'T' command - case 'N': // Name - case 'H': // Hardware - case 'P': // Panel - case 'R': // Roster - case 'Q': // Quit - Serial.print(c); - Serial.println(" is a WiThrottle cmd"); - return(true); - default: - return(false); - } -} - -static void WiThrottle::parseHCommand(char *s) { - switch(s[1]) { - case 'U': - // get device UDID and do something with it. - return; - default: - return; - } -} - -static void WiThrottle::parseNCommand(char *s) { - // Get the Name and store it somewhere... if needed. - Serial.print("Name = "); - Serial.println(String(s)); - // Reply * e.g. *10 for 10 seconds or *0 for no heartbeat expected - return; -} - -static void WiThrottle::parseMCommand(char *s) { - char *key, *action; - switch(s[1]) { - case 'A': - key = strtok(s, "<;>"); - action = strtok(NULL, "<;>"); - Serial.println(key); - Serial.println(action); - doThrottleCommand(key, action); - case '+': - case '-': - default: - return; - } -} - -static void WiThrottle::doThrottleCommand(char *key, char *action) { - int reg, dir, spd, f; - byte byte1, byte2; - // TODO: When supporting multiple throttles, KEY will tell us which - // throttle to do the action on. - switch(action[0]) { - case 'V': // Velocity - // DCC++ Format: - // DCC++ Returns: - if (address < 0) { return; } - reg = getRegisterForCab(address); - dir = getDirForCab(address); - sprintf(command, "t %d %d %s %d", reg, address, (action+1), dir ); - SerialCommand::parse(command); - break; - - case 'X': // E-Stop - // DCC++ Format: with SPEED = -1 - // DCC++ Returns: - if (address < 0) { return; } - reg = getRegisterForCab(address); - dir = getDirForCab(address); - sprintf(command, "t %d %d -1 %d", reg, address, (action+1), dir); - SerialCommand::parse(command); - break; - - case 'F': // Function - case 'f': // force function (v>=2.0) - // DCC++ Format: - // DCC++ Returns: (none) - f = strtol((action+2), NULL,10); - if (f == 0 || f > 28) { - // Invalid conversion - break; - } - byte1 = getFuncByte1((action[1] == '1'), f); - byte2 = getFuncByte2((action[1] == '1'), f); - if (byte2 == 255) { - sprintf(command, "f %d %d", address, byte1); - } else { - sprintf(command, "f %d %d %d", address, byte1, byte2); - } - SerialCommand::parse(command); - break; - - case 'R': // Direction - // DCC++ Format: - // DCC++ Returns: - if (address < 0) { return; } - reg = getRegisterForCab(address); - spd = getSpeedForCab(address); - sprintf(command, "t %d %d %d %c", reg, address, spd, - (action[1] == '0' ? '0' : '1')); - Serial.print("cmd = " + String(command)); - SerialCommand::parse(command); - break; - - case 'I': // Idle - // DCC++ Format: - // DCC++ Returns: - if (address < 0) { return; } - reg = getRegisterForCab(address); - dir = getDirForCab(address); - sprintf(command, "t %d %d 0 %d", reg, address, dir); - SerialCommand::parse(command); - break; - - case 'r': // Release - case 'd': // Dispatch - address = 0; - break; - - case 'L': // set long address - case 'S': // set short address - address = strtol((action+1), NULL, 10); - break; - - case 'q': // request (v>=2.0) - handleRequest(action); - break; - - case 'E': // set address from roster (v>=1.7) - case 'C': // consist - case 'c': // consist lead from roster (v>=1.7) - case 's': // speed step mode (v>= 2.0) - case 'm': // momentary (v>=2.0) - case 'Q': // quit - default: - return; - } -} - -byte WiThrottle::getFuncByte1(bool t, int f) { - if (t) { - return(byte1FuncOnVals[f]); - } else { - return(byte1FuncOffVals[f]); - } -} - -byte WiThrottle::getFuncByte2(bool t, int f) { - if (f < 13) { - return(255); - } else if (t) { - return(byte2FuncOnVals[f-13]); - } else { - return(0); - } -} - -void WiThrottle::handleRequest(char *s) { - // TODO: Handle Requests - return; -} - -int WiThrottle::getRegisterForCab(int c) { - return(FORCED_REGISTER_NUMBER); -} - -int WiThrottle::getSpeedForCab(int c) { - int spd = mainRegs.speedTable[FORCED_REGISTER_NUMBER]; - if (spd < 0) { spd = -spd; } - return(spd); -} - -int WiThrottle::getDirForCab(int c) { - int spd = mainRegs.speedTable[FORCED_REGISTER_NUMBER]; - return(spd >= 0 ? 1 : 0); -} - -void WiThrottle::sendIntroMessage(void) { - INTERFACE.println("VN2.0"); - INTERFACE.println("RL0"); - INTERFACE.println("PPA0"); // PPA0=off PPA1=on PPA2=unknown - // TODO: Send roster here -#if COMM_TYPE == 1 - INTERFACE.print("PW"); - INTERFACE.println(ETHERNET_PORT); -#endif -} diff --git a/DCCpp_Uno/WiThrottle.hpp b/DCCpp_Uno/WiThrottle.hpp deleted file mode 100644 index d39ca48..0000000 --- a/DCCpp_Uno/WiThrottle.hpp +++ /dev/null @@ -1,37 +0,0 @@ -/********************************************************************** - -WiThrottle.h -COPYRIGHT (c) 2017 Mark S. Underwood - -Part of DCC++ BASE STATION for the Arduino - -**********************************************************************/ - -#ifndef WITHROTTLE_H -#define WITHROTTLE_H - - -class WiThrottle { -private: - //static char command[MAX_COMMAND_LENGTH+1]; - //static int address; // someday will be an array? - -protected: - static void parseHCommand(char *s); - static void parseNCommand(char *s); - static void parseMCommand(char *s); - static void doThrottleCommand(char *key, char *action); - static byte getFuncByte1(bool t, int f); - static byte getFuncByte2(bool t, int f); - static int getRegisterForCab(int c); - static int getSpeedForCab(int c); - static int getDirForCab(int c); - static void handleRequest(char *s); -public: - static void parseToDCCpp(char *s); - static bool isWTCommand(char c); - static void readCommand(char c); - static void sendIntroMessage(void); -}; - -#endif // WITHROTTLE_H From 9aaaa0f17d76da5c6aba097fcdefae5731fa267d Mon Sep 17 00:00:00 2001 From: TwinDad Date: Wed, 1 Feb 2017 16:11:43 -0500 Subject: [PATCH 09/21] Forgot to add Inglenook --- DCCpp_Uno/Inglenook.cpp | 109 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 DCCpp_Uno/Inglenook.cpp diff --git a/DCCpp_Uno/Inglenook.cpp b/DCCpp_Uno/Inglenook.cpp new file mode 100644 index 0000000..9429ff3 --- /dev/null +++ b/DCCpp_Uno/Inglenook.cpp @@ -0,0 +1,109 @@ +#include +#include "LCD.h" +//#include + +#include "Inglenook.h" + +// Game State Machine States +#define STATE_IDLE 0 +#define STATE_MENUS 1 +#define STATE_ACTION 2 +#define STATE_BUILD 3 +int game_state = STATE_MENUS; + +// Array holding the current sorting of the train to be built. +int train[TRAIN_LENGTH]; + +InglenookGame::InglenookGame() { + ; // Do nothing +} + +static void InglenookGame::begin() { + randomSeed(analogRead(44)); + car_index = -1; +} + +/* +void InglenookGame::printWelcome(LCD *lcd, char *row1, char *row2) { + lcd->clear(); + sprintf(row1, "INGLENOOK GAME"); + sprintf(row2, "Select to start"); + lcd->updateDisplay(row1, row2); // TODO: Fix this. +} +*/ + +int InglenookGame::carIndex() { + return(car_index); +} + +void InglenookGame::setCarIndex(int c) { + car_index = c; +} + +void InglenookGame::buildTrain() { + bool cars_used[NUM_CARS] = { false, false, false, false, false, false, false, false }; + int car = 0; + bool found_one = false; + game_state = STATE_BUILD; + for (int i = 0; i < TRAIN_LENGTH; i++) { + found_one = false; + do { + car = random(0, NUM_CARS) & 0xFFFF; + //Serial.println("found " + String(car)); + if (cars_used[car] == false) { + // Car is not used. Use it. + cars_used[car] = true; + train[i] = car; + found_one = true; + //Serial.println("using " + String(car)); + } // if + } while (!found_one); + //Serial.println("i = " + String(i) + " car = " + String(car)); + } // for +} + +void InglenookGame::doDisplayTrain(LCD *lcd, char *row1, char *row2) { + // DEBUG: + //Serial.println("BUILD THIS TRAIN"); + //for (int i = 0; i < TRAIN_LENGTH; i++) { + //Serial.print(String(i+1) + ": " + carnames[train[i]] + "; "); + //} + //Serial.println(""); + // END DEBUG + lcd->clear(); + sprintf(row1, "BUILD THIS TRAIN"); + sprintf(row2, ""); + for (int i = 0; i < TRAIN_LENGTH; i++) { + sprintf(row2, "%s%d ", row2, train[i]+1); + } + lcd->updateDisplay(row1, row2); + lcd->noBlink(); +} + +void InglenookGame::doListTrain(LCD *lcd, char *row1, char *row2, int car) { + String s1, s2; + switch(car) { + case 1: + s1 = "2:" + carnames[train[1]]; + s2 = "3:" + carnames[train[2]]; + break; + case 2: + s1 = "3:" + carnames[train[2]]; + s2 = "4:" + carnames[train[3]]; + break; + case 3: + s1 = "4:" + carnames[train[3]]; + s2 = "5:" + carnames[train[4]]; + break; + case 0: + default: + s1 = "1:" + carnames[train[0]]; + s2 = "2:" + carnames[train[1]]; + break; + } // switch(car) + lcd->clear(); + sprintf(row1, s1.c_str()); + sprintf(row2, s2.c_str()); + lcd->updateDisplay(row1, row2); +} + From be60fff93535f449a75e11e255a862052b4b6b50 Mon Sep 17 00:00:00 2001 From: TwinDad Date: Wed, 1 Feb 2017 16:11:43 -0500 Subject: [PATCH 10/21] Forgot to add Inglenook --- DCCpp_Uno/Inglenook.cpp | 109 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 DCCpp_Uno/Inglenook.cpp diff --git a/DCCpp_Uno/Inglenook.cpp b/DCCpp_Uno/Inglenook.cpp new file mode 100644 index 0000000..9429ff3 --- /dev/null +++ b/DCCpp_Uno/Inglenook.cpp @@ -0,0 +1,109 @@ +#include +#include "LCD.h" +//#include + +#include "Inglenook.h" + +// Game State Machine States +#define STATE_IDLE 0 +#define STATE_MENUS 1 +#define STATE_ACTION 2 +#define STATE_BUILD 3 +int game_state = STATE_MENUS; + +// Array holding the current sorting of the train to be built. +int train[TRAIN_LENGTH]; + +InglenookGame::InglenookGame() { + ; // Do nothing +} + +static void InglenookGame::begin() { + randomSeed(analogRead(44)); + car_index = -1; +} + +/* +void InglenookGame::printWelcome(LCD *lcd, char *row1, char *row2) { + lcd->clear(); + sprintf(row1, "INGLENOOK GAME"); + sprintf(row2, "Select to start"); + lcd->updateDisplay(row1, row2); // TODO: Fix this. +} +*/ + +int InglenookGame::carIndex() { + return(car_index); +} + +void InglenookGame::setCarIndex(int c) { + car_index = c; +} + +void InglenookGame::buildTrain() { + bool cars_used[NUM_CARS] = { false, false, false, false, false, false, false, false }; + int car = 0; + bool found_one = false; + game_state = STATE_BUILD; + for (int i = 0; i < TRAIN_LENGTH; i++) { + found_one = false; + do { + car = random(0, NUM_CARS) & 0xFFFF; + //Serial.println("found " + String(car)); + if (cars_used[car] == false) { + // Car is not used. Use it. + cars_used[car] = true; + train[i] = car; + found_one = true; + //Serial.println("using " + String(car)); + } // if + } while (!found_one); + //Serial.println("i = " + String(i) + " car = " + String(car)); + } // for +} + +void InglenookGame::doDisplayTrain(LCD *lcd, char *row1, char *row2) { + // DEBUG: + //Serial.println("BUILD THIS TRAIN"); + //for (int i = 0; i < TRAIN_LENGTH; i++) { + //Serial.print(String(i+1) + ": " + carnames[train[i]] + "; "); + //} + //Serial.println(""); + // END DEBUG + lcd->clear(); + sprintf(row1, "BUILD THIS TRAIN"); + sprintf(row2, ""); + for (int i = 0; i < TRAIN_LENGTH; i++) { + sprintf(row2, "%s%d ", row2, train[i]+1); + } + lcd->updateDisplay(row1, row2); + lcd->noBlink(); +} + +void InglenookGame::doListTrain(LCD *lcd, char *row1, char *row2, int car) { + String s1, s2; + switch(car) { + case 1: + s1 = "2:" + carnames[train[1]]; + s2 = "3:" + carnames[train[2]]; + break; + case 2: + s1 = "3:" + carnames[train[2]]; + s2 = "4:" + carnames[train[3]]; + break; + case 3: + s1 = "4:" + carnames[train[3]]; + s2 = "5:" + carnames[train[4]]; + break; + case 0: + default: + s1 = "1:" + carnames[train[0]]; + s2 = "2:" + carnames[train[1]]; + break; + } // switch(car) + lcd->clear(); + sprintf(row1, s1.c_str()); + sprintf(row2, s2.c_str()); + lcd->updateDisplay(row1, row2); +} + From 23cb182caccc107283d6abf4b082ad56b44cd22a Mon Sep 17 00:00:00 2001 From: TwinDad Date: Wed, 1 Feb 2017 16:15:17 -0500 Subject: [PATCH 11/21] Removed LCDThrottle and related files. This branch should be "pure" WiThrottle. NOTE: Compiles, but may not execute. Not tested. --- DCCpp_Uno/Config.h | 9 +- DCCpp_Uno/DCCpp_Uno.ino | 16 - DCCpp_Uno/EEStore.cpp | 16 - DCCpp_Uno/Inglenook.cpp | 109 ----- DCCpp_Uno/Inglenook.h | 40 -- DCCpp_Uno/LCD.cpp | 195 --------- DCCpp_Uno/LCD.h | 61 --- DCCpp_Uno/LCDThrottle.cpp | 815 -------------------------------------- DCCpp_Uno/LCDThrottle.h | 69 ---- 9 files changed, 1 insertion(+), 1329 deletions(-) delete mode 100644 DCCpp_Uno/Inglenook.cpp delete mode 100644 DCCpp_Uno/Inglenook.h delete mode 100644 DCCpp_Uno/LCD.cpp delete mode 100644 DCCpp_Uno/LCD.h delete mode 100644 DCCpp_Uno/LCDThrottle.cpp delete mode 100644 DCCpp_Uno/LCDThrottle.h diff --git a/DCCpp_Uno/Config.h b/DCCpp_Uno/Config.h index 19ecb9f..c862135 100644 --- a/DCCpp_Uno/Config.h +++ b/DCCpp_Uno/Config.h @@ -60,14 +60,7 @@ Part of DCC++ BASE STATION for the Arduino // ENABLE THE WITHROTTLE INTERFACE // -#define WITHROTTLE_SUPPORT 0 - -///////////////////////////////////////////////////////////////////////////////////// -// -// ENABLE THE LCD THROTTLE. REQUIRES AN LCD WITH 5 BUTTONS. -// - -#define LCD_THROTTLE 1 +#define WITHROTTLE_SUPPORT 1 ///////////////////////////////////////////////////////////////////////////////////// diff --git a/DCCpp_Uno/DCCpp_Uno.ino b/DCCpp_Uno/DCCpp_Uno.ino index 34e16d2..e330543 100644 --- a/DCCpp_Uno/DCCpp_Uno.ino +++ b/DCCpp_Uno/DCCpp_Uno.ino @@ -177,9 +177,6 @@ DCC++ BASE STATION is configured through the Config.h file that contains all use #include "EEStore.h" #include "Config.h" #include "Comm.h" -#if (LCD_THROTTLE == 1) -#include "LCDThrottle.h" -#endif void showConfiguration(); @@ -199,20 +196,12 @@ volatile RegisterList progRegs(2); // create a shorter list CurrentMonitor mainMonitor(CURRENT_MONITOR_PIN_MAIN,""); // create monitor for current on Main Track CurrentMonitor progMonitor(CURRENT_MONITOR_PIN_PROG,""); // create monitor for current on Program Track -#if (LCD_THROTTLE == 1) -LCDThrottle *lcdThrottle; -#endif - /////////////////////////////////////////////////////////////////////////////// // MAIN ARDUINO LOOP /////////////////////////////////////////////////////////////////////////////// void loop(){ -#if (LCD_THROTTLE == 1) - lcdThrottle->run(); -#endif - SerialCommand::process(); // check for, and process, and new serial commands if(CurrentMonitor::checkTime()){ // if sufficient time has elapsed since last update, check current draw on Main and Program Tracks @@ -233,11 +222,6 @@ void setup(){ Serial.begin(115200); // configure serial interface Serial.flush(); - #if (LCD_THROTTLE == 1) - lcdThrottle = new LCDThrottle(); - lcdThrottle->begin(1); - #endif - #ifdef SDCARD_CS pinMode(SDCARD_CS,OUTPUT); digitalWrite(SDCARD_CS,HIGH); // Deselect the SD card diff --git a/DCCpp_Uno/EEStore.cpp b/DCCpp_Uno/EEStore.cpp index 8c86587..a58d431 100644 --- a/DCCpp_Uno/EEStore.cpp +++ b/DCCpp_Uno/EEStore.cpp @@ -13,16 +13,6 @@ Part of DCC++ BASE STATION for the Arduino #include "Sensor.h" #include "Outputs.h" #include -#if (LCD_THROTTLE == 1) -// NOTE: Because the turnout/sensor/output data is variable length, the -// LCDThrottle data MUST come first in the list of things to be read/written -// We only allocate space for them (using advance()) and don't actually -// read/write them, but if we don't do that, then their locations could -// move and cause problems. And I don't want to have to re-write all the -// sensor/turnout/output data every time the user changes between -// standard and switcher throttle display. -#include "LCDThrottle.h" -#endif // LCD_THROTTLE /////////////////////////////////////////////////////////////////////////////// @@ -42,9 +32,6 @@ void EEStore::init(){ } reset(); // set memory pointer to first free EEPROM space -#if (LCD_THROTTLE == 1) - advance(sizeof(struct LCDThrottleData)); -#endif Turnout::load(); // load turnout definitions Sensor::load(); // load sensor definitions Output::load(); // load output definitions @@ -67,9 +54,6 @@ void EEStore::clear(){ void EEStore::store(){ reset(); -#if (LCD_THROTTLE == 1) - advance(sizeof(LCDThrottleData)); -#endif Turnout::store(); Sensor::store(); Output::store(); diff --git a/DCCpp_Uno/Inglenook.cpp b/DCCpp_Uno/Inglenook.cpp deleted file mode 100644 index 9429ff3..0000000 --- a/DCCpp_Uno/Inglenook.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include -#include "LCD.h" -//#include - -#include "Inglenook.h" - -// Game State Machine States -#define STATE_IDLE 0 -#define STATE_MENUS 1 -#define STATE_ACTION 2 -#define STATE_BUILD 3 -int game_state = STATE_MENUS; - -// Array holding the current sorting of the train to be built. -int train[TRAIN_LENGTH]; - -InglenookGame::InglenookGame() { - ; // Do nothing -} - -static void InglenookGame::begin() { - randomSeed(analogRead(44)); - car_index = -1; -} - -/* -void InglenookGame::printWelcome(LCD *lcd, char *row1, char *row2) { - lcd->clear(); - sprintf(row1, "INGLENOOK GAME"); - sprintf(row2, "Select to start"); - lcd->updateDisplay(row1, row2); // TODO: Fix this. -} -*/ - -int InglenookGame::carIndex() { - return(car_index); -} - -void InglenookGame::setCarIndex(int c) { - car_index = c; -} - -void InglenookGame::buildTrain() { - bool cars_used[NUM_CARS] = { false, false, false, false, false, false, false, false }; - int car = 0; - bool found_one = false; - game_state = STATE_BUILD; - for (int i = 0; i < TRAIN_LENGTH; i++) { - found_one = false; - do { - car = random(0, NUM_CARS) & 0xFFFF; - //Serial.println("found " + String(car)); - if (cars_used[car] == false) { - // Car is not used. Use it. - cars_used[car] = true; - train[i] = car; - found_one = true; - //Serial.println("using " + String(car)); - } // if - } while (!found_one); - //Serial.println("i = " + String(i) + " car = " + String(car)); - } // for -} - -void InglenookGame::doDisplayTrain(LCD *lcd, char *row1, char *row2) { - // DEBUG: - //Serial.println("BUILD THIS TRAIN"); - //for (int i = 0; i < TRAIN_LENGTH; i++) { - //Serial.print(String(i+1) + ": " + carnames[train[i]] + "; "); - //} - //Serial.println(""); - // END DEBUG - lcd->clear(); - sprintf(row1, "BUILD THIS TRAIN"); - sprintf(row2, ""); - for (int i = 0; i < TRAIN_LENGTH; i++) { - sprintf(row2, "%s%d ", row2, train[i]+1); - } - lcd->updateDisplay(row1, row2); - lcd->noBlink(); -} - -void InglenookGame::doListTrain(LCD *lcd, char *row1, char *row2, int car) { - String s1, s2; - switch(car) { - case 1: - s1 = "2:" + carnames[train[1]]; - s2 = "3:" + carnames[train[2]]; - break; - case 2: - s1 = "3:" + carnames[train[2]]; - s2 = "4:" + carnames[train[3]]; - break; - case 3: - s1 = "4:" + carnames[train[3]]; - s2 = "5:" + carnames[train[4]]; - break; - case 0: - default: - s1 = "1:" + carnames[train[0]]; - s2 = "2:" + carnames[train[1]]; - break; - } // switch(car) - lcd->clear(); - sprintf(row1, s1.c_str()); - sprintf(row2, s2.c_str()); - lcd->updateDisplay(row1, row2); -} - diff --git a/DCCpp_Uno/Inglenook.h b/DCCpp_Uno/Inglenook.h deleted file mode 100644 index 04f2c7d..0000000 --- a/DCCpp_Uno/Inglenook.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef INGLENOOK_H -#define INGLENOOK_H - -// NOTE: These are actually set by the rules of the game... -// WARNING: There are several places (initialization, etc.) where -// the value of these is assumed fixed at 5 and 8. -#define TRAIN_LENGTH 5 -#define NUM_CARS 8 - -const String carnames[NUM_CARS] = { // String names of cars on the layout (for display) - "Blue Boxcar", - "Red Boxcar", - "Green Boxcar", - "Black Tank Car", - "Black Gondola", - "Grey Hopper", - "Brown Flatcar", - "Brown Boxcar" -}; - -class InglenookGame { - private: - int car_index; - - public: - InglenookGame(); - void begin(); - void buildTrain(void); - void doDisplayTrain(LCD *lcd, char *row1, char *row2); - int carIndex(); - void setCarIndex(int c); - void doMenuDisplay(LCD *lcd, char *row1, char *row2); - void doListTrain(LCD *lcd, char *row1, char *row2, int car); - - protected: - //void printWelcome(LCD *lcd, char *row1, char *row2); -}; - - -#endif diff --git a/DCCpp_Uno/LCD.cpp b/DCCpp_Uno/LCD.cpp deleted file mode 100644 index 12ab9de..0000000 --- a/DCCpp_Uno/LCD.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include -#include - -// Include our own header with definitions and such. -#include "LCD.h" - -// Include the necessary libraries for the different display shields. -#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) -#include -#include -#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) -#include -#include // is this necessary? -#else -#error CANNOT COMPILE -- INVALID LCD LIBRARY SELECTED -#endif - -// Create the local object of the display we are -// controlling. -#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) -static LCDKeypad lcd = LCDKeypad(); -#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) -static Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield(); -#endif - -// Define the size of the display. Pretty much assumes -// 16x2 but could be modded to change that. -#define LCD_NUM_ROWS 2 -#define LCD_NUM_COLS 16 - -// State machine for handling processing of the buttons. -#define DISPLAY_STATE_RUN 0 -#define DISPLAY_STATE_DEBOUNCE 1 -#define DISPLAY_STATE_DEBOUNCE_COMPLETE 2 -#define DISPLAY_STATE_LONG_DEBOUNCE 3 -#define DISPLAY_STATE_LONG_DEBOUNCE_COMPLETE 4 -#define DISPLAY_STATE_LONG_DEBOUNCE_WAIT 5 - -// ctor -LCD::LCD() { - // Nothing to do ... yet ... -} - -// Setup function. Call from setup() -void LCD::begin() { - buttonVal = KEYS_NONE; - displayState = DISPLAY_STATE_RUN; - lcd.begin(LCD_NUM_COLS, LCD_NUM_ROWS); -} - -// Run loop for processing the buttons. Call from loop() -void LCD::run() { - debounceButtons(); -} - -//------------------------------------------------ -// Pass-through methods for handling the display -void LCD::clear() { - lcd.clear(); -} - -void LCD::setCursor(int c, int r) { - lcd.setCursor(c, r); -} - -void LCD::blink() { - lcd.blink(); -} - -void LCD::noBlink() { - lcd.noBlink(); -} - -void LCD::cursor() { - lcd.cursor(); -} - -void LCD::noCursor() { - lcd.noCursor(); -} - -//------------------------------------------------ -// Custom methods - -// Update the two lines of the display with these two strings. -void LCD::updateDisplay(char *row1, char *row2) { - lcd.clear(); - lcd.setCursor(0,0); lcd.print(row1); - lcd.setCursor(0,1); lcd.print(row2); -} - - -// Get the current button value. -int LCD::getButtons() { - int bv = buttonVal; - buttonVal = KEYS_NONE; - return(bv); -} - -//------------------------------------------------ -// Private / Protected internal methods - -// Retrieve the hardware button value and translate -// it into one of our "standard" button values. -int LCD::getButton() { -#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) - // OSEPP LCDKeypad - int b = lcd.button(); - //Serial.print(String(b) + " "); - return(b); -#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) - // ADAFRUIT RGBLCD - uint8_t buttons = lcd.readButtons(); - if (buttons & BUTTON_UP) { - return(KEYS_UP); - } - if (buttons & BUTTON_DOWN) { - return(KEYS_DOWN); - } - if (buttons & BUTTON_LEFT) { - return(KEYS_LEFT); - } - if (buttons & BUTTON_RIGHT) { - return(KEYS_RIGHT); - } - if (buttons & BUTTON_SELECT) { - return(KEYS_SELECT); - } - return(KEYS_NONE); -#endif -} - -// Debounce the button press and figure out if it's a long press. -void LCD::debounceButtons() { - int button = getButton(); - static long startDebounce; - static int keyval; - int retv; - switch(displayState) { - case DISPLAY_STATE_RUN: - if (button != KEYS_NONE) { - Serial.println("Raw Key: " + String(button)); - startDebounce = millis(); - keyval = button; - displayState = DISPLAY_STATE_DEBOUNCE; - } - // Always return KEYS_NONE from this state - break; - - case DISPLAY_STATE_DEBOUNCE: - // actively debouncing... - if ((button != KEYS_NONE) && ((millis() - startDebounce) > LONG_PRESS_MS)) { - // Longer than 2 second hold - displayState = DISPLAY_STATE_DEBOUNCE_COMPLETE; - switch(keyval) { - case KEYS_RIGHT: - keyval = KEYS_LONG_RIGHT; break; - case KEYS_UP: - keyval = KEYS_LONG_UP; break; - case KEYS_DOWN: - keyval = KEYS_LONG_DOWN; break; - case KEYS_LEFT: - keyval = KEYS_LONG_LEFT; break; - case KEYS_SELECT: - keyval = KEYS_LONG_SELECT; break; - default: - break; - } - buttonVal = keyval; - keyval = KEYS_NONE; - Serial.println("2 second button press! Val = " + String(buttonVal)); - - } else if (button == KEYS_NONE) { - // Short press. - Serial.println("Short Press. Debounced Key: " + String(keyval)); - // Debounce complete. Decide if it's long or not. - displayState = DISPLAY_STATE_DEBOUNCE_COMPLETE; - buttonVal = keyval; - } else { - // Not finished debouncing yet. Do nothing. - } // KEYS_NONE - break; - - case DISPLAY_STATE_LONG_DEBOUNCE_WAIT: - case DISPLAY_STATE_DEBOUNCE_COMPLETE: - // Long press detected, waiting for release before handling more button presses - if (button == KEYS_NONE) { - // User has released button. - displayState = DISPLAY_STATE_RUN; - buttonVal = KEYS_NONE; - } - break; - - } // switch(displayState) -} diff --git a/DCCpp_Uno/LCD.h b/DCCpp_Uno/LCD.h deleted file mode 100644 index edd6668..0000000 --- a/DCCpp_Uno/LCD.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef LCD_H -#define LCD_H - -//------------------------------------------------ -// Generic wrapper class for various different -// 16x2 LCD shields with buttons.. Namely: -// * the 4/8-pin OSEPP model -// * the Adafruit I2C version -// -// Allows for long vs. short presses of the buttons -// (but not momentary action -- yet) - -// LCD Display Types: -// -// 0 = OSEPP LCDKeypad -// 1 = Adafruit RGB LCD -#define LCD_DISPLAY_TYPE_OSEPP 0 -#define LCD_DISPLAY_TYPE_ADAFRUIT 1 -#define LCD_DISPLAY_TYPE LCD_DISPLAY_TYPE_ADAFRUIT - -// Defines how long a "long press" is in milliseconds. -#define LONG_PRESS_MS 2000 - -// Define generic button names for multi-library compatibility -// Return values from getButtons(); -#define KEYS_NONE -1 -#define KEYS_RIGHT 0 -#define KEYS_UP 1 -#define KEYS_DOWN 2 -#define KEYS_LEFT 3 -#define KEYS_SELECT 4 -#define KEYS_LONG_RIGHT 128 -#define KEYS_LONG_UP 129 -#define KEYS_LONG_DOWN 130 -#define KEYS_LONG_LEFT 131 -#define KEYS_LONG_SELECT 132 - - -class LCD { - private: - byte displayState; - int buttonVal; - public: - LCD(); - void begin(); // Call from setup() - void run(); // Call from loop() - int getButtons(); - void updateDisplay(char *row1, char *row2); - void clear(); - void setCursor(int c, int r); - void blink(); - void noBlink(); - void cursor(); - void noCursor(); - protected: - int getButton(); - void debounceButtons(); - -}; - -#endif // LCD_H diff --git a/DCCpp_Uno/LCDThrottle.cpp b/DCCpp_Uno/LCDThrottle.cpp deleted file mode 100644 index 7bd98f0..0000000 --- a/DCCpp_Uno/LCDThrottle.cpp +++ /dev/null @@ -1,815 +0,0 @@ -#include -#include -#include -#include "SerialCommand.h" -#include "EEStore.h" -#include "LCDThrottle.h" -#include "Inglenook.h" - -//-------------------------------------------------------------------- -/* LCD Throttle - * - * Uses an LCD with buttons to provide a directly-connected throttle - * interface for DCC++. Includes an "Inglenook Sidings" game that will - * randomize a set of cars and tell you a 5-car train to build. - * - * Boots up in the Menu state. Menu options include: - * -- Use Throttle - * -- Set Track Power On/Off - * -- Set Address - * -- Set Display Mode - * -- Set maximum speed (in 128 speed steps) - * -- Play Inglenook Sidings - * - * A long (2-sec+) press on SELECT will toggle between Menu and Throttle view. - * - * Throttle views (Display Modes): - * -- Standard: Separate Direction and Speed indicators - * -- Switcher: Combined bidirectional Direction+Speed indicator - * - * Each click of LEFT/RIGHT will increase/decrease the speed by a fraction - * of the maximum speed set in the menus. The amount depends on the view mode. - * Standard view has 15 steps, so (e.g.) with max speed = 60, each click is - * 4 speed steps. - * Switcher view has +/- 7 steps, so (e.g.) with max speed = 63, each click is - * 9 speed steps. - * - * KNOWN BUGS: - * -- The "spinners" for the highest digit of the Set Address and Set Max Speed - * menu items don't work right. - * -- Some of the menu responses to navigation buttons aren't consistent - */ -//-------------------------------------------------------------------- - -#define MAX_SPEED 126 /*126 */ -#define MAX_NOTCH_NORMAL 15 -#define MAX_NOTCH_SWITCHER 7 -#define DEFAULT_CAB 3 - -#define THROTTLE_STATE_RUN 0 -#define THROTTLE_STATE_DEBOUNCE 1 -#define THROTTLE_STATE_MENUS 2 -#define THROTTLE_STATE_MENU_ACTION 3 -#define THROTTLE_STATE_GAME_MENUS 4 -#define THROTTLE_STATE_GAME_BUILD 5 - -struct LCDThrottleData LCDT_EEPROM_Store; - -// MAX_COMMAND_LENGTH is defined in DCC++ SerialCommand.h -//#define MAX_COMMAND_LENGTH 30 -// Buffer for writing DCC++ commands to the core base station code. -char command[MAX_COMMAND_LENGTH]; - -// Holder for display string construction -static char display[2][17]; - -// Menu subsystem "stuff" -static void menuUseEvent(MenuUseEvent e); -static void menuChangeEvent(MenuChangeEvent e); -static MenuBackend *menu; - -/** Constructor - * - */ -LCDThrottle::LCDThrottle() { - ; // do nothing -} - - -void LCDThrottle::begin(int reg) { - lcd = new LCD(); - lcd->begin(); - //EEPROM_GetAll(); - load(); - if (maxSpeed == 0) { - // EEPROM not yet initialized -> set default. - maxSpeed = MAX_SPEED; - } - if (cab == 0) { - // EEPROM not yet initialized -> set default. - cab = DEFAULT_CAB; - } - throttleState = THROTTLE_STATE_MENUS; // Always start in the menus. - jumpbackState = THROTTLE_STATE_MENUS; - this->reg = reg; // Store the register we should use. - notch = 0; // Idle the loco - speed = 0; - dir = FORWARD; - // TODO: Get the actual track power state from the base station. - power_state = false; // Assume track power is off. - sendPowerCommand(power_state); // Don't assume. Force it off. - // Fire up the Inglenook game code. - game = InglenookGame(); - game.begin(); - // Construct the menus. - menu = new MenuBackend(menuUseEvent, menuChangeEvent); - menuSetup(); - doMenuDisplay(); -} - -/** run() - * - * Main Run loop - */ -void LCDThrottle::run() { - // Run the underlying LCD stuff - lcd->run(); - - // Grab any button presses and process them - int button = lcd->getButtons(); - - switch(throttleState) { - - case THROTTLE_STATE_RUN: - switch(button) { - case KEYS_RIGHT: - // Speed up - increaseSpeed(); - sendThrottleCommand(); - updateDisplay(); - break; - - case KEYS_LEFT: - // Slow down - decreaseSpeed(); - sendThrottleCommand(); - updateDisplay(); - break; - - case KEYS_UP: - case KEYS_DOWN: - // For now, dumbly toggle direction with either up or down key. - // Maybe make this smarter or repurpose later. - // Possibly use up/down keys for Functions. - dir = (dir == FORWARD ? REVERSE : FORWARD); - sendThrottleCommand(); - updateDisplay(); - break; - - case KEYS_SELECT: - // For now, this (Short tap) is emergency stop. - speed = -1; - notch = 0; - sendThrottleCommand(); - // reset speed to Zero after sending "Emergency Stop" special value of -1 - // This won't hurt b/c the Base Station will set the loco's speed to zero as well. - speed = 0; - updateDisplay(); - break; - - case KEYS_LONG_SELECT: - // Idle the Loco and switch to Menus mode. - speed = 0; - notch = 0; - sendThrottleCommand(); - //Serial.println("JumpbackState == " + String(jumpbackState)); - // Jump back to the last non-throttle state we were in. - // This is so when playing the game you can hop back and forth - // directly between the throttle and the built train view. - throttleState = jumpbackState; - // What to display depends on which state we're returning to. - // TODO: Update the updateDisplay() method to handle this instead, if possible. - if (throttleState == THROTTLE_STATE_GAME_BUILD) { - game.doDisplayTrain(lcd, display[0], display[1]); - } else if (throttleState == THROTTLE_STATE_GAME_MENUS) { - doGameMenuDisplay(); - } else { - doMenuDisplay(); - } - break; - - default: - break; - // Do nothing. - } // switch(button) - break; - - case THROTTLE_STATE_DEBOUNCE: - // ??? - break; - - case THROTTLE_STATE_MENUS: - // Present the top-level menu. - switch(button) { - case KEYS_UP: - Serial.println("Up/Left"); - // Don't let the menu system move up to Root. - if (menu->getCurrent() == "Set Track Power") { - // do nothing. - } else { - menu->moveUp(); - } - doMenuDisplay(); - break; - - case KEYS_DOWN: - Serial.println("Down/Right"); - menu->moveDown(); - doMenuDisplay(); - break; - - case KEYS_RIGHT: - //menu->moveRight(); - doMenuDisplay(); - break; - - case KEYS_LEFT: - menu->moveLeft(); - doMenuDisplay(); - break; - - case KEYS_SELECT: - Serial.println("Select"); - throttleState = THROTTLE_STATE_MENU_ACTION; - doMenuAction(button); - break; - - case KEYS_LONG_SELECT: - // Jump to throttle mode. Save Menus as jumpback state. - Serial.println("Long Select"); - throttleState = THROTTLE_STATE_RUN; - jumpbackState = THROTTLE_STATE_MENUS; - updateDisplay(); - break; - } - break; - - case THROTTLE_STATE_MENU_ACTION: - // Handle top-level menu actions. - switch(button) { - case KEYS_SELECT: - // Do the action and return. - //EEPROM_StoreAll(); - store(); - throttleState = THROTTLE_STATE_MENUS; - doMenuDisplay(); - break; - - case KEYS_UP: - case KEYS_DOWN: - case KEYS_LEFT: - case KEYS_RIGHT: - doMenuAction(button); - break; - - case KEYS_LONG_SELECT: - // Jump back to throttle state. Don't retun here - // Return to the menu itself instead. - //EEPROM_StoreAll(); - store(); - throttleState = THROTTLE_STATE_RUN; - jumpbackState = THROTTLE_STATE_MENUS; - button = KEYS_NONE; - updateDisplay(); - break; - } - break; - - case THROTTLE_STATE_GAME_MENUS: - case THROTTLE_STATE_GAME_BUILD: - // Handle the game action. - doGameMenus(button); - break; - - default: - break; - - } // switch(throttleState) -} - -/** menuSetup() - * - * Construct the menu tree - */ -void LCDThrottle::menuSetup() { - MenuItem *miPower = new MenuItem("Set Track Power"); - MenuItem *miRun = new MenuItem("Use Throttle"); - MenuItem *miAddr = new MenuItem("Set Address"); - MenuItem *miDisp = new MenuItem("Select Display"); - MenuItem *miMax = new MenuItem("Max Speed"); - MenuItem *miGame = new MenuItem("Inglenook"); - menu->getRoot().add(*miPower); - miPower->add(*miRun); - miRun->add(*miAddr); - miAddr->add(*miDisp); - miDisp->add(*miMax); - miMax->add(*miGame); - MenuItem *miBuild = new MenuItem("Build Train"); - MenuItem *miList = new MenuItem("List Cars"); - MenuItem *miCarList[7] = { - new MenuItem("Car 0"), - new MenuItem("Car 1"), - new MenuItem("Car 2"), - new MenuItem("Car 3"), - new MenuItem("Car 4"), - new MenuItem("Car 5"), - new MenuItem("Car 6") - }; - miGame->addRight(*miBuild); - miBuild->add(*miList); - miList->addRight(*miCarList[0]); - for (int i = 0; i < NUM_CARS-2; i++) { - miCarList[i]->add(*miCarList[i+1]); - } - // Get off of the root node. - menu->moveDown(); -} - -/** doMenuDisplay() - * - * Change the display in response to the current top-level menu selection - */ -void LCDThrottle::doMenuDisplay() { - lcd->noCursor(); - - if (menu->getCurrent() == "Set Track Power") { - lcd->updateDisplay("THROTTLE MENU:", (power_state == true ? "Track Power ON" : "Track Power OFF")); - } - if (menu->getCurrent() == "Use Throttle") { - lcd->updateDisplay("THROTTLE MENU:", "Use Throttle"); - } - if (menu->getCurrent() == "Set Address") { - lcd->updateDisplay("THROTTLE MENU:", "Set Address"); - } - if (menu->getCurrent() == "Select Display") { - lcd->updateDisplay("THROTTLE MENU:", "Select Display"); - } - if (menu->getCurrent() == "Max Speed") { - lcd->updateDisplay("THROTTLE MENU:", "Max Speed Step"); - } - if (menu->getCurrent() == "Inglenook") { - lcd->updateDisplay("THROTTLE MENU:", "Inglenook Game"); - } -} - -/** doMenuAction() - * - * Take action in response to the user selecting a menu item - */ -void LCDThrottle::doMenuAction(int button) { - Serial.println("doMenuAction(" + String(button) + ")"); - - // Set Track Power: - // Selecting this menu item toggles the track power on or off. - if (menu->getCurrent() == "Set Track Power") { - power_state = !power_state; - sendPowerCommand(power_state); - doMenuDisplay(); - } - - // Use Throttle - // Selecting this jumps you to the throttle mode. - if(menu->getCurrent() == "Use Throttle") { - throttleState = THROTTLE_STATE_RUN; - jumpbackState = THROTTLE_STATE_MENUS; - updateDisplay(); - } - - // Inglenook - // Selecting this moves you into the game menus. - if (menu->getCurrent() == "Inglenook") { - throttleState = THROTTLE_STATE_GAME_MENUS; - menu->moveRight(); - doGameMenuDisplay(); - } - - // Select Display - // Selecting this allows you to change the throttle display mode - // Up/Down keys toggle the value. - // TODO: Left/Right probably should too - if (menu->getCurrent() == "Select Display") { - if (button == KEYS_UP || button == KEYS_DOWN) { - if (displayMode == DISPLAY_MODE_NORMAL) { - displayMode = DISPLAY_MODE_SWITCHER; - } else { - displayMode = DISPLAY_MODE_NORMAL; - } - } - lcd->updateDisplay("Select Display:", - displayMode == DISPLAY_MODE_NORMAL ? - "Standard" : "Switcher"); - } // Select Display - - // Set Address - // Selecting this allows you to "dial in" the loco address. - if (menu->getCurrent() == "Set Address") { - cab = calcIncValue(button, 3, cab, 9999, "%04d", "Set Address:"); - } // Set Address - - // Max Speed - // Selecting this allows you to change the maximum speed setting of the - // loco (effectively setting the speed range of the throttle "knob") - if (menu->getCurrent() == "Max Speed") { - maxSpeed = calcIncValue(button, 2, maxSpeed, 126, "%03d", "Max Speed:"); - } // Set Address - -} - -/** calcIncValue() - * - * Calculate the incremented value to show when "dialing" a settings number. - * TODO: Handling of the most significant digit is broken. - */ -int LCDThrottle::calcIncValue(int button, int maxpos, int val, int maxval, const char *fmt, const char *label) { - static int incval, incpos; - if (button == KEYS_SELECT) { - incval = 1; - incpos = maxpos; - - } else if (button == KEYS_UP) { - if (val + incval > 9999) { - // This will roll over the value. - // For now, don't do anything. - // TODO: Figure out how to roll ONLY the correct digit to zero. - } - else if (val == 9999) { val = 0; } - else { val += incval; } - - } else if (button == KEYS_DOWN) { - if (val - incval < 0) { - // This will roll under the value. - // For now, don't do anything. - // TODO: Figure out how to roll ONLY the correct digit to 9. - } - else if (val == 0) { val = maxval; } - else { val -= incval; } - } else if (button == KEYS_LEFT) { - if (incval < pow(10, maxpos)) { incval *= 10; } - if (incpos > 0) { incpos -= 1; } - } else if (button == KEYS_RIGHT) { - if (incval > 1) { incval /= 10; } - if (incpos < maxpos) { incpos += 1; } - } - Serial.println("val: " + String(val) + " incval: " + String(incval) + " incpos: " + String(incpos)); - sprintf(display[1], fmt, val); - lcd->updateDisplay(label, display[1]); - lcd->setCursor(incpos, 1); - lcd->cursor(); - return(val); -} - -/** sendPowerCommand() - * - * Send a Power on/off command to the DCC++ Base Station - */ -void LCDThrottle::sendPowerCommand(bool on) { - sprintf(command, ""); - sprintf(command, on == true ? "1" : "0"); - Serial.println("LCD Command: " + String(command)); - SerialCommand::parse(command); -} - -/** sendThrottleCommand() - * - * Send a Throttle command to the DCC++ Base Station - */ -void LCDThrottle::sendThrottleCommand() { - sprintf(command, ""); - sprintf(command, "t%d %d %d %d", reg, cab, speed, dir); - Serial.println("LCD Command: " + String(command)); - SerialCommand::parse(command); -} - -/** increaseSpeed() - * - * Increment the current speed by one "notch" - */ -void LCDThrottle::increaseSpeed() { - int tmp_notch; - if (displayMode == DISPLAY_MODE_NORMAL) { - // in DISPLAY_MODE_NORMAL, notch is 0->maxnotch. - // since this is the "increase" function we never have to worry about - // flipping the direction bit. - notch = (notch == MAX_NOTCH_NORMAL ? MAX_NOTCH_NORMAL : notch + 1); - speed = notch * (maxSpeed / MAX_NOTCH_NORMAL); - } else { - // in DISPLAY_MODE_SWITCHER the direction can change when "increasing" - // the throttle, it depends. Have to deal with absolute value. - // First, get the signed version of "notch" and increment it. - tmp_notch = (dir == REVERSE ? -notch : notch); - tmp_notch = (tmp_notch == MAX_NOTCH_SWITCHER ? MAX_NOTCH_SWITCHER : tmp_notch + 1); - // Now handle the possible sign change by setting the direction and - // storing notch = abs(tmp_notch) - if (tmp_notch >= 0) { - dir = FORWARD; - notch = tmp_notch; - } else { - dir = REVERSE; - notch = -tmp_notch; - } - speed = notch * (maxSpeed / MAX_NOTCH_SWITCHER); - } // if(displayMode) - - Serial.println("inc: N= " + String(notch) + " S=" + String(speed)); -} - -/** decreaseSpeed() - * - * Decrement the current speed by one "notch" - */ -void LCDThrottle::decreaseSpeed() { - int tmp_notch; - if (displayMode == DISPLAY_MODE_NORMAL) { - notch = (notch == 0 ? 0 : notch - 1); - speed = notch * (maxSpeed / MAX_NOTCH_NORMAL); - } else { - tmp_notch = (dir == REVERSE ? -notch : notch); - tmp_notch = (tmp_notch == -MAX_NOTCH_SWITCHER ? -MAX_NOTCH_SWITCHER : tmp_notch - 1); - if (tmp_notch < 0) { - dir = REVERSE; - notch = -tmp_notch; - } else { - dir = FORWARD; - notch = tmp_notch; - } - speed = notch * (maxSpeed / MAX_NOTCH_SWITCHER); - } - Serial.println("dec: N= " + String(notch) + " S=" + String(speed)); -} - -/** updateDisplay() - * - * Update the display when in Throttle mode - */ -void LCDThrottle::updateDisplay() { - switch(displayMode) { - case DISPLAY_MODE_SWITCHER: - // SWITCHER: Speed/Direction together - // Loco: - // <------0------> - // The blinking cursor shows the current value - lcd->clear(); - // Draw the line. - sprintf(display[0], "Loco: %04d", cab); - if (power_state == true) { - sprintf(display[1], "<------0------>"); - } else { - sprintf(display[1], "Track Power Off"); - } - lcd->updateDisplay(display[0], display[1]); - Serial.println("D0:" + String(display[0])); - Serial.println("D1:" + String(display[1])); - // Figure out where to put the cursor - if (notch == 0) { - lcd->setCursor(7,1); - } else { - int tmp_notch = notch; - if (tmp_notch == 0) { - tmp_notch += 7; - } else { - tmp_notch = (dir == FORWARD ? tmp_notch + 7 : 7 - tmp_notch); - } - Serial.println("S=" + String(speed) + " N=" + String(notch) + " T=" + String(tmp_notch)); - lcd->setCursor(tmp_notch, 1); - } - if (power_state == true) { - lcd->blink(); - } else { - lcd->noBlink(); - } - break; - - case DISPLAY_MODE_NORMAL: - default: - // NORMAL: Speed + Direction - // <-- --> - // 0--------------+ - // The length of the bar shows the speed - // The arrow shows the direction - lcd->clear(); - if (dir == REVERSE) { - sprintf(display[0], "<--- Loco: %04d", cab); - } else { - sprintf(display[0], "Loco: %04d --->", cab); - } - if (power_state == true) { - sprintf(display[1], "0 "); - if (speed > 0) { - for (int i = 0; i < notch-1; i++) { - display[1][i+1] = '-'; - } - display[1][notch] = '|'; - display[1][notch+1] = 0; - } - } else { - sprintf(display[1], "Track Power Off"); - } - lcd->updateDisplay(display[0], display[1]); - Serial.println("D0:" + String(display[0])); - Serial.println("D1:" + String(display[1])); - break; - } -} - -//--------------------------------------------------------------- -// MenuBackend support functions - -// TODO: I could probably use these effectively to clean up a bunch -// of those switch() statements above. - -/** menuChangeEvent() - * - * Callback for change events. - */ -static void menuChangeEvent(MenuChangeEvent changed) { - // Update the display to reflect the current menu state - // For now we'll use serial output. - Serial.print("Menu change "); - Serial.print(changed.from.getName()); - Serial.print(" -> "); - Serial.println(changed.to.getName()); -} - -/** menuUseEvent() - * - * Callback for "use" events - */ -static void menuUseEvent(MenuUseEvent used) { - //Serial.print("Menu use "); - //Serial.println(used.item.getName()); -} - -//--------------------------------------------------------------- -// EEPROM Interface Functions - -// Memory Locations (byte address) -// NOTE: Deprecated in favor of struct LCDThrottleData and -// DCC++ EEStore interface - -// DCC++ - compliant EEPROM access. -/** load() - * - * load sticky data from EEStore interface - */ -void LCDThrottle::load() { - EEStore::reset(); - EEPROM.get(EEStore::pointer(), displayMode); - EEStore::advance(sizeof(displayMode)); - EEPROM.get(EEStore::pointer(), cab); - EEStore::advance(sizeof(cab)); - EEPROM.get(EEStore::pointer(), maxSpeed); - EEStore::advance(sizeof(maxSpeed)); -} - -/** load() - * - * store sticky data from EEStore interface - */ -void LCDThrottle::store() { - EEStore::reset(); - EEPROM.put(EEStore::pointer(), displayMode); - EEStore::advance(sizeof(displayMode)); - EEPROM.put(EEStore::pointer(), cab); - EEStore::advance(sizeof(cab)); - EEPROM.put(EEStore::pointer(), maxSpeed); - EEStore::advance(sizeof(maxSpeed)); -} - -//--------------------------------------------------------------- -// Inglenook Game Functions - -/** doGameMenus(int button) - * - * Handle the Inglenook Game sub-menu - * - * param button: int -> current button value - */ -void LCDThrottle::doGameMenus(int button) { - switch(throttleState) { - case THROTTLE_STATE_GAME_MENUS: - switch(button) { - case KEYS_UP: - menu->moveUp(); - doGameMenuDisplay(); - break; - - case KEYS_DOWN: - menu->moveDown(); - doGameMenuDisplay(); - break; - - case KEYS_LEFT: - menu->moveLeft(); - if (menu->getCurrent() == "Inglenook") { - throttleState = THROTTLE_STATE_MENUS; - doMenuDisplay(); - } else { - doGameMenuDisplay(); - } - break; - - case KEYS_RIGHT: - menu->moveRight(); - doGameMenuDisplay(); - break; - - case KEYS_SELECT: - if (menu->getCurrent() == "List Cars") { - menu->moveRight(); // for this one "use" == "move right" - doGameMenuDisplay(); - } else if (menu->getCurrent() == "Build Train") { - throttleState = THROTTLE_STATE_GAME_BUILD; - game.buildTrain(); - game.doDisplayTrain(lcd, display[0], display[1]); - } - break; - - case KEYS_LONG_SELECT: - throttleState = THROTTLE_STATE_RUN; - jumpbackState = THROTTLE_STATE_GAME_MENUS; - updateDisplay(); - break; - } - break; - - case THROTTLE_STATE_GAME_BUILD: - switch(button) { - case KEYS_NONE: - break; - - case KEYS_LEFT: - case KEYS_SELECT: - throttleState = THROTTLE_STATE_GAME_MENUS; - doGameMenuDisplay(); - break; - - case KEYS_DOWN: - case KEYS_RIGHT: - if (game.carIndex() >= 3) { - game.setCarIndex(3); - } else { - game.setCarIndex(game.carIndex()+1); - } - Serial.println("List Train: Car index " + String(game.carIndex())); - game.doListTrain(lcd, display[0], display[1], game.carIndex()); - break; - - case KEYS_UP: - if (game.carIndex() > -1) { - game.setCarIndex(game.carIndex()-1); - } - if (game.carIndex() == -1) { - game.doDisplayTrain(lcd, display[0], display[1]); - } else { - Serial.println("List Train: Car index " + String(game.carIndex())); - game.doListTrain(lcd, display[0], display[1], game.carIndex()); - } - break; - - case KEYS_LONG_SELECT: - throttleState = THROTTLE_STATE_RUN; - jumpbackState = THROTTLE_STATE_GAME_BUILD; - updateDisplay(); - break; - - } // switch(buttons) - } // switch(state) -} - -/** doGameMenuDisplay() - * - * Handle display output for the Inglenook Game sub-menu - */ -void LCDThrottle::doGameMenuDisplay() { - lcd->clear(); - - if (menu->getCurrent() == "Build Train") { - sprintf(display[0], "Build Train"); - sprintf(display[1], "List Cars"); - } - if (menu->getCurrent() == "List Cars") { - sprintf(display[0], "List Cars"); - sprintf(display[1], ""); - } - if (menu->getCurrent() == "Car 0") { - sprintf(display[0],"%s%s", "1:",carnames[0].c_str()); - sprintf(display[1],"%s%s", "2:",carnames[1].c_str()); - } - if (menu->getCurrent() == "Car 1") { - sprintf(display[0],"%s%s", "2:",carnames[1].c_str()); - sprintf(display[1],"%s%s", "3:",carnames[2].c_str()); - } - if (menu->getCurrent() == "Car 2") { - sprintf(display[0],"%s%s", "3:",carnames[2].c_str()); - sprintf(display[1],"%s%s", "4:",carnames[3].c_str()); - } - if (menu->getCurrent() == "Car 3") { - sprintf(display[0],"%s%s", "4:",carnames[3].c_str()); - sprintf(display[1],"%s%s", "5:",carnames[4].c_str()); - } - if (menu->getCurrent() == "Car 4") { - sprintf(display[0],"%s%s", "5:",carnames[4].c_str()); - sprintf(display[1],"%s%s", "6:",carnames[5].c_str()); - } - if (menu->getCurrent() == "Car 5") { - sprintf(display[0],"%s%s", "6:",carnames[5].c_str()); - sprintf(display[1],"%s%s", "7:",carnames[6].c_str()); - } - if ((menu->getCurrent() == "Car 6") || (menu->getCurrent() == "Car 7")) { - sprintf(display[0],"%s%s", "7:",carnames[6].c_str()); - sprintf(display[1],"%s%s", "8:",carnames[7].c_str()); - } - lcd->updateDisplay(display[0], display[1]); - lcd->setCursor(0,0); - lcd->blink(); -} diff --git a/DCCpp_Uno/LCDThrottle.h b/DCCpp_Uno/LCDThrottle.h deleted file mode 100644 index ebdd5d5..0000000 --- a/DCCpp_Uno/LCDThrottle.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef LCD_THROTTLE_H -#define LCD_THROTTLE_H - -#include "LCD.h" -#include "Inglenook.h" - -// DCC++ Throttle using the buttons on a 16x2 + 5 button Display Shield - -#define DISPLAY_MODE_NORMAL 1 -#define DISPLAY_MODE_SWITCHER 2 -#define DISPLAY_MODE DISPLAY_MODE_NORMAL - -// Throttle Directions -#define FORWARD 1 -#define REVERSE 0 - -class MenuItem; - -class LCDThrottle { -private: - int throttleState; -int jumpbackState; - int reg; - int cab; - int speed; - int dir; - byte displayMode; - int notch; - LCD *lcd; - int maxSpeed; - bool power_state; - InglenookGame game; - - public: - LCDThrottle(); - void begin(int reg); - void run(); - - protected: - void sendThrottleCommand(); - void sendPowerCommand(bool on); - int debounceButtons(); - int getButton(); - void increaseSpeed(); - void decreaseSpeed(); - void updateDisplay(); - void menuSetup(); - void doMenuDisplay(); - void doMenuAction(int button); - void load(); - void store(); - void doGameMenus(int b); - void setupInglenookMenu(MenuItem *m); - void inglenookBegin(); - void doGameMenuDisplay(); - int calcIncValue(int button, int maxpos, int val, int maxval, const char *fmt, const char *label); - -}; - -struct LCDThrottleData { -byte dislay; -byte reserved1; -int address; -int maxspeed; -int reserved[5]; -}; - - -#endif // LCD_THROTTLE_H From 5141379ef0f603a385681f1c0809aa0755e757af Mon Sep 17 00:00:00 2001 From: TwinDad Date: Tue, 7 Feb 2017 08:31:57 -0500 Subject: [PATCH 12/21] Add support for power control and turnout control. NOTE: Turnout control does not yet work right, in that the throttle doesn't show the turnouts. Still debugging that. Also, having a static table of turnout addresses compiled into the code seems a bit of a hack. Cleaned up some comments etc. as well. Switched to DHCP-assigned address. --- DCCpp_Uno/Config.h | 4 +- DCCpp_Uno/WiThrottle.cpp | 174 +++++++++++++++++++++++++++++++++++---- DCCpp_Uno/WiThrottle.hpp | 7 ++ 3 files changed, 167 insertions(+), 18 deletions(-) diff --git a/DCCpp_Uno/Config.h b/DCCpp_Uno/Config.h index c862135..3f9e2f0 100644 --- a/DCCpp_Uno/Config.h +++ b/DCCpp_Uno/Config.h @@ -31,7 +31,7 @@ Part of DCC++ BASE STATION for the Arduino // 2 = Arduino.org Ethernet/SD-Card Shield // 3 = Seeed Studio Ethernet/SD-Card Shield W5200 -#define COMM_INTERFACE 0 +#define COMM_INTERFACE 1 ///////////////////////////////////////////////////////////////////////////////////// // @@ -39,7 +39,7 @@ Part of DCC++ BASE STATION for the Arduino // //#define IP_ADDRESS { 192, 168, 1, 200 } -#define IP_ADDRESS { 192, 168, 0, 2 } +//#define IP_ADDRESS { 192, 168, 0, 42 } ///////////////////////////////////////////////////////////////////////////////////// // diff --git a/DCCpp_Uno/WiThrottle.cpp b/DCCpp_Uno/WiThrottle.cpp index 5863bb1..6793828 100644 --- a/DCCpp_Uno/WiThrottle.cpp +++ b/DCCpp_Uno/WiThrottle.cpp @@ -13,14 +13,30 @@ Part of DCC++ BASE STATION for the Arduino #include "Comm.h" #include "SerialCommand.h" #include "PacketRegister.h" +#include "Outputs.h" +#include "Accessories.h" #include "WiThrottle.hpp" #define FORCED_REGISTER_NUMBER 1 -extern RegisterList mainRegs; +// Store decoder addresses for DCC turnouts here. These take +// priority over actual built-in outputs. One should take +// care not to have the name spaces overlap... + +#define MAX_DCC_TURNOUTS 5 +struct TurnoutData dccTurnouts[MAX_DCC_TURNOUTS] = { + { 1, 0, 1, 1 }, + { 1, 0, 2, 2 }, + { 1, 0, 3, 3 }, + { 1, 0, 4, 4 }, + { 1, 0, 5, 5 }, +}; + +//extern RegisterList mainRegs; static char command[MAX_COMMAND_LENGTH+1]; static int address = 3; +static int speed = 0; static const byte byte1FuncOnVals[29] = { 144, 129, 130, 132, 136, 177, 178, 180, 184, @@ -87,6 +103,7 @@ static void WiThrottle::readCommand(char c) { static void WiThrottle::parseToDCCpp(char *s) { // Do something :) + Serial.print("RX: "); Serial.println(s); switch (s[0]) { case 'T': // Throttle @@ -96,7 +113,7 @@ static void WiThrottle::parseToDCCpp(char *s) { case 'M': // Multi-Throttle parseMCommand(s); break; - case 'C': // Old "T" command + case 'C': // Old "T" command - not used anymore. Kept for backward compatibility if (s[1] == 'T') { doThrottleCommand(NULL, s+2); } @@ -107,9 +124,11 @@ static void WiThrottle::parseToDCCpp(char *s) { case 'H': // Hardware (get device UUID) parseHCommand(s); break; + case 'P': // Panel stuff + parsePCommand(s); + break; case '*': // Heartbeat case 'D': // Direct hex packet - case 'P': // Panel stuff case 'R': // Roster stuff case 'Q': // Quit break; @@ -131,18 +150,53 @@ static bool WiThrottle::isWTCommand(char c) { case 'P': // Panel case 'R': // Roster case 'Q': // Quit - Serial.print(c); - Serial.println(" is a WiThrottle cmd"); + //Serial.print(c); + //Serial.println(" is a WiThrottle cmd"); return(true); default: return(false); } } +static void WiThrottle::parsePCommand(char *p) { + // Commands: + // PPAx : Track power on/off + // PTAx : Turnout throw/close + // PRAx : Route set/unset + switch(p[1]) { + case 'P': + // Track power on or off + switch(p[2]) { + case 'A': + if (p[3] == '1') { + // Track power ON + sprintf(command, "1"); + SerialCommand::parse(command); + } else if (p[3] == '0') { + // Track power OFF + sprintf(command, "0"); + SerialCommand::parse(command); + } // p[3] + // Else ignore. + break; + } // p[2] + break; + + case 'T': + // Turnout command + // C = close T = throw 2 = toggle + if (p[2] == 'A') { + handleTurnout(p+3); + } + break; + } // switch(p[1]) +} + static void WiThrottle::parseHCommand(char *s) { switch(s[1]) { case 'U': // get device UDID and do something with it. + Serial.println("RX Device ID: " + String(s+2)); return; default: return; @@ -154,19 +208,21 @@ static void WiThrottle::parseNCommand(char *s) { Serial.print("Name = "); Serial.println(String(s)); // Reply * e.g. *10 for 10 seconds or *0 for no heartbeat expected + doPrintln("*0"); return; } static void WiThrottle::parseMCommand(char *s) { char *key, *action; - switch(s[1]) { + switch(s[2]) { case 'A': + case '+': key = strtok(s, "<;>"); action = strtok(NULL, "<;>"); Serial.println(key); Serial.println(action); doThrottleCommand(key, action); - case '+': + break; case '-': default: return; @@ -186,6 +242,10 @@ static void WiThrottle::doThrottleCommand(char *key, char *action) { reg = getRegisterForCab(address); dir = getDirForCab(address); sprintf(command, "t %d %d %s %d", reg, address, (action+1), dir ); + sscanf(action+1, "%d", &spd); + //speed = strtol((action+1), NULL, 10); + Serial.print("new speed = "); + Serial.println(spd); SerialCommand::parse(command); break; @@ -248,6 +308,7 @@ static void WiThrottle::doThrottleCommand(char *key, char *action) { case 'L': // set long address case 'S': // set short address address = strtol((action+1), NULL, 10); + doPrintln("MT+L" + String(address) + "<;>"); break; case 'q': // request (v>=2.0) @@ -293,23 +354,104 @@ int WiThrottle::getRegisterForCab(int c) { } int WiThrottle::getSpeedForCab(int c) { - int spd = mainRegs.speedTable[FORCED_REGISTER_NUMBER]; - if (spd < 0) { spd = -spd; } + int spd = speed; + //int spd = mainRegs.speedTable[FORCED_REGISTER_NUMBER]; + //if (spd < 0) { spd = -spd; } return(spd); } int WiThrottle::getDirForCab(int c) { - int spd = mainRegs.speedTable[FORCED_REGISTER_NUMBER]; + // In the speedTable, reverse speeds are negative. + // See PacketRegister::setThrottle() + int spd = 0; + //int spd = mainRegs.speedTable[FORCED_REGISTER_NUMBER]; return(spd >= 0 ? 1 : 0); } void WiThrottle::sendIntroMessage(void) { - INTERFACE.println("VN2.0"); - INTERFACE.println("RL0"); - INTERFACE.println("PPA0"); // PPA0=off PPA1=on PPA2=unknown - // TODO: Send roster here + // Send version number of protocol supported + doPrintln("VN2.0"); + // Send the roster (no roster entries, so 0) + doPrintln("RL0"); + // Send Power Status + // check pin SIGNAL_ENABLE_PIN_MAIN + // PPA0=off PPA1=on PPA2=unknown + if (digitalRead(SIGNAL_ENABLE_PIN_MAIN) == HIGH) { + doPrintln("PPA1"); + } else { + doPrintln("PPA0"); + } + // Send any defined consists. + doPrintln("RCC0"); // 0 Consists defined. + // Send turnout info ... + doPrintln("PTT]\[Turnouts}|{Turnout]\[Closed}|{2]\[Thrown}|{4"); + // TODO: Send turnouts ("PTL") and Routes ("PRT") here. + // Send the port number + listTurnouts(); #if COMM_TYPE == 1 - INTERFACE.print("PW"); - INTERFACE.println(ETHERNET_PORT); + doPrint("PW"); + doPrintln(ETHERNET_PORT); #endif } + +#define TURNOUT_UNKNOWN 1 +#define TURNOUT_CLOSED 2 +#define TURNOUT_THROWN 4 + +void WiThrottle::listTurnouts(void) { + if (Output::firstOutput != NULL) { + doPrint("PTL"); + for (int i = 0; i < MAX_DCC_TURNOUTS; i++) { + if (dccTurnouts[i].id > 0) { + sprintf(command,"]\\[%d}|{%d}|{%d]", dccTurnouts[i].address, dccTurnouts[i].id, TURNOUT_UNKNOWN); + doPrint(command); + } + } + /* + Output *pt = Output::firstOutput; + while (pt != NULL) { + sprintf(command, "]\\[%d|%d|%d]", pt->data.id, pt->data.id, + (pt->data.oStatus == 0 ? TURNOUT_CLOSED : TURNOUT_THROWN)); + INTERFACE.print(command); + pt = pt->nextOutput; + } // while + */ + doPrintln("]"); // Send close bracket and EOL + } // if +} + +void WiThrottle::handleTurnout(char *s) { + // Substring of PTAxxx command, starting with byte 3 + int addr = strtol(s+1, NULL, 10); + for (int i = 0; i < MAX_DCC_TURNOUTS; i++) { + if (dccTurnouts[i].address == addr) { + sprintf(command, "a %d 0 %d", addr, (s[0] == 'C' ? 0 : 1)); + SerialCommand::parse(command); + dccTurnouts[i].tStatus = (s[0] == 'C'? 0 : 1); + return; + } + } // end for loop. + // If we got here, then it's not a defined DCC turnout. Might be + // an Output. Handle that differently. + // err... for now don't handle it at all. +} + +void WiThrottle::doPrint(char *x) { + Serial.print(x); + INTERFACE.print(x); +} + +void WiThrottle::doPrintln(char *x) { + Serial.println(x); + INTERFACE.println(x); +} + +void WiThrottle::doPrint(StringSumHelper& x) { + Serial.print(x); + INTERFACE.print(x); +} + +void WiThrottle::doPrintln(StringSumHelper& x) { + Serial.println(x); + INTERFACE.println(x); +} diff --git a/DCCpp_Uno/WiThrottle.hpp b/DCCpp_Uno/WiThrottle.hpp index d39ca48..5a94dfe 100644 --- a/DCCpp_Uno/WiThrottle.hpp +++ b/DCCpp_Uno/WiThrottle.hpp @@ -20,6 +20,7 @@ class WiThrottle { static void parseHCommand(char *s); static void parseNCommand(char *s); static void parseMCommand(char *s); + static void parsePCommand(char *s); static void doThrottleCommand(char *key, char *action); static byte getFuncByte1(bool t, int f); static byte getFuncByte2(bool t, int f); @@ -27,6 +28,12 @@ class WiThrottle { static int getSpeedForCab(int c); static int getDirForCab(int c); static void handleRequest(char *s); + static void listTurnouts(void); + static void handleTurnout(char *s); + static void doPrint(char *x); + static void doPrintln(char *x); + static void doPrint(StringSumHelper& x); + static void doPrintln(StringSumHelper& x); public: static void parseToDCCpp(char *s); static bool isWTCommand(char c); From 3e0f9cc4537badecb1f7a303215357bd2c1a279a Mon Sep 17 00:00:00 2001 From: TwinDad Date: Tue, 7 Feb 2017 12:20:20 -0500 Subject: [PATCH 13/21] Enabled looking at mainRegs for speed/direction --- DCCpp_Uno/WiThrottle.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/DCCpp_Uno/WiThrottle.cpp b/DCCpp_Uno/WiThrottle.cpp index 6793828..8d1fa0e 100644 --- a/DCCpp_Uno/WiThrottle.cpp +++ b/DCCpp_Uno/WiThrottle.cpp @@ -32,7 +32,7 @@ struct TurnoutData dccTurnouts[MAX_DCC_TURNOUTS] = { { 1, 0, 5, 5 }, }; -//extern RegisterList mainRegs; +extern RegisterList mainRegs; static char command[MAX_COMMAND_LENGTH+1]; static int address = 3; @@ -354,17 +354,17 @@ int WiThrottle::getRegisterForCab(int c) { } int WiThrottle::getSpeedForCab(int c) { - int spd = speed; - //int spd = mainRegs.speedTable[FORCED_REGISTER_NUMBER]; - //if (spd < 0) { spd = -spd; } + //int spd = speed; + int spd = mainRegs.speedTable[FORCED_REGISTER_NUMBER]; + if (spd < 0) { spd = -spd; } return(spd); } int WiThrottle::getDirForCab(int c) { // In the speedTable, reverse speeds are negative. // See PacketRegister::setThrottle() - int spd = 0; - //int spd = mainRegs.speedTable[FORCED_REGISTER_NUMBER]; + //int spd = 0; + int spd = mainRegs.speedTable[FORCED_REGISTER_NUMBER]; return(spd >= 0 ? 1 : 0); } From 89fd8bceb1c25c1af763517001ae23f295392427 Mon Sep 17 00:00:00 2001 From: TwinDad Date: Tue, 7 Feb 2017 22:44:30 -0500 Subject: [PATCH 14/21] 1) Created an ersatz "session" by separating the while(client.connected()) and while(client.available()) conditions in the input loop in SerialCommand This helps with when to send the welcome message and keeps things a bit more clean. Should have no effect on normal Ethernet connections. If it does have an adverse effect, we can always put the variant while() constructs inside a compiler directive. 2) Fixed the Track Power control 3) Fixed the direction control on the throttle. --- DCCpp_Uno/SerialCommand.cpp | 30 +++++++++++++--------- DCCpp_Uno/WiThrottle.cpp | 51 +++++++++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 23 deletions(-) diff --git a/DCCpp_Uno/SerialCommand.cpp b/DCCpp_Uno/SerialCommand.cpp index fa22a9f..2c05a44 100644 --- a/DCCpp_Uno/SerialCommand.cpp +++ b/DCCpp_Uno/SerialCommand.cpp @@ -74,20 +74,24 @@ void SerialCommand::process(){ #ifdef WITHROTTLE_SUPPORT WiThrottle::sendIntroMessage(); #endif - while(client.connected() && client.available()){ // while there is data on the network - c=client.read(); + while(client.connected()) { + while (client.available()) { // while there is data on the network + c=client.read(); #ifdef WITHROTTLE_SUPPORT - if (WiThrottle::isWTCommand(c)) { - WiThrottle::readCommand(c); - } else + if (WiThrottle::isWTCommand(c)) { + WiThrottle::readCommand(c); + } else { #endif - if(c=='<') // start of new command - sprintf(commandString,""); - else if(c=='>') // end of new command - parse(commandString); - else if(strlen(commandString)') - } // while + // Process as a DCC++ command + if(c=='<') // start of new command + sprintf(commandString,""); + else if(c=='>') // end of new command + parse(commandString); + else if(strlen(commandString)') + } // else withrottle + } // while available + }// while connected } #endif @@ -97,6 +101,8 @@ void SerialCommand::process(){ /////////////////////////////////////////////////////////////////////////////// void SerialCommand::parse(char *com){ + + Serial.println("WCMD: " + String(com)); switch(com[0]){ diff --git a/DCCpp_Uno/WiThrottle.cpp b/DCCpp_Uno/WiThrottle.cpp index 8d1fa0e..5c99f92 100644 --- a/DCCpp_Uno/WiThrottle.cpp +++ b/DCCpp_Uno/WiThrottle.cpp @@ -37,6 +37,7 @@ extern RegisterList mainRegs; static char command[MAX_COMMAND_LENGTH+1]; static int address = 3; static int speed = 0; +static int dir = 1; static const byte byte1FuncOnVals[29] = { 144, 129, 130, 132, 136, 177, 178, 180, 184, @@ -170,12 +171,16 @@ static void WiThrottle::parsePCommand(char *p) { case 'A': if (p[3] == '1') { // Track power ON + Serial.println("Power ON"); sprintf(command, "1"); SerialCommand::parse(command); + INTERFACE.println("PPA1"); } else if (p[3] == '0') { // Track power OFF + Serial.println("Power OFF"); sprintf(command, "0"); SerialCommand::parse(command); + INTERFACE.println("PPA0"); } // p[3] // Else ignore. break; @@ -219,8 +224,8 @@ static void WiThrottle::parseMCommand(char *s) { case '+': key = strtok(s, "<;>"); action = strtok(NULL, "<;>"); - Serial.println(key); - Serial.println(action); + Serial.println("Key = " + String(key)); + Serial.println("Action = " + String(action)); doThrottleCommand(key, action); break; case '-': @@ -230,7 +235,7 @@ static void WiThrottle::parseMCommand(char *s) { } static void WiThrottle::doThrottleCommand(char *key, char *action) { - int reg, dir, spd, f; + int reg, spd, f; byte byte1, byte2; // TODO: When supporting multiple throttles, KEY will tell us which // throttle to do the action on. @@ -240,12 +245,14 @@ static void WiThrottle::doThrottleCommand(char *key, char *action) { // DCC++ Returns: if (address < 0) { return; } reg = getRegisterForCab(address); - dir = getDirForCab(address); + //dir = getDirForCab(address); sprintf(command, "t %d %d %s %d", reg, address, (action+1), dir ); sscanf(action+1, "%d", &spd); //speed = strtol((action+1), NULL, 10); Serial.print("new speed = "); - Serial.println(spd); + Serial.print(action+1); + Serial.print(" dir = "); + Serial.println(dir); SerialCommand::parse(command); break; @@ -261,13 +268,21 @@ static void WiThrottle::doThrottleCommand(char *key, char *action) { case 'F': // Function case 'f': // force function (v>=2.0) + // WiThrottle Format: FxVV + // x = 1 (on) or 0 (off) + // VV is the function number // DCC++ Format: // DCC++ Returns: (none) f = strtol((action+2), NULL,10); - if (f == 0 || f > 28) { + Serial.println("F = " + String(f) + " is " + String(action[1] == '1' ? "ON" : "OFF")); + if (f < 0 || f > 28) { // Invalid conversion break; } + // NOTE: strtol() returns zero on an invalid conversion + // That is harmless here. F0 is the headlight, so the worst + // thing that will happen on an invalid conversion is we tooggle + // the headlight. Oh well. byte1 = getFuncByte1((action[1] == '1'), f); byte2 = getFuncByte2((action[1] == '1'), f); if (byte2 == 255) { @@ -284,9 +299,14 @@ static void WiThrottle::doThrottleCommand(char *key, char *action) { if (address < 0) { return; } reg = getRegisterForCab(address); spd = getSpeedForCab(address); - sprintf(command, "t %d %d %d %c", reg, address, spd, - (action[1] == '0' ? '0' : '1')); - Serial.print("cmd = " + String(command)); + if (action[1] == '0') { + dir = 0; + } else { + dir = 1; + } + sprintf(command, "t %d %d %d %d", reg, address, spd, dir); + Serial.println("cmd = " + String(command)); + Serial.println("dir = " + String(dir)); SerialCommand::parse(command); break; @@ -315,12 +335,12 @@ static void WiThrottle::doThrottleCommand(char *key, char *action) { handleRequest(action); break; + case 'Q': // quit case 'E': // set address from roster (v>=1.7) case 'C': // consist case 'c': // consist lead from roster (v>=1.7) case 's': // speed step mode (v>= 2.0) case 'm': // momentary (v>=2.0) - case 'Q': // quit default: return; } @@ -364,11 +384,20 @@ int WiThrottle::getDirForCab(int c) { // In the speedTable, reverse speeds are negative. // See PacketRegister::setThrottle() //int spd = 0; + // Have to special-handle the speed = 0 case + // since there is no implied direction. + int spd = mainRegs.speedTable[FORCED_REGISTER_NUMBER]; - return(spd >= 0 ? 1 : 0); + if (spd == 0) { + return(dir); + } else { + dir = (spd > 0 ? 1 : 0); + return(dir); + } } void WiThrottle::sendIntroMessage(void) { + client = &c; // Send version number of protocol supported doPrintln("VN2.0"); // Send the roster (no roster entries, so 0) From f88501baa0e584dfdfb123876353869f0a2536b1 Mon Sep 17 00:00:00 2001 From: TwinDad Date: Tue, 7 Feb 2017 23:37:19 -0500 Subject: [PATCH 15/21] Function keys now work properly. Also removed the line from printIntroMessage() that was a compile-time error. --- DCCpp_Uno/WiThrottle.cpp | 44 +++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/DCCpp_Uno/WiThrottle.cpp b/DCCpp_Uno/WiThrottle.cpp index 5c99f92..627178e 100644 --- a/DCCpp_Uno/WiThrottle.cpp +++ b/DCCpp_Uno/WiThrottle.cpp @@ -34,11 +34,22 @@ struct TurnoutData dccTurnouts[MAX_DCC_TURNOUTS] = { extern RegisterList mainRegs; -static char command[MAX_COMMAND_LENGTH+1]; +static char message[MAX_COMMAND_LENGTH+1]; +static char *command = SerialCommand::commandString; static int address = 3; static int speed = 0; static int dir = 1; +bool fstate[29] = { + false, false, false, false, false, + false, false, false, false, false, + false, false, false, false, false, + false, false, false, false, false, + false, false, false, false, false, + false, false, false, false +}; + + static const byte byte1FuncOnVals[29] = { 144, 129, 130, 132, 136, 177, 178, 180, 184, 161, 162, 164, 168, @@ -273,8 +284,9 @@ static void WiThrottle::doThrottleCommand(char *key, char *action) { // VV is the function number // DCC++ Format: // DCC++ Returns: (none) + address = strtol(key+4, NULL, 10); f = strtol((action+2), NULL,10); - Serial.println("F = " + String(f) + " is " + String(action[1] == '1' ? "ON" : "OFF")); + Serial.println("addr = " + String(address) + " F = " + String(f) + " is " + String(action[1] == '1' ? "ON" : "OFF")); if (f < 0 || f > 28) { // Invalid conversion break; @@ -283,14 +295,27 @@ static void WiThrottle::doThrottleCommand(char *key, char *action) { // That is harmless here. F0 is the headlight, so the worst // thing that will happen on an invalid conversion is we tooggle // the headlight. Oh well. - byte1 = getFuncByte1((action[1] == '1'), f); - byte2 = getFuncByte2((action[1] == '1'), f); - if (byte2 == 255) { - sprintf(command, "f %d %d", address, byte1); - } else { - sprintf(command, "f %d %d %d", address, byte1, byte2); + if ((action[1] == '1') || (f == 2)) { + // Button Pressed.. Take action + // Horn (F2) is momentary. Take action even if action[1] == 0 + // Toggle the state. + fstate[f] = !fstate[f]; + // Get the bytes to send. + byte1 = getFuncByte1(fstate[f], f); + byte2 = getFuncByte2(fstate[f], f); + // Build the DCC++ message and send it + if (byte2 == 255) { + sprintf(message, "f %d %d", address, byte1); + } else { + sprintf(message, "f %d %d %d", address, byte1, byte2); + } + SerialCommand::parse(message); + // Send the response to the WiThrottle + doPrint(key); + doPrint("<;>"); + action[1] = (fstate[f] == true ? '1' : '0'); + doPrintln(action); } - SerialCommand::parse(command); break; case 'R': // Direction @@ -397,7 +422,6 @@ int WiThrottle::getDirForCab(int c) { } void WiThrottle::sendIntroMessage(void) { - client = &c; // Send version number of protocol supported doPrintln("VN2.0"); // Send the roster (no roster entries, so 0) From d0a5db66f25692d90331f0c49d4323515e84f8eb Mon Sep 17 00:00:00 2001 From: TwinDad Date: Wed, 8 Feb 2017 00:02:23 -0500 Subject: [PATCH 16/21] Enable dual throttles on a single phone --- DCCpp_Uno/WiThrottle.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/DCCpp_Uno/WiThrottle.cpp b/DCCpp_Uno/WiThrottle.cpp index 627178e..9f457b3 100644 --- a/DCCpp_Uno/WiThrottle.cpp +++ b/DCCpp_Uno/WiThrottle.cpp @@ -238,6 +238,11 @@ static void WiThrottle::parseMCommand(char *s) { Serial.println("Key = " + String(key)); Serial.println("Action = " + String(action)); doThrottleCommand(key, action); + if (s[2] == '+') { + doPrint(key); + doPrint("<;>"); + doPrintln(action); + } break; case '-': default: @@ -248,6 +253,7 @@ static void WiThrottle::parseMCommand(char *s) { static void WiThrottle::doThrottleCommand(char *key, char *action) { int reg, spd, f; byte byte1, byte2; + address = strtol(key+4, NULL, 10); // TODO: When supporting multiple throttles, KEY will tell us which // throttle to do the action on. switch(action[0]) { From efc76196790f51572b07ab06c39da1c469e06c2b Mon Sep 17 00:00:00 2001 From: TwinDad Date: Wed, 8 Feb 2017 14:39:39 -0500 Subject: [PATCH 17/21] Fix responses to function commands for EngineDriver. Not tested. --- DCCpp_Uno/WiThrottle.cpp | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/DCCpp_Uno/WiThrottle.cpp b/DCCpp_Uno/WiThrottle.cpp index 9f457b3..654670a 100644 --- a/DCCpp_Uno/WiThrottle.cpp +++ b/DCCpp_Uno/WiThrottle.cpp @@ -290,7 +290,14 @@ static void WiThrottle::doThrottleCommand(char *key, char *action) { // VV is the function number // DCC++ Format: // DCC++ Returns: (none) - address = strtol(key+4, NULL, 10); + if (key[0] == 'M') { + address = strtol(key+4, NULL, 10); + } else { + ; // previous versions of the interface (current EngineDriver) + // send the older TF command, not the M command. In this case + // there is no address to be parsed in the command. + // Keep whatever address is stored from previous commands + } f = strtol((action+2), NULL,10); Serial.println("addr = " + String(address) + " F = " + String(f) + " is " + String(action[1] == '1' ? "ON" : "OFF")); if (f < 0 || f > 28) { @@ -317,8 +324,14 @@ static void WiThrottle::doThrottleCommand(char *key, char *action) { } SerialCommand::parse(message); // Send the response to the WiThrottle - doPrint(key); - doPrint("<;>"); + // Needs to be tested for EngineDriver + if (key[0] == 'M') { + doPrint(key); + doPrint("<;>"); + } else { + // Old "T" support + doPrint("T"); + } action[1] = (fstate[f] == true ? '1' : '0'); doPrintln(action); } @@ -360,6 +373,15 @@ static void WiThrottle::doThrottleCommand(char *key, char *action) { case 'S': // set short address address = strtol((action+1), NULL, 10); doPrintln("MT+L" + String(address) + "<;>"); + // Formulate a response + if (key[0] == 'M') { + doPrint(key); + doPrintln("<;>"); + } else { + // Old "T" or "S" support + // Needs to be tested + doPrintln(key); + } break; case 'q': // request (v>=2.0) From 37af673ee5f7a811c5e0bb3dc36b450ce7857b6d Mon Sep 17 00:00:00 2001 From: TwinDad Date: Wed, 8 Feb 2017 14:49:22 -0500 Subject: [PATCH 18/21] Resolved conflicts and restored files for merged withrottle2 and lcd_throttle --- DCCpp_Uno/Inglenook.h | 40 ++ DCCpp_Uno/LCD.cpp | 195 +++++++++ DCCpp_Uno/LCD.h | 61 +++ DCCpp_Uno/LCDThrottle.cpp | 815 ++++++++++++++++++++++++++++++++++++ DCCpp_Uno/LCDThrottle.h | 69 +++ DCCpp_Uno/SerialCommand.cpp | 3 + 6 files changed, 1183 insertions(+) create mode 100644 DCCpp_Uno/Inglenook.h create mode 100644 DCCpp_Uno/LCD.cpp create mode 100644 DCCpp_Uno/LCD.h create mode 100644 DCCpp_Uno/LCDThrottle.cpp create mode 100644 DCCpp_Uno/LCDThrottle.h diff --git a/DCCpp_Uno/Inglenook.h b/DCCpp_Uno/Inglenook.h new file mode 100644 index 0000000..04f2c7d --- /dev/null +++ b/DCCpp_Uno/Inglenook.h @@ -0,0 +1,40 @@ +#ifndef INGLENOOK_H +#define INGLENOOK_H + +// NOTE: These are actually set by the rules of the game... +// WARNING: There are several places (initialization, etc.) where +// the value of these is assumed fixed at 5 and 8. +#define TRAIN_LENGTH 5 +#define NUM_CARS 8 + +const String carnames[NUM_CARS] = { // String names of cars on the layout (for display) + "Blue Boxcar", + "Red Boxcar", + "Green Boxcar", + "Black Tank Car", + "Black Gondola", + "Grey Hopper", + "Brown Flatcar", + "Brown Boxcar" +}; + +class InglenookGame { + private: + int car_index; + + public: + InglenookGame(); + void begin(); + void buildTrain(void); + void doDisplayTrain(LCD *lcd, char *row1, char *row2); + int carIndex(); + void setCarIndex(int c); + void doMenuDisplay(LCD *lcd, char *row1, char *row2); + void doListTrain(LCD *lcd, char *row1, char *row2, int car); + + protected: + //void printWelcome(LCD *lcd, char *row1, char *row2); +}; + + +#endif diff --git a/DCCpp_Uno/LCD.cpp b/DCCpp_Uno/LCD.cpp new file mode 100644 index 0000000..12ab9de --- /dev/null +++ b/DCCpp_Uno/LCD.cpp @@ -0,0 +1,195 @@ +#include +#include + +// Include our own header with definitions and such. +#include "LCD.h" + +// Include the necessary libraries for the different display shields. +#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) +#include +#include +#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) +#include +#include // is this necessary? +#else +#error CANNOT COMPILE -- INVALID LCD LIBRARY SELECTED +#endif + +// Create the local object of the display we are +// controlling. +#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) +static LCDKeypad lcd = LCDKeypad(); +#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) +static Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield(); +#endif + +// Define the size of the display. Pretty much assumes +// 16x2 but could be modded to change that. +#define LCD_NUM_ROWS 2 +#define LCD_NUM_COLS 16 + +// State machine for handling processing of the buttons. +#define DISPLAY_STATE_RUN 0 +#define DISPLAY_STATE_DEBOUNCE 1 +#define DISPLAY_STATE_DEBOUNCE_COMPLETE 2 +#define DISPLAY_STATE_LONG_DEBOUNCE 3 +#define DISPLAY_STATE_LONG_DEBOUNCE_COMPLETE 4 +#define DISPLAY_STATE_LONG_DEBOUNCE_WAIT 5 + +// ctor +LCD::LCD() { + // Nothing to do ... yet ... +} + +// Setup function. Call from setup() +void LCD::begin() { + buttonVal = KEYS_NONE; + displayState = DISPLAY_STATE_RUN; + lcd.begin(LCD_NUM_COLS, LCD_NUM_ROWS); +} + +// Run loop for processing the buttons. Call from loop() +void LCD::run() { + debounceButtons(); +} + +//------------------------------------------------ +// Pass-through methods for handling the display +void LCD::clear() { + lcd.clear(); +} + +void LCD::setCursor(int c, int r) { + lcd.setCursor(c, r); +} + +void LCD::blink() { + lcd.blink(); +} + +void LCD::noBlink() { + lcd.noBlink(); +} + +void LCD::cursor() { + lcd.cursor(); +} + +void LCD::noCursor() { + lcd.noCursor(); +} + +//------------------------------------------------ +// Custom methods + +// Update the two lines of the display with these two strings. +void LCD::updateDisplay(char *row1, char *row2) { + lcd.clear(); + lcd.setCursor(0,0); lcd.print(row1); + lcd.setCursor(0,1); lcd.print(row2); +} + + +// Get the current button value. +int LCD::getButtons() { + int bv = buttonVal; + buttonVal = KEYS_NONE; + return(bv); +} + +//------------------------------------------------ +// Private / Protected internal methods + +// Retrieve the hardware button value and translate +// it into one of our "standard" button values. +int LCD::getButton() { +#if (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_OSEPP) + // OSEPP LCDKeypad + int b = lcd.button(); + //Serial.print(String(b) + " "); + return(b); +#elif (LCD_DISPLAY_TYPE == LCD_DISPLAY_TYPE_ADAFRUIT) + // ADAFRUIT RGBLCD + uint8_t buttons = lcd.readButtons(); + if (buttons & BUTTON_UP) { + return(KEYS_UP); + } + if (buttons & BUTTON_DOWN) { + return(KEYS_DOWN); + } + if (buttons & BUTTON_LEFT) { + return(KEYS_LEFT); + } + if (buttons & BUTTON_RIGHT) { + return(KEYS_RIGHT); + } + if (buttons & BUTTON_SELECT) { + return(KEYS_SELECT); + } + return(KEYS_NONE); +#endif +} + +// Debounce the button press and figure out if it's a long press. +void LCD::debounceButtons() { + int button = getButton(); + static long startDebounce; + static int keyval; + int retv; + switch(displayState) { + case DISPLAY_STATE_RUN: + if (button != KEYS_NONE) { + Serial.println("Raw Key: " + String(button)); + startDebounce = millis(); + keyval = button; + displayState = DISPLAY_STATE_DEBOUNCE; + } + // Always return KEYS_NONE from this state + break; + + case DISPLAY_STATE_DEBOUNCE: + // actively debouncing... + if ((button != KEYS_NONE) && ((millis() - startDebounce) > LONG_PRESS_MS)) { + // Longer than 2 second hold + displayState = DISPLAY_STATE_DEBOUNCE_COMPLETE; + switch(keyval) { + case KEYS_RIGHT: + keyval = KEYS_LONG_RIGHT; break; + case KEYS_UP: + keyval = KEYS_LONG_UP; break; + case KEYS_DOWN: + keyval = KEYS_LONG_DOWN; break; + case KEYS_LEFT: + keyval = KEYS_LONG_LEFT; break; + case KEYS_SELECT: + keyval = KEYS_LONG_SELECT; break; + default: + break; + } + buttonVal = keyval; + keyval = KEYS_NONE; + Serial.println("2 second button press! Val = " + String(buttonVal)); + + } else if (button == KEYS_NONE) { + // Short press. + Serial.println("Short Press. Debounced Key: " + String(keyval)); + // Debounce complete. Decide if it's long or not. + displayState = DISPLAY_STATE_DEBOUNCE_COMPLETE; + buttonVal = keyval; + } else { + // Not finished debouncing yet. Do nothing. + } // KEYS_NONE + break; + + case DISPLAY_STATE_LONG_DEBOUNCE_WAIT: + case DISPLAY_STATE_DEBOUNCE_COMPLETE: + // Long press detected, waiting for release before handling more button presses + if (button == KEYS_NONE) { + // User has released button. + displayState = DISPLAY_STATE_RUN; + buttonVal = KEYS_NONE; + } + break; + + } // switch(displayState) +} diff --git a/DCCpp_Uno/LCD.h b/DCCpp_Uno/LCD.h new file mode 100644 index 0000000..edd6668 --- /dev/null +++ b/DCCpp_Uno/LCD.h @@ -0,0 +1,61 @@ +#ifndef LCD_H +#define LCD_H + +//------------------------------------------------ +// Generic wrapper class for various different +// 16x2 LCD shields with buttons.. Namely: +// * the 4/8-pin OSEPP model +// * the Adafruit I2C version +// +// Allows for long vs. short presses of the buttons +// (but not momentary action -- yet) + +// LCD Display Types: +// +// 0 = OSEPP LCDKeypad +// 1 = Adafruit RGB LCD +#define LCD_DISPLAY_TYPE_OSEPP 0 +#define LCD_DISPLAY_TYPE_ADAFRUIT 1 +#define LCD_DISPLAY_TYPE LCD_DISPLAY_TYPE_ADAFRUIT + +// Defines how long a "long press" is in milliseconds. +#define LONG_PRESS_MS 2000 + +// Define generic button names for multi-library compatibility +// Return values from getButtons(); +#define KEYS_NONE -1 +#define KEYS_RIGHT 0 +#define KEYS_UP 1 +#define KEYS_DOWN 2 +#define KEYS_LEFT 3 +#define KEYS_SELECT 4 +#define KEYS_LONG_RIGHT 128 +#define KEYS_LONG_UP 129 +#define KEYS_LONG_DOWN 130 +#define KEYS_LONG_LEFT 131 +#define KEYS_LONG_SELECT 132 + + +class LCD { + private: + byte displayState; + int buttonVal; + public: + LCD(); + void begin(); // Call from setup() + void run(); // Call from loop() + int getButtons(); + void updateDisplay(char *row1, char *row2); + void clear(); + void setCursor(int c, int r); + void blink(); + void noBlink(); + void cursor(); + void noCursor(); + protected: + int getButton(); + void debounceButtons(); + +}; + +#endif // LCD_H diff --git a/DCCpp_Uno/LCDThrottle.cpp b/DCCpp_Uno/LCDThrottle.cpp new file mode 100644 index 0000000..7bd98f0 --- /dev/null +++ b/DCCpp_Uno/LCDThrottle.cpp @@ -0,0 +1,815 @@ +#include +#include +#include +#include "SerialCommand.h" +#include "EEStore.h" +#include "LCDThrottle.h" +#include "Inglenook.h" + +//-------------------------------------------------------------------- +/* LCD Throttle + * + * Uses an LCD with buttons to provide a directly-connected throttle + * interface for DCC++. Includes an "Inglenook Sidings" game that will + * randomize a set of cars and tell you a 5-car train to build. + * + * Boots up in the Menu state. Menu options include: + * -- Use Throttle + * -- Set Track Power On/Off + * -- Set Address + * -- Set Display Mode + * -- Set maximum speed (in 128 speed steps) + * -- Play Inglenook Sidings + * + * A long (2-sec+) press on SELECT will toggle between Menu and Throttle view. + * + * Throttle views (Display Modes): + * -- Standard: Separate Direction and Speed indicators + * -- Switcher: Combined bidirectional Direction+Speed indicator + * + * Each click of LEFT/RIGHT will increase/decrease the speed by a fraction + * of the maximum speed set in the menus. The amount depends on the view mode. + * Standard view has 15 steps, so (e.g.) with max speed = 60, each click is + * 4 speed steps. + * Switcher view has +/- 7 steps, so (e.g.) with max speed = 63, each click is + * 9 speed steps. + * + * KNOWN BUGS: + * -- The "spinners" for the highest digit of the Set Address and Set Max Speed + * menu items don't work right. + * -- Some of the menu responses to navigation buttons aren't consistent + */ +//-------------------------------------------------------------------- + +#define MAX_SPEED 126 /*126 */ +#define MAX_NOTCH_NORMAL 15 +#define MAX_NOTCH_SWITCHER 7 +#define DEFAULT_CAB 3 + +#define THROTTLE_STATE_RUN 0 +#define THROTTLE_STATE_DEBOUNCE 1 +#define THROTTLE_STATE_MENUS 2 +#define THROTTLE_STATE_MENU_ACTION 3 +#define THROTTLE_STATE_GAME_MENUS 4 +#define THROTTLE_STATE_GAME_BUILD 5 + +struct LCDThrottleData LCDT_EEPROM_Store; + +// MAX_COMMAND_LENGTH is defined in DCC++ SerialCommand.h +//#define MAX_COMMAND_LENGTH 30 +// Buffer for writing DCC++ commands to the core base station code. +char command[MAX_COMMAND_LENGTH]; + +// Holder for display string construction +static char display[2][17]; + +// Menu subsystem "stuff" +static void menuUseEvent(MenuUseEvent e); +static void menuChangeEvent(MenuChangeEvent e); +static MenuBackend *menu; + +/** Constructor + * + */ +LCDThrottle::LCDThrottle() { + ; // do nothing +} + + +void LCDThrottle::begin(int reg) { + lcd = new LCD(); + lcd->begin(); + //EEPROM_GetAll(); + load(); + if (maxSpeed == 0) { + // EEPROM not yet initialized -> set default. + maxSpeed = MAX_SPEED; + } + if (cab == 0) { + // EEPROM not yet initialized -> set default. + cab = DEFAULT_CAB; + } + throttleState = THROTTLE_STATE_MENUS; // Always start in the menus. + jumpbackState = THROTTLE_STATE_MENUS; + this->reg = reg; // Store the register we should use. + notch = 0; // Idle the loco + speed = 0; + dir = FORWARD; + // TODO: Get the actual track power state from the base station. + power_state = false; // Assume track power is off. + sendPowerCommand(power_state); // Don't assume. Force it off. + // Fire up the Inglenook game code. + game = InglenookGame(); + game.begin(); + // Construct the menus. + menu = new MenuBackend(menuUseEvent, menuChangeEvent); + menuSetup(); + doMenuDisplay(); +} + +/** run() + * + * Main Run loop + */ +void LCDThrottle::run() { + // Run the underlying LCD stuff + lcd->run(); + + // Grab any button presses and process them + int button = lcd->getButtons(); + + switch(throttleState) { + + case THROTTLE_STATE_RUN: + switch(button) { + case KEYS_RIGHT: + // Speed up + increaseSpeed(); + sendThrottleCommand(); + updateDisplay(); + break; + + case KEYS_LEFT: + // Slow down + decreaseSpeed(); + sendThrottleCommand(); + updateDisplay(); + break; + + case KEYS_UP: + case KEYS_DOWN: + // For now, dumbly toggle direction with either up or down key. + // Maybe make this smarter or repurpose later. + // Possibly use up/down keys for Functions. + dir = (dir == FORWARD ? REVERSE : FORWARD); + sendThrottleCommand(); + updateDisplay(); + break; + + case KEYS_SELECT: + // For now, this (Short tap) is emergency stop. + speed = -1; + notch = 0; + sendThrottleCommand(); + // reset speed to Zero after sending "Emergency Stop" special value of -1 + // This won't hurt b/c the Base Station will set the loco's speed to zero as well. + speed = 0; + updateDisplay(); + break; + + case KEYS_LONG_SELECT: + // Idle the Loco and switch to Menus mode. + speed = 0; + notch = 0; + sendThrottleCommand(); + //Serial.println("JumpbackState == " + String(jumpbackState)); + // Jump back to the last non-throttle state we were in. + // This is so when playing the game you can hop back and forth + // directly between the throttle and the built train view. + throttleState = jumpbackState; + // What to display depends on which state we're returning to. + // TODO: Update the updateDisplay() method to handle this instead, if possible. + if (throttleState == THROTTLE_STATE_GAME_BUILD) { + game.doDisplayTrain(lcd, display[0], display[1]); + } else if (throttleState == THROTTLE_STATE_GAME_MENUS) { + doGameMenuDisplay(); + } else { + doMenuDisplay(); + } + break; + + default: + break; + // Do nothing. + } // switch(button) + break; + + case THROTTLE_STATE_DEBOUNCE: + // ??? + break; + + case THROTTLE_STATE_MENUS: + // Present the top-level menu. + switch(button) { + case KEYS_UP: + Serial.println("Up/Left"); + // Don't let the menu system move up to Root. + if (menu->getCurrent() == "Set Track Power") { + // do nothing. + } else { + menu->moveUp(); + } + doMenuDisplay(); + break; + + case KEYS_DOWN: + Serial.println("Down/Right"); + menu->moveDown(); + doMenuDisplay(); + break; + + case KEYS_RIGHT: + //menu->moveRight(); + doMenuDisplay(); + break; + + case KEYS_LEFT: + menu->moveLeft(); + doMenuDisplay(); + break; + + case KEYS_SELECT: + Serial.println("Select"); + throttleState = THROTTLE_STATE_MENU_ACTION; + doMenuAction(button); + break; + + case KEYS_LONG_SELECT: + // Jump to throttle mode. Save Menus as jumpback state. + Serial.println("Long Select"); + throttleState = THROTTLE_STATE_RUN; + jumpbackState = THROTTLE_STATE_MENUS; + updateDisplay(); + break; + } + break; + + case THROTTLE_STATE_MENU_ACTION: + // Handle top-level menu actions. + switch(button) { + case KEYS_SELECT: + // Do the action and return. + //EEPROM_StoreAll(); + store(); + throttleState = THROTTLE_STATE_MENUS; + doMenuDisplay(); + break; + + case KEYS_UP: + case KEYS_DOWN: + case KEYS_LEFT: + case KEYS_RIGHT: + doMenuAction(button); + break; + + case KEYS_LONG_SELECT: + // Jump back to throttle state. Don't retun here + // Return to the menu itself instead. + //EEPROM_StoreAll(); + store(); + throttleState = THROTTLE_STATE_RUN; + jumpbackState = THROTTLE_STATE_MENUS; + button = KEYS_NONE; + updateDisplay(); + break; + } + break; + + case THROTTLE_STATE_GAME_MENUS: + case THROTTLE_STATE_GAME_BUILD: + // Handle the game action. + doGameMenus(button); + break; + + default: + break; + + } // switch(throttleState) +} + +/** menuSetup() + * + * Construct the menu tree + */ +void LCDThrottle::menuSetup() { + MenuItem *miPower = new MenuItem("Set Track Power"); + MenuItem *miRun = new MenuItem("Use Throttle"); + MenuItem *miAddr = new MenuItem("Set Address"); + MenuItem *miDisp = new MenuItem("Select Display"); + MenuItem *miMax = new MenuItem("Max Speed"); + MenuItem *miGame = new MenuItem("Inglenook"); + menu->getRoot().add(*miPower); + miPower->add(*miRun); + miRun->add(*miAddr); + miAddr->add(*miDisp); + miDisp->add(*miMax); + miMax->add(*miGame); + MenuItem *miBuild = new MenuItem("Build Train"); + MenuItem *miList = new MenuItem("List Cars"); + MenuItem *miCarList[7] = { + new MenuItem("Car 0"), + new MenuItem("Car 1"), + new MenuItem("Car 2"), + new MenuItem("Car 3"), + new MenuItem("Car 4"), + new MenuItem("Car 5"), + new MenuItem("Car 6") + }; + miGame->addRight(*miBuild); + miBuild->add(*miList); + miList->addRight(*miCarList[0]); + for (int i = 0; i < NUM_CARS-2; i++) { + miCarList[i]->add(*miCarList[i+1]); + } + // Get off of the root node. + menu->moveDown(); +} + +/** doMenuDisplay() + * + * Change the display in response to the current top-level menu selection + */ +void LCDThrottle::doMenuDisplay() { + lcd->noCursor(); + + if (menu->getCurrent() == "Set Track Power") { + lcd->updateDisplay("THROTTLE MENU:", (power_state == true ? "Track Power ON" : "Track Power OFF")); + } + if (menu->getCurrent() == "Use Throttle") { + lcd->updateDisplay("THROTTLE MENU:", "Use Throttle"); + } + if (menu->getCurrent() == "Set Address") { + lcd->updateDisplay("THROTTLE MENU:", "Set Address"); + } + if (menu->getCurrent() == "Select Display") { + lcd->updateDisplay("THROTTLE MENU:", "Select Display"); + } + if (menu->getCurrent() == "Max Speed") { + lcd->updateDisplay("THROTTLE MENU:", "Max Speed Step"); + } + if (menu->getCurrent() == "Inglenook") { + lcd->updateDisplay("THROTTLE MENU:", "Inglenook Game"); + } +} + +/** doMenuAction() + * + * Take action in response to the user selecting a menu item + */ +void LCDThrottle::doMenuAction(int button) { + Serial.println("doMenuAction(" + String(button) + ")"); + + // Set Track Power: + // Selecting this menu item toggles the track power on or off. + if (menu->getCurrent() == "Set Track Power") { + power_state = !power_state; + sendPowerCommand(power_state); + doMenuDisplay(); + } + + // Use Throttle + // Selecting this jumps you to the throttle mode. + if(menu->getCurrent() == "Use Throttle") { + throttleState = THROTTLE_STATE_RUN; + jumpbackState = THROTTLE_STATE_MENUS; + updateDisplay(); + } + + // Inglenook + // Selecting this moves you into the game menus. + if (menu->getCurrent() == "Inglenook") { + throttleState = THROTTLE_STATE_GAME_MENUS; + menu->moveRight(); + doGameMenuDisplay(); + } + + // Select Display + // Selecting this allows you to change the throttle display mode + // Up/Down keys toggle the value. + // TODO: Left/Right probably should too + if (menu->getCurrent() == "Select Display") { + if (button == KEYS_UP || button == KEYS_DOWN) { + if (displayMode == DISPLAY_MODE_NORMAL) { + displayMode = DISPLAY_MODE_SWITCHER; + } else { + displayMode = DISPLAY_MODE_NORMAL; + } + } + lcd->updateDisplay("Select Display:", + displayMode == DISPLAY_MODE_NORMAL ? + "Standard" : "Switcher"); + } // Select Display + + // Set Address + // Selecting this allows you to "dial in" the loco address. + if (menu->getCurrent() == "Set Address") { + cab = calcIncValue(button, 3, cab, 9999, "%04d", "Set Address:"); + } // Set Address + + // Max Speed + // Selecting this allows you to change the maximum speed setting of the + // loco (effectively setting the speed range of the throttle "knob") + if (menu->getCurrent() == "Max Speed") { + maxSpeed = calcIncValue(button, 2, maxSpeed, 126, "%03d", "Max Speed:"); + } // Set Address + +} + +/** calcIncValue() + * + * Calculate the incremented value to show when "dialing" a settings number. + * TODO: Handling of the most significant digit is broken. + */ +int LCDThrottle::calcIncValue(int button, int maxpos, int val, int maxval, const char *fmt, const char *label) { + static int incval, incpos; + if (button == KEYS_SELECT) { + incval = 1; + incpos = maxpos; + + } else if (button == KEYS_UP) { + if (val + incval > 9999) { + // This will roll over the value. + // For now, don't do anything. + // TODO: Figure out how to roll ONLY the correct digit to zero. + } + else if (val == 9999) { val = 0; } + else { val += incval; } + + } else if (button == KEYS_DOWN) { + if (val - incval < 0) { + // This will roll under the value. + // For now, don't do anything. + // TODO: Figure out how to roll ONLY the correct digit to 9. + } + else if (val == 0) { val = maxval; } + else { val -= incval; } + } else if (button == KEYS_LEFT) { + if (incval < pow(10, maxpos)) { incval *= 10; } + if (incpos > 0) { incpos -= 1; } + } else if (button == KEYS_RIGHT) { + if (incval > 1) { incval /= 10; } + if (incpos < maxpos) { incpos += 1; } + } + Serial.println("val: " + String(val) + " incval: " + String(incval) + " incpos: " + String(incpos)); + sprintf(display[1], fmt, val); + lcd->updateDisplay(label, display[1]); + lcd->setCursor(incpos, 1); + lcd->cursor(); + return(val); +} + +/** sendPowerCommand() + * + * Send a Power on/off command to the DCC++ Base Station + */ +void LCDThrottle::sendPowerCommand(bool on) { + sprintf(command, ""); + sprintf(command, on == true ? "1" : "0"); + Serial.println("LCD Command: " + String(command)); + SerialCommand::parse(command); +} + +/** sendThrottleCommand() + * + * Send a Throttle command to the DCC++ Base Station + */ +void LCDThrottle::sendThrottleCommand() { + sprintf(command, ""); + sprintf(command, "t%d %d %d %d", reg, cab, speed, dir); + Serial.println("LCD Command: " + String(command)); + SerialCommand::parse(command); +} + +/** increaseSpeed() + * + * Increment the current speed by one "notch" + */ +void LCDThrottle::increaseSpeed() { + int tmp_notch; + if (displayMode == DISPLAY_MODE_NORMAL) { + // in DISPLAY_MODE_NORMAL, notch is 0->maxnotch. + // since this is the "increase" function we never have to worry about + // flipping the direction bit. + notch = (notch == MAX_NOTCH_NORMAL ? MAX_NOTCH_NORMAL : notch + 1); + speed = notch * (maxSpeed / MAX_NOTCH_NORMAL); + } else { + // in DISPLAY_MODE_SWITCHER the direction can change when "increasing" + // the throttle, it depends. Have to deal with absolute value. + // First, get the signed version of "notch" and increment it. + tmp_notch = (dir == REVERSE ? -notch : notch); + tmp_notch = (tmp_notch == MAX_NOTCH_SWITCHER ? MAX_NOTCH_SWITCHER : tmp_notch + 1); + // Now handle the possible sign change by setting the direction and + // storing notch = abs(tmp_notch) + if (tmp_notch >= 0) { + dir = FORWARD; + notch = tmp_notch; + } else { + dir = REVERSE; + notch = -tmp_notch; + } + speed = notch * (maxSpeed / MAX_NOTCH_SWITCHER); + } // if(displayMode) + + Serial.println("inc: N= " + String(notch) + " S=" + String(speed)); +} + +/** decreaseSpeed() + * + * Decrement the current speed by one "notch" + */ +void LCDThrottle::decreaseSpeed() { + int tmp_notch; + if (displayMode == DISPLAY_MODE_NORMAL) { + notch = (notch == 0 ? 0 : notch - 1); + speed = notch * (maxSpeed / MAX_NOTCH_NORMAL); + } else { + tmp_notch = (dir == REVERSE ? -notch : notch); + tmp_notch = (tmp_notch == -MAX_NOTCH_SWITCHER ? -MAX_NOTCH_SWITCHER : tmp_notch - 1); + if (tmp_notch < 0) { + dir = REVERSE; + notch = -tmp_notch; + } else { + dir = FORWARD; + notch = tmp_notch; + } + speed = notch * (maxSpeed / MAX_NOTCH_SWITCHER); + } + Serial.println("dec: N= " + String(notch) + " S=" + String(speed)); +} + +/** updateDisplay() + * + * Update the display when in Throttle mode + */ +void LCDThrottle::updateDisplay() { + switch(displayMode) { + case DISPLAY_MODE_SWITCHER: + // SWITCHER: Speed/Direction together + // Loco: + // <------0------> + // The blinking cursor shows the current value + lcd->clear(); + // Draw the line. + sprintf(display[0], "Loco: %04d", cab); + if (power_state == true) { + sprintf(display[1], "<------0------>"); + } else { + sprintf(display[1], "Track Power Off"); + } + lcd->updateDisplay(display[0], display[1]); + Serial.println("D0:" + String(display[0])); + Serial.println("D1:" + String(display[1])); + // Figure out where to put the cursor + if (notch == 0) { + lcd->setCursor(7,1); + } else { + int tmp_notch = notch; + if (tmp_notch == 0) { + tmp_notch += 7; + } else { + tmp_notch = (dir == FORWARD ? tmp_notch + 7 : 7 - tmp_notch); + } + Serial.println("S=" + String(speed) + " N=" + String(notch) + " T=" + String(tmp_notch)); + lcd->setCursor(tmp_notch, 1); + } + if (power_state == true) { + lcd->blink(); + } else { + lcd->noBlink(); + } + break; + + case DISPLAY_MODE_NORMAL: + default: + // NORMAL: Speed + Direction + // <-- --> + // 0--------------+ + // The length of the bar shows the speed + // The arrow shows the direction + lcd->clear(); + if (dir == REVERSE) { + sprintf(display[0], "<--- Loco: %04d", cab); + } else { + sprintf(display[0], "Loco: %04d --->", cab); + } + if (power_state == true) { + sprintf(display[1], "0 "); + if (speed > 0) { + for (int i = 0; i < notch-1; i++) { + display[1][i+1] = '-'; + } + display[1][notch] = '|'; + display[1][notch+1] = 0; + } + } else { + sprintf(display[1], "Track Power Off"); + } + lcd->updateDisplay(display[0], display[1]); + Serial.println("D0:" + String(display[0])); + Serial.println("D1:" + String(display[1])); + break; + } +} + +//--------------------------------------------------------------- +// MenuBackend support functions + +// TODO: I could probably use these effectively to clean up a bunch +// of those switch() statements above. + +/** menuChangeEvent() + * + * Callback for change events. + */ +static void menuChangeEvent(MenuChangeEvent changed) { + // Update the display to reflect the current menu state + // For now we'll use serial output. + Serial.print("Menu change "); + Serial.print(changed.from.getName()); + Serial.print(" -> "); + Serial.println(changed.to.getName()); +} + +/** menuUseEvent() + * + * Callback for "use" events + */ +static void menuUseEvent(MenuUseEvent used) { + //Serial.print("Menu use "); + //Serial.println(used.item.getName()); +} + +//--------------------------------------------------------------- +// EEPROM Interface Functions + +// Memory Locations (byte address) +// NOTE: Deprecated in favor of struct LCDThrottleData and +// DCC++ EEStore interface + +// DCC++ - compliant EEPROM access. +/** load() + * + * load sticky data from EEStore interface + */ +void LCDThrottle::load() { + EEStore::reset(); + EEPROM.get(EEStore::pointer(), displayMode); + EEStore::advance(sizeof(displayMode)); + EEPROM.get(EEStore::pointer(), cab); + EEStore::advance(sizeof(cab)); + EEPROM.get(EEStore::pointer(), maxSpeed); + EEStore::advance(sizeof(maxSpeed)); +} + +/** load() + * + * store sticky data from EEStore interface + */ +void LCDThrottle::store() { + EEStore::reset(); + EEPROM.put(EEStore::pointer(), displayMode); + EEStore::advance(sizeof(displayMode)); + EEPROM.put(EEStore::pointer(), cab); + EEStore::advance(sizeof(cab)); + EEPROM.put(EEStore::pointer(), maxSpeed); + EEStore::advance(sizeof(maxSpeed)); +} + +//--------------------------------------------------------------- +// Inglenook Game Functions + +/** doGameMenus(int button) + * + * Handle the Inglenook Game sub-menu + * + * param button: int -> current button value + */ +void LCDThrottle::doGameMenus(int button) { + switch(throttleState) { + case THROTTLE_STATE_GAME_MENUS: + switch(button) { + case KEYS_UP: + menu->moveUp(); + doGameMenuDisplay(); + break; + + case KEYS_DOWN: + menu->moveDown(); + doGameMenuDisplay(); + break; + + case KEYS_LEFT: + menu->moveLeft(); + if (menu->getCurrent() == "Inglenook") { + throttleState = THROTTLE_STATE_MENUS; + doMenuDisplay(); + } else { + doGameMenuDisplay(); + } + break; + + case KEYS_RIGHT: + menu->moveRight(); + doGameMenuDisplay(); + break; + + case KEYS_SELECT: + if (menu->getCurrent() == "List Cars") { + menu->moveRight(); // for this one "use" == "move right" + doGameMenuDisplay(); + } else if (menu->getCurrent() == "Build Train") { + throttleState = THROTTLE_STATE_GAME_BUILD; + game.buildTrain(); + game.doDisplayTrain(lcd, display[0], display[1]); + } + break; + + case KEYS_LONG_SELECT: + throttleState = THROTTLE_STATE_RUN; + jumpbackState = THROTTLE_STATE_GAME_MENUS; + updateDisplay(); + break; + } + break; + + case THROTTLE_STATE_GAME_BUILD: + switch(button) { + case KEYS_NONE: + break; + + case KEYS_LEFT: + case KEYS_SELECT: + throttleState = THROTTLE_STATE_GAME_MENUS; + doGameMenuDisplay(); + break; + + case KEYS_DOWN: + case KEYS_RIGHT: + if (game.carIndex() >= 3) { + game.setCarIndex(3); + } else { + game.setCarIndex(game.carIndex()+1); + } + Serial.println("List Train: Car index " + String(game.carIndex())); + game.doListTrain(lcd, display[0], display[1], game.carIndex()); + break; + + case KEYS_UP: + if (game.carIndex() > -1) { + game.setCarIndex(game.carIndex()-1); + } + if (game.carIndex() == -1) { + game.doDisplayTrain(lcd, display[0], display[1]); + } else { + Serial.println("List Train: Car index " + String(game.carIndex())); + game.doListTrain(lcd, display[0], display[1], game.carIndex()); + } + break; + + case KEYS_LONG_SELECT: + throttleState = THROTTLE_STATE_RUN; + jumpbackState = THROTTLE_STATE_GAME_BUILD; + updateDisplay(); + break; + + } // switch(buttons) + } // switch(state) +} + +/** doGameMenuDisplay() + * + * Handle display output for the Inglenook Game sub-menu + */ +void LCDThrottle::doGameMenuDisplay() { + lcd->clear(); + + if (menu->getCurrent() == "Build Train") { + sprintf(display[0], "Build Train"); + sprintf(display[1], "List Cars"); + } + if (menu->getCurrent() == "List Cars") { + sprintf(display[0], "List Cars"); + sprintf(display[1], ""); + } + if (menu->getCurrent() == "Car 0") { + sprintf(display[0],"%s%s", "1:",carnames[0].c_str()); + sprintf(display[1],"%s%s", "2:",carnames[1].c_str()); + } + if (menu->getCurrent() == "Car 1") { + sprintf(display[0],"%s%s", "2:",carnames[1].c_str()); + sprintf(display[1],"%s%s", "3:",carnames[2].c_str()); + } + if (menu->getCurrent() == "Car 2") { + sprintf(display[0],"%s%s", "3:",carnames[2].c_str()); + sprintf(display[1],"%s%s", "4:",carnames[3].c_str()); + } + if (menu->getCurrent() == "Car 3") { + sprintf(display[0],"%s%s", "4:",carnames[3].c_str()); + sprintf(display[1],"%s%s", "5:",carnames[4].c_str()); + } + if (menu->getCurrent() == "Car 4") { + sprintf(display[0],"%s%s", "5:",carnames[4].c_str()); + sprintf(display[1],"%s%s", "6:",carnames[5].c_str()); + } + if (menu->getCurrent() == "Car 5") { + sprintf(display[0],"%s%s", "6:",carnames[5].c_str()); + sprintf(display[1],"%s%s", "7:",carnames[6].c_str()); + } + if ((menu->getCurrent() == "Car 6") || (menu->getCurrent() == "Car 7")) { + sprintf(display[0],"%s%s", "7:",carnames[6].c_str()); + sprintf(display[1],"%s%s", "8:",carnames[7].c_str()); + } + lcd->updateDisplay(display[0], display[1]); + lcd->setCursor(0,0); + lcd->blink(); +} diff --git a/DCCpp_Uno/LCDThrottle.h b/DCCpp_Uno/LCDThrottle.h new file mode 100644 index 0000000..ebdd5d5 --- /dev/null +++ b/DCCpp_Uno/LCDThrottle.h @@ -0,0 +1,69 @@ +#ifndef LCD_THROTTLE_H +#define LCD_THROTTLE_H + +#include "LCD.h" +#include "Inglenook.h" + +// DCC++ Throttle using the buttons on a 16x2 + 5 button Display Shield + +#define DISPLAY_MODE_NORMAL 1 +#define DISPLAY_MODE_SWITCHER 2 +#define DISPLAY_MODE DISPLAY_MODE_NORMAL + +// Throttle Directions +#define FORWARD 1 +#define REVERSE 0 + +class MenuItem; + +class LCDThrottle { +private: + int throttleState; +int jumpbackState; + int reg; + int cab; + int speed; + int dir; + byte displayMode; + int notch; + LCD *lcd; + int maxSpeed; + bool power_state; + InglenookGame game; + + public: + LCDThrottle(); + void begin(int reg); + void run(); + + protected: + void sendThrottleCommand(); + void sendPowerCommand(bool on); + int debounceButtons(); + int getButton(); + void increaseSpeed(); + void decreaseSpeed(); + void updateDisplay(); + void menuSetup(); + void doMenuDisplay(); + void doMenuAction(int button); + void load(); + void store(); + void doGameMenus(int b); + void setupInglenookMenu(MenuItem *m); + void inglenookBegin(); + void doGameMenuDisplay(); + int calcIncValue(int button, int maxpos, int val, int maxval, const char *fmt, const char *label); + +}; + +struct LCDThrottleData { +byte dislay; +byte reserved1; +int address; +int maxspeed; +int reserved[5]; +}; + + +#endif // LCD_THROTTLE_H diff --git a/DCCpp_Uno/SerialCommand.cpp b/DCCpp_Uno/SerialCommand.cpp index 7be080c..1e53994 100644 --- a/DCCpp_Uno/SerialCommand.cpp +++ b/DCCpp_Uno/SerialCommand.cpp @@ -22,6 +22,9 @@ Part of DCC++ BASE STATION for the Arduino #include "Outputs.h" #include "EEStore.h" #include "Comm.h" +#ifdef WITHROTTLE_SUPPORT +#include "WiThrottle.hpp" +#endif extern int __heap_start, *__brkval; From 0f297f97321b76a27968f813e843ff07a5e7c525 Mon Sep 17 00:00:00 2001 From: TwinDad Date: Thu, 9 Feb 2017 15:15:45 -0500 Subject: [PATCH 19/21] 1) Added code for LCD Throttle support. Disabled in Config.h 2) Added support for Bonjour/Zeroconf registration. Disabled in Config.h WARNING: If using the EthernetBonjour library (which this does), you MUST make the changes to the Ethernet library as outlined in this Arduino forum thread: http://forum.arduino.cc/index.php?topic=234340.0 ALSO, you must change line 148 of EthernetBonjour.cpp to call this->beginMulti() instead of this->beginMulticast() ... apparently a typo still in the library. FINALLY, you might want to change #define HAS_NAME_BROWSING in EthernetBonjour.cpp to 0, to save some space, since we are registering a service, not browsing for one. I have not (yet) tested that though. --- DCCpp_Uno/Config.h | 9 ++++++++- DCCpp_Uno/DCCpp_Uno.ino | 31 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/DCCpp_Uno/Config.h b/DCCpp_Uno/Config.h index 00945f9..e1cc68d 100644 --- a/DCCpp_Uno/Config.h +++ b/DCCpp_Uno/Config.h @@ -62,12 +62,19 @@ Part of DCC++ BASE STATION for the Arduino #define WITHROTTLE_SUPPORT 1 +///////////////////////////////////////////////////////////////////////////////////// +// +// ENABLE BONJOUR/ZEROCONF SUPPORT +// + +#define BONJOUR 0 + ///////////////////////////////////////////////////////////////////////////////////// // // ENABLE THE LCD THROTTLE. REQUIRES AN LCD WITH 5 BUTTONS. // -#define LCD_THROTTLE 1 +#define LCD_THROTTLE 0 ///////////////////////////////////////////////////////////////////////////////////// diff --git a/DCCpp_Uno/DCCpp_Uno.ino b/DCCpp_Uno/DCCpp_Uno.ino index e330543..c4321de 100644 --- a/DCCpp_Uno/DCCpp_Uno.ino +++ b/DCCpp_Uno/DCCpp_Uno.ino @@ -177,6 +177,12 @@ DCC++ BASE STATION is configured through the Config.h file that contains all use #include "EEStore.h" #include "Config.h" #include "Comm.h" +#if (LCD_THROTTLE == 1) +#include "LCDThrottle.h" +#endif +#if ((COMM_TYPE == 1) && (BONJOUR == 1)) +#include +#endif void showConfiguration(); @@ -185,6 +191,9 @@ void showConfiguration(); #if COMM_TYPE == 1 byte mac[] = MAC_ADDRESS; // Create MAC address (to be used for DHCP when initializing server) EthernetServer INTERFACE(ETHERNET_PORT); // Create and instance of an EnternetServer +#if (BONJOUR == 1) + const char bonjourname = "DCCpp._withrottle._tcp"; +#endif #endif // NEXT DECLARE GLOBAL OBJECTS TO PROCESS AND STORE DCC PACKETS AND MONITOR TRACK CURRENTS. @@ -196,12 +205,24 @@ volatile RegisterList progRegs(2); // create a shorter list CurrentMonitor mainMonitor(CURRENT_MONITOR_PIN_MAIN,""); // create monitor for current on Main Track CurrentMonitor progMonitor(CURRENT_MONITOR_PIN_PROG,""); // create monitor for current on Program Track +#if (LCD_THROTTLE == 1) +LCDThrottle *lcdThrottle; +#endif + /////////////////////////////////////////////////////////////////////////////// // MAIN ARDUINO LOOP /////////////////////////////////////////////////////////////////////////////// void loop(){ +#if (LCD_THROTTLE == 1) + lcdThrottle->run(); +#endif + +#if ((COMM_TYPE == 1 && BONJOUR == 1)) + EthernetBonjour.run(); +#endif + SerialCommand::process(); // check for, and process, and new serial commands if(CurrentMonitor::checkTime()){ // if sufficient time has elapsed since last update, check current draw on Main and Program Tracks @@ -222,6 +243,11 @@ void setup(){ Serial.begin(115200); // configure serial interface Serial.flush(); + #if (LCD_THROTTLE == 1) + lcdThrottle = new LCDThrottle(); + lcdThrottle->begin(1); + #endif + #ifdef SDCARD_CS pinMode(SDCARD_CS,OUTPUT); digitalWrite(SDCARD_CS,HIGH); // Deselect the SD card @@ -253,6 +279,11 @@ void setup(){ Ethernet.begin(mac); // Start networking using DHCP to get an IP Address #endif INTERFACE.begin(); + + #if (BONJOUR == 1) + EthernetBonjour.begin("arduino"); + EthernetBonjour.addServiceRecord("DCCpp._withrottle", ETHERNET_PORT, MDNSServiceTCP, "jmri=4.5.7"); + #endif #endif SerialCommand::init(&mainRegs, &progRegs, &mainMonitor); // create structure to read and parse commands from serial line From 9084cbd0c5eacaa80d4def2250ecb8ef91e85d2d Mon Sep 17 00:00:00 2001 From: TwinDad Date: Tue, 28 Feb 2017 21:54:42 -0500 Subject: [PATCH 20/21] Try to get withrottle working with the ESP8266 over serial --- DCCpp_Uno/Config.h | 2 +- DCCpp_Uno/Sensor.cpp | 4 ++-- DCCpp_Uno/SerialCommand.cpp | 22 ++++++++++++++++------ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/DCCpp_Uno/Config.h b/DCCpp_Uno/Config.h index e1cc68d..3fc1ed8 100644 --- a/DCCpp_Uno/Config.h +++ b/DCCpp_Uno/Config.h @@ -31,7 +31,7 @@ Part of DCC++ BASE STATION for the Arduino // 2 = Arduino.org Ethernet/SD-Card Shield // 3 = Seeed Studio Ethernet/SD-Card Shield W5200 -#define COMM_INTERFACE 1 +#define COMM_INTERFACE 0 ///////////////////////////////////////////////////////////////////////////////////// // diff --git a/DCCpp_Uno/Sensor.cpp b/DCCpp_Uno/Sensor.cpp index 11fca36..8e1fce8 100644 --- a/DCCpp_Uno/Sensor.cpp +++ b/DCCpp_Uno/Sensor.cpp @@ -65,7 +65,7 @@ decide to ignore the return and only react to triggers. void Sensor::check(){ Sensor *tt; - +/* for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ tt->signal=tt->signal*(1.0-SENSOR_DECAY)+digitalRead(tt->data.pin)*SENSOR_DECAY; @@ -81,7 +81,7 @@ void Sensor::check(){ INTERFACE.print(">"); } } // loop over all sensors - + */ } // Sensor::check /////////////////////////////////////////////////////////////////////////////// diff --git a/DCCpp_Uno/SerialCommand.cpp b/DCCpp_Uno/SerialCommand.cpp index 1e53994..613b970 100644 --- a/DCCpp_Uno/SerialCommand.cpp +++ b/DCCpp_Uno/SerialCommand.cpp @@ -34,6 +34,7 @@ char SerialCommand::commandString[MAX_COMMAND_LENGTH+1]; volatile RegisterList *SerialCommand::mRegs; volatile RegisterList *SerialCommand::pRegs; CurrentMonitor *SerialCommand::mMonitor; +bool newConnect; /////////////////////////////////////////////////////////////////////////////// @@ -42,6 +43,7 @@ void SerialCommand::init(volatile RegisterList *_mRegs, volatile RegisterList *_ pRegs=_pRegs; mMonitor=_mMonitor; sprintf(commandString,""); + newConnect = true; } // SerialCommand:SerialCommand /////////////////////////////////////////////////////////////////////////////// @@ -52,13 +54,21 @@ void SerialCommand::process(){ #if COMM_TYPE == 0 while(INTERFACE.available()>0){ // while there is data on the serial line + if (newConnect) { + WiThrottle::sendIntroMessage(); + newConnect = false; + } c=INTERFACE.read(); - if(c=='<') // start of new command - sprintf(commandString,""); - else if(c=='>') // end of new command - parse(commandString); - else if(strlen(commandString)') + if (WiThrottle::isWTCommand(c)) { + WiThrottle::readCommand(c); + } else { + if(c=='<') // start of new command + sprintf(commandString,""); + else if(c=='>') // end of new command + parse(commandString); + else if(strlen(commandString)') + } } // while #elif COMM_TYPE == 1 From 54200122bf8a294adf1d927191a0efe67ff62335 Mon Sep 17 00:00:00 2001 From: TwinDad Date: Sun, 18 Mar 2018 19:10:41 -0400 Subject: [PATCH 21/21] Add "-2" response code to prog track writes. Indicates to JMRI that current feedback is not supported. Suppresses the 308 error that occurs when using DCC++ with a motor shield that doesn't support feedback. --- DCCpp_Uno/Config.h | 4 ++++ DCCpp_Uno/PacketRegister.cpp | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/DCCpp_Uno/Config.h b/DCCpp_Uno/Config.h index 3fc1ed8..e75875e 100644 --- a/DCCpp_Uno/Config.h +++ b/DCCpp_Uno/Config.h @@ -16,6 +16,10 @@ Part of DCC++ BASE STATION for the Arduino #define MOTOR_SHIELD_TYPE 1 +// SET THIS TO 1 IF THE MOTOR SHIELD HAS CURRENT FEEDBACK +// SET THIS TO 0 IF THE MOTOR SHIELD DOES NOT HAVE CURRENT FEEDBACK +#define MOTOR_SHIELD_SUPPORTS_FEEDBACK 0 + ///////////////////////////////////////////////////////////////////////////////////// // // DEFINE NUMBER OF MAIN TRACK REGISTER diff --git a/DCCpp_Uno/PacketRegister.cpp b/DCCpp_Uno/PacketRegister.cpp index 2d69d60..b47d2c0 100644 --- a/DCCpp_Uno/PacketRegister.cpp +++ b/DCCpp_Uno/PacketRegister.cpp @@ -322,10 +322,14 @@ void RegisterList::writeCVByte(char *s) volatile{ if(c>ACK_SAMPLE_THRESHOLD) d=1; } - - if(d==0) // verify unsuccessful +#if (MOTOR_SHIELD_SUPPORTS_FEEDBACK > 0) + if(d==0) { // verify unsuccessful bValue=-1; - + } +#else + bValue=-2; +#endif + INTERFACE.print(" 0) + if(d==0) { // verify unsuccessful bValue=-1; + } +#else + bValue=-2; +#endif INTERFACE.print("