1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-26 17:46:14 +01:00

Merge branch 'master' into NetworkIntegration

This commit is contained in:
Gregor Baues 2020-10-28 13:32:31 +01:00
commit fe274cd059
14 changed files with 306 additions and 182 deletions

View File

@ -22,51 +22,10 @@
DCCEXParser * CommandDistributor::parser=0; DCCEXParser * CommandDistributor::parser=0;
bool CommandDistributor::parse(byte clientId,byte * buffer, Print * streamer) { void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * streamer) {
if (buffer[0] == '<') {
// 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(); if (!parser) parser = new DCCEXParser();
parser->parse(streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async 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); 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;

View File

@ -19,19 +19,13 @@
#ifndef CommandDistributor_h #ifndef CommandDistributor_h
#define CommandDistributor_h #define CommandDistributor_h
#include "DCCEXParser.h" #include "DCCEXParser.h"
#include "RingStream.h"
typedef void (*HTTP_CALLBACK)(Print *stream, byte *cmd);
class CommandDistributor { class CommandDistributor {
public : public :
static void setHTTPCallback(HTTP_CALLBACK callback); static void parse(byte clientId,byte* buffer, RingStream * streamer);
static bool parse(byte clientId,byte* buffer, Print * streamer);
private: private:
static HTTP_CALLBACK httpCallback;
static bool isHTTP(byte * buffer);
static DCCEXParser * parser; static DCCEXParser * parser;
}; };

View File

@ -155,6 +155,15 @@ int DCC::changeFn( int cab, byte functionNumber, bool pressed) {
return funcstate; return funcstate;
} }
int DCC::getFn( int cab, byte functionNumber) {
if (cab<=0 || functionNumber>28) return -1; // unknown
int reg = lookupSpeedTable(cab);
if (reg<0) return -1;
unsigned long funcmask = (1UL<<functionNumber);
return (speedTable[reg].functions & funcmask)? 1 : 0;
}
// Set the group flag to say we have touched the particular group. // Set the group flag to say we have touched the particular group.
// A group will be reminded only if it has been touched. // A group will be reminded only if it has been touched.
void DCC::updateGroupflags(byte & flags, int functionNumber) { void DCC::updateGroupflags(byte & flags, int functionNumber) {

1
DCC.h
View File

@ -72,6 +72,7 @@ public:
static void setFunction(int cab, byte fByte, byte eByte); static void setFunction(int cab, byte fByte, byte eByte);
static void setFn(int cab, byte functionNumber, bool on); static void setFn(int cab, byte functionNumber, bool on);
static int changeFn(int cab, byte functionNumber, bool pressed); static int changeFn(int cab, byte functionNumber, bool pressed);
static int getFn(int cab, byte functionNumber);
static void updateGroupflags(byte &flags, int functionNumber); static void updateGroupflags(byte &flags, int functionNumber);
static void setAccessory(int aAdd, byte aNum, bool activate); static void setAccessory(int aAdd, byte aNum, bool activate);
static bool writeTextPacket(byte *b, int nBytes); static bool writeTextPacket(byte *b, int nBytes);

99
RingStream.cpp Normal file
View File

@ -0,0 +1,99 @@
/*
* © 2020, 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 <https://www.gnu.org/licenses/>.
*/
#include "RingStream.h"
#include "DIAG.h"
RingStream::RingStream( const uint16_t len)
{
_len=len;
_buffer=new byte[len];
_pos_write=0;
_pos_read=0;
_buffer[0]=0;
_overflow=false;
_mark=0;
_count=0;
}
size_t RingStream::write(uint8_t b) {
if (_overflow) return 0;
_buffer[_pos_write] = b;
++_pos_write;
if (_pos_write==_len) _pos_write=0;
if (_pos_write==_pos_read) {
_overflow=true;
return 0;
}
_count++;
return 1;
}
int RingStream::read() {
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
byte b=_buffer[_pos_read];
_pos_read++;
if (_pos_read==_len) _pos_read=0;
_overflow=false;
return b;
}
int RingStream::count() {
return (read()<<8) | read();
}
int RingStream::freeSpace() {
// allow space for client flag and length bytes
if (_pos_read>_pos_write) return _pos_read-_pos_write-3;
else return _len - _pos_write + _pos_read-3;
}
// mark start of message with client id (0...9)
void RingStream::mark(uint8_t b) {
_mark=_pos_write;
write(b); // client id
write((uint8_t)0); // count MSB placemarker
write((uint8_t)0); // count LSB placemarker
_count=0;
}
bool RingStream::commit() {
if (_overflow) {
DIAG(F("\nRingStream(%d) commit(%d) OVERFLOW\n"),_len, _count);
// just throw it away
_pos_write=_mark;
_overflow=false;
return false; // commit failed
}
if (_count==0) {
// ignore empty response
_pos_write=_mark;
return true; // true=commit ok
}
// Go back to the _mark and inject the count 1 byte later
_mark++;
if (_mark==_len) _mark=0;
_buffer[_mark]=highByte(_count);
_mark++;
if (_mark==_len) _mark=0;
_buffer[_mark]=lowByte(_count);
return true; // commit worked
}

47
RingStream.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef RingStream_h
#define RingStream_h
/*
* © 2020, 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 <https://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
class RingStream : public Print {
public:
RingStream( const uint16_t len);
virtual size_t write(uint8_t b);
using Print::write;
int read();
int count();
int freeSpace();
void mark(uint8_t b);
bool commit();
private:
int _len;
int _pos_write;
int _pos_read;
bool _overflow;
int _mark;
int _count;
byte * _buffer;
};
#endif

View File

@ -47,9 +47,16 @@ void StringFormatter::diag( const __FlashStringHelper* input...) {
} }
void StringFormatter::lcd(byte row, const __FlashStringHelper* input...) { void StringFormatter::lcd(byte row, const __FlashStringHelper* input...) {
va_list args;
// Issue the LCD as a diag first
diag(F("\nLCD%d:"),row);
va_start(args, input);
send2(diagSerial,input,args);
diag(F("\n"));
if (!LCDDisplay::lcdDisplay) return; if (!LCDDisplay::lcdDisplay) return;
LCDDisplay::lcdDisplay->setRow(row); LCDDisplay::lcdDisplay->setRow(row);
va_list args;
va_start(args, input); va_start(args, input);
send2(LCDDisplay::lcdDisplay,input,args); send2(LCDDisplay::lcdDisplay,input,args);
} }

View File

@ -99,17 +99,9 @@ WiThrottle::~WiThrottle() {
} }
} }
void WiThrottle::parse(Print & stream, byte * cmdx) { void WiThrottle::parse(RingStream * stream, byte * cmdx) {
// we have to take a copy of the cmd buffer as the reply will get built into the cmdx byte * cmd=cmdx;
byte local[150];
for (byte i=0;i<sizeof(local)-1;i++) {
local[i]=cmdx[i];
if (!cmdx[i]) break;
}
local[149]='\0'; // prevent runaway parser
byte * cmd=local;
heartBeat=millis(); heartBeat=millis();
if (Diag::WITHROTTLE) DIAG(F("\n%l WiThrottle(%d)<-[%e]\n"),millis(),clientid,cmd); if (Diag::WITHROTTLE) DIAG(F("\n%l WiThrottle(%d)<-[%e]\n"),millis(),clientid,cmd);
@ -213,7 +205,7 @@ int WiThrottle::getLocoId(byte * cmd) {
if (cmd[0]!='L' && cmd[0]!='S') return 0; // should not match any locos if (cmd[0]!='L' && cmd[0]!='S') return 0; // should not match any locos
return getInt(cmd+1); return getInt(cmd+1);
} }
void WiThrottle::multithrottle(Print & stream, byte * cmd){ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
char throttleChar=cmd[1]; char throttleChar=cmd[1];
int locoid=getLocoId(cmd+3); // -1 for * int locoid=getLocoId(cmd+3); // -1 for *
byte * aval=cmd; byte * aval=cmd;
@ -239,8 +231,11 @@ void WiThrottle::multithrottle(Print & stream, byte * cmd){
myLocos[loco].throttle=throttleChar; myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid; myLocos[loco].cab=locoid;
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
// TODO... get known Fn states from DCC (need memoryStream improvements to handle data length) //Get known Fn states from DCC
// for(fKey=0; fKey<29; fKey++)StringFormatter::send(stream,F("M%cA%c<;>F0&s\n"),throttleChar,cmd[3],fkey); for(int fKey=0; fKey<=28; fKey++) {
int fstate=DCC::getFn(locoid,fKey);
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c<;>F%d%d\n"),throttleChar,cmd[3],fstate,fKey);
}
StringFormatter::send(stream, F("M%cA%c%d<;>V%d\n"), throttleChar, cmd[3], locoid, DCCToWiTSpeed(DCC::getThrottleSpeed(locoid))); StringFormatter::send(stream, F("M%cA%c%d<;>V%d\n"), throttleChar, cmd[3], locoid, DCCToWiTSpeed(DCC::getThrottleSpeed(locoid)));
StringFormatter::send(stream, F("M%cA%c%d<;>R%d\n"), throttleChar, cmd[3], locoid, DCC::getThrottleDirection(locoid)); StringFormatter::send(stream, F("M%cA%c%d<;>R%d\n"), throttleChar, cmd[3], locoid, DCC::getThrottleDirection(locoid));
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128 StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
@ -261,7 +256,7 @@ void WiThrottle::multithrottle(Print & stream, byte * cmd){
} }
} }
void WiThrottle::locoAction(Print & stream, byte* aval, char throttleChar, int cab){ void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar, int cab){
// Note cab=-1 for all cabs in the consist called throttleChar. // Note cab=-1 for all cabs in the consist called throttleChar.
// DIAG(F("\nLoco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab); // DIAG(F("\nLoco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab);
switch (aval[0]) { switch (aval[0]) {
@ -339,10 +334,19 @@ int WiThrottle::WiTToDCCSpeed(int WiTSpeed) {
return WiTSpeed + 1; //offset others by 1 return WiTSpeed + 1; //offset others by 1
} }
void WiThrottle::loop() { void WiThrottle::loop(RingStream * stream) {
// for each WiThrottle, check the heartbeat // for each WiThrottle, check the heartbeat
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
wt->checkHeartbeat(); wt->checkHeartbeat();
// TODO... any broadcasts to be done
(void)stream;
/* MUST follow this model in this loop.
* stream->mark();
* send 1 digit client id, and any data
* stream->commit()
*/
} }
void WiThrottle::checkHeartbeat() { void WiThrottle::checkHeartbeat() {

View File

@ -19,6 +19,7 @@
#ifndef WiThrottle_h #ifndef WiThrottle_h
#define WiThrottle_h #define WiThrottle_h
#include "RingStream.h"
struct MYLOCO { struct MYLOCO {
char throttle; //indicates which throttle letter on client, often '0','1' or '2' char throttle; //indicates which throttle letter on client, often '0','1' or '2'
@ -27,8 +28,8 @@ struct MYLOCO {
class WiThrottle { class WiThrottle {
public: public:
static void loop(); static void loop(RingStream * stream);
void parse(Print & stream, byte * cmd); void parse(RingStream * stream, byte * cmd);
static WiThrottle* getThrottle( int wifiClient); static WiThrottle* getThrottle( int wifiClient);
static bool annotateLeftRight; static bool annotateLeftRight;
private: private:
@ -56,9 +57,9 @@ class WiThrottle {
bool lastPowerState; // last power state sent to this client bool lastPowerState; // last power state sent to this client
int DCCToWiTSpeed(int DCCSpeed); int DCCToWiTSpeed(int DCCSpeed);
int WiTToDCCSpeed(int WiTSpeed); int WiTToDCCSpeed(int WiTSpeed);
void multithrottle(Print & stream, byte * cmd); void multithrottle(RingStream * stream, byte * cmd);
void locoAction(Print & stream, byte* aval, char throttleChar, int cab); void locoAction(RingStream * stream, byte* aval, char throttleChar, int cab);
void accessory(Print & stream, byte* cmd); void accessory(RingStream *, byte* cmd);
void checkHeartbeat(); void checkHeartbeat();
}; };
#endif #endif

View File

@ -1,5 +1,6 @@
#include <Arduino.h> #include <Arduino.h>
#include "WifiInboundHandler.h" #include "WifiInboundHandler.h"
#include "RingStream.h"
#include "CommandDistributor.h" #include "CommandDistributor.h"
#include "DIAG.h" #include "DIAG.h"
@ -16,14 +17,10 @@ void WifiInboundHandler::loop() {
WifiInboundHandler::WifiInboundHandler(Stream * ESStream) { WifiInboundHandler::WifiInboundHandler(Stream * ESStream) {
wifiStream=ESStream; wifiStream=ESStream;
for (int clientId=0;clientId<MAX_CLIENTS;clientId++) {
clientStatus[clientId]=UNUSED;
// Note buffer is 1 byte longer than MemStream is told
// so that we can always inject a '\0' at stream->available()
clientBuffer[clientId]=new byte[MAX_WIFI_BUFFER+1];
clientStream[clientId]=new MemStream(clientBuffer[clientId], MAX_WIFI_BUFFER);
}
clientPendingCIPSEND=-1; clientPendingCIPSEND=-1;
inboundRing=new RingStream(INBOUND_RING);
outboundRing=new RingStream(OUTBOUND_RING);
pendingCipsend=false;
} }
@ -31,38 +28,48 @@ WifiInboundHandler::WifiInboundHandler(Stream * ESStream) {
// +IPD,x,lll:data is stored in streamer[x] // +IPD,x,lll:data is stored in streamer[x]
// Other input returns // Other input returns
void WifiInboundHandler::loop1() { void WifiInboundHandler::loop1() {
// First handle all inbound traffic events because they will block the sending
// First handle all inbound traffic events if (loop2()!=INBOUND_IDLE) return;
if (loop2()!=INBOUND_IDLE) return;
WiThrottle::loop(outboundRing);
// if nothing is already CIPSEND pending, we can CIPSEND one reply // if nothing is already CIPSEND pending, we can CIPSEND one reply
if (clientPendingCIPSEND<0) { if (clientPendingCIPSEND<0) {
for (int clientId=0;clientId<MAX_CLIENTS;clientId++) { clientPendingCIPSEND=outboundRing->read();
if (clientStatus[clientId]==REPLY_PENDING) { if (clientPendingCIPSEND>=0) {
clientPendingCIPSEND=clientId; currentReplySize=outboundRing->count();
if (Diag::WIFI) DIAG( F("\nWiFi: [[CIPSEND=%d,%d]]"), clientId, clientStream[clientId]->available()); pendingCipsend=true;
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
if (pendingCipsend) {
for (int clientId=0;clientId<MAX_CLIENTS;clientId++) { if (Diag::WIFI) DIAG( F("\nWiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
if (clientStatus[clientId]==CLOSE_AFTER_SEND) { StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
if (Diag::WIFI) DIAG(F("AT+CIPCLOSE=%d\r\n"), clientId); pendingCipsend=false;
StringFormatter::send(wifiStream, F("AT+CIPCLOSE=%d\r\n"), clientId);
clientStatus[clientId]=UNUSED;
return; return;
} }
if (clientStatus[clientId]==READY_TO_PROCESS) {
processCommand(clientId);
// 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("\nWifi 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\n"),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
outboundRing->commit();
return; return;
} }
} }
}
// This is a Finite State Automation (FSA) handling the inbound bytes from an ES AT command processor // This is a Finite State Automation (FSA) handling the inbound bytes from an ES AT command processor
@ -78,7 +85,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
} }
switch (loopState) { switch (loopState) {
case ANYTHING: // looking for +IPD, > , busy , n,CONNECTED, n,CLOSED case ANYTHING: // looking for +IPD, > , busy , n,CONNECTED, n,CLOSED, ERROR, SEND OK
if (ch == '+') { if (ch == '+') {
loopState = IPD; loopState = IPD;
@ -86,10 +93,14 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
} }
if (ch=='>') { if (ch=='>') {
if (Diag::WIFI) DIAG(F("[[XMIT %d]]"),clientStream[clientPendingCIPSEND]->available()); if (Diag::WIFI) DIAG(F("[XMIT %d]"),currentReplySize);
wifiStream->write(clientBuffer[clientPendingCIPSEND], clientStream[clientPendingCIPSEND]->available()); for (int i=0;i<currentReplySize;i++) {
clientStatus[clientPendingCIPSEND]=clientCloseAfterReply[clientPendingCIPSEND]? CLOSE_AFTER_SEND: UNUSED; int cout=outboundRing->read();
wifiStream->write(cout);
if (Diag::WIFI) StringFormatter::printEscape(cout); // DIAG in disguise
}
clientPendingCIPSEND=-1; clientPendingCIPSEND=-1;
pendingCipsend=false;
loopState=SKIPTOEND; loopState=SKIPTOEND;
break; break;
} }
@ -98,21 +109,32 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
loopState=SKIPTOEND; loopState=SKIPTOEND;
break; break;
} }
if (ch=='S') { // SEND OK probably
loopState=SKIPTOEND;
break;
}
if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND
if (clientPendingCIPSEND>=0) { pendingCipsend=(clientPendingCIPSEND>=0);
clientStatus[clientPendingCIPSEND]=REPLY_PENDING;
clientPendingCIPSEND=-1;
}
loopState=SKIPTOEND; loopState=SKIPTOEND;
break; break;
} }
if (ch>='0' && ch<=('0'+MAX_CLIENTS)) { if (ch>='0' && ch<='9') {
runningClientId=ch-'0'; runningClientId=ch-'0';
loopState=GOT_CLIENT_ID; loopState=GOT_CLIENT_ID;
break; break;
} }
if (ch=='E' || ch=='l') { // ERROR or "link is not valid"
if (clientPendingCIPSEND>=0) {
// A CIPSEND was errored... just toss it away
purgeCurrentCIPSEND();
}
loopState=SKIPTOEND;
break;
}
break; break;
@ -133,7 +155,7 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
break; break;
case IPD4_CLIENT: // reading connection id case IPD4_CLIENT: // reading connection id
if (ch >= '0' || ch <('0'+MAX_CLIENTS)){ if (ch >= '0' || ch <='9'){
runningClientId=ch-'0'; runningClientId=ch-'0';
loopState=IPD5; loopState=IPD5;
} }
@ -151,8 +173,14 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
loopState=ANYTHING; loopState=ANYTHING;
break; break;
} }
clientStream[runningClientId]->flush(); // prepare streamer for input if (Diag::WIFI) DIAG(F("\nWifi inbound data(%d:%d):"),runningClientId,dataLength);
clientStatus[runningClientId]=INBOUND_ARRIVING; if (inboundRing->freeSpace()<=(dataLength+1)) {
// This input would overflow the inbound ring, ignore it
loopState=IPD_IGNORE_DATA;
if (Diag::WIFI) DIAG(F("\nWifi OVERFLOW IGNORING:"));
break;
}
inboundRing->mark(runningClientId);
loopState=IPD_DATA; loopState=IPD_DATA;
break; break;
} }
@ -160,32 +188,30 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
break; break;
case IPD_DATA: // reading data case IPD_DATA: // reading data
clientStream[runningClientId]->write(ch); // NOTE: The MemStream will throw away bytes that do not fit in the buffer. inboundRing->write(ch);
// This protects against buffer overflows even with things as innocent
// as a browser which send massive, irrlevent HTTP headers.
dataLength--; dataLength--;
if (dataLength == 0) { if (dataLength == 0) {
clientStatus[runningClientId]=READY_TO_PROCESS; inboundRing->commit();
loopState = ANYTHING; loopState = ANYTHING;
} }
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 case GOT_CLIENT_ID: // got x before CLOSE or CONNECTED
loopState=(ch==',') ? GOT_CLIENT_ID2: SKIPTOEND; loopState=(ch==',') ? GOT_CLIENT_ID2: SKIPTOEND;
break; break;
case GOT_CLIENT_ID2: // got "x," before CLOSE or CONNECTED case GOT_CLIENT_ID2: // got "x,"
loopState=(ch=='C') ? GOT_CLIENT_ID3: SKIPTOEND; if (ch=='C') {
// got "x C" before CLOSE or CONNECTED, or CONNECT FAILED
if (runningClientId==clientPendingCIPSEND) purgeCurrentCIPSEND();
}
loopState=SKIPTOEND;
break; break;
case GOT_CLIENT_ID3: // got "x C" before CLOSE or CONNECTED (which is ignored)
if(ch=='L') {
// CLOSE
clientStatus[runningClientId]=UNUSED;
if (runningClientId==clientPendingCIPSEND) clientPendingCIPSEND=-1;
}
loopState=SKIPTOEND;
break;
case SKIPTOEND: // skipping for /n case SKIPTOEND: // skipping for /n
if (ch=='\n') loopState=ANYTHING; if (ch=='\n') loopState=ANYTHING;
@ -195,24 +221,10 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
return (loopState==ANYTHING) ? INBOUND_IDLE: INBOUND_BUSY; return (loopState==ANYTHING) ? INBOUND_IDLE: INBOUND_BUSY;
} }
void WifiInboundHandler::purgeCurrentCIPSEND() {
void WifiInboundHandler::processCommand(byte clientId) { // A CIPSEND was sent but errored... or the client closed just toss it away
clientStatus[clientId]=PROCESSING; if (Diag::WIFI) DIAG(F("Wifi: DROPPING CIPSEND=%d,%d\n"),clientPendingCIPSEND,currentReplySize);
byte * buffer=clientBuffer[clientId]; for (int i=0;i<=currentReplySize;i++) outboundRing->read();
MemStream * streamer=clientStream[clientId]; pendingCipsend=false;
buffer[streamer->available()]='\0'; clientPendingCIPSEND=-1;
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;
}
} }

View File

@ -1,8 +1,8 @@
#ifndef WifiInboundHandler_h #ifndef WifiInboundHandler_h
#define WifiInboundHandler_h #define WifiInboundHandler_h
#include "MemStream.h" #include "RingStream.h"
#include "DCCEXParser.h" #include "WiThrottle.h"
#include "DIAG.h" #include "DIAG.h"
class WifiInboundHandler { class WifiInboundHandler {
@ -14,9 +14,7 @@ class WifiInboundHandler {
static WifiInboundHandler * singleton; static WifiInboundHandler * singleton;
static const byte MAX_CLIENTS=5;
static const byte MAX_WIFI_BUFFER=255;
enum INBOUND_STATE { enum INBOUND_STATE {
INBOUND_BUSY, // keep calling in loop() INBOUND_BUSY, // keep calling in loop()
INBOUND_IDLE // Nothing happening, outbound may xcall CIPSEND INBOUND_IDLE // Nothing happening, outbound may xcall CIPSEND
@ -35,38 +33,31 @@ class WifiInboundHandler {
IPD5, // got +IPD,c IPD5, // got +IPD,c
IPD6_LENGTH, // got +IPD,c, reading length IPD6_LENGTH, // got +IPD,c, reading length
IPD_DATA, // got +IPD,c,ll,: collecting data IPD_DATA, // got +IPD,c,ll,: collecting data
IPD_IGNORE_DATA, // got +IPD,c,ll,: ignoring the data that won't fit inblound Ring
GOT_CLIENT_ID, // clientid prefix to CONNECTED / CLOSED GOT_CLIENT_ID, // clientid prefix to CONNECTED / CLOSED
GOT_CLIENT_ID2, // clientid prefix to CONNECTED / CLOSED GOT_CLIENT_ID2, // clientid prefix to CONNECTED / CLOSED
GOT_CLIENT_ID3 // 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); WifiInboundHandler(Stream * ESStream);
void loop1(); void loop1();
INBOUND_STATE loop2(); INBOUND_STATE loop2();
void processCommand(byte clientId); void purgeCurrentCIPSEND();
Stream * wifiStream; Stream * wifiStream;
DCCEXParser *parser; static const int INBOUND_RING = 512;
static const int OUTBOUND_RING = 2048;
RingStream * inboundRing;
RingStream * outboundRing;
LOOP_STATE loopState=ANYTHING; LOOP_STATE loopState=ANYTHING;
int runningClientId; // latest client inbound processing data or CLOSE int runningClientId; // latest client inbound processing data or CLOSE
int dataLength; // dataLength of +IPD 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; int clientPendingCIPSEND=-1;
int currentReplySize;
bool pendingCipsend;
}; };
#endif #endif

View File

@ -22,7 +22,7 @@
#include <avr/pgmspace.h> #include <avr/pgmspace.h>
#include "DIAG.h" #include "DIAG.h"
#include "StringFormatter.h" #include "StringFormatter.h"
#include "WiThrottle.h"
#include "WifiInboundHandler.h" #include "WifiInboundHandler.h"
const char PROGMEM READY_SEARCH[] = "\r\nready\r\n"; const char PROGMEM READY_SEARCH[] = "\r\nready\r\n";
@ -90,6 +90,11 @@ bool WifiInterface::setup(long serial_link_speed,
} }
#endif #endif
DCCEXParser::setAtCommandCallback(ATCommand);
// CAUTION... ONLY CALL THIS ONCE
WifiInboundHandler::setup(wifiStream);
return wifiUp; return wifiUp;
} }
@ -109,8 +114,6 @@ bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid
checkForOK(200, OK_SEARCH, true); checkForOK(200, OK_SEARCH, true);
} }
DCCEXParser::setAtCommandCallback(ATCommand);
WifiInboundHandler::setup(wifiStream);
DIAG(F("\n++ Wifi Setup %S ++\n"), connected ? F("OK") : F("FAILED")); DIAG(F("\n++ Wifi Setup %S ++\n"), connected ? F("OK") : F("FAILED"));
return connected; return connected;
@ -233,6 +236,8 @@ bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringH
} }
} }
StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1)
checkForOK(10000, OK_SEARCH, true); // ignore result in case it already was off
StringFormatter::send(wifiStream, F("AT+CIPMUX=1\r\n")); // configure for multiple connections StringFormatter::send(wifiStream, F("AT+CIPMUX=1\r\n")); // configure for multiple connections
if (!checkForOK(10000, OK_SEARCH, true)) return false; if (!checkForOK(10000, OK_SEARCH, true)) return false;
@ -295,7 +300,6 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor
void WifiInterface::loop() { void WifiInterface::loop() {
if (connected) { if (connected) {
WiThrottle::loop();
WifiInboundHandler::loop(); WifiInboundHandler::loop();
} }
} }

View File

@ -20,7 +20,6 @@
#ifndef WifiInterface_h #ifndef WifiInterface_h
#define WifiInterface_h #define WifiInterface_h
#include "DCCEXParser.h" #include "DCCEXParser.h"
#include "MemStream.h"
#include <Arduino.h> #include <Arduino.h>
#include <avr/pgmspace.h> #include <avr/pgmspace.h>
@ -50,8 +49,5 @@ private:
static int datalength; static int datalength;
static int connectionId; static int connectionId;
static unsigned long loopTimeoutStart; static unsigned long loopTimeoutStart;
static const byte MAX_WIFI_BUFFER = 250;
static byte buffer[MAX_WIFI_BUFFER + 1];
static MemStream * streamer;
}; };
#endif #endif

View File

@ -4,7 +4,7 @@
#include "StringFormatter.h" #include "StringFormatter.h"
// const char VERSION[] PROGMEM ="0.2.0"; // const char VERSION[] PROGMEM ="0.2.0";
#define VERSION "0.2.0" #define VERSION "3.0.0"
#endif #endif