From 1b802cc6005ef72e757fef1298a82beb4ec6262c Mon Sep 17 00:00:00 2001 From: mstevetodd Date: Tue, 29 Sep 2020 11:51:01 -0400 Subject: [PATCH 1/6] add support for FireBox_Mk1, reduce heartbeat, separate eStop time (#43) --- DCC.h | 2 ++ WiThrottle.cpp | 27 ++++++++++++++------------- WiThrottle.h | 5 +++-- defines.h | 2 +- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/DCC.h b/DCC.h index 0e8fcc6..580d8b7 100644 --- a/DCC.h +++ b/DCC.h @@ -156,6 +156,8 @@ private: #define ARDUINO_TYPE "MEGA" #elif defined(ARDUINO_ARCH_MEGAAVR) #define ARDUINO_TYPE "UNOWIFIR2" +#elif defined(ARDUINO_SAMD_ZERO) + #define ARDUINO_TYPE "FireBoxMK1" #else #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560 #endif diff --git a/WiThrottle.cpp b/WiThrottle.cpp index f9c5a41..044b00b 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -46,6 +46,8 @@ #include "StringFormatter.h" #include "Turnouts.h" #include "DIAG.h" +#include "GITHUB_SHA.h" +#include "version.h" #define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;loco\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab); } } - if (Diag::WITHROTTLE) DIAG(F("WiThrottle(%d) Quit\n"), clientid); + if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit\n"),millis(),clientid); delete this; break; } @@ -343,19 +346,17 @@ void WiThrottle::loop() { } void WiThrottle::checkHeartbeat() { - // if 2 heartbeats missed... drop connection and eStop any locos still assigned to this client - if(heartBeatEnable && (millis()-heartBeat > HEARTBEAT_TIMEOUT*2000)) { - if (Diag::WITHROTTLE) DIAG(F("\n\nWiThrottle(%d) hearbeat missed, dropping connection\n\n"),clientid); + // if eStop time passed... eStop any locos still assigned to this client and then drop the connection + if(heartBeatEnable && (millis()-heartBeat > ESTOP_SECONDS*1000)) { + if (Diag::WITHROTTLE) DIAG(F("\n\n%l WiThrottle(%d) eStop(%ds) timeout, drop connection\n"), millis(), clientid, ESTOP_SECONDS); LOOPLOCOS('*', -1) { if (myLocos[loco].throttle!='\0') { - if (Diag::WITHROTTLE) DIAG(F(" eStopping cab %d\n"), myLocos[loco].cab); + if (Diag::WITHROTTLE) DIAG(F("%l eStopping cab %d\n"),millis(),myLocos[loco].cab); DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab)); // speed 1 is eStop } } delete this; - } else { - // TODO Check if anything has changed on my locos since last notified! - } + } } char WiThrottle::LorS(int cab) { diff --git a/WiThrottle.h b/WiThrottle.h index ad29ee5..c1b5bba 100644 --- a/WiThrottle.h +++ b/WiThrottle.h @@ -35,8 +35,9 @@ class WiThrottle { WiThrottle( int wifiClientId); ~WiThrottle(); - static const int MAX_MY_LOCO=10; //maximum number of locos assigned to a single client - static const int HEARTBEAT_TIMEOUT=2;// heartbeat at 2secs to provide messaging transport + static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client + static const int HEARTBEAT_SECONDS=4; // heartbeat at 4secs to provide messaging transport + static const int ESTOP_SECONDS=8; // eStop if no incoming messages for more than 8secs static WiThrottle* firstThrottle; static int getInt(byte * cmd); static int getLocoId(byte * cmd); diff --git a/defines.h b/defines.h index 700687b..99b49bd 100644 --- a/defines.h +++ b/defines.h @@ -22,7 +22,7 @@ // // WIFI_ON: All prereqs for running with WIFI are met // -#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) +#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO)) #define WIFI_ON #endif From 6feda4e217b81784e76f09bd756cf6358ca42a8d Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 2 Oct 2020 17:12:48 +0100 Subject: [PATCH 2/6] startup Info on USB (#44) Add status info to CS startup and ash Git SHA for version tracking --- DCC.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DCC.cpp b/DCC.cpp index 29fa9d7..0c8c322 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -20,6 +20,8 @@ #include "DCC.h" #include "DCCWaveform.h" #include "DIAG.h" +#include "GITHUB_SHA.h" +#include "version.h" // This module is responsible for converting API calls into @@ -45,6 +47,7 @@ __FlashStringHelper* DCC::shieldName=NULL; void DCC::begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) { shieldName=(__FlashStringHelper*)motorShieldName; + DIAG(F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); DCCWaveform::begin(mainDriver,progDriver, timerNumber); } From 75d7547f1163497ab5ee65aa1de8ffa50cd014d3 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 3 Oct 2020 14:07:25 +0200 Subject: [PATCH 3/6] Turnouts stored to EEPROM without trashing other stuff --- CommandStation-EX.ino | 10 ++++++-- DCCEXParser.cpp | 2 +- EEStore.cpp | 15 +++++++++++- EEStore.h | 3 +++ Turnouts.cpp | 53 +++++++++++++++++++++++++++++++++++-------- Turnouts.h | 3 +++ 6 files changed, 72 insertions(+), 14 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 5f1f87e..8dd29ba 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -11,7 +11,7 @@ #include "config.h" #include "DCCEX.h" - +#include "EEStore.h" //////////////////////////////////////////////////////////////// // @@ -32,7 +32,6 @@ DCCEXParser serialParser; void setup() { - //////////////////////////////////////////// // // More display stuff. Need to put this in a .h file and make @@ -69,6 +68,13 @@ void setup() // This is normally Serial but uses SerialUSB on a SAMD processor Serial.begin(115200); + // Responsibility 1b ;-) Load stuff from EEprom + (void)EEPROM; // tell compiler not to warn this is unused + EEStore::init(); +#ifdef EESTOREDEBUG + EEStore::dump(128); +#endif + // 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. diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 8a96e23..5761845 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -168,9 +168,9 @@ void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback) // See documentation on DCC class for info on this section void DCCEXParser::parse(Print *stream, byte *com, bool blocking) { + (void)EEPROM; // tell compiler not to warn this is unused 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 diff --git a/EEStore.cpp b/EEStore.cpp index f52d1a1..f7bf061 100644 --- a/EEStore.cpp +++ b/EEStore.cpp @@ -2,7 +2,9 @@ #include "Turnouts.h" #include "Sensors.h" #include "Outputs.h" - +#ifdef EESTOREDEBUG +#include "DIAG.h" +#endif #if defined(ARDUINO_ARCH_SAMD) ExternalEEPROM EEPROM; @@ -72,5 +74,16 @@ int EEStore::pointer(){ } /////////////////////////////////////////////////////////////////////////////// +#ifdef EESTOREDEBUG +void EEStore::dump(int num) { + byte b; + for (int n=0 ; nactivate(state); - if(n>0) EEPROM.put(n,tt->data.tStatus); + EEStore::store(); turnoutlistHash++; return true; } - bool Turnout::isActive(int n){ +bool Turnout::isActive(int n){ Turnout * tt=get(n); if (tt==NULL) return false; return tt->data.tStatus & STATUS_ACTIVE; } // activate is virtual here so that it can be overridden by a non-DCC turnout mechanism - void Turnout::activate(bool state) { - if (state) data.tStatus|=STATUS_ACTIVE; - else data.tStatus &= ~STATUS_ACTIVE; - if (data.tStatus & STATUS_PWM) PWMServoDriver::setServo(data.tStatus & STATUS_PWMPIN, (data.inactiveAngle+(state?data.moveAngle:0))); - else DCC::setAccessory(data.address,data.subAddress, state); +void Turnout::activate(bool state) { +#ifdef EESTOREDEBUG + DIAG(F("\nTurnout::activate(%d)\n"),state); +#endif + if (state) + data.tStatus|=STATUS_ACTIVE; + else + data.tStatus &= ~STATUS_ACTIVE; + if (data.tStatus & STATUS_PWM) + PWMServoDriver::setServo(data.tStatus & STATUS_PWMPIN, (data.inactiveAngle+(state?data.moveAngle:0))); + else + DCC::setAccessory(data.address,data.subAddress, state); + EEStore::store(); } /////////////////////////////////////////////////////////////////////////////// @@ -81,6 +96,9 @@ void Turnout::load(){ else tt=create(data.id,data.address,data.subAddress); tt->data.tStatus=data.tStatus; EEStore::advance(sizeof(tt->data)); +#ifdef EESTOREDEBUG + tt->print(tt); +#endif } } @@ -93,6 +111,9 @@ void Turnout::store(){ EEStore::eeStore->data.nTurnouts=0; while(tt!=NULL){ +#ifdef EESTOREDEBUG + tt->print(tt); +#endif EEPROM.put(EEStore::pointer(),tt->data); EEStore::advance(sizeof(tt->data)); tt=tt->nextTurnout; @@ -129,7 +150,19 @@ Turnout *Turnout::create(int id){ turnoutlistHash++; return tt; } - /////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// +// print debug info about the state of a turnout +// +#ifdef EESTOREDEBUG +void Turnout::print(Turnout *tt) { + if (tt->data.tStatus & STATUS_PWM ) + DIAG(F("Turnout %d ZeroAngle %d MoveAngle %d Status %d\n"),tt->data.id, tt->data.inactiveAngle, tt->data.moveAngle,tt->data.tStatus & STATUS_ACTIVE); + else + DIAG(F("Turnout %d Addr %d Subaddr %d Status %d\n"),tt->data.id, tt->data.address, tt->data.subAddress,tt->data.tStatus & STATUS_ACTIVE); +} +#endif Turnout *Turnout::firstTurnout=NULL; int Turnout::turnoutlistHash=0; //bump on every change so clients know when to refresh their lists diff --git a/Turnouts.h b/Turnouts.h index 5c9b590..2aff97d 100644 --- a/Turnouts.h +++ b/Turnouts.h @@ -49,6 +49,9 @@ class Turnout { static Turnout *create(int id , byte pin , int activeAngle, int inactiveAngle); static Turnout *create(int id); void activate(bool state); +#ifdef EESTOREDEBUG + void print(Turnout *tt); +#endif }; // Turnout #endif From a8000aae544f528c8f53e551370715772495fa6c Mon Sep 17 00:00:00 2001 From: dexslab Date: Sun, 4 Oct 2020 11:29:48 -0400 Subject: [PATCH 4/6] Changes for compatibility with Installer --- CommandStation-EX.ino | 6 +++--- defines.h | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 5f1f87e..aceedef 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -73,7 +73,7 @@ void setup() // NOTE: References to Serial1 are for the serial port used to connect // your wifi chip/shield. -#ifdef WIFI_ON +#if WIFI_ON bool wifiUp = false; const __FlashStringHelper *wifiESSID = F(WIFI_SSID); const __FlashStringHelper *wifiPassword = F(WIFI_PASSWORD); @@ -123,10 +123,10 @@ void loop() serialParser.loop(Serial); // Responsibility 3: Optionally handle any incoming WiFi traffic -#ifdef WIFI_ON +#if 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 diff --git a/defines.h b/defines.h index 700687b..5d7f178 100644 --- a/defines.h +++ b/defines.h @@ -23,7 +23,9 @@ // WIFI_ON: All prereqs for running with WIFI are met // #if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) -#define WIFI_ON +#define WIFI_ON true +#else +#define WIFI_ON false #endif //////////////////////////////////////////////////////////////////////////////// From 6dc4bcdb71d9808005cf42fd1abdf18919f7f793 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 4 Oct 2020 21:20:13 +0200 Subject: [PATCH 5/6] D EEPROM command --- CommandStation-EX.ino | 8 -------- DCC.cpp | 7 ++++++- DCCEXParser.cpp | 6 ++++++ EEStore.cpp | 7 ++----- EEStore.h | 2 -- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 8dd29ba..563bdb2 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -11,7 +11,6 @@ #include "config.h" #include "DCCEX.h" -#include "EEStore.h" //////////////////////////////////////////////////////////////// // @@ -68,13 +67,6 @@ void setup() // This is normally Serial but uses SerialUSB on a SAMD processor Serial.begin(115200); - // Responsibility 1b ;-) Load stuff from EEprom - (void)EEPROM; // tell compiler not to warn this is unused - EEStore::init(); -#ifdef EESTOREDEBUG - EEStore::dump(128); -#endif - // 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. diff --git a/DCC.cpp b/DCC.cpp index 29fa9d7..956fea6 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -20,7 +20,7 @@ #include "DCC.h" #include "DCCWaveform.h" #include "DIAG.h" - +#include "EEStore.h" // This module is responsible for converting API calls into // messages to be sent to the waveform generator. @@ -45,6 +45,11 @@ __FlashStringHelper* DCC::shieldName=NULL; void DCC::begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) { shieldName=(__FlashStringHelper*)motorShieldName; + + // Load stuff from EEprom + (void)EEPROM; // tell compiler not to warn this is unused + EEStore::init(); + DCCWaveform::begin(mainDriver,progDriver, timerNumber); } diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 5761845..2b62dc2 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -46,6 +46,7 @@ const int HASH_KEYWORD_ON = 2657; const int HASH_KEYWORD_DCC = 6436; const int HASH_KEYWORD_SLOW = -17209; const int HASH_KEYWORD_PROGBOOST = -6353; +const int HASH_KEYWORD_EEPROM = -7168; int DCCEXParser::stashP[MAX_PARAMS]; bool DCCEXParser::stashBusy; @@ -608,6 +609,11 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[]) DCC::setProgTrackBoost(true); return true; + case HASH_KEYWORD_EEPROM: + if (params >= 1) + EEStore::dump(p[1]); + return true; + default: // invalid/unknown break; } diff --git a/EEStore.cpp b/EEStore.cpp index f7bf061..a3d2d55 100644 --- a/EEStore.cpp +++ b/EEStore.cpp @@ -2,9 +2,7 @@ #include "Turnouts.h" #include "Sensors.h" #include "Outputs.h" -#ifdef EESTOREDEBUG #include "DIAG.h" -#endif #if defined(ARDUINO_ARCH_SAMD) ExternalEEPROM EEPROM; @@ -74,15 +72,14 @@ int EEStore::pointer(){ } /////////////////////////////////////////////////////////////////////////////// -#ifdef EESTOREDEBUG void EEStore::dump(int num) { byte b; + DIAG(F("\nAddr 0x char\n")); for (int n=0 ; n Date: Mon, 5 Oct 2020 18:42:31 +0100 Subject: [PATCH 6/6] Wifi reliability (#45) * First pass at wifi inbound FSA * Fixup detector loop * Remove asyncBanned Unused, uninitialized * Move wifi setup loop out of .ino Wifi auto detect Serial 1,2,3 * Correct capitalization * Uno compiles clean * Command distributor Moved command execution routing out of Wifi code for future use by Ethernet interface. Co-authored-by: Fred --- CommandDistributor.cpp | 54 +++++++++ CommandDistributor.h | 20 ++++ CommandStation-EX.ino | 27 +---- DCCEXParser.cpp | 2 +- DCCEXParser.h | 1 - WifiInboundHandler.cpp | 214 ++++++++++++++++++++++++++++++++++++ WifiInboundHandler.h | 72 ++++++++++++ WifiInterface.cpp | 244 ++++++++++++----------------------------- WifiInterface.h | 14 +-- defines.h | 11 -- 10 files changed, 440 insertions(+), 219 deletions(-) create mode 100644 CommandDistributor.cpp create mode 100644 CommandDistributor.h create mode 100644 WifiInboundHandler.cpp create mode 100644 WifiInboundHandler.h diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp new file mode 100644 index 0000000..5b43017 --- /dev/null +++ b/CommandDistributor.cpp @@ -0,0 +1,54 @@ +#include +#include "CommandDistributor.h" +#include "WiThrottle.h" + +DCCEXParser * CommandDistributor::parser=0; + +bool CommandDistributor::parse(byte clientId,byte * buffer, Print * streamer) { + + + // 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. + + + bool closeAfter=false; + // Intercept HTTP requests + if (isHTTP(buffer)) { + if (httpCallback) httpCallback(streamer, buffer); + closeAfter = true; + } + else if (buffer[0] == '<') { + if (!parser) parser = new DCCEXParser(); + parser->parse(streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async + } + else WiThrottle::getThrottle(clientId)->parse(*streamer, buffer); + + return closeAfter; +} + +bool CommandDistributor::isHTTP(byte * buffer) { + + // POST GET PUT PATCH DELETE + // You may think a simple strstr() is better... but not when ram & time is in short supply + switch (buffer[0]) { + case 'P': + if (buffer[1] == 'U' && buffer[2] == 'T' && buffer[3] == ' ' ) return true; + if (buffer[1] == 'O' && buffer[2] == 'S' && buffer[3] == 'T' && buffer[4] == ' ') return true; + if (buffer[1] == 'A' && buffer[2] == 'T' && buffer[3] == 'C' && buffer[4] == 'H' && buffer[5] == ' ') return true; + return false; + case 'G': + if (buffer[1] == 'E' && buffer[2] == 'T' && buffer[3] == ' ' ) return true; + return false; + case 'D': + if (buffer[1] == 'E' && buffer[2] == 'L' && buffer[3] == 'E' && buffer[4] == 'T' && buffer[5] == 'E' && buffer[6] == ' ') return true; + return false; + default: + return false; + } +} + +void CommandDistributor::setHTTPCallback(HTTP_CALLBACK callback) { + httpCallback = callback; +} +HTTP_CALLBACK CommandDistributor::httpCallback=0; diff --git a/CommandDistributor.h b/CommandDistributor.h new file mode 100644 index 0000000..e27b60e --- /dev/null +++ b/CommandDistributor.h @@ -0,0 +1,20 @@ +#ifndef CommandDistributor_h +#define CommandDistributor_h +#include "DCCEXParser.h" + +typedef void (*HTTP_CALLBACK)(Print *stream, byte *cmd); + +class CommandDistributor { + +public : + static void setHTTPCallback(HTTP_CALLBACK callback); + static bool parse(byte clientId,byte* buffer, Print * streamer); + + +private: + static HTTP_CALLBACK httpCallback; + static bool isHTTP(byte * buffer); + static DCCEXParser * parser; +}; + +#endif diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 39aeefc..462bd79 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -68,32 +68,9 @@ void setup() 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. -#if 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; - - 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 +#ifdef WIFI_ON + WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT); #endif // WIFI_ON // Responsibility 3: Start the DCC engine. diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 2b62dc2..4f4030c 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -623,7 +623,7 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[]) // CALLBACKS must be static bool DCCEXParser::stashCallback(Print *stream, int p[MAX_PARAMS]) { - if (stashBusy || asyncBanned) + if (stashBusy ) return false; stashBusy = true; stashStream = stream; diff --git a/DCCEXParser.h b/DCCEXParser.h index 5721f6c..2c2f337 100644 --- a/DCCEXParser.h +++ b/DCCEXParser.h @@ -38,7 +38,6 @@ struct DCCEXParser static const int MAX_BUFFER=50; // longest command sent in byte bufferLength=0; bool inCommandPayload=false; - bool asyncBanned; // true when called with stream that must complete before returning byte buffer[MAX_BUFFER+2]; int splitValues( int result[MAX_PARAMS], const byte * command); diff --git a/WifiInboundHandler.cpp b/WifiInboundHandler.cpp new file mode 100644 index 0000000..a214e1f --- /dev/null +++ b/WifiInboundHandler.cpp @@ -0,0 +1,214 @@ +#include +#include "WifiInboundHandler.h" +#include "CommandDistributor.h" +#include "DIAG.h" + +WifiInboundHandler * WifiInboundHandler::singleton; + +void WifiInboundHandler::setup(Stream * ESStream) { + singleton=new WifiInboundHandler(ESStream); +} + +void WifiInboundHandler::loop() { + singleton->loop1(); +} + + +WifiInboundHandler::WifiInboundHandler(Stream * ESStream) { + wifiStream=ESStream; + for (int clientId=0;clientIdavailable() + clientBuffer[clientId]=new byte[MAX_WIFI_BUFFER+1]; + clientStream[clientId]=new MemStream(clientBuffer[clientId], MAX_WIFI_BUFFER); + } + clientPendingCIPSEND=-1; +} + + +// Handle any inbound transmission +// +IPD,x,lll:data is stored in streamer[x] +// Other input returns +void WifiInboundHandler::loop1() { + + // First handle all inbound traffic events + if (loop2()!=INBOUND_IDLE) return; + + // if nothing is already CIPSEND pending, we can CIPSEND one reply + if (clientPendingCIPSEND<0) { + for (int clientId=0;clientIdavailable()); + StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientId, clientStream[clientId]->available()); + clientStatus[clientId]=CIPSEND_PENDING; + return; + } + } + } + + // if something waiting to close we can call one of them + + for (int clientId=0;clientIdavailable()) { + int ch = wifiStream->read(); + + // echo the char to the diagnostic stream in escaped format + if (Diag::WIFI) { + // DIAG(F(" %d/"), loopState); + StringFormatter::printEscape(ch); // DIAG in disguise + } + + switch (loopState) { + case ANYTHING: // looking for +IPD, > , busy , n,CONNECTED, n,CLOSED + + if (ch == '+') { + loopState = IPD; + break; + } + + if (ch=='>') { + if (Diag::WIFI) DIAG(F("[[XMIT %d]]"),clientStream[clientPendingCIPSEND]->available()); + wifiStream->write(clientBuffer[clientPendingCIPSEND], clientStream[clientPendingCIPSEND]->available()); + clientStatus[clientPendingCIPSEND]=clientCloseAfterReply[clientPendingCIPSEND]? CLOSE_AFTER_SEND: UNUSED; + clientPendingCIPSEND=-1; + loopState=SKIPTOEND; + break; + } + + if (ch=='R') { // Received ... bytes + loopState=SKIPTOEND; + break; + } + + if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND + if (clientPendingCIPSEND>=0) { + clientStatus[clientPendingCIPSEND]=REPLY_PENDING; + clientPendingCIPSEND=-1; + } + loopState=SKIPTOEND; + break; + } + + if (ch>='0' && ch<=('0'+MAX_CLIENTS)) { + runningClientId=ch-'0'; + loopState=GOT_CLIENT_ID; + break; + } + + break; + + case IPD: // Looking for I in +IPD + loopState = (ch == 'I') ? IPD1 : SKIPTOEND; + break; + + case IPD1: // Looking for P in +IPD + loopState = (ch == 'P') ? IPD2 : SKIPTOEND; + break; + + case IPD2: // Looking for D in +IPD + loopState = (ch == 'D') ? IPD3 : SKIPTOEND; + break; + + case IPD3: // Looking for , After +IPD + loopState = (ch == ',') ? IPD4_CLIENT : SKIPTOEND; + break; + + case IPD4_CLIENT: // reading connection id + if (ch >= '0' || ch <('0'+MAX_CLIENTS)){ + runningClientId=ch-'0'; + loopState=IPD5; + } + else loopState=SKIPTOEND; + break; + + case IPD5: // Looking for , After +IPD,client + loopState = (ch == ',') ? IPD6_LENGTH : SKIPTOEND; + dataLength=0; // ready to start collecting the length + break; + + case IPD6_LENGTH: // reading for length + if (ch == ':') { + if (dataLength==0) { + loopState=ANYTHING; + break; + } + clientStream[runningClientId]->flush(); // prepare streamer for input + clientStatus[runningClientId]=INBOUND_ARRIVING; + loopState=IPD_DATA; + break; + } + dataLength = dataLength * 10 + (ch - '0'); + break; + + case IPD_DATA: // reading data + clientStream[runningClientId]->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) { + clientStatus[runningClientId]=READY_TO_PROCESS; + loopState = ANYTHING; + } + break; + + case GOT_CLIENT_ID: // got x before CLOSE or CONNECTED + loopState=(ch==',') ? GOT_CLIENT_ID2: SKIPTOEND; + break; + + case GOT_CLIENT_ID2: // got "x," before CLOSE or CONNECTED + loopState=(ch=='C') ? GOT_CLIENT_ID3: SKIPTOEND; + break; + + case GOT_CLIENT_ID3: // got "x C" before CLOSE or CONNECTED (which is ignored) + if(ch=='L') clientStatus[runningClientId]=UNUSED; + loopState=SKIPTOEND; + break; + + case SKIPTOEND: // skipping for /n + if (ch=='\n') loopState=ANYTHING; + break; + } // switch + } // available + return (loopState==ANYTHING) ? INBOUND_IDLE: INBOUND_BUSY; +} + + +void WifiInboundHandler::processCommand(byte clientId) { + clientStatus[clientId]=PROCESSING; + byte * buffer=clientBuffer[clientId]; + MemStream * streamer=clientStream[clientId]; + buffer[streamer->available()]='\0'; + + if (Diag::WIFI) DIAG(F("\n%l Wifi(%d)<-[%e]\n"), millis(),clientId, buffer); + streamer->setBufferContentPosition(0, 0); // reset write position to start of buffer + + clientCloseAfterReply[clientId]=CommandDistributor::parse(clientId,buffer,streamer); + + if (streamer->available() == 0) { + clientStatus[clientId]=UNUSED; + } + else { + 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(), clientId, buffer, streamer->available()); + clientStatus[clientId]=REPLY_PENDING; + } +} diff --git a/WifiInboundHandler.h b/WifiInboundHandler.h new file mode 100644 index 0000000..87ac89a --- /dev/null +++ b/WifiInboundHandler.h @@ -0,0 +1,72 @@ +#ifndef WifiInboundHandler_h +#define WifiInboundHandler_h + +#include "MemStream.h" +#include "DCCEXParser.h" +#include "DIAG.h" + +class WifiInboundHandler { + public: + static void setup(Stream * ESStream); + static void loop(); + + private: + + static WifiInboundHandler * singleton; + + static const byte MAX_CLIENTS=5; + static const byte MAX_WIFI_BUFFER=255; + + enum INBOUND_STATE { + INBOUND_BUSY, // keep calling in loop() + INBOUND_IDLE // Nothing happening, outbound may xcall CIPSEND + }; + + enum LOOP_STATE { + ANYTHING, // ready for +IPD, n CLOSED, n CONNECTED, busy etc... + SKIPTOEND, // skip to newline + + // +IPD,client,length:data + IPD, // got + + IPD1, // got +I + IPD2, // got +IP + IPD3, // got +IPD + IPD4_CLIENT, // got +IPD, reading cient id + IPD5, // got +IPD,c + IPD6_LENGTH, // got +IPD,c, reading length + IPD_DATA, // got +IPD,c,ll,: collecting data + + GOT_CLIENT_ID, // clientid prefix to CONNECTED / CLOSED + GOT_CLIENT_ID2, // clientid prefix to CONNECTED / CLOSED + GOT_CLIENT_ID3 // clientid prefix to CONNECTED / CLOSED + }; + + enum CLIENT_STATUS { + UNUSED, // client slot not in use + INBOUND_ARRIVING, // data is arriving + READY_TO_PROCESS, // data has arrived, may call parser now + PROCESSING, // command in progress + REPLY_PENDING, // reply is ready to CIPSEND + CIPSEND_PENDING, // CIPSEND waiting for > + CLOSE_PENDING, // CLOSE received + CLOSE_AFTER_SEND // Send CLOSE after CIPSEND completed + }; + + WifiInboundHandler(Stream * ESStream); + void loop1(); + INBOUND_STATE loop2(); + void processCommand(byte clientId); + Stream * wifiStream; + + DCCEXParser *parser; + + LOOP_STATE loopState=ANYTHING; + int runningClientId; // latest client inbound processing data or CLOSE + int dataLength; // dataLength of +IPD + byte * clientBuffer[MAX_CLIENTS]; + MemStream * clientStream[MAX_CLIENTS]; + CLIENT_STATUS clientStatus[MAX_CLIENTS]; + bool clientCloseAfterReply[MAX_CLIENTS]; + int clientPendingCIPSEND=-1; +}; +#endif diff --git a/WifiInterface.cpp b/WifiInterface.cpp index f027ea0..c751cab 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -23,26 +23,75 @@ #include "DIAG.h" #include "StringFormatter.h" #include "WiThrottle.h" - +#include "WifiInboundHandler.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"; -const char PROGMEM PROMPT_SEARCH[] = ">"; const char PROGMEM SEND_OK_SEARCH[] = "\r\nSEND OK\r\n"; const char PROGMEM IPD_SEARCH[] = "+IPD"; const unsigned long LOOP_TIMEOUT = 2000; bool WifiInterface::connected = false; -bool WifiInterface::closeAfter = false; -DCCEXParser WifiInterface::parser; -byte WifiInterface::loopstate = 0; -unsigned long WifiInterface::loopTimeoutStart = 0; -int WifiInterface::datalength = 0; -int WifiInterface::connectionId; -byte WifiInterface::buffer[MAX_WIFI_BUFFER+1]; -MemStream * WifiInterface::streamer; -Stream * WifiInterface::wifiStream = NULL; -HTTP_CALLBACK WifiInterface::httpCallback = 0; +Stream * WifiInterface::wifiStream; + + +//////////////////////////////////////////////////////////////////////////////// +// +// Figure out number of serial ports depending on hardware +// +#if defined(ARDUINO_AVR_UNO) +#define NUM_SERIAL 0 +#endif + +#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) +#define NUM_SERIAL 3 +#endif + +#ifndef NUM_SERIAL +#define NUM_SERIAL 1 +#endif + +bool WifiInterface::setup(long serial_link_speed, + const __FlashStringHelper *wifiESSID, + const __FlashStringHelper *wifiPassword, + const __FlashStringHelper *hostname, + const int port) { + + bool wifiUp = false; + +#if NUM_SERIAL == 0 + // no warning about unused parameters. + (void) serial_link_speed; + (void) wifiESSID; + (void) wifiPassword; + (void) hostname; + (void) port; +#endif + +#if NUM_SERIAL > 0 + Serial1.begin(serial_link_speed); + wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port); +#endif + +// Other serials are tried, depending on hardware. +#if NUM_SERIAL > 1 + if (!wifiUp) + { + Serial2.begin(serial_link_speed); + wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port); + } +#endif + +#if NUM_SERIAL > 2 + if (!wifiUp) + { + Serial3.begin(serial_link_speed); + wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port); + } +#endif + +return wifiUp; +} bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password, const __FlashStringHelper* hostname, int port) { @@ -59,9 +108,10 @@ bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo checkForOK(200, OK_SEARCH, true); } - streamer=new MemStream(buffer, MAX_WIFI_BUFFER); - parser.setAtCommandCallback(ATCommand); - + + DCCEXParser::setAtCommandCallback(ATCommand); + WifiInboundHandler::setup(wifiStream); + DIAG(F("\n++ Wifi Setup %S ++\n"), connected ? F("OK") : F("FAILED")); return connected; } @@ -78,7 +128,7 @@ bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringH // If there is, just shortcut the setup and continue to read the data as normal. if (checkForOK(200,IPD_SEARCH, true)) { DIAG(F("\nPreconfigured Wifi already running with data waiting\n")); - loopstate=4; // carry on from correct place + // loopstate=4; // carry on from correct place... or not as the case may be return true; } @@ -215,9 +265,7 @@ void WifiInterface::ATCommand(const byte * command) { } } -void WifiInterface::setHTTPCallback(HTTP_CALLBACK callback) { - httpCallback = callback; -} + bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor, bool echo, bool escapeEcho) { unsigned long startTime = millis(); @@ -244,162 +292,10 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor return false; } -bool WifiInterface::isHTTP() { - - // POST GET PUT PATCH DELETE - // You may think a simple strstr() is better... but not when ram & time is in short supply - switch (buffer[0]) { - case 'P': - if (buffer[1] == 'U' && buffer[2] == 'T' && buffer[3] == ' ' ) return true; - if (buffer[1] == 'O' && buffer[2] == 'S' && buffer[3] == 'T' && buffer[4] == ' ') return true; - if (buffer[1] == 'A' && buffer[2] == 'T' && buffer[3] == 'C' && buffer[4] == 'H' && buffer[5] == ' ') return true; - return false; - case 'G': - if (buffer[1] == 'E' && buffer[2] == 'T' && buffer[3] == ' ' ) return true; - return false; - case 'D': - if (buffer[1] == 'E' && buffer[2] == 'L' && buffer[3] == 'E' && buffer[4] == 'T' && buffer[5] == 'E' && buffer[6] == ' ') return true; - return false; - default: - return false; - } -} void WifiInterface::loop() { - if (!connected) return; - - WiThrottle::loop(); // check heartbeats - - // read anything into a buffer, collecting info on the way - while (loopstate != 99 && wifiStream->available()) { - int ch = wifiStream->read(); - - // echo the char to the diagnostic stream in escaped format - if (Diag::WIFI) StringFormatter::printEscape(ch); // DIAG in disguise - - switch (loopstate) { - case 0: // looking for +IPD - connectionId = 0; - if (ch == '+') loopstate = 1; - break; - case 1: // Looking for I in +IPD - loopstate = (ch == 'I') ? 2 : 0; - break; - case 2: // Looking for P in +IPD - loopstate = (ch == 'P') ? 3 : 0; - break; - case 3: // Looking for D in +IPD - loopstate = (ch == 'D') ? 4 : 0; - break; - case 4: // Looking for , After +IPD - loopstate = (ch == ',') ? 5 : 0; - break; - case 5: // reading connection id - if (ch == ',') loopstate = 6; - else connectionId = 10 * connectionId + (ch - '0'); - break; - 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 - break; - case 7: // reading data - 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 - loopstate = 99; - } - break; - - case 10: // Waiting for > so we can send reply - if (millis() - loopTimeoutStart > LOOP_TIMEOUT) { - if (Diag::WIFI) DIAG(F("\nWifi TIMEOUT on wait for > prompt or ERROR\n")); - loopstate = 0; // go back to +IPD - break; - } - if (ch == '>') { - // DIAG(F("\n> [%e]\n"),buffer); - wifiStream->print((char *) buffer); - loopTimeoutStart = millis(); - loopstate = closeAfter ? 11 : 0; - break; - } - if (ch == '.') { // busy during send, delay and retry - loopstate = 12; // look for SEND OK finished - break; - } - break; - case 11: // Waiting for SEND OK or ERROR to complete so we can closeAfter - if (millis() - loopTimeoutStart > LOOP_TIMEOUT) { - if (Diag::WIFI) DIAG(F("\nWifi TIMEOUT on wait for SEND OK or ERROR\n")); - loopstate = 0; // go back to +IPD - break; - } - if (ch == 'K') { // assume its in SEND OK - if (Diag::WIFI) DIAG(F("\n Wifi AT+CIPCLOSE=%d\r\n"), connectionId); - StringFormatter::send(wifiStream, F("AT+CIPCLOSE=%d\r\n"), connectionId); - loopstate = 0; // wait for +IPD - } - break; - - case 12: // Waiting for OK after send busy - if (ch == '+') { // Uh-oh IPD problem - if (Diag::WIFI) DIAG(F("\n\n Wifi ASYNC CLASH - LOST REPLY\n")); - connectionId = 0; - 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()); - loopTimeoutStart = millis(); - loopstate = 10; // non-blocking loop waits for > before sending - break; - } - break; - } // switch - } // while - if (loopstate != 99) return; - - // 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 - // 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. - - closeAfter = false; - - // Intercept HTTP requests - if (isHTTP()) { - 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; + if (connected) { + WiThrottle::loop(); + WifiInboundHandler::loop(); } - 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); - - if (streamer->available() == 0) { - // No reply - if (closeAfter) { - if (Diag::WIFI) DIAG(F("AT+CIPCLOSE=%d\r\n"), connectionId); - StringFormatter::send(wifiStream, F("AT+CIPCLOSE=%d\r\n"), connectionId); - } - loopstate = 0; // go back to waiting for +IPD - 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()); - loopTimeoutStart = millis(); - loopstate = 10; // non-blocking loop waits for > before sending } diff --git a/WifiInterface.h b/WifiInterface.h index ecf9005..b2bdd20 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -24,26 +24,26 @@ #include #include -typedef void (*HTTP_CALLBACK)(Print *stream, byte *cmd); - class WifiInterface { public: - static bool setup(Stream &setupStream, const __FlashStringHelper *SSSid, const __FlashStringHelper *password, - const __FlashStringHelper *hostname, int port); + static bool setup(long serial_link_speed, + const __FlashStringHelper *wifiESSID, + const __FlashStringHelper *wifiPassword, + const __FlashStringHelper *hostname, + const int port = 2560); static void loop(); static void ATCommand(const byte *command); - static void setHTTPCallback(HTTP_CALLBACK callback); private: + static bool setup(Stream &setupStream, const __FlashStringHelper *SSSid, const __FlashStringHelper *password, + const __FlashStringHelper *hostname, int port); 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; diff --git a/defines.h b/defines.h index 444c167..4a511c4 100644 --- a/defines.h +++ b/defines.h @@ -36,14 +36,3 @@ // 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