diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 84fdf3e..614fed9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,5 +13,7 @@ jobs: run: pip install wheel - name: Install PlatformIO Core run: pip install -U https://github.com/platformio/platformio/archive/v4.2.1.zip + - name: Copy generic config over + run: cp config.example.h config.h - name: Compile Command Station (AVR) run: python -m platformio run diff --git a/.gitignore b/.gitignore index f49f28f..e5404bb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ Release/* .gcc-flags.json .pio/ .vscode/ +config.h \ No newline at end of file diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index e3d7cac..5f1f87e 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -1,74 +1,141 @@ -/* - * © 2020, Chris Harlow. All rights reserved. - * - * This file is a demonstattion of setting up a DCC-EX - * Command station to support direct connection of WiThrottle devices - * such as "Engine Driver". If you contriol your layout through JMRI - * then DON'T connect throttles to this wifi, connect them to JMRI. - * - * This is just 3 statements longer than the basic setup. - * - * THIS SETUP DOES NOT APPLY TO ARDUINO UNO WITH ONLY A SINGLE SERIAL PORT. - * REFER TO SEPARATE EXAMPLE. - */ +//////////////////////////////////////////////////////////////////////////////////// +// © 2020, Chris Harlow. All rights reserved. +// +// This file is a demonstattion of setting up a DCC-EX +// Command station with optional support for direct connection of WiThrottle devices +// such as "Engine Driver". If you contriol your layout through JMRI +// then DON'T connect throttles to this wifi, connect them to JMRI. +// +// THE WIFI FEATURE IS NOT SUPPORTED ON ARDUINO DEVICES WITH ONLY 2KB RAM. +//////////////////////////////////////////////////////////////////////////////////// +#include "config.h" #include "DCCEX.h" -#ifdef ARDUINO_AVR_UNO - #include - SoftwareSerial Serial1(15,16); // YOU must get these pins correct to use Wifi on a UNO - #define WIFI_BAUD 9600 -#else - #define WIFI_BAUD 115200 -#endif -// Create a serial command parser... Enables certain diagnostics and commands -// to be issued from the USB serial console -// This is NOT intended for JMRI.... +//////////////////////////////////////////////////////////////// +// +// Enables an I2C 2x24 or 4x24 LCD Screen +#if ENABLE_LCD +bool lcdEnabled = false; +#if defined(LIB_TYPE_PCF8574) +LiquidCrystal_PCF8574 lcdDisplay(LCD_ADDRESS); +#elif defined(LIB_TYPE_I2C) +LiquidCrystal_I2C lcdDisplay = LiquidCrystal_I2C(LCD_ADDRESS, LCD_COLUMNS, LCD_LINES); +#endif +#endif -DCCEXParser serialParser; +// Create a serial command parser for the USB connection, +// This supports JMRI or manual diagnostics and commands +// to be issued from the USB serial console. +DCCEXParser serialParser; -void setup() { +void setup() +{ + +//////////////////////////////////////////// +// +// More display stuff. Need to put this in a .h file and make +// it a class +#if ENABLE_LCD + Wire.begin(); + // Check that we can find the LCD by its address before attempting to use it. + Wire.beginTransmission(LCD_ADDRESS); + if (Wire.endTransmission() == 0) + { + lcdEnabled = true; + lcdDisplay.begin(LCD_COLUMNS, LCD_LINES); + lcdDisplay.setBacklight(255); + lcdDisplay.clear(); + lcdDisplay.setCursor(0, 0); + lcdDisplay.print("DCC++ EX v"); + lcdDisplay.print(VERSION); + lcdDisplay.setCursor(0, 1); +#if COMM_INTERFACE >= 1 + lcdDisplay.print("IP: PENDING"); +#else + lcdDisplay.print("SERIAL: READY"); +#endif +#if LCD_LINES > 2 + lcdDisplay.setCursor(0, 3); + lcdDisplay.print("TRACK POWER: OFF"); +#endif + } +#endif // The main sketch has responsibilities during setup() - - // Responsibility 1: Start the usb connection for diagnostics + + // Responsibility 1: Start the usb connection for diagnostics // This is normally Serial but uses SerialUSB on a SAMD processor Serial.begin(115200); - // Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi - // NOTE: References to Serial1 are for the serial port used to connect - // your wifi chip/shield. - - Serial1.begin(WIFI_BAUD); - WifiInterface::setup(Serial1, F("Your network name"), F("your network password"),F("DCCEX"),3532); - - // Responsibility 3: Start the DCC engine. - // Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s) - // Standard supported devices have pre-configured macros but custome hardware installations require - // detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic. +// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi +// NOTE: References to Serial1 are for the serial port used to connect +// your wifi chip/shield. - // STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h +#ifdef WIFI_ON + bool wifiUp = false; + const __FlashStringHelper *wifiESSID = F(WIFI_SSID); + const __FlashStringHelper *wifiPassword = F(WIFI_PASSWORD); + const __FlashStringHelper *dccex = F(WIFI_HOSTNAME); + const uint16_t port = IP_PORT; - // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the - // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 - - DCC::begin(STANDARD_MOTOR_SHIELD); + Serial1.begin(WIFI_SERIAL_LINK_SPEED); + wifiUp = WifiInterface::setup(Serial1, wifiESSID, wifiPassword, dccex, port); +#if NUM_SERIAL > 1 + if (!wifiUp) + { + Serial2.begin(WIFI_SERIAL_LINK_SPEED); + wifiUp = WifiInterface::setup(Serial2, wifiESSID, wifiPassword, dccex, port); + } +#if NUM_SERIAL > 2 + if (!wifiUp) + { + Serial3.begin(WIFI_SERIAL_LINK_SPEED); + wifiUp = WifiInterface::setup(Serial3, wifiESSID, wifiPassword, dccex, port); + } +#endif // >2 +#endif // >1 +#endif // WIFI_ON + // Responsibility 3: Start the DCC engine. + // Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s) + // Standard supported devices have pre-configured macros but custome hardware installations require + // detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic. + + // STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h + + // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the + // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 + + DCC::begin(MOTOR_SHIELD_TYPE); } -void loop() { +void loop() +{ // The main sketch has responsibilities during loop() - + // Responsibility 1: Handle DCC background processes // (loco reminders and power checks) - DCC::loop(); + DCC::loop(); // Responsibility 2: handle any incoming commands on USB connection serialParser.loop(Serial); - // Responsibility 3: Optionally handle any incoming WiFi traffic +// Responsibility 3: Optionally handle any incoming WiFi traffic +#ifdef WIFI_ON WifiInterface::loop(); +#endif +// Optionally report any decrease in memory (will automatically trigger on first call) +#if ENABLE_FREE_MEM_WARNING + static int ramLowWatermark = 32767; // replaced on first loop + int freeNow = freeMemory(); + if (freeNow < ramLowWatermark) + { + ramLowWatermark = freeNow; + DIAG(F("\nFree RAM=%d\n"), ramLowWatermark); + } +#endif } diff --git a/DCC.cpp b/DCC.cpp index e7ca624..29fa9d7 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -41,8 +41,10 @@ const byte FN_GROUP_3=0x04; const byte FN_GROUP_4=0x08; const byte FN_GROUP_5=0x10; +__FlashStringHelper* DCC::shieldName=NULL; -void DCC::begin(MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) { +void DCC::begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) { + shieldName=(__FlashStringHelper*)motorShieldName; DCCWaveform::begin(mainDriver,progDriver, timerNumber); } @@ -205,7 +207,14 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) { void DCC::setProgTrackSyncMain(bool on) { DCCWaveform::progTrackSyncMain=on; } +void DCC::setProgTrackBoost(bool on) { + DCCWaveform::progTrackBoosted=on; +} +__FlashStringHelper* DCC::getMotorShieldName() { + return shieldName; +} + const ackOp PROGMEM WRITE_BIT0_PROG[] = { BASELINE, W0,WACK, @@ -568,7 +577,7 @@ void DCC::ackManagerLoop(bool blocking) { DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); DCCWaveform::progTrack.sentResetsSincePacket = 0; DCCWaveform::progTrack.autoPowerOff=true; - return; + if (!blocking) return; } if (checkResets(blocking, DCCWaveform::progTrack.autoPowerOff ? 20 : 3)) return; DCCWaveform::progTrack.setAckBaseline(); diff --git a/DCC.h b/DCC.h index 05e9f9e..0e8fcc6 100644 --- a/DCC.h +++ b/DCC.h @@ -24,118 +24,152 @@ typedef void (*ACK_CALLBACK)(int result); -enum ackOp { // Program opcodes for the ack Manager -BASELINE, // ensure enough resets sent before starting and obtain baseline current -W0,W1, // issue write bit (0..1) packet -WB, // issue write byte packet -VB, // Issue validate Byte packet -V0, // Issue validate bit=0 packet -V1, // issue validate bit=1 packlet -WACK, // wait for ack (or absence of ack) -ITC1, // If True Callback(1) (if prevous WACK got an ACK) -ITC0, // If True callback(0); -ITCB, // If True callback(byte) -NAKFAIL, // if false callback(-1) -FAIL, // callback(-1) -STARTMERGE, // Clear bit and byte settings ready for merge pass -MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes) -SETBIT, // sets bit number to next prog byte -SETCV, // sets cv number to next prog byte -STASHLOCOID, // keeps current byte value for later -COMBINELOCOID, // combines current value with stashed value and returns it -ITSKIP, // skip to SKIPTARGET if ack true -SKIPTARGET=0xFF // jump to target +enum ackOp +{ // Program opcodes for the ack Manager + BASELINE, // ensure enough resets sent before starting and obtain baseline current + W0, + W1, // issue write bit (0..1) packet + WB, // issue write byte packet + VB, // Issue validate Byte packet + V0, // Issue validate bit=0 packet + V1, // issue validate bit=1 packlet + WACK, // wait for ack (or absence of ack) + ITC1, // If True Callback(1) (if prevous WACK got an ACK) + ITC0, // If True callback(0); + ITCB, // If True callback(byte) + NAKFAIL, // if false callback(-1) + FAIL, // callback(-1) + STARTMERGE, // Clear bit and byte settings ready for merge pass + MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes) + SETBIT, // sets bit number to next prog byte + SETCV, // sets cv number to next prog byte + STASHLOCOID, // keeps current byte value for later + COMBINELOCOID, // combines current value with stashed value and returns it + ITSKIP, // skip to SKIPTARGET if ack true + SKIPTARGET = 0xFF // jump to target }; // Allocations with memory implications..! // Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created - #ifdef ARDUINO_AVR_UNO - const byte MAX_LOCOS=20; - #else - const byte MAX_LOCOS=50; - #endif +#ifdef ARDUINO_AVR_UNO +const byte MAX_LOCOS = 20; +#else +const byte MAX_LOCOS = 50; +#endif - -class DCC { - public: - - static void begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber=1); +class DCC +{ +public: + static void begin(const __FlashStringHelper *motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver, byte timerNumber = 1); static void loop(); // Public DCC API functions - static void setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection); + static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection); static uint8_t getThrottleSpeed(int cab); static bool getThrottleDirection(int cab); static void writeCVByteMain(int cab, int cv, byte bValue); static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue); - static void setFunction( int cab, byte fByte, byte eByte); - static void setFn( int cab, byte functionNumber, bool on); - static int changeFn( int cab, byte functionNumber, bool pressed); - static void updateGroupflags(byte & flags, int functionNumber); - static void setAccessory(int aAdd, byte aNum, bool activate) ; - static bool writeTextPacket( byte *b, int nBytes); - static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable - - // ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1 - static void readCV(int cv, ACK_CALLBACK callback, bool blocking=false); - static void readCVBit(int cv, byte bitNum, ACK_CALLBACK callback, bool blocking=false); // -1 for error - static void writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking=false) ; - static void writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking=false); - static void verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking=false) ; - static void verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking=false); - - static void getLocoId(ACK_CALLBACK callback, bool blocking=false); + static void setFunction(int cab, byte fByte, byte eByte); + static void setFn(int cab, byte functionNumber, bool on); + static int changeFn(int cab, byte functionNumber, bool pressed); + static void updateGroupflags(byte &flags, int functionNumber); + static void setAccessory(int aAdd, byte aNum, bool activate); + static bool writeTextPacket(byte *b, int nBytes); + static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable + static void setProgTrackBoost(bool on); // when true, special prog track current limit does not apply + + // ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1 + static void readCV(int cv, ACK_CALLBACK callback, bool blocking = false); + static void readCVBit(int cv, byte bitNum, ACK_CALLBACK callback, bool blocking = false); // -1 for error + static void writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking = false); + static void writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false); + static void verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking = false); + static void verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false); + + static void getLocoId(ACK_CALLBACK callback, bool blocking = false); // Enhanced API functions - static void forgetLoco(int cab); // removes any speed reminders for this loco - static void forgetAllLocos(); // removes all speed reminders - static void displayCabList(Print * stream); -private: - struct LOCO { - int loco; - byte speedCode; - byte groupFlags; - unsigned long functions; + static void forgetLoco(int cab); // removes any speed reminders for this loco + static void forgetAllLocos(); // removes all speed reminders + static void displayCabList(Print *stream); + + static __FlashStringHelper *getMotorShieldName(); + +private: + struct LOCO + { + int loco; + byte speedCode; + byte groupFlags; + unsigned long functions; }; static byte loopStatus; - static void setThrottle2( uint16_t cab, uint8_t speedCode); + static void setThrottle2(uint16_t cab, uint8_t speedCode); static void updateLocoReminder(int loco, byte speedCode); - static void setFunctionInternal( int cab, byte fByte, byte eByte); + static void setFunctionInternal(int cab, byte fByte, byte eByte); static bool issueReminder(int reg); static int nextLoco; + static __FlashStringHelper *shieldName; + static LOCO speedTable[MAX_LOCOS]; static byte cv1(byte opcode, int cv); static byte cv2(int cv); static int lookupSpeedTable(int locoId); static void issueReminders(); static void callback(int value); - -// ACK MANAGER - static ackOp const * ackManagerProg; - static byte ackManagerByte; - static byte ackManagerBitNum; - static int ackManagerCv; - static byte ackManagerStash; + + // ACK MANAGER + static ackOp const *ackManagerProg; + static byte ackManagerByte; + static byte ackManagerBitNum; + static int ackManagerCv; + static byte ackManagerStash; static bool ackReceived; - static ACK_CALLBACK ackManagerCallback; + static ACK_CALLBACK ackManagerCallback; static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback, bool blocking); static void ackManagerLoop(bool blocking); static bool checkResets(bool blocking, uint8_t numResets); - static const int PROG_REPEATS=8; // repeats of programming commands (some decoders need at least 8 to be reliable) - - + static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable) // NMRA codes # - static const byte SET_SPEED=0x3f; + static const byte SET_SPEED = 0x3f; static const byte WRITE_BYTE_MAIN = 0xEC; static const byte WRITE_BIT_MAIN = 0xE8; static const byte WRITE_BYTE = 0x7C; - static const byte VERIFY_BYTE= 0x74; - static const byte BIT_MANIPULATE=0x78; - static const byte WRITE_BIT=0xF0; - static const byte VERIFY_BIT=0xE0; - static const byte BIT_ON=0x08; - static const byte BIT_OFF=0x00; + static const byte VERIFY_BYTE = 0x74; + static const byte BIT_MANIPULATE = 0x78; + static const byte WRITE_BIT = 0xF0; + static const byte VERIFY_BIT = 0xE0; + static const byte BIT_ON = 0x08; + static const byte BIT_OFF = 0x00; }; +#ifdef ARDUINO_AVR_MEGA // is using Mega 1280, define as Mega 2560 (pinouts and functionality are identical) +#define ARDUINO_AVR_MEGA2560 +#endif + +#if defined(ARDUINO_AVR_UNO) +#define ARDUINO_TYPE "UNO" +#elif defined(ARDUINO_AVR_NANO) +#define ARDUINO_TYPE "NANO" +#elif defined(ARDUINO_AVR_MEGA2560) +#define ARDUINO_TYPE "MEGA" +#elif defined(ARDUINO_ARCH_MEGAAVR) +#define ARDUINO_TYPE "UNOWIFIR2" +#else +#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560 +#endif + +#if ENABLE_LCD +#include +#if defined(LIB_TYPE_PCF8574) +#include +extern LiquidCrystal_PCF8574 lcdDisplay; +#elif defined(LIB_TYPE_I2C) +#include +extern LiquidCrystal_I2C lcdDisplay; +#endif +extern bool lcdEnabled; +#endif + #endif diff --git a/DCCEX.h b/DCCEX.h index e94b193..091bd47 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -1,7 +1,17 @@ +// This include is intended to visually simplify the .ino for the end users. +// If there were any #ifdefs required they are much better handled in here. + #ifndef DCCEX_h #define DCCEX_h + +#include "defines.h" #include "DCC.h" #include "DIAG.h" #include "DCCEXParser.h" +#include "version.h" #include "WifiInterface.h" -#endif +#include "EthernetInterface.h" + +#include + +#endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 858f2a5..8a96e23 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -1,7 +1,8 @@ /* * © 2020, Chris Harlow. All rights reserved. + * © 2020, Harald Barth. * - * This file is part of Asbelos DCC API + * This file is part of CommandStation-EX * * This is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,535 +21,639 @@ #include "DCCEXParser.h" #include "DCC.h" #include "DCCWaveform.h" -#include "WifiInterface.h" #include "Turnouts.h" #include "Outputs.h" #include "Sensors.h" #include "freeMemory.h" #include "GITHUB_SHA.h" +#include "version.h" #include "EEStore.h" #include "DIAG.h" -const char VERSION[] PROGMEM ="0.1.9"; - // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. // To discover new keyword numbers , use the <$ YOURKEYWORD> command -const int HASH_KEYWORD_PROG=-29718; -const int HASH_KEYWORD_MAIN=11339; -const int HASH_KEYWORD_JOIN=-30750; -const int HASH_KEYWORD_CABS=-11981; -const int HASH_KEYWORD_RAM=25982; -const int HASH_KEYWORD_CMD=9962; -const int HASH_KEYWORD_WIT=31594; -const int HASH_KEYWORD_WIFI=-5583; -const int HASH_KEYWORD_ACK=3113; -const int HASH_KEYWORD_ON=2657; -const int HASH_KEYWORD_DCC=6436; -const int HASH_KEYWORD_SLOW=-17209; - +const int HASH_KEYWORD_PROG = -29718; +const int HASH_KEYWORD_MAIN = 11339; +const int HASH_KEYWORD_JOIN = -30750; +const int HASH_KEYWORD_CABS = -11981; +const int HASH_KEYWORD_RAM = 25982; +const int HASH_KEYWORD_CMD = 9962; +const int HASH_KEYWORD_WIT = 31594; +const int HASH_KEYWORD_WIFI = -5583; +const int HASH_KEYWORD_ACK = 3113; +const int HASH_KEYWORD_ON = 2657; +const int HASH_KEYWORD_DCC = 6436; +const int HASH_KEYWORD_SLOW = -17209; +const int HASH_KEYWORD_PROGBOOST = -6353; int DCCEXParser::stashP[MAX_PARAMS]; bool DCCEXParser::stashBusy; - - Print * DCCEXParser::stashStream=NULL; + +Print *DCCEXParser::stashStream = NULL; // This is a JMRI command parser, one instance per incoming stream // It doesnt know how the string got here, nor how it gets back. // It knows nothing about hardware or tracks... it just parses strings and // calls the corresponding DCC api. -// Non-DCC things like turnouts, pins and sensors are handled in additional JMRI interface classes. +// Non-DCC things like turnouts, pins and sensors are handled in additional JMRI interface classes. DCCEXParser::DCCEXParser() {} -void DCCEXParser::flush() { - if (Diag::CMD) DIAG(F("\nBuffer flush")); - bufferLength=0; - inCommandPayload=false; +void DCCEXParser::flush() +{ + if (Diag::CMD) + DIAG(F("\nBuffer flush")); + bufferLength = 0; + inCommandPayload = false; } -void DCCEXParser::loop(Stream & stream) { - while(stream.available()) { - if (bufferLength==MAX_BUFFER) { - flush(); +void DCCEXParser::loop(Stream &stream) +{ + while (stream.available()) + { + if (bufferLength == MAX_BUFFER) + { + flush(); + } + char ch = stream.read(); + if (ch == '<') + { + inCommandPayload = true; + bufferLength = 0; + buffer[0] = '\0'; + } + else if (ch == '>') + { + buffer[bufferLength] = '\0'; + parse(&stream, buffer, false); // Parse this allowing async responses + inCommandPayload = false; + break; + } + else if (inCommandPayload) + { + buffer[bufferLength++] = ch; + } } - char ch = stream.read(); - if (ch == '<') { - inCommandPayload = true; - bufferLength=0; - buffer[0]='\0'; - } - else if (ch == '>') { - buffer[bufferLength]='\0'; - parse( & stream, buffer, false); // Parse this allowing async responses - inCommandPayload = false; - break; - } else if(inCommandPayload) { - buffer[bufferLength++]= ch; +} + +int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd) +{ + byte state = 1; + byte parameterCount = 0; + int runningValue = 0; + const byte *remainingCmd = cmd + 1; // skips the opcode + bool signNegative = false; + + // clear all parameters in case not enough found + for (int i = 0; i < MAX_PARAMS; i++) + result[i] = 0; + + while (parameterCount < MAX_PARAMS) + { + byte hot = *remainingCmd; + + switch (state) + { + + case 1: // skipping spaces before a param + if (hot == ' ') + break; + if (hot == '\0' || hot == '>') + return parameterCount; + state = 2; + continue; + + case 2: // checking sign + signNegative = false; + runningValue = 0; + state = 3; + if (hot != '-') + continue; + signNegative = true; + break; + case 3: // building a parameter + if (hot >= '0' && hot <= '9') + { + runningValue = 10 * runningValue + (hot - '0'); + break; + } + if (hot >= 'A' && hot <= 'Z') + { + // Since JMRI got modified to send keywords in some rare cases, we need this + // Super Kluge to turn keywords into a hash value that can be recognised later + runningValue = ((runningValue << 5) + runningValue) ^ hot; + break; + } + result[parameterCount] = runningValue * (signNegative ? -1 : 1); + parameterCount++; + state = 1; + continue; + } + remainingCmd++; } - } - } - - int DCCEXParser::splitValues( int result[MAX_PARAMS], const byte * cmd) { - byte state=1; - byte parameterCount=0; - int runningValue=0; - const byte * remainingCmd=cmd+1; // skips the opcode - bool signNegative=false; - - // clear all parameters in case not enough found - for (int i=0;i') return parameterCount; - state=2; - continue; - - case 2: // checking sign - signNegative=false; - runningValue=0; - state=3; - if (hot!='-') continue; - signNegative=true; - break; - case 3: // building a parameter - if (hot>='0' && hot<='9') { - runningValue=10*runningValue+(hot-'0'); - break; - } - if (hot>='A' && hot<='Z') { - // Since JMRI got modified to send keywords in some rare cases, we need this - // Super Kluge to turn keywords into a hash value that can be recognised later - runningValue = ((runningValue << 5) + runningValue) ^ hot; - break; - } - result[parameterCount] = runningValue * (signNegative ?-1:1); - parameterCount++; - state=1; - continue; - } - remainingCmd++; - } - return parameterCount; + return parameterCount; } -FILTER_CALLBACK DCCEXParser::filterCallback=0; -void DCCEXParser::setFilter(FILTER_CALLBACK filter) { - filterCallback=filter; +FILTER_CALLBACK DCCEXParser::filterCallback = 0; +AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0; +void DCCEXParser::setFilter(FILTER_CALLBACK filter) +{ + filterCallback = filter; } - +void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback) +{ + atCommandCallback = callback; +} + // See documentation on DCC class for info on this section -void DCCEXParser::parse(Print * stream, byte *com, bool blocking) { - if (Diag::CMD) DIAG(F("\nPARSING:%s\n"),com); - (void) EEPROM; // tell compiler not to warn thi is unused - int p[MAX_PARAMS]; - while (com[0]=='<' || com[0]==' ') com++; // strip off any number of < or spaces - byte params=splitValues(p, com); - byte opcode=com[0]; - - if (filterCallback) filterCallback(stream,opcode,params,p); - +void DCCEXParser::parse(Print *stream, byte *com, bool blocking) +{ + if (Diag::CMD) + DIAG(F("\nPARSING:%s\n"), com); + (void)EEPROM; // tell compiler not to warn thi is unused + int p[MAX_PARAMS]; + while (com[0] == '<' || com[0] == ' ') + com++; // strip off any number of < or spaces + byte params = splitValues(p, com); + byte opcode = com[0]; + + if (filterCallback) + filterCallback(stream, opcode, params, p); + // Functions return from this switch if complete, break from switch implies error to send - switch(opcode) { - case '\0': return; // filterCallback asked us to ignore - case 't': // THROTTLE + switch (opcode) + { + case '\0': + return; // filterCallback asked us to ignore + case 't': // THROTTLE + { + int cab; + int tspeed; + int direction; + + if (params == 4) + { // + cab = p[1]; + tspeed = p[2]; + direction = p[3]; + } + else if (params == 3) + { // + cab = p[0]; + tspeed = p[1]; + direction = p[2]; + } + else + break; + + // Convert JMRI bizarre -1=emergency stop, 0-126 as speeds + // to DCC 0=stop, 1= emergency stop, 2-127 speeds + if (tspeed > 126 || tspeed < -1) + break; // invalid JMRI speed code + if (tspeed < 0) + tspeed = 1; // emergency stop DCC speed + else if (tspeed > 0) + tspeed++; // map 1-126 -> 2-127 + if (cab == 0 && tspeed > 1) + break; // ignore broadcasts of speed>1 + + if (direction < 0 || direction > 1) + break; // invalid direction code + + DCC::setThrottle(cab, tspeed, direction); + if (params == 4) + StringFormatter::send(stream, F(""), p[0], p[2], p[3]); + else + StringFormatter::send(stream, F("")); + return; + } + case 'f': // FUNCTION + if (parsef(stream, params, p)) + return; + break; + + case 'a': // ACCESSORY + if (p[2] != (p[2] & 1)) + return; + DCC::setAccessory(p[0], p[1], p[2] == 1); + return; + + case 'T': // TURNOUT + if (parseT(stream, params, p)) + return; + break; + + case 'Z': // OUTPUT + if (parseZ(stream, params, p)) + return; + break; + + case 'S': // SENSOR + if (parseS(stream, params, p)) + return; + break; + + case 'w': // WRITE CV on MAIN + DCC::writeCVByteMain(p[0], p[1], p[2]); + return; + + case 'b': // WRITE CV BIT ON MAIN + DCC::writeCVBitMain(p[0], p[1], p[2], p[3]); + return; + + case 'W': // WRITE CV ON PROG + if (!stashCallback(stream, p)) + break; + DCC::writeCVByte(p[0], p[1], callback_W, blocking); + return; + + case 'V': // VERIFY CV ON PROG + if (params == 2) + { // + if (!stashCallback(stream, p)) + break; + DCC::verifyCVByte(p[0], p[1], callback_Vbyte, blocking); + return; + } + if (params == 3) { - int cab; - int tspeed; - int direction; - - if (params==4) { // - cab=p[1]; - tspeed=p[2]; - direction=p[3]; - } - else if (params==3) { // - cab=p[0]; - tspeed=p[1]; - direction=p[2]; - } - else break; - - // Convert JMRI bizarre -1=emergency stop, 0-126 as speeds - // to DCC 0=stop, 1= emergency stop, 2-127 speeds - if (tspeed>126 || tspeed<-1) break; // invalid JMRI speed code - if (tspeed<0) tspeed=1; // emergency stop DCC speed - else if (tspeed>0) tspeed++; // map 1-126 -> 2-127 - if (cab == 0 && tspeed>1) break; // ignore broadcasts of speed>1 - - if (direction<0 || direction>1) break; // invalid direction code - - DCC::setThrottle(cab,tspeed,direction); - if (params==4) StringFormatter::send(stream,F(""), p[0], p[2],p[3]); - else StringFormatter::send(stream,F("")); - return; - } - case 'f': // FUNCTION - if (parsef(stream,params,p)) return; - break; - - case 'a': // ACCESSORY - if(p[2] != (p[2] & 1)) return; - DCC::setAccessory(p[0],p[1],p[2]==1); - return; - - case 'T': // TURNOUT - if (parseT(stream,params,p)) return; - break; - - case 'Z': // OUTPUT - if (parseZ(stream,params,p)) return; - break; - - case 'S': // SENSOR - if (parseS(stream,params,p)) return; - break; - - case 'w': // WRITE CV on MAIN - DCC::writeCVByteMain(p[0],p[1],p[2]); - return; - - case 'b': // WRITE CV BIT ON MAIN - DCC::writeCVBitMain(p[0],p[1],p[2],p[3]); - return; - - case 'W': // WRITE CV ON PROG - if (!stashCallback(stream,p)) break; - DCC::writeCVByte(p[0],p[1],callback_W,blocking); - return; - - case 'V': // VERIFY CV ON PROG - if (params==2) { // - if (!stashCallback(stream,p)) break; - DCC::verifyCVByte(p[0],p[1],callback_Vbyte,blocking); - return; - } - if (params==3) { - if (!stashCallback(stream,p)) break; - DCC::verifyCVBit(p[0],p[1],p[2],callback_Vbit,blocking); - return; + if (!stashCallback(stream, p)) + break; + DCC::verifyCVBit(p[0], p[1], p[2], callback_Vbit, blocking); + return; } break; - - case 'B': // WRITE CV BIT ON PROG - if (!stashCallback(stream,p)) break; - DCC::writeCVBit(p[0],p[1],p[2],callback_B,blocking); + + case 'B': // WRITE CV BIT ON PROG + if (!stashCallback(stream, p)) + break; + DCC::writeCVBit(p[0], p[1], p[2], callback_B, blocking); return; - - - case 'R': // READ CV ON PROG - if (params==3) { // - if (!stashCallback(stream,p)) break; - DCC::readCV(p[0],callback_R,blocking); - return; + + case 'R': // READ CV ON PROG + if (params == 3) + { // + if (!stashCallback(stream, p)) + break; + DCC::readCV(p[0], callback_R, blocking); + return; } - if (params==0) { // New read loco id - if (!stashCallback(stream,p)) break; - DCC::getLocoId(callback_Rloco,blocking); - return; + if (params == 0) + { // New read loco id + if (!stashCallback(stream, p)) + break; + DCC::getLocoId(callback_Rloco, blocking); + return; } break; - - case '1': // POWERON <1 [MAIN|PROG]> - case '0': // POWEROFF <0 [MAIN | PROG] > - if (params>1) break; + + case '1': // POWERON <1 [MAIN|PROG]> + case '0': // POWEROFF <0 [MAIN | PROG] > + if (params > 1) + break; { - POWERMODE mode= opcode=='1'?POWERMODE::ON:POWERMODE::OFF; - DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off - if (params==0) { - DCCWaveform::mainTrack.setPowerMode(mode); - DCCWaveform::progTrack.setPowerMode(mode); - StringFormatter::send(stream,F(""),opcode); - return; - } - switch (p[0]) { - case HASH_KEYWORD_MAIN: - DCCWaveform::mainTrack.setPowerMode(mode); - StringFormatter::send(stream,F(""),opcode); - return; - - case HASH_KEYWORD_PROG: - DCCWaveform::progTrack.setPowerMode(mode); - StringFormatter::send(stream,F(""),opcode); - return; - case HASH_KEYWORD_JOIN: - DCCWaveform::mainTrack.setPowerMode(mode); - DCCWaveform::progTrack.setPowerMode(mode); - if (mode==POWERMODE::ON) { - DCC::setProgTrackSyncMain(true); - StringFormatter::send(stream,F(""),opcode); - } - else StringFormatter::send(stream,F("")); - return; - - } - break; - } - return; - - case 'c': // READ CURRENT - StringFormatter::send(stream,F(""), DCCWaveform::mainTrack.getLastCurrent()); - return; + POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF; + DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off + if (params == 0) + { + DCCWaveform::mainTrack.setPowerMode(mode); + DCCWaveform::progTrack.setPowerMode(mode); + if (mode == POWERMODE::OFF) + DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off + StringFormatter::send(stream, F(""), opcode); + return; + } + switch (p[0]) + { + case HASH_KEYWORD_MAIN: + DCCWaveform::mainTrack.setPowerMode(mode); + StringFormatter::send(stream, F(""), opcode); + return; - case 'Q': // SENSORS - Sensor::checkAll(); - for(Sensor * tt=Sensor::firstSensor;tt!=NULL;tt=tt->nextSensor){ - StringFormatter::send(stream,F("<%c %d>"), tt->active?'Q':'q', tt->data.snum); + case HASH_KEYWORD_PROG: + DCCWaveform::progTrack.setPowerMode(mode); + if (mode == POWERMODE::OFF) + DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off + StringFormatter::send(stream, F(""), opcode); + return; + case HASH_KEYWORD_JOIN: + DCCWaveform::mainTrack.setPowerMode(mode); + DCCWaveform::progTrack.setPowerMode(mode); + if (mode == POWERMODE::ON) + { + DCC::setProgTrackSyncMain(true); + StringFormatter::send(stream, F(""), opcode); + } + else + StringFormatter::send(stream, F("")); + return; + } + break; } return; - case 's': // - StringFormatter::send(stream,F(""),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON ); - StringFormatter::send(stream,F(""), VERSION, F(GITHUB_SHA)); - // TODO Send stats of speed reminders table - // TODO send status of turnouts etc etc + case 'c': // READ CURRENT + StringFormatter::send(stream, F(""), DCCWaveform::mainTrack.getLastCurrent()); return; - case 'E': // STORE EPROM + case 'Q': // SENSORS + Sensor::checkAll(); + for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor) + { + StringFormatter::send(stream, F("<%c %d>"), tt->active ? 'Q' : 'q', tt->data.snum); + } + return; + + case 's': // + StringFormatter::send(stream, F(""), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON); + StringFormatter::send(stream, F(""), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); + // TODO Send stats of speed reminders table + // TODO send status of turnouts etc etc + return; + + case 'E': // STORE EPROM EEStore::store(); - StringFormatter::send(stream,F(""), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs); + StringFormatter::send(stream, F(""), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs); return; - case 'e': // CLEAR EPROM + case 'e': // CLEAR EPROM EEStore::clear(); StringFormatter::send(stream, F("")); return; - case ' ': // < > - StringFormatter::send(stream,F("\n")); - return; - - case 'D': // < > - if (parseD(stream,params,p)) return; + case ' ': // < > + StringFormatter::send(stream, F("\n")); return; - case '#': // NUMBER OF LOCOSLOTS <#> - StringFormatter::send(stream,F("<# %d>"), MAX_LOCOS); - return; + case 'D': // < > + if (parseD(stream, params, p)) + return; + return; + + case '#': // NUMBER OF LOCOSLOTS <#> + StringFormatter::send(stream, F("<# %d>"), MAX_LOCOS); + return; case 'F': // New command to call the new Loco Function API - if (Diag::CMD) DIAG(F("Setting loco %d F%d %S"),p[0],p[1],p[2]?F("ON"):F("OFF")); - DCC::setFn(p[0],p[1],p[2]==1); - return; - - case '+' : // Complex Wifi interface command (not usual parse) - WifiInterface::ATCommand(com); - break; - - default: //anything else will diagnose and drop out to - DIAG(F("\nOpcode=%c params=%d\n"),opcode,params); - for (int i=0;i + DIAG(F("\nOpcode=%c params=%d\n"), opcode, params); + for (int i = 0; i < params; i++) + DIAG(F("p[%d]=%d (0x%x)\n"), i, p[i], p[i]); + break; + + } // end of opcode switch // Any fallout here sends an - StringFormatter::send(stream, F("")); + StringFormatter::send(stream, F("")); } -bool DCCEXParser::parseZ( Print * stream,int params, int p[]){ - - - switch (params) { - - case 2: // - { - Output * o=Output::get(p[0]); - if(o==NULL) return false; - o->activate(p[1]); - StringFormatter::send(stream,F(""), p[0],p[1]); - } - return true; +bool DCCEXParser::parseZ(Print *stream, int params, int p[]) +{ - case 3: // - Output::create(p[0],p[1],p[2],1); - return true; + switch (params) + { - case 1: // - return Output::remove(p[0]); - - case 0: // - { - bool gotone=false; - for(Output * tt=Output::firstOutput;tt!=NULL;tt=tt->nextOutput){ - gotone=true; - StringFormatter::send(stream,F(""), tt->data.id, tt->data.pin, tt->data.iFlag, tt->data.oStatus); - } - return gotone; - } - default: - return false; - } - } - -//=================================== -bool DCCEXParser::parsef(Print * stream, int params, int p[]) { - // JMRI sends this info in DCC message format but it's not exactly - // convenient for other processing - if (params==2) { - byte groupcode=p[1] & 0xE0; - if (groupcode == 0x80) { - byte normalized= (p[1]<<1 & 0x1e ) | (p[1]>>4 & 0x01); - funcmap(p[0],normalized,0,4); - } - else if (groupcode == 0xC0) { - funcmap(p[0],p[1],5,8); - } - else if (groupcode == 0xA0) { - funcmap(p[0],p[1],9,12); - } - } - if (params==3) { - if (p[1]==222) funcmap(p[0],p[2],13,20); - else if (p[1]==223) funcmap(p[0],p[2],21,28); + case 2: // + { + Output *o = Output::get(p[0]); + if (o == NULL) + return false; + o->activate(p[1]); + StringFormatter::send(stream, F(""), p[0], p[1]); } - (void)stream;// NO RESPONSE - return true; -} + return true; -void DCCEXParser::funcmap(int cab, byte value, byte fstart, byte fstop) { - for (int i=fstart;i<=fstop;i++) { - DCC::setFn(cab, i, value & 1); - value>>=1; - } + case 3: // + Output::create(p[0], p[1], p[2], 1); + return true; + + case 1: // + return Output::remove(p[0]); + + case 0: // + { + bool gotone = false; + for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput) + { + gotone = true; + StringFormatter::send(stream, F(""), tt->data.id, tt->data.pin, tt->data.iFlag, tt->data.oStatus); + } + return gotone; + } + default: + return false; + } } //=================================== -bool DCCEXParser::parseT(Print * stream, int params, int p[]) { - switch(params){ - case 0: // show all turnouts - { - bool gotOne=false; - for(Turnout *tt=Turnout::firstTurnout;tt!=NULL;tt=tt->nextTurnout){ - gotOne=true; - StringFormatter::send(stream,F(""), tt->data.id, tt->data.tStatus & STATUS_ACTIVE); - } - return gotOne; // will if none found - } - - case 1: // delete turnout - if (!Turnout::remove(p[0])) return false; - StringFormatter::send(stream,F("")); - return true; - - case 2: // activate turnout - { - Turnout* tt=Turnout::get(p[0]); - if (!tt) return false; - tt->activate(p[1]); - StringFormatter::send(stream,F(""), tt->data.id, tt->data.tStatus & STATUS_ACTIVE); - } - return true; - - case 3: // define turnout - if (!Turnout::create(p[0],p[1],p[2])) return false; - StringFormatter::send(stream,F("")); - return true; - - default: - return false; // will +bool DCCEXParser::parsef(Print *stream, int params, int p[]) +{ + // JMRI sends this info in DCC message format but it's not exactly + // convenient for other processing + if (params == 2) + { + byte groupcode = p[1] & 0xE0; + if (groupcode == 0x80) + { + byte normalized = (p[1] << 1 & 0x1e) | (p[1] >> 4 & 0x01); + funcmap(p[0], normalized, 0, 4); } + else if (groupcode == 0xC0) + { + funcmap(p[0], p[1], 5, 8); + } + else if (groupcode == 0xA0) + { + funcmap(p[0], p[1], 9, 12); + } + } + if (params == 3) + { + if (p[1] == 222) + funcmap(p[0], p[2], 13, 20); + else if (p[1] == 223) + funcmap(p[0], p[2], 21, 28); + } + (void)stream; // NO RESPONSE + return true; } -bool DCCEXParser::parseS( Print * stream,int params, int p[]) { - - switch(params){ - case 3: // create sensor. pullUp indicator (0=LOW/1=HIGH) - Sensor::create(p[0],p[1],p[2]); - return true; +void DCCEXParser::funcmap(int cab, byte value, byte fstart, byte fstop) +{ + for (int i = fstart; i <= fstop; i++) + { + DCC::setFn(cab, i, value & 1); + value >>= 1; + } +} - case 1: // S id> remove sensor - if (Sensor::remove(p[0])) return true; - break; - - case 0: // lit sensor states - for(Sensor * tt=Sensor::firstSensor;tt!=NULL;tt=tt->nextSensor){ - StringFormatter::send(stream, F(""), tt->data.snum, tt->data.pin, tt->data.pullUp); - } - return true; - - default: // invalid number of arguments - break; +//=================================== +bool DCCEXParser::parseT(Print *stream, int params, int p[]) +{ + switch (params) + { + case 0: // show all turnouts + { + bool gotOne = false; + for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout) + { + gotOne = true; + StringFormatter::send(stream, F(""), tt->data.id, tt->data.tStatus & STATUS_ACTIVE); } + return gotOne; // will if none found + } + + case 1: // delete turnout + if (!Turnout::remove(p[0])) + return false; + StringFormatter::send(stream, F("")); + return true; + + case 2: // activate turnout + { + Turnout *tt = Turnout::get(p[0]); + if (!tt) + return false; + tt->activate(p[1]); + StringFormatter::send(stream, F(""), tt->data.id, tt->data.tStatus & STATUS_ACTIVE); + } + return true; + + case 3: // define turnout + if (!Turnout::create(p[0], p[1], p[2])) + return false; + StringFormatter::send(stream, F("")); + return true; + + default: + return false; // will + } +} + +bool DCCEXParser::parseS(Print *stream, int params, int p[]) +{ + + switch (params) + { + case 3: // create sensor. pullUp indicator (0=LOW/1=HIGH) + Sensor::create(p[0], p[1], p[2]); + return true; + + case 1: // S id> remove sensor + if (Sensor::remove(p[0])) + return true; + break; + + case 0: // lit sensor states + for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor) + { + StringFormatter::send(stream, F(""), tt->data.snum, tt->data.pin, tt->data.pullUp); + } + return true; + + default: // invalid number of arguments + break; + } return false; } -bool DCCEXParser::parseD( Print * stream,int params, int p[]) { - if (params==0) return false; - bool onOff=(params>0) && (p[1]==1 || p[1]==HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off - switch(p[0]){ - case HASH_KEYWORD_CABS: // - DCC::displayCabList(stream); - return true; +bool DCCEXParser::parseD(Print *stream, int params, int p[]) +{ + if (params == 0) + return false; + bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off + switch (p[0]) + { + case HASH_KEYWORD_CABS: // + DCC::displayCabList(stream); + return true; - case HASH_KEYWORD_RAM: // - StringFormatter::send(stream,F("\nFree memory=%d\n"),freeMemory()); - break; + case HASH_KEYWORD_RAM: // + StringFormatter::send(stream, F("\nFree memory=%d\n"), freeMemory()); + break; - case HASH_KEYWORD_ACK: // - Diag::ACK=onOff; - return true; + case HASH_KEYWORD_ACK: // + Diag::ACK = onOff; + return true; - case HASH_KEYWORD_CMD: // - Diag::CMD=onOff; - return true; + case HASH_KEYWORD_CMD: // + Diag::CMD = onOff; + return true; - case HASH_KEYWORD_WIFI: // - Diag::WIFI=onOff; - return true; + case HASH_KEYWORD_WIFI: // + Diag::WIFI = onOff; + return true; - case HASH_KEYWORD_WIT: // - Diag::WITHROTTLE=onOff; - return true; + case HASH_KEYWORD_WIT: // + Diag::WITHROTTLE = onOff; + return true; - case HASH_KEYWORD_DCC: - DCCWaveform::setDiagnosticSlowWave(params>=1 && p[1]==HASH_KEYWORD_SLOW); - return true; - default: // invalid/unknown - break; - } + case HASH_KEYWORD_DCC: + DCCWaveform::setDiagnosticSlowWave(params >= 1 && p[1] == HASH_KEYWORD_SLOW); + return true; + + case HASH_KEYWORD_PROGBOOST: + DCC::setProgTrackBoost(true); + return true; + + default: // invalid/unknown + break; + } return false; } - - // CALLBACKS must be static -bool DCCEXParser::stashCallback(Print * stream,int p[MAX_PARAMS]) { - if (stashBusy || asyncBanned) return false; - stashBusy=true; - stashStream=stream; - memcpy(stashP,p,MAX_PARAMS*sizeof(p[0])); - return true; - } - void DCCEXParser::callback_W(int result) { - StringFormatter::send(stashStream,F(""), stashP[2], stashP[3],stashP[0],result==1?stashP[1]:-1); - stashBusy=false; - } - -void DCCEXParser::callback_B(int result) { - StringFormatter::send(stashStream,F(""), stashP[3],stashP[4], stashP[0],stashP[1],result==1?stashP[2]:-1); - stashBusy=false; +// CALLBACKS must be static +bool DCCEXParser::stashCallback(Print *stream, int p[MAX_PARAMS]) +{ + if (stashBusy || asyncBanned) + return false; + stashBusy = true; + stashStream = stream; + memcpy(stashP, p, MAX_PARAMS * sizeof(p[0])); + return true; } -void DCCEXParser::callback_Vbit(int result) { - StringFormatter::send(stashStream,F(""), stashP[0], stashP[1],result); - stashBusy=false; -} -void DCCEXParser::callback_Vbyte(int result) { - StringFormatter::send(stashStream,F(""), stashP[0],result); - stashBusy=false; +void DCCEXParser::callback_W(int result) +{ + StringFormatter::send(stashStream, F(""), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1); + stashBusy = false; } -void DCCEXParser::callback_R(int result) { - StringFormatter::send(stashStream,F(""),stashP[1],stashP[2],stashP[0],result); - stashBusy=false; +void DCCEXParser::callback_B(int result) +{ + StringFormatter::send(stashStream, F(""), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1); + stashBusy = false; +} +void DCCEXParser::callback_Vbit(int result) +{ + StringFormatter::send(stashStream, F(""), stashP[0], stashP[1], result); + stashBusy = false; +} +void DCCEXParser::callback_Vbyte(int result) +{ + StringFormatter::send(stashStream, F(""), stashP[0], result); + stashBusy = false; } -void DCCEXParser::callback_Rloco(int result) { - StringFormatter::send(stashStream,F(""),result); - stashBusy=false; +void DCCEXParser::callback_R(int result) +{ + StringFormatter::send(stashStream, F(""), stashP[1], stashP[2], stashP[0], result); + stashBusy = false; +} + +void DCCEXParser::callback_Rloco(int result) +{ + StringFormatter::send(stashStream, F(""), result); + stashBusy = false; } - diff --git a/DCCEXParser.h b/DCCEXParser.h index e154ff9..5721f6c 100644 --- a/DCCEXParser.h +++ b/DCCEXParser.h @@ -21,6 +21,7 @@ #include typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int p[]); +typedef void (*AT_COMMAND_CALLBACK)(const byte * command); struct DCCEXParser { @@ -29,6 +30,7 @@ struct DCCEXParser void parse(Print * stream, byte * command, bool blocking); void flush(); static void setFilter(FILTER_CALLBACK filter); + static void setAtCommandCallback(AT_COMMAND_CALLBACK filter); static const int MAX_PARAMS=10; // Must not exceed this private: @@ -59,6 +61,7 @@ struct DCCEXParser static void callback_Vbit(int result); static void callback_Vbyte(int result); static FILTER_CALLBACK filterCallback; + static AT_COMMAND_CALLBACK atCommandCallback; static void funcmap(int cab, byte value, byte fstart, byte fstop); }; diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index f66da28..a70ab16 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -30,6 +30,7 @@ DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); bool DCCWaveform::progTrackSyncMain=false; +bool DCCWaveform::progTrackBoosted=false; VirtualTimer * DCCWaveform::interruptTimer=NULL; void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber) { @@ -123,10 +124,13 @@ void DCCWaveform::setPowerMode(POWERMODE mode) { void DCCWaveform::checkPowerOverload() { + static int progTripValue = motorDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once, hence static + if (millis() - lastSampleTaken < sampleDelay) return; lastSampleTaken = millis(); int tripValue= motorDriver->rawCurrentTripValue; - if (!isMainTrack && (ackPending || progTrackSyncMain)) tripValue=ACK_CURRENT_TRIP; + if (!isMainTrack && !ackPending && !progTrackSyncMain && !progTrackBoosted) + tripValue=progTripValue; switch (powerMode) { case POWERMODE::OFF: @@ -143,12 +147,15 @@ void DCCWaveform::checkPowerOverload() { if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT; } else { setPowerMode(POWERMODE::OVERLOAD); - unsigned int mA=motorDriver->convertToMilliamps(lastCurrent); - unsigned int maxmA=motorDriver->convertToMilliamps(tripValue); + unsigned int mA=motorDriver->raw2mA(lastCurrent); + unsigned int maxmA=motorDriver->raw2mA(tripValue); DIAG(F("\n*** %S TRACK POWER OVERLOAD current=%d max=%d offtime=%l ***\n"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, power_sample_overload_wait); power_good_counter=0; sampleDelay = power_sample_overload_wait; - power_sample_overload_wait *= 2; + if (power_sample_overload_wait >= 10000) + power_sample_overload_wait = 10000; + else + power_sample_overload_wait *= 2; } break; case POWERMODE::OVERLOAD: @@ -287,7 +294,7 @@ int DCCWaveform::getLastCurrent() { void DCCWaveform::setAckBaseline() { if (isMainTrack) return; ackThreshold=motorDriver->getCurrentRaw() + (int)(65 / motorDriver->senseFactor); - if (Diag::ACK) DIAG(F("\nACK-BASELINE %d/%dmA"),ackThreshold,motorDriver->convertToMilliamps(ackThreshold)); + if (Diag::ACK) DIAG(F("\nACK-BASELINE %d/%dmA"),ackThreshold,motorDriver->raw2mA(ackThreshold)); } void DCCWaveform::setAckPending() { @@ -303,7 +310,7 @@ void DCCWaveform::setAckPending() { byte DCCWaveform::getAck() { if (ackPending) return (2); // still waiting if (Diag::ACK) DIAG(F("\nACK-%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("OK"):F("FAIL"), ackCheckDuration, - ackMaxCurrent,motorDriver->convertToMilliamps(ackMaxCurrent), ackPulseDuration); + ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration); if (ackDetected) return (1); // Yes we had an ack return(0); // pending set off but not detected means no ACK. } diff --git a/DCCWaveform.h b/DCCWaveform.h index 96e5887..d607420 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -22,15 +22,17 @@ #include "MotorDriver.h" #include "ArduinoTimers.h" +// Wait times for power management. Unit: milliseconds const int POWER_SAMPLE_ON_WAIT = 100; const int POWER_SAMPLE_OFF_WAIT = 1000; const int POWER_SAMPLE_OVERLOAD_WAIT = 20; +// Ack time thresholds. Unit: microseconds const int MIN_ACK_PULSE_DURATION = 2000; const int MAX_ACK_PULSE_DURATION = 8500; - -const int PREAMBLE_BITS_MAIN = 20; +// Number of preamble bits. +const int PREAMBLE_BITS_MAIN = 16; const int PREAMBLE_BITS_PROG = 22; @@ -67,6 +69,7 @@ class DCCWaveform { void setAckPending(); //prog track only byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting static bool progTrackSyncMain; // true when prog track is a siding switched to main + static bool progTrackBoosted; // true when prog track is not current limited inline void doAutoPowerOff() { if (autoPowerOff) { setPowerMode(POWERMODE::OFF); @@ -105,7 +108,9 @@ class DCCWaveform { POWERMODE powerMode; unsigned long lastSampleTaken; unsigned int sampleDelay; - static const int ACK_CURRENT_TRIP=1000; // During ACK processing limit can be higher + // Trip current for programming track, 250mA. Change only if you really + // need to be non-NMRA-compliant because of decoders that are not either. + static const int TRIP_CURRENT_PROG=250; unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; unsigned int power_good_counter = 0; diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp new file mode 100644 index 0000000..3498871 --- /dev/null +++ b/EthernetInterface.cpp @@ -0,0 +1,304 @@ +/* + * © 2020,Gregor Baues, Chris Harlow. All rights reserved. + * + * This file is part of DCC-EX/CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + * + * Ethernet Interface added by Gregor Baues + */ + +#include "EthernetInterface.h" +#include "DIAG.h" +#include "StringFormatter.h" + +//#include +#include +#include + + +// Support Functions +/** + * @brief Aquire IP Address from DHCP; if that fails try a statically configured address + * + * @return true + * @return false + */ +bool EthernetInterface::setupConnection() +{ + + singleton=this; + + DIAG(F("\nInitialize Ethernet with DHCP:")); + server = EthernetServer(LISTEN_PORT); // Ethernet Server listening on default port LISTEN_PORT + ip = IPAddress(IP_ADDRESS); // init with fixed IP address needed to get to the server + connected = false; // Connection status + streamer= new MemStream(buffer, MAX_ETH_BUFFER, MAX_ETH_BUFFER, true); // streamer who writes the results to the buffer + + if (Ethernet.begin(EthernetInterface::mac) == 0) + { + DIAG(F("\nFailed to configure Ethernet using DHCP ... Trying with fixed IP")); + Ethernet.begin(EthernetInterface::mac, EthernetInterface::ip); // default ip address + + if (Ethernet.hardwareStatus() == EthernetNoHardware) + { + DIAG(F("\nEthernet shield was not found. Sorry, can't run without hardware. :(")); + return false; + }; + if (Ethernet.linkStatus() == LinkOFF) + { + DIAG(F("\nEthernet cable is not connected.")); + return false; + } + } + + ip = Ethernet.localIP(); // reassign the obtained ip address + + DIAG(F("\nLocal IP address: [%d.%d.%d.%d]"), ip[0], ip[1], ip[2], ip[3]); + DIAG(F("\nListening on port: [%d]"), port); + dnsip = Ethernet.dnsServerIP(); + DIAG(F("\nDNS server IP address: [%d.%d.%d.%d] "), ip[0], ip[1], ip[2], ip[3]); + return true; +} + +/** + * @brief Handles command requests recieved via UDP. UDP is a connection less, unreliable protocol as it doesn't maintain state but fast. + * + */ + void EthernetInterface::udpHandler() { + singleton->udpHandler2(); + } + void EthernetInterface::udpHandler2() +{ + + int packetSize = Udp.parsePacket(); + if (packetSize) + { + DIAG(F("\nReceived packet of size:[%d]\n"), packetSize); + IPAddress remote = Udp.remoteIP(); + DIAG(F("From: [%d.%d.%d.%d:"), remote[0], remote[1], remote[2], remote[3]); + char portBuffer[6]; + DIAG(F("%s]\n"), utoa(Udp.remotePort(), portBuffer, 10)); // DIAG has issues with unsigend int's so go through utoa + + // read the packet into packetBufffer + Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE); + + DIAG(F("Command: [%s]\n"), packetBuffer); + + streamer->flush(); + + Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); + + ethParser.parse(streamer, (byte *)packetBuffer, true); // set to true so it is sync cf. WifiInterface + + if (streamer->available() == 0) + { + DIAG(F("\nNo response\n")); + } + else + { + // send the reply + DIAG(F("Response: %s\n"), (char *)buffer); + Udp.write((char *)buffer); + Udp.endPacket(); + } + + memset(packetBuffer, 0, UDP_TX_PACKET_MAX_SIZE); // reset PacktBuffer + return; + } +} + +/** + * @brief Handles command requests recieved via TCP. Supports up to the max# of simultaneous requests which is 8. The connection gets closed as soon as we finished processing + * + */ + void EthernetInterface::tcpHandler() +{ + singleton->tcpHandler2(); +} + void EthernetInterface::tcpHandler2() +{ + // get client from the server + EthernetClient client = getServer().accept(); + + // check for new client + if (client) + { + for (byte i = 0; i < MAX_SOCK_NUM; i++) + { + if (!clients[i]) + { + // On accept() the EthernetServer doesn't track the client anymore + // so we store it in our client array + clients[i] = client; + break; + } + } + } + + // check for incoming data from all possible clients + for (byte i = 0; i < MAX_SOCK_NUM; i++) + { + if (clients[i] && clients[i].available() > 0) + { + // read bytes from a client + int count = clients[i].read(buffer, MAX_ETH_BUFFER); + buffer[count] = '\0'; // terminate the string properly + DIAG(F("\nReceived packet of size:[%d]\n"), count); + DIAG(F("From Client #: [%d]\n"), i); + DIAG(F("Command: [%s]\n"), buffer); + + // as we use buffer for recv and send we have to reset the write position + streamer->setBufferContentPosition(0, 0); + + ethParser.parse(streamer, buffer, true); // set to true to that the execution in DCC is sync + + if (streamer->available() == 0) + { + DIAG(F("No response\n")); + } + else + { + buffer[streamer->available()] = '\0'; // mark end of buffer, so it can be used as a string later + DIAG(F("Response: %s\n"), (char *)buffer); + if (clients[i].connected()) + { + clients[i].write(buffer, streamer->available()); + } + } + } + // stop any clients which disconnect + for (byte i = 0; i < MAX_SOCK_NUM; i++) + { + if (clients[i] && !clients[i].connected()) + { + DIAG(F("Disconnect client #%d \n"), i); + clients[i].stop(); + } + } + } +} + +// Class Functions +/** + * @brief Setup Ethernet Connection + * + * @param pt Protocol used + * @param localPort Port number for the connection + */ +void EthernetInterface::setup(protocolType pt, uint16_t localPort) +{ + DIAG(F("\n++++++ Ethernet Setup In Progress ++++++++\n")); + port = localPort; + if (setupConnection()) + { + DIAG(F("\nProtocol: [%s]\n"), pt ? "UDP" : "TCP"); + switch (pt) + { + case UDP: + { + if (Udp.begin(localPort)) + { + connected = true; + protocolHandler = udpHandler; + } + else + { + DIAG(F("\nUDP client failed to start")); + connected = false; + } + break; + }; + case TCP: + { + Ethernet.begin(mac, ip); + EthernetServer server(localPort); + setServer(server); + server.begin(); + connected = true; + protocolHandler = tcpHandler; + break; + }; + default: + { + DIAG(F("Unkown Ethernet protocol; Setup failed")); + connected = false; + return; + } + } + } + else + { + connected = false; + }; + DIAG(F("\n++++++ Ethernet Setup %S ++++++++\n"), connected ? F("OK") : F("FAILED")); +}; + +/** + * @brief Setup Ethernet on default port and user choosen protocol + * + * @param pt Protocol UDP or TCP + */ +void EthernetInterface::setup(protocolType pt) +{ + setup(pt, LISTEN_PORT); +}; + +/** + * @brief Ethernet setup with defaults TCP / Listen Port + * + */ +void EthernetInterface::setup() +{ + setup(TCP, LISTEN_PORT); +} + +/** + * @brief Main loop for the EthernetInterface + * + */ +void EthernetInterface::loop() +{ + switch (Ethernet.maintain()) + { + case 1: + //renewed fail + DIAG(F("\nError: renewed fail")); + break; + + case 2: + //renewed success + DIAG(F("\nRenewed success: ")); + ip = Ethernet.localIP(); // reassign the obtained ip address + DIAG(F("\nLocal IP address: [%d.%d.%d.%d]"),ip[0], ip[1], ip[2], ip[3]); + break; + + case 3: + //rebind fail + DIAG(F("Error: rebind fail")); + break; + + case 4: + //rebind success + DIAG(F("Rebind success")); + ip = Ethernet.localIP(); // reassign the obtained ip address + DIAG(F("\nLocal IP address: [%d.%d.%d.%d]"), ip[0], ip[1], ip[2], ip[3]); + break; + + default: + //nothing happened + break; + } + protocolHandler(); +} diff --git a/EthernetInterface.h b/EthernetInterface.h new file mode 100644 index 0000000..b702f04 --- /dev/null +++ b/EthernetInterface.h @@ -0,0 +1,107 @@ +/* + * © 2020,Gregor Baues, Chris Harlow. All rights reserved. + * + * This file is part of DCC-EX/CommandStation-EX + * + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + * + * Ethernet Interface added by Gregor Baues + */ + +#ifndef EthernetInterface_h +#define EthernetInterface_h + +#include "DCCEXParser.h" +#include "MemStream.h" +#include +#include +#include + +/* some generated mac addresses as EthernetShields don't have one by default in HW. + * Sometimes they come on a sticker on the EthernetShield then use this address otherwise + * just choose one from below or generate one yourself. Only condition is that there is no + * other device on your network with the same Mac address. + * + * 52:b8:8a:8e:ce:21 + * e3:e9:73:e1:db:0d + * 54:2b:13:52:ac:0c + * c2:d8:d4:7d:7c:cb + * 86:cf:fa:9f:07:79 + */ + +/** + * @brief Network Configuration + * + */ +#define MAC_ADDRESS { 0x52, 0xB8, 0x8A, 0x8E, 0xCE, 0x21 } // MAC address of your networking card found on the sticker on your card or take one from above +#define IP_ADDRESS 10, 0, 0, 101 // Just in case we don't get an adress from DHCP try a static one; make sure + // this one is not used elsewhere and corresponds to your network layout +#define LISTEN_PORT 3366 // default listen port for the server +#define MAX_ETH_BUFFER 250 + +typedef void (*HTTP_CALLBACK)(Print * stream, byte * cmd); + +enum protocolType { + TCP, + UDP +}; + +typedef void (*protocolCallback)(); + +class EthernetInterface { + +private: + EthernetServer server; + + public: + DCCEXParser ethParser; + bool connected; + byte mac[6]; + IPAddress ip; + uint16_t port; + IPAddress dnsip; + + void setup(protocolType pt, uint16_t lp); // specific port nummber + void setup(protocolType pt); // uses default port number + void setup(); // all defaults (protocol/port) + + protocolCallback protocolHandler; + + void loop(); + + private: + static EthernetInterface * singleton; + + char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; // buffer to hold incoming UDP packet, + uint8_t buffer[MAX_ETH_BUFFER]; // buffer provided to the streamer to be filled with the reply (used by TCP also for the recv) + MemStream * streamer; // streamer who writes the results to the buffer + EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield + + bool setupConnection(); + static void udpHandler(); + static void tcpHandler(); + void udpHandler2(); + void tcpHandler2(); + EthernetUDP Udp; + + EthernetServer getServer() { + return server; + }; + void setServer(EthernetServer s) { + server = s; + }; +}; + +#endif diff --git a/MemStream.h b/MemStream.h index 444864f..61ef6bb 100644 --- a/MemStream.h +++ b/MemStream.h @@ -24,34 +24,43 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define MemStream_h #include +#if defined(ARDUINO_ARCH_MEGAAVR) +#include +#else #include +#endif + #include class MemStream : public Stream { private: - uint8_t * _buffer; - const uint16_t _len; - bool _buffer_overflow; - uint16_t _pos_read; - uint16_t _pos_write; - bool _allowWrite; - + uint8_t *_buffer; + const uint16_t _len; + bool _buffer_overflow; + uint16_t _pos_read; + uint16_t _pos_write; + bool _allowWrite; public: // public methods - MemStream(uint8_t *buffer, const uint16_t len, uint16_t content_len = 0, bool allowWrite=true); + MemStream(uint8_t *buffer, const uint16_t len, uint16_t content_len = 0, bool allowWrite = true); ~MemStream() {} operator const uint8_t *() const { return _buffer; } - operator const char *() const { return (const char*)_buffer; } + operator const char *() const { return (const char *)_buffer; } uint16_t current_length() const { return _pos_write; } bool listen() { return true; } void end() {} bool isListening() { return true; } - bool overflow() { bool ret = _buffer_overflow; _buffer_overflow = false; return ret; } + bool overflow() + { + bool ret = _buffer_overflow; + _buffer_overflow = false; + return ret; + } int peek(); virtual size_t write(uint8_t byte); diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 75b5d05..5420488 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -76,6 +76,9 @@ int MotorDriver::getCurrentRaw() { return analogReadFast(currentPin); } -unsigned int MotorDriver::convertToMilliamps( int raw) { +unsigned int MotorDriver::raw2mA( int raw) { return (unsigned int)(raw * senseFactor); } +int MotorDriver::mA2raw( unsigned int mA) { + return (int)(mA / senseFactor); +} diff --git a/MotorDriver.h b/MotorDriver.h index 8dbee0a..cce07ad 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -27,7 +27,8 @@ class MotorDriver { virtual void setSignal( bool high); virtual void setBrake( bool on); virtual int getCurrentRaw(); - virtual unsigned int convertToMilliamps( int rawValue); + virtual unsigned int raw2mA( int raw); + virtual int mA2raw( unsigned int mA); byte powerPin, signalPin, signalPin2, brakePin,currentPin,faultPin; float senseFactor; diff --git a/MotorDrivers.h b/MotorDrivers.h index f31aeac..3695e0a 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -1,5 +1,8 @@ #ifndef MotorDrivers_h #define MotorDrivers_h +#if defined(ARDUINO_ARCH_MEGAAVR) +#include +#endif // *** PLEASE NOTE *** THIS FILE IS **NOT** INTENDED TO BE EDITED WHEN CONFIGURING A SYSTEM. // It will be overwritten if the library is updated. @@ -7,37 +10,37 @@ // This file contains configurations for known/supported motor shields. // A configuration defined by macro here can be used in your sketch. // A custom hardware setup will require your sketch to create MotorDriver instances -// similar to those defined here, WITHOUT editing this file. - +// similar to those defined here, WITHOUT editing this file. You can put your +// custom defines in config.h. const byte UNUSED_PIN = 255; // MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, byte brake_pin, byte current_pin, // float senseFactor, unsigned int tripMilliamps, byte faultPin); - + // Arduino standard Motor Shield -#define STANDARD_MOTOR_SHIELD \ - new MotorDriver(3 , 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \ - new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 250 , UNUSED_PIN) +#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ + new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \ + new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) // Pololu Motor Shield -#define POLOLU_MOTOR_SHIELD \ - new MotorDriver(4, 7, UNUSED_PIN, 9 , A0, 18, 2000, 12), \ - new MotorDriver(2, 8, UNUSED_PIN, 10, A1, 18, 250 , UNUSED_PIN) +#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \ + new MotorDriver(4, 7, UNUSED_PIN, 9, A0, 18, 3000, 12), \ + new MotorDriver(2, 8, UNUSED_PIN, 10, A1, 18, 3000, UNUSED_PIN) -// Firebox Mk1 -#define FIREBOX_MK1 \ - new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \ - new MotorDriver(4, 8, 9, UNUSED_PIN, A1, 5.00, 250 , UNUSED_PIN) +// Firebox Mk1 +#define FIREBOX_MK1 F("FIREBOX_MK1"), \ + new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \ + new MotorDriver(4, 8, 9, UNUSED_PIN, A1, 5.00, 1000, UNUSED_PIN) -// Firebox Mk1S -#define FIREBOX_MK1S \ - new MotorDriver(24, 21, 22, 25, 23, 9.766, 5500, UNUSED_PIN), \ - new MotorDriver(30, 27, 28, 31, 29, 5.00, 250 , UNUSED_PIN) +// Firebox Mk1S +#define FIREBOX_MK1S F("FIREBOX_MK1A"), \ + new MotorDriver(24, 21, 22, 25, 23, 9.766, 5500, UNUSED_PIN), \ + new MotorDriver(30, 27, 28, 31, 29, 5.00, 1000, UNUSED_PIN) // FunduMoto Motor Shield -#define FUNDUMOTO_SHIELD \ - new MotorDriver(10 , 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ - new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 250 , UNUSED_PIN) +#define FUNDUMOTO_SHIELD F("FUNDUMOTO_SHIELD"), \ + new MotorDriver(10, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ + new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) #endif diff --git a/README.md b/README.md index 4bdc9c1..09437ac 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,20 @@ The DCC++ EX Command Station consists of an Arduino micro controller fitted with This repository, CommandStation-EX, contains a complete DCC++ EX Commmand Station sketch designed for compiling and uploading into an Arduino Uno, Mega, or Nano. All sketch files are in the folder named CommandStation-EX and its subforlders. More information about the sketch can be found in the included PDF file. -To utilize this sketch, you can use our automated installer, or download a zip file from this repository and open the file "CommandStation-EX.ino" after unzipping it to your Arduino IDE projects folder. Please do not rename the folder containing the sketch code, nor add any files in that folder. The Arduino IDE relies on the structure and name of the folder to properly display and compile the code. +To utilize this sketch, you can use the following methods: + +* our automated installer +* download a zip file from this repository (green Code button above) and unzip it +* use git clone on this repository + +With the manual methods you unzip or git clone to the Arduino IDE +projects folder and then open the file "CommandStation-EX.ino" in the +Arduino IDE. Please do not rename the folder containing the sketch +code, nor add any files in that folder. The Arduino IDE relies on the +structure and name of the folder to properly display and compile the +code. If you do not run the installer, you have to copy +config.example.h to config.h. If you do not have the standard config +you edit config.h according to the help texts in config.h. The latest production release of the Master branch is 3.0.1: diff --git a/Timer.cpp b/Timer.cpp index 1430e9c..3a73401 100644 --- a/Timer.cpp +++ b/Timer.cpp @@ -85,10 +85,15 @@ ISR(TIMER2_OVF_vect) #include "ATMEGA4809/Timer.h" -Timer TimerA(0); +Timer TimerA(1); +Timer TimerB(2); -ISR(TCA0_OVF_vect) { +ISR(TIMER1_OVF_vect) { TimerA.isrCallback(); } +ISR(TIMER2_OVF_vect) { + TimerB.isrCallback(); +} + #endif diff --git a/WifiInterface.cpp b/WifiInterface.cpp index c08a33e..f027ea0 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -1,7 +1,8 @@ /* © 2020, Chris Harlow. All rights reserved. + © 2020, Harald Barth. - This file is part of Asbelos DCC API + This file is part of CommandStation-EX This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,10 +17,14 @@ You should have received a copy of the GNU General Public License along with CommandStation. If not, see . */ -#include "WifiInterface.h" + +#include "WifiInterface.h" /* config.h and defines.h included here */ +#include #include "DIAG.h" #include "StringFormatter.h" #include "WiThrottle.h" + + const char PROGMEM READY_SEARCH[] = "\r\nready\r\n"; const char PROGMEM OK_SEARCH[] = "\r\nOK\r\n"; const char PROGMEM END_DETAIL_SEARCH[] = "@ 1000"; @@ -35,30 +40,36 @@ unsigned long WifiInterface::loopTimeoutStart = 0; int WifiInterface::datalength = 0; int WifiInterface::connectionId; byte WifiInterface::buffer[MAX_WIFI_BUFFER+1]; -MemStream WifiInterface::streamer(buffer, MAX_WIFI_BUFFER); +MemStream * WifiInterface::streamer; Stream * WifiInterface::wifiStream = NULL; HTTP_CALLBACK WifiInterface::httpCallback = 0; - -void WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password, +bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password, const __FlashStringHelper* hostname, int port) { + static uint8_t ntry = 0; + ntry++; wifiStream = &setupStream; - DIAG(F("\n++++++ Wifi Setup In Progress ++++++++\n")); + DIAG(F("\n++ Wifi Setup Try %d ++\n"), ntry); + connected = setup2( SSid, password, hostname, port); if (connected) { StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo checkForOK(200, OK_SEARCH, true); } - - DIAG(F("\n++++++ Wifi Setup %S ++++++++\n"), connected ? F("OK") : F("FAILED")); + streamer=new MemStream(buffer, MAX_WIFI_BUFFER); + parser.setAtCommandCallback(ATCommand); + + DIAG(F("\n++ Wifi Setup %S ++\n"), connected ? F("OK") : F("FAILED")); + return connected; } bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringHelper* password, const __FlashStringHelper* hostname, int port) { - int ipOK = 0; + bool ipOK = false; + bool oldCmd = false; char macAddress[17]; // mac address extraction @@ -71,66 +82,99 @@ bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringH return true; } - + StringFormatter::send(wifiStream, F("AT\r\n")); // Is something here that understands AT? + if(!checkForOK(200, OK_SEARCH, true)) + return false; // No AT compatible WiFi module here + StringFormatter::send(wifiStream, F("ATE1\r\n")); // Turn on the echo, se we can see what's happening - checkForOK(2000, OK_SEARCH, true); // Makes this visible on the console + checkForOK(2000, OK_SEARCH, true); // Makes this visible on the console // Display the AT version information StringFormatter::send(wifiStream, F("AT+GMR\r\n")); checkForOK(2000, OK_SEARCH, true, false); // Makes this visible on the console - delay(8000); // give a preconfigured ES8266 a chance to connect to a router - - StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); + StringFormatter::send(wifiStream, F("AT+CWMODE=1\r\n")); // configure as "station" = WiFi client + checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change" - // looking fpr mac addr eg +CIFSR:APMAC,"be:dd:c2:5c:6b:b7" - if (checkForOK(5000, (const char*) F("+CIFSR:APMAC,\""), true,false)) { - // Copy 17 byte mac address - for (int i=0; i<17;i++) { - while(!wifiStream->available()); - macAddress[i]=wifiStream->read(); - StringFormatter::printEscape(macAddress[i]); - } - } - char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'}; + // If the source code looks unconfigured, check if the + // ESP8266 is preconfigured. We check the first 13 chars + // of the password. + if (strncmp_P("Your network ",(const char*)password,13) == 0) { + delay(8000); // give a preconfigured ES8266 a chance to connect to a router + + StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); + if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false)) + if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false)) + ipOK = true; + } else { + + if (!ipOK) { + + // Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME + StringFormatter::send(wifiStream, F("AT+CWJAP?\r\n")); + if (checkForOK(2000, OK_SEARCH, true)) { + oldCmd=true; + while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE + + // AT command early version supports CWJAP/CWSAP + if (SSid) { + StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password); + ipOK = checkForOK(16000, OK_SEARCH, true); + } + DIAG(F("\n**\n")); + + } else { + // later version supports CWJAP_CUR + + StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client + checkForOK(2000, OK_SEARCH, true); // dont care if not supported - if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false)) - if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false)) - ipOK = 1; + if (SSid) { + StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password); + ipOK = checkForOK(20000, OK_SEARCH, true); + } + } + delay(8000); // give a preconfigured ES8266 a chance to connect to a router + + if (ipOK) { + // But we really only have the ESSID and password correct + // Let's check for IP + ipOK = false; + StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); + if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false)) + if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false)) + ipOK = true; + } + } + } if (!ipOK) { - StringFormatter::send(wifiStream, F("AT+CWMODE=3\r\n")); // configure as server or access point + // If we have not managed to get this going in station mode, go for AP mode + + StringFormatter::send(wifiStream, F("AT+CWMODE=2\r\n")); // configure as AccessPoint. checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change" - // Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME - StringFormatter::send(wifiStream, F("AT+CWJAP?\r\n")); - if (checkForOK(2000, OK_SEARCH, true)) { - while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE - - // AT command early version supports CWJAP/CWSAP - if (SSid) { - StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password); - checkForOK(16000, OK_SEARCH, true); // can ignore failure as AP mode may still be ok + // Figure out MAC addr + StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); + // looking fpr mac addr eg +CIFSR:APMAC,"be:dd:c2:5c:6b:b7" + if (checkForOK(5000, (const char*) F("+CIFSR:APMAC,\""), true,false)) { + // Copy 17 byte mac address + for (int i=0; i<17;i++) { + while(!wifiStream->available()); + macAddress[i]=wifiStream->read(); + StringFormatter::printEscape(macAddress[i]); } - DIAG(F("\n**\n")); - - // establish the APname + } + char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'}; + + if (oldCmd) { + while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE + StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail); checkForOK(16000, OK_SEARCH, true); // can ignore failure as AP mode may still be ok - } - else { - // later version supports CWJAP_CUR - - StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client - checkForOK(2000, OK_SEARCH, true); // dont care if not supported + } else { - - if (SSid) { - StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password); - checkForOK(20000, OK_SEARCH, true); // can ignore failure as AP mode may still be ok - } - StringFormatter::send(wifiStream, F("AT+CWSAP_CUR=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail); checkForOK(20000, OK_SEARCH, true); // can ignore failure as SSid mode may still be ok @@ -257,15 +301,15 @@ void WifiInterface::loop() { case 6: // reading for length if (ch == ':') loopstate = (datalength == 0) ? 99 : 7; // 99 is getout without reading next char else datalength = datalength * 10 + (ch - '0'); - streamer.flush(); // basically sets write point at start of buffer + streamer->flush(); // basically sets write point at start of buffer break; case 7: // reading data - streamer.write(ch); // NOTE: The MemStream will throw away bytes that do not fit in the buffer. + streamer->write(ch); // NOTE: The MemStream will throw away bytes that do not fit in the buffer. // This protects against buffer overflows even with things as innocent // as a browser which send massive, irrlevent HTTP headers. datalength--; if (datalength == 0) { - buffer[streamer.available()]='\0'; // mark end of buffer, so it can be used as a string later + buffer[streamer->available()]='\0'; // mark end of buffer, so it can be used as a string later loopstate = 99; } break; @@ -308,8 +352,8 @@ void WifiInterface::loop() { loopstate = 1; } if (ch == 'K') { // assume its in SEND OK - if (Diag::WIFI) DIAG(F("\n\n Wifi BUSY RETRYING.. AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available()); - StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available()); + if (Diag::WIFI) DIAG(F("\n\n Wifi BUSY RETRYING.. AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available()); + StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available()); loopTimeoutStart = millis(); loopstate = 10; // non-blocking loop waits for > before sending break; @@ -322,7 +366,7 @@ void WifiInterface::loop() { // AT this point we have read an incoming message into the buffer if (Diag::WIFI) DIAG(F("\n%l Wifi(%d)<-[%e]\n"), millis(),connectionId, buffer); - streamer.setBufferContentPosition(0, 0); // reset write position to start of buffer + streamer->setBufferContentPosition(0, 0); // reset write position to start of buffer // SIDE EFFECT WARNING::: // We know that parser will read the entire buffer before starting to write to it. // Otherwise we would have to copy the buffer elsewhere and RAM is in short supply. @@ -331,18 +375,18 @@ void WifiInterface::loop() { // Intercept HTTP requests if (isHTTP()) { - if (httpCallback) httpCallback(&streamer, buffer); + if (httpCallback) httpCallback(streamer, buffer); else { StringFormatter::send(streamer, F("HTTP/1.1 404 Not Found\nContent-Type: text/html\nConnnection: close\n\n")); StringFormatter::send(streamer, F("This is not a web server.
")); } closeAfter = true; } - else if (buffer[0] == '<') parser.parse(&streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async + else if (buffer[0] == '<') parser.parse(streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async - else WiThrottle::getThrottle(connectionId)->parse(streamer, buffer); + else WiThrottle::getThrottle(connectionId)->parse(*streamer, buffer); - if (streamer.available() == 0) { + if (streamer->available() == 0) { // No reply if (closeAfter) { if (Diag::WIFI) DIAG(F("AT+CIPCLOSE=%d\r\n"), connectionId); @@ -352,10 +396,10 @@ void WifiInterface::loop() { return; } // prepare to send reply - buffer[streamer.available()]='\0'; // mark end of buffer, so it can be used as a string later - if (Diag::WIFI) DIAG(F("%l WiFi(%d)->[%e] l(%d)\n"), millis(), connectionId, buffer, streamer.available()); - if (Diag::WIFI) DIAG(F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available()); - StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer.available()); + buffer[streamer->available()]='\0'; // mark end of buffer, so it can be used as a string later + if (Diag::WIFI) DIAG(F("%l WiFi(%d)->[%e] l(%d)\n"), millis(), connectionId, buffer, streamer->available()); + if (Diag::WIFI) DIAG(F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available()); + StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), connectionId, streamer->available()); loopTimeoutStart = millis(); loopstate = 10; // non-blocking loop waits for > before sending } diff --git a/WifiInterface.h b/WifiInterface.h index 7baa2c3..ecf9005 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -1,7 +1,8 @@ /* * © 2020, Chris Harlow. All rights reserved. + * © 2020, Harald Barth. * - * This file is part of Asbelos DCC API + * This file is part of CommandStation-EX * * This is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,7 +17,6 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ - #ifndef WifiInterface_h #define WifiInterface_h #include "DCCEXParser.h" @@ -24,34 +24,34 @@ #include #include -typedef void (*HTTP_CALLBACK)(Print * stream, byte * cmd); +typedef void (*HTTP_CALLBACK)(Print *stream, byte *cmd); -class WifiInterface { +class WifiInterface +{ - public: - static void setup(Stream & setupStream, const __FlashStringHelper* SSSid, const __FlashStringHelper* password, - const __FlashStringHelper* hostname, int port); - static void loop(); - static void ATCommand(const byte * command); - static void setHTTPCallback(HTTP_CALLBACK callback); - - private: - static Stream * wifiStream; - static DCCEXParser parser; - static bool setup2( const __FlashStringHelper* SSSid, const __FlashStringHelper* password, - const __FlashStringHelper* hostname, int port); - static bool checkForOK(const unsigned int timeout, const char* waitfor, bool echo, bool escapeEcho=true); - static bool isHTTP(); - static HTTP_CALLBACK httpCallback; - static bool connected; - static bool closeAfter; - static byte loopstate; - static int datalength; - static int connectionId; - static unsigned long loopTimeoutStart; - static const byte MAX_WIFI_BUFFER=250; - static byte buffer[MAX_WIFI_BUFFER+1]; - static MemStream streamer; +public: + static bool setup(Stream &setupStream, const __FlashStringHelper *SSSid, const __FlashStringHelper *password, + const __FlashStringHelper *hostname, int port); + static void loop(); + static void ATCommand(const byte *command); + static void setHTTPCallback(HTTP_CALLBACK callback); + +private: + static Stream *wifiStream; + static DCCEXParser parser; + static bool setup2(const __FlashStringHelper *SSSid, const __FlashStringHelper *password, + const __FlashStringHelper *hostname, int port); + static bool checkForOK(const unsigned int timeout, const char *waitfor, bool echo, bool escapeEcho = true); + static bool isHTTP(); + static HTTP_CALLBACK httpCallback; + static bool connected; + static bool closeAfter; + static byte loopstate; + static int datalength; + static int connectionId; + static unsigned long loopTimeoutStart; + static const byte MAX_WIFI_BUFFER = 250; + static byte buffer[MAX_WIFI_BUFFER + 1]; + static MemStream * streamer; }; - #endif diff --git a/config.example.h b/config.example.h new file mode 100644 index 0000000..bb3e302 --- /dev/null +++ b/config.example.h @@ -0,0 +1,106 @@ +/********************************************************************** + +Config.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman +COPYRIGHT (c) 2020 Fred Decker + +The configuration file for DCC++ EX Command Station + +**********************************************************************/ + +///////////////////////////////////////////////////////////////////////////////////// +// NOTE: Before connecting these boards and selecting one in this software +// check the quick install guides!!! Some of these boards require a voltage +// generating resitor on the current sense pin of the device. Failure to select +// the correct resistor could damage the sense pin on your Arduino or destroy +// the device. +// +// DEFINE MOTOR_SHIELD_TYPE BELOW ACCORDING TO THE FOLLOWING TABLE: +// +// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel +// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track) +// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection) +// FIREBOX_MK1 : The Firebox MK1 +// FIREBOX_MK1S : The Firebox MK1S +// | +// +-----------------------v +// +#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD + +///////////////////////////////////////////////////////////////////////////////////// +// +// The IP port to talk to a WIFI or Ethernet shield. +// +#define IP_PORT 2560 + +///////////////////////////////////////////////////////////////////////////////////// +// +// NOTE: Only supported on Arduino Mega +// Set to false if you not even want it on the Arduino Mega +// +#define ENABLE_WIFI true + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE WiFi Parameters (only in effect if WIFI is on) +// +#define WIFI_SSID "Your network name" +#define WIFI_PASSWORD "Your network passwd" +#define WIFI_HOSTNAME "dccex" + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP +// +//#define IP_ADDRESS { 192, 168, 1, 200 } + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE +// +// Uncomment to use with Ethernet Shields +// +// NOTE: This is not used with ESP8266 WiFi modules. +// +// #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF } + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE LCD SCREEN USAGE BY THE BASE STATION +// +// Note: This feature requires an I2C enabled LCD screen using a PCF8574 based chipset. +// or one using a Hitachi HD44780. +// +// To enable, uncomment the line below and make sure only the correct LIB_TYPE line +// is uncommented below to select the library used for your LCD backpack + +//#define ENABLE_LCD + +#ifdef ENABLE_LCD + #define LIB_TYPE_PCF8574 + //#define LIB_TYPE_I2C + // This defines the I2C address for the LCD device + #define LCD_ADDRESS 0x27 //common defaults are 0x27 and 0x3F + + // This defines the number of columns the LCD device has + #define LCD_COLUMNS 16 + + // This defines the number of lines the LCD device has + #define LCD_LINES 2 +#endif + + +///////////////////////////////////////////////////////////////////////////////////// +// +// Enable custom command filtering +#define ENABLE_CUSTOM_FILTER false + +///////////////////////////////////////////////////////////////////////////////////// +// +// Enable custom command filtering +#define ENABLE_CUSTOM_CALLBACK false + +///////////////////////////////////////////////////////////////////////////////////// +// +// Enable custom command filtering +#define ENABLE_FREE_MEM_WARNING false diff --git a/defines.h b/defines.h new file mode 100644 index 0000000..700687b --- /dev/null +++ b/defines.h @@ -0,0 +1,45 @@ +/* + © 2020, Harald Barth. + + This file is part of CommandStation-EX + + This is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + It is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with CommandStation. If not, see . + +*/ + +//////////////////////////////////////////////////////////////////////////////// +// +// WIFI_ON: All prereqs for running with WIFI are met +// +#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) +#define WIFI_ON +#endif + +//////////////////////////////////////////////////////////////////////////////// +// +// This defines the speed at which the Arduino will communicate with the ESP8266 module. +// Currently only devices which can communicate at 115200 are supported. +// +#define WIFI_SERIAL_LINK_SPEED 115200 + +//////////////////////////////////////////////////////////////////////////////// +// +// Figure out number of serial ports depending on hardware +// +#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) +#define NUM_SERIAL 3 +#endif +#ifndef NUM_SERIAL +#define NUM_SERIAL 1 +#endif diff --git a/freeMemory.cpp b/freeMemory.cpp new file mode 100644 index 0000000..30fa16d --- /dev/null +++ b/freeMemory.cpp @@ -0,0 +1,42 @@ +/* + * © 2020, Harald Barth + * + * This file is part of Asbelos DCC-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +#include "freeMemory.h" + +// thanks go to https://github.com/mpflaga/Arduino-MemoryFree +#if defined(__arm__) +extern "C" char* sbrk(int); +#elif defined(__AVR__) +extern char *__brkval; +extern char *__malloc_heap_start; +#else +#error Unsupported board type +#endif + + +int freeMemory() { + char top; +#if defined(__arm__) + return &top - reinterpret_cast(sbrk(0)); +#elif defined(__AVR__) + return __brkval ? &top - __brkval : &top - __malloc_heap_start; +#else +#error bailed out alredy above +#endif +} diff --git a/freeMemory.h b/freeMemory.h index e3af2fe..2bd35c1 100644 --- a/freeMemory.h +++ b/freeMemory.h @@ -1,20 +1,23 @@ +/* + * © 2020, Harald Barth + * + * This file is part of DCC-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + #ifndef freeMemory_h #define freeMemory_h - -// thanks go to https://github.com/mpflaga/Arduino-MemoryFree -#ifdef __arm__ -// should use uinstd.h to define sbrk but Due causes a conflict -extern "C" char* sbrk(int incr); -#else // __ARM__ -extern char *__brkval; -#endif // __arm__ - -int freeMemory() { - char top; -#ifdef __arm__ - return &top - reinterpret_cast(sbrk(0)); -#else // __arm__ - return __brkval ? &top - __brkval : &top - __malloc_heap_start; -#endif // __arm__ -} +int freeMemory(); #endif diff --git a/objdump.bat b/objdump.bat index bee2d0f..af30cb5 100644 --- a/objdump.bat +++ b/objdump.bat @@ -1,12 +1,14 @@ ECHO ON FOR /F "delims=" %%i IN ('dir %TMP%\arduino_build_* /b /ad-h /t:c /od') DO SET a=%%i echo Most recent subfolder: %a% >%TMP%\OBJDUMP_%a%.txt -avr-objdump --private=mem-usage %TMP%\%a%\DCCEX.ino.elf >>%TMP%\OBJDUMP_%a%.txt +SET ELF=%TMP%\%a%\CommandStation-EX.ino.elf + +avr-objdump --private=mem-usage %ELF% >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt -avr-objdump -x -C %TMP%\%a%\DCCEX.ino.elf | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -x -C %ELF% | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt -avr-objdump -x -C %TMP%\%a%\DCCEX.ino.elf | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -x -C %ELF% | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt -avr-objdump -x -C %TMP%\%a%\DCC.ino.elf | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -x -C %ELF% | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt notepad %TMP%\OBJDUMP_%a%.txt EXIT diff --git a/objdump.sh b/objdump.sh index 2e18af2..6e8e7d3 100755 --- a/objdump.sh +++ b/objdump.sh @@ -4,9 +4,10 @@ ARDUINOBIN=$(ls -l $(type -p arduino)| awk '{print $NF ; exit 0}') PATH=$(dirname "$ARDUINOBIN")/hardware/tools/avr/bin:$PATH -avr-objdump --private=mem-usage /tmp/arduino_build_233823/Blinkhabaplus.ino.elf +LASTBUILD=$(ls -tr /tmp/arduino_build_*/*.ino.elf | tail -1) +avr-objdump --private=mem-usage "$LASTBUILD" for segment in .text .data .bss ; do echo '++++++++++++++++++++++++++++++++++' - avr-objdump -x -C /tmp/arduino_build_233823/Blinkhabaplus.ino.elf | awk '$2 == "'$segment'" && $3 != 0 {print $3,$2} ; $4 == "'$segment'" && $5 != 0 { print $5,$6}' | sort -r + avr-objdump -x -C "$LASTBUILD" | awk '$2 == "'$segment'" && $3 != 0 {print $3,$2} ; $4 == "'$segment'" && $5 != 0 { print $5,$6}' | sort -r done diff --git a/platformio.ini b/platformio.ini index 58fd2a1..f53a690 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,6 +24,8 @@ upload_protocol = atmel-ice lib_deps = ${env.lib_deps} SparkFun External EEPROM Arduino Library +monitor_speed = 115200 +monitor_flags = --echo [env:mega2560] platform = atmelavr @@ -32,6 +34,11 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 + arduino-libraries/Ethernet + SPI + mathertel/LiquidCrystal_PCF8574 +monitor_speed = 115200 +monitor_flags = --echo [env:mega328] platform = atmelavr @@ -40,6 +47,11 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 + arduino-libraries/Ethernet + SPI + mathertel/LiquidCrystal_PCF8574 +monitor_speed = 115200 +monitor_flags = --echo [env:unowifiR2] platform = atmelmegaavr @@ -48,6 +60,11 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 + arduino-libraries/Ethernet + SPI + mathertel/LiquidCrystal_PCF8574 +monitor_speed = 115200 +monitor_flags = --echo [env:uno] platform = atmelavr @@ -56,3 +73,8 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 + arduino-libraries/Ethernet + SPI + mathertel/LiquidCrystal_PCF8574 +monitor_speed = 115200 +monitor_flags = --echo diff --git a/version.h b/version.h new file mode 100644 index 0000000..6e45e70 --- /dev/null +++ b/version.h @@ -0,0 +1,10 @@ +#ifndef version_h +#define version_h + +#include "StringFormatter.h" + +// const char VERSION[] PROGMEM ="0.2.0"; +#define VERSION "0.2.0" + + +#endif \ No newline at end of file