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 5f1f87e..462bd79 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -12,7 +12,6 @@ #include "config.h" #include "DCCEX.h" - //////////////////////////////////////////////////////////////// // // Enables an I2C 2x24 or 4x24 LCD Screen @@ -32,7 +31,6 @@ DCCEXParser serialParser; void setup() { - //////////////////////////////////////////// // // More display stuff. Need to put this in a .h file and make @@ -70,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. #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; - - 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 + 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. @@ -123,10 +98,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/DCC.cpp b/DCC.cpp index 29fa9d7..ca563ac 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -20,7 +20,9 @@ #include "DCC.h" #include "DCCWaveform.h" #include "DIAG.h" - +#include "EEStore.h" +#include "GITHUB_SHA.h" +#include "version.h" // This module is responsible for converting API calls into // messages to be sent to the waveform generator. @@ -45,6 +47,12 @@ __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)); + + // 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 8a96e23..4f4030c 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; @@ -168,9 +169,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 @@ -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; } @@ -617,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/EEStore.cpp b/EEStore.cpp index f52d1a1..a3d2d55 100644 --- a/EEStore.cpp +++ b/EEStore.cpp @@ -2,7 +2,7 @@ #include "Turnouts.h" #include "Sensors.h" #include "Outputs.h" - +#include "DIAG.h" #if defined(ARDUINO_ARCH_SAMD) ExternalEEPROM EEPROM; @@ -72,5 +72,15 @@ int EEStore::pointer(){ } /////////////////////////////////////////////////////////////////////////////// +void EEStore::dump(int num) { + byte b; + DIAG(F("\nAddr 0x char\n")); + 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 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;