diff --git a/WifiInboundHandler.cpp b/WifiInboundHandler.cpp index a509409..9d2dc95 100644 --- a/WifiInboundHandler.cpp +++ b/WifiInboundHandler.cpp @@ -1,25 +1,27 @@ /* - * © 2021 Fred Decker - * © 2021 Fred Decker - * © 2020-2021 Chris Harlow - * © 2020, Chris Harlow. All rights reserved. - * © 2020, Harald Barth. - * - * This file is part of Asbelos DCC API - * - * 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 . - */ + © 2022 Mark Muzzin + © 2021 Fred Decker + © 2021 Fred Decker + © 2020-2021 Chris Harlow + © 2020, Chris Harlow. All rights reserved. + © 2020, Harald Barth. + + This file is part of Asbelos DCC API + + 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 ARDUINO_AVR_UNO_WIFI_REV2 #include #include "WifiInboundHandler.h" @@ -30,7 +32,7 @@ WifiInboundHandler * WifiInboundHandler::singleton; void WifiInboundHandler::setup(Stream * ESStream) { - singleton=new WifiInboundHandler(ESStream); + singleton = new WifiInboundHandler(ESStream); } void WifiInboundHandler::loop() { @@ -39,221 +41,320 @@ void WifiInboundHandler::loop() { WifiInboundHandler::WifiInboundHandler(Stream * ESStream) { - wifiStream=ESStream; - clientPendingCIPSEND=-1; - inboundRing=new RingStream(INBOUND_RING); - outboundRing=new RingStream(OUTBOUND_RING); - pendingCipsend=false; -} + wifiStream = ESStream; + clientPendingCIPSEND = -1; + inboundRing = new RingStream(INBOUND_RING); + outboundRing = new RingStream(OUTBOUND_RING); + cipSendStatus = CIP_SEND_NONE; +} // 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 because they will block the sending - if (loop2()!=INBOUND_IDLE) return; +// Other input returns +void WifiInboundHandler::loop1() +{ + // First handle all inbound traffic events because they will block the sending + if (loop2() != INBOUND_IDLE) return; - WiThrottle::loop(outboundRing); - - // if nothing is already CIPSEND pending, we can CIPSEND one reply - if (clientPendingCIPSEND<0) { - clientPendingCIPSEND=outboundRing->read(); - if (clientPendingCIPSEND>=0) { - currentReplySize=outboundRing->count(); - pendingCipsend=true; - } - } - + WiThrottle::loop(outboundRing); - if (pendingCipsend) { - if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize); - StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize); - pendingCipsend=false; - return; + // If no sends are pending we can check if there is any more data to send + if (cipSendStatus == CIP_SEND_NONE) { + + // If nothing is already CIPSEND pending, we can CIPSEND one reply + if (clientPendingCIPSEND < 0) { + + //Read the next character in the outbound ring. If its -1, no data in the ring + clientPendingCIPSEND = outboundRing->read(); + + //If there is data in the ring + if (clientPendingCIPSEND >= 0 ) { + if (Diag::WIFI) DIAG( F("cipSendStatus = CIP_SEND_PENDING")); + //Get the length of the reply to send + currentReplySize = outboundRing->count(); + //Set the pending CIPSEND flag + cipSendStatus = CIP_SEND_PENDING; } - - - // if something waiting to execute, we can call it - int clientId=inboundRing->read(); - if (clientId>=0) { - int count=inboundRing->count(); - if (Diag::WIFI) DIAG(F("Wifi EXEC: %d %d:"),clientId,count); - byte cmd[count+1]; - for (int i=0;iread(); - cmd[count]=0; - if (Diag::WIFI) DIAG(F("%e"),cmd); - - outboundRing->mark(clientId); // remember start of outbound data - CommandDistributor::parse(clientId,cmd,outboundRing); - // The commit call will either write the lenbgth bytes - // OR rollback to the mark because the reply is empty or commend generated more than fits the buffer - if (!outboundRing->commit()) { - DIAG(F("OUTBOUND FULL processing cmd:%s"),cmd); - } - return; - } - } + } + } + if (cipSendStatus == CIP_SEND_PENDING) { + if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize); + StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize); + if (Diag::WIFI) DIAG( F("pendingCipsend = CIP_SEND_SENT")); + cipSendStatus = CIP_SEND_SENT; + return; + } + // if something waiting to execute, we can call it + int clientId = inboundRing->read(); + if (clientId >= 0) { + int count = inboundRing->count(); + if (Diag::WIFI) DIAG(F("Wifi EXEC: %d %d:"), clientId, count); + byte cmd[count + 1]; + for (int i = 0; i < count; i++) cmd[i] = inboundRing->read(); + cmd[count] = 0; + if (Diag::WIFI) DIAG(F("%e"), cmd); -// This is a Finite State Automation (FSA) handling the inbound bytes from an ES AT command processor + outboundRing->mark(clientId); // remember start of outbound data + CommandDistributor::parse(clientId, cmd, outboundRing); + // The commit call will either write the length bytes + // OR rollback to the mark because the reply is empty or commend generated more than fits the buffer + if (outboundRing->commit() == false) { + DIAG(F("OUTBOUND FULL processing cmd:%s"), cmd); + outboundRing->flush(); + inboundRing->flush(); + } + return; + } +} + +// Fixed length atoi +int WifiInboundHandler::antoi(char* str, int len) { + int res = 0; + + for (int i = 0; i != len; ++i) + res = res * 10 + str[i] - '0'; + + return res; +} + +// This loop processes the inbound bytes from an ES AT command processor WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { - while (wifiStream->available()) { - 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 + INBOUND_STATE retVal = INBOUND_IDLE; + char *stringStartPos = NULL; + char *stringEndPos = NULL; + char ch = 0; + int i; + bool exitLoop = false; + + //Main message input loop + //This loop fills the small receive buffer one message at a time + //The execption is the +IPD message. Only the data up to the ':' character (end of length) + //is put in this buffer. The actual +IPD data is written directly into the inbound buffer + while (!exitLoop) + { + //We exit this loop either if there is no data available, or we have a complete message to process + while (wifiStream->available() && messageToProcess == RECV_MSG_NONE) + { + //Read next character + ch = wifiStream->read(); + + //Add to receive buffer if there is space + if (recBufferPos < INBOUND_CMD_BUFFER) { + recBuffer[recBufferPos++] = ch; + } + else { + if (Diag::WIFI) DIAG(F("WARN: Message too long for INBOUND BUFFER, purging")); + memset(recBuffer, 0, INBOUND_CMD_BUFFER); + recBufferPos = 0; + } + + if (Diag::WIFI) StringFormatter::printEscape(ch); + + //Process data ending in \r\n + if ((recBuffer[recBufferPos - 2] == '\r') && (recBuffer[recBufferPos - 1] == '\n')) { + messageToProcess = RECV_MSG_CRLF; + } + + //Ready to send data, will always be the first char in the message + else if (recBuffer[0] == '>') { + messageToProcess = RECV_MSG_SND_DATA; + } + + //IPD message + else if ((stringStartPos = strstr(recBuffer, "+IPD,")) != NULL && (stringEndPos = strstr(recBuffer, ":")) != NULL) { + //Advance the start string to the beginning of the length + stringStartPos += strlen("+IPD,"); + + //Get the end of the client ID + stringEndPos = strstr(stringStartPos, ","); + + //Get the client Id + runningClientId = antoi(stringStartPos, stringEndPos - stringStartPos); + + //Get the data length + stringStartPos = stringEndPos + 1; + stringEndPos = strstr(stringStartPos, ":"); + dataLength = antoi(stringStartPos, stringEndPos - stringStartPos); + + if(dataLength > 0) { + //Set the remaining data length + dataRemaining = dataLength; + messageToProcess = RECV_MSG_IPD_MSG; + } + } } - switch (loopState) { - case ANYTHING: // looking for +IPD, > , busy , n,CONNECTED, n,CLOSED, ERROR, SEND OK - - if (ch == '+') { - loopState = IPD; - break; + //Process messages + switch (messageToProcess) { + case RECV_MSG_NONE: + //Check if the buffer position is not zero + if (recBufferPos != 0) { + //No message to process, but buffer has some data already + retVal = INBOUND_BUSY; } - - if (ch=='>') { - if (Diag::WIFI) DIAG(F("[XMIT %d]"),currentReplySize); - for (int i=0;iread(); - wifiStream->write(cout); - if (Diag::WIFI) StringFormatter::printEscape(cout); // DIAG in disguise - } - clientPendingCIPSEND=-1; - pendingCipsend=false; - loopState=SKIPTOEND; - break; + else { + //No message to process, no data in buffer + retVal = INBOUND_IDLE; } - - if (ch=='R') { // Received ... bytes - loopState=SKIPTOEND; - break; - } - - if (ch=='S') { // SEND OK probably - loopState=SKIPTOEND; - break; - } - - if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND - pendingCipsend=(clientPendingCIPSEND>=0); - loopState=SKIPTOEND; - break; - } - - if (ch>='0' && ch<='9') { - runningClientId=ch-'0'; - loopState=GOT_CLIENT_ID; - break; + exitLoop = true; + break; + + case RECV_MSG_CRLF: + + //Check for a SEND FAIL message + if (strstr(recBuffer, "SEND FAIL") != NULL) { + if (Diag::WIFI) DIAG(F("[SEND FAIL - detected")); + if (cipSendStatus == CIP_SEND_DATA_SENT) { + if (Diag::WIFI) DIAG( F("pendingCipsend = CIP_SEND_NONE")); + cipSendStatus = CIP_SEND_NONE; + } } - if (ch=='E' || ch=='l') { // ERROR or "link is not valid" - if (clientPendingCIPSEND>=0) { + //Check for a SEND OK message + else if (strstr(recBuffer, "SEND OK") != NULL) { + if (Diag::WIFI) DIAG(F("[SEND OK - detected")); + if (cipSendStatus == CIP_SEND_DATA_SENT) { + if (Diag::WIFI) DIAG( F("pendingCipsend = CIP_SEND_NONE")); + cipSendStatus = CIP_SEND_NONE; + } + } + + //Check for an error + else if (strstr(recBuffer, "ERROR") != NULL) { + if (clientPendingCIPSEND >= 0) { + if (Diag::WIFI) DIAG(F("[ERROR detected - purging CIPSEND")); // A CIPSEND was errored... just toss it away - purgeCurrentCIPSEND(); + purgeCurrentCIPSEND(); } - loopState=SKIPTOEND; - 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 <='9'){ - runningClientId=ch-'0'; - loopState=IPD5; + + //Check for an tcp connection closed + else if (strstr(recBuffer, "link is not valid") != NULL) { + if (clientPendingCIPSEND >= 0) { + if (Diag::WIFI) DIAG(F("['link is not valid' detected - purging CIPSEND]")); + purgeCurrentCIPSEND(); + } } - else loopState=SKIPTOEND; break; - - case IPD5: // Looking for , After +IPD,client - loopState = (ch == ',') ? IPD6_LENGTH : SKIPTOEND; - dataLength=0; // ready to start collecting the length + + case RECV_MSG_SND_DATA: + if (Diag::WIFI) DIAG(F("[XMIT %d]"), currentReplySize); + for (i = 0; i < currentReplySize; i++) + { + //Read data from the outboundRing and write to to Wifi + int cout = outboundRing->read(); + wifiStream->write(cout); + if (Diag::WIFI) StringFormatter::printEscape(cout); // DIAG in disguise + } + + //Set CIPSEND flag to -1 + clientPendingCIPSEND = -1; + if (Diag::WIFI) DIAG( F("pendingCipsend = CIP_SEND_DATA_SENT")); + cipSendStatus = CIP_SEND_DATA_SENT; break; - - case IPD6_LENGTH: // reading for length - if (ch == ':') { - if (dataLength==0) { - loopState=ANYTHING; - break; - } - if (Diag::WIFI) DIAG(F("Wifi inbound data(%d:%d):"),runningClientId,dataLength); - if (inboundRing->freeSpace()<=(dataLength+1)) { - // This input would overflow the inbound ring, ignore it - loopState=IPD_IGNORE_DATA; - if (Diag::WIFI) DIAG(F("Wifi OVERFLOW IGNORING:")); - break; - } + + case RECV_MSG_IPD_MSG: + + //Mark the location with the client ID + if (dataRemaining == dataLength) { + if (Diag::WIFI) DIAG(F("Wifi inbound data(%d:%d):"), runningClientId, dataLength); inboundRing->mark(runningClientId); - loopState=IPD_DATA; - break; } - dataLength = dataLength * 10 + (ch - '0'); - break; - - case IPD_DATA: // reading data - inboundRing->write(ch); - dataLength--; - if (dataLength == 0) { - inboundRing->commit(); - loopState = ANYTHING; + + while (wifiStream->available()) { + //Read next character (data byte) + ch = wifiStream->read(); + if (Diag::WIFI) DIAG(F("[+IPD %d]"), dataRemaining); + if (Diag::WIFI) StringFormatter::printEscape(ch); + dataRemaining--; + + //Check if we would overflow the inbound ring + if (inboundRing->freeSpace() <= (dataLength + 1)) + { + DIAG(F("Wifi OVERFLOW IGNORING:")); + + //If we have an overflow, flush the remaining bytes + do{ + ch = wifiStream->read(); + }while(dataRemaining--); + + outboundRing->flush(); + inboundRing->flush(); + + messageToProcess = RECV_MSG_IPD_DONE; + break; + } + else + { + // Write data to inbound ring + inboundRing->write(ch); + + // Commit inbound ring data + if (dataRemaining == 0) { + if (Diag::WIFI) DIAG(F("COMMIT")); + if(inboundRing->commit() == false) { + outboundRing->flush(); + inboundRing->flush(); + } + messageToProcess = RECV_MSG_IPD_DONE; + break; + } + } } break; - case IPD_IGNORE_DATA: // ignoring data that would not fit in inbound ring - dataLength--; - if (dataLength == 0) 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," - if (ch=='C') { - // got "x C" before CLOSE or CONNECTED, or CONNECT FAILED - if (runningClientId==clientPendingCIPSEND) purgeCurrentCIPSEND(); - else CommandDistributor::forget(runningClientId); + //Catch-all for messages we don't care about + default: + //Check if the buffer position is not zero + if (recBufferPos != 0) + { + //In process of receiving, return with busy so we can wait for the remaining part of the message + retVal = INBOUND_BUSY; + } + else { + // Not receiving a message so this message was not important + messageToProcess = RECV_MSG_GENERIC; } - loopState=SKIPTOEND; break; - - case SKIPTOEND: // skipping for /n - if (ch=='\n') loopState=ANYTHING; - break; - } // switch - } // available - return (loopState==ANYTHING) ? INBOUND_IDLE: INBOUND_BUSY; + } + + //If we processed a message, clear and get ready for the next message + if (messageToProcess != RECV_MSG_NONE && messageToProcess != RECV_MSG_IPD_MSG) { + //Reset buffer to start processing next message + memset(recBuffer, 0, INBOUND_CMD_BUFFER); + //Reset receive buffer position + recBufferPos = 0; + //Reset message to process + messageToProcess = RECV_MSG_NONE; + } + } + + return retVal; } void WifiInboundHandler::purgeCurrentCIPSEND() { - // A CIPSEND was sent but errored... or the client closed just toss it away - CommandDistributor::forget(clientPendingCIPSEND); - DIAG(F("Wifi: DROPPING CIPSEND=%d,%d"),clientPendingCIPSEND,currentReplySize); - for (int i=0;iread(); - pendingCipsend=false; - clientPendingCIPSEND=-1; + // A CIPSEND was sent but errored... or the client closed just toss it away + //CommandDistributor::forget(clientPendingCIPSEND); + DIAG(F("Wifi: DROPPING CIPSEND=%d,%d"), clientPendingCIPSEND, currentReplySize); + for (int i = 0; i < currentReplySize; i++) { + outboundRing->read(); + } + + //If CIP SEND was sent, flush out by writing 0s */ + if (cipSendStatus == CIP_SEND_SENT) { + for (int i = 0; i < currentReplySize; i++) { + int c = 0; + wifiStream->write(c); + } + } + + cipSendStatus = CIP_SEND_NONE; + clientPendingCIPSEND = -1; } #endif diff --git a/WifiInboundHandler.h b/WifiInboundHandler.h index d3410cd..143d6e3 100644 --- a/WifiInboundHandler.h +++ b/WifiInboundHandler.h @@ -1,24 +1,25 @@ /* - * © 2021 Harald Barth - * © 2021 Fred Decker - * (c) 2021 Fred Decker. All rights reserved. - * (c) 2020 Chris Harlow. All rights reserved. - * - * 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 . - */ + © 2022 Mark Muzzin + © 2021 Harald Barth + © 2021 Fred Decker + (c) 2021 Fred Decker. All rights reserved. + (c) 2020 Chris Harlow. All rights reserved. + + 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 . +*/ #ifndef WifiInboundHandler_h #define WifiInboundHandler_h @@ -27,57 +28,59 @@ #include "DIAG.h" class WifiInboundHandler { - public: - static void setup(Stream * ESStream); - static void loop(); - - private: + public: + static void setup(Stream * ESStream); + static void loop(); - static WifiInboundHandler * singleton; - - - enum INBOUND_STATE : byte { - INBOUND_BUSY, // keep calling in loop() - INBOUND_IDLE // Nothing happening, outbound may xcall CIPSEND - }; + private: - enum LOOP_STATE : byte { - 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 - IPD_IGNORE_DATA, // got +IPD,c,ll,: ignoring the data that won't fit inblound Ring + static WifiInboundHandler * singleton; - GOT_CLIENT_ID, // clientid prefix to CONNECTED / CLOSED - GOT_CLIENT_ID2 // clientid prefix to CONNECTED / CLOSED - }; + enum INBOUND_STATE : byte { + INBOUND_BUSY, // Keep calling in loop() + INBOUND_IDLE // Nothing happening, outbound may xcall CIPSEND + }; - - WifiInboundHandler(Stream * ESStream); - void loop1(); - INBOUND_STATE loop2(); - void purgeCurrentCIPSEND(); - Stream * wifiStream; - - static const int INBOUND_RING = 512; - static const int OUTBOUND_RING = 2048; - - RingStream * inboundRing; - RingStream * outboundRing; - - LOOP_STATE loopState=ANYTHING; - int runningClientId; // latest client inbound processing data or CLOSE - int dataLength; // dataLength of +IPD - int clientPendingCIPSEND=-1; - int currentReplySize; - bool pendingCipsend; + enum RECV_MSG_STATE : byte { + RECV_MSG_NONE, // No message to process + RECV_MSG_CRLF, // Message ending in CRLF + RECV_MSG_SND_DATA, // Send data message + RECV_MSG_IPD_MSG, // +IPD Message in progress + RECV_MSG_IPD_DONE, // +IPD Message done + RECV_MSG_GENERIC // Catchall for incoming messages not used + }; + + enum CIP_SEND_STATUS : byte { + CIP_SEND_NONE, + CIP_SEND_PENDING, + CIP_SEND_SENT, + CIP_SEND_DATA_SENT + }; + + WifiInboundHandler(Stream * ESStream); + void loop1(); + INBOUND_STATE loop2(); + void purgeCurrentCIPSEND(); + int antoi(char* str, int len); + Stream * wifiStream; + + static const int INBOUND_CMD_BUFFER = 32; + static const int INBOUND_RING = 512; + static const int OUTBOUND_RING = 2048; + + RingStream * inboundRing; + RingStream * outboundRing; + + RECV_MSG_STATE messageToProcess = RECV_MSG_NONE; // Track the current message to process + + int runningClientId; // latest client inbound processing data or CLOSE + int dataLength; // dataLength of +IPD + int dataRemaining = 0; // remaining +IPD data to receive + int clientPendingCIPSEND = -1; + int currentReplySize; + CIP_SEND_STATUS cipSendStatus; + char recBuffer[INBOUND_CMD_BUFFER] = {'\0'}; + int recBufferPos = 0; + int recBufferWatermark = 0; }; #endif