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..ebf51de 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); } @@ -206,6 +208,10 @@ void DCC::setProgTrackSyncMain(bool on) { DCCWaveform::progTrackSyncMain=on; } +__FlashStringHelper* DCC::getMotorShieldName() { + return shieldName; +} + const ackOp PROGMEM WRITE_BIT0_PROG[] = { BASELINE, W0,WACK, @@ -568,7 +574,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..1e1058b 100644 --- a/DCC.h +++ b/DCC.h @@ -24,118 +24,151 @@ 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 + + // 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..82cd9d9 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,629 @@ #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; 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); + 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); + 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; + 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.h b/DCCWaveform.h index 96e5887..b1c6f7d 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -30,7 +30,7 @@ const int MIN_ACK_PULSE_DURATION = 2000; const int MAX_ACK_PULSE_DURATION = 8500; -const int PREAMBLE_BITS_MAIN = 20; +const int PREAMBLE_BITS_MAIN = 16; const int PREAMBLE_BITS_PROG = 22; 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/MotorDrivers.h b/MotorDrivers.h index f31aeac..79bcd4f 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. @@ -8,36 +11,35 @@ // 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. - 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, TRIP_CURRENT_PROG, 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, 2000, 12), \ + new MotorDriver(2, 8, UNUSED_PIN, 10, A1, 18, TRIP_CURRENT_PROG, 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, TRIP_CURRENT_PROG, 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, TRIP_CURRENT_PROG, 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, TRIP_CURRENT_PROG, UNUSED_PIN) #endif 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 86da502..9fdcd26 100644 --- a/platformio.ini +++ b/platformio.ini @@ -34,6 +34,7 @@ lib_deps = DIO2 arduino-libraries/Ethernet SPI + mathertel/LiquidCrystal_PCF8574 [env:mega328] platform = atmelavr @@ -58,3 +59,4 @@ framework = arduino lib_deps = ${env.lib_deps} DIO2 + mathertel/LiquidCrystal_PCF8574 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