1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-27 01:56:14 +01:00
This commit is contained in:
SteveT 2020-10-07 08:53:02 -04:00
commit e112be7087
14 changed files with 515 additions and 224 deletions

54
CommandDistributor.cpp Normal file
View File

@ -0,0 +1,54 @@
#include <Arduino.h>
#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;

20
CommandDistributor.h Normal file
View File

@ -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

View File

@ -12,7 +12,6 @@
#include "config.h" #include "config.h"
#include "DCCEX.h" #include "DCCEX.h"
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// //
// Enables an I2C 2x24 or 4x24 LCD Screen // Enables an I2C 2x24 or 4x24 LCD Screen
@ -32,7 +31,6 @@ DCCEXParser serialParser;
void setup() void setup()
{ {
//////////////////////////////////////////// ////////////////////////////////////////////
// //
// More display stuff. Need to put this in a .h file and make // More display stuff. Need to put this in a .h file and make
@ -70,32 +68,9 @@ void setup()
Serial.begin(115200); Serial.begin(115200);
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi // 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 #ifdef WIFI_ON
bool wifiUp = false; WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT);
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
#endif // WIFI_ON #endif // WIFI_ON
// Responsibility 3: Start the DCC engine. // Responsibility 3: Start the DCC engine.
@ -123,7 +98,7 @@ void loop()
serialParser.loop(Serial); serialParser.loop(Serial);
// Responsibility 3: Optionally handle any incoming WiFi traffic // Responsibility 3: Optionally handle any incoming WiFi traffic
#ifdef WIFI_ON #if WIFI_ON
WifiInterface::loop(); WifiInterface::loop();
#endif #endif

10
DCC.cpp
View File

@ -20,7 +20,9 @@
#include "DCC.h" #include "DCC.h"
#include "DCCWaveform.h" #include "DCCWaveform.h"
#include "DIAG.h" #include "DIAG.h"
#include "EEStore.h"
#include "GITHUB_SHA.h"
#include "version.h"
// This module is responsible for converting API calls into // This module is responsible for converting API calls into
// messages to be sent to the waveform generator. // 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) { void DCC::begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) {
shieldName=(__FlashStringHelper*)motorShieldName; shieldName=(__FlashStringHelper*)motorShieldName;
DIAG(F("<iDCC-EX V-%S / %S / %S G-%S>\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); DCCWaveform::begin(mainDriver,progDriver, timerNumber);
} }

View File

@ -46,6 +46,7 @@ const int HASH_KEYWORD_ON = 2657;
const int HASH_KEYWORD_DCC = 6436; const int HASH_KEYWORD_DCC = 6436;
const int HASH_KEYWORD_SLOW = -17209; const int HASH_KEYWORD_SLOW = -17209;
const int HASH_KEYWORD_PROGBOOST = -6353; const int HASH_KEYWORD_PROGBOOST = -6353;
const int HASH_KEYWORD_EEPROM = -7168;
int DCCEXParser::stashP[MAX_PARAMS]; int DCCEXParser::stashP[MAX_PARAMS];
bool DCCEXParser::stashBusy; bool DCCEXParser::stashBusy;
@ -168,9 +169,9 @@ void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
// See documentation on DCC class for info on this section // See documentation on DCC class for info on this section
void DCCEXParser::parse(Print *stream, byte *com, bool blocking) void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
{ {
(void)EEPROM; // tell compiler not to warn this is unused
if (Diag::CMD) if (Diag::CMD)
DIAG(F("\nPARSING:%s\n"), com); DIAG(F("\nPARSING:%s\n"), com);
(void)EEPROM; // tell compiler not to warn thi is unused
int p[MAX_PARAMS]; int p[MAX_PARAMS];
while (com[0] == '<' || com[0] == ' ') while (com[0] == '<' || com[0] == ' ')
com++; // strip off any number of < or spaces com++; // strip off any number of < or spaces
@ -608,6 +609,11 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
DCC::setProgTrackBoost(true); DCC::setProgTrackBoost(true);
return true; return true;
case HASH_KEYWORD_EEPROM:
if (params >= 1)
EEStore::dump(p[1]);
return true;
default: // invalid/unknown default: // invalid/unknown
break; break;
} }
@ -617,7 +623,7 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
// CALLBACKS must be static // CALLBACKS must be static
bool DCCEXParser::stashCallback(Print *stream, int p[MAX_PARAMS]) bool DCCEXParser::stashCallback(Print *stream, int p[MAX_PARAMS])
{ {
if (stashBusy || asyncBanned) if (stashBusy )
return false; return false;
stashBusy = true; stashBusy = true;
stashStream = stream; stashStream = stream;

View File

@ -38,7 +38,6 @@ struct DCCEXParser
static const int MAX_BUFFER=50; // longest command sent in static const int MAX_BUFFER=50; // longest command sent in
byte bufferLength=0; byte bufferLength=0;
bool inCommandPayload=false; bool inCommandPayload=false;
bool asyncBanned; // true when called with stream that must complete before returning
byte buffer[MAX_BUFFER+2]; byte buffer[MAX_BUFFER+2];
int splitValues( int result[MAX_PARAMS], const byte * command); int splitValues( int result[MAX_PARAMS], const byte * command);

View File

@ -2,7 +2,7 @@
#include "Turnouts.h" #include "Turnouts.h"
#include "Sensors.h" #include "Sensors.h"
#include "Outputs.h" #include "Outputs.h"
#include "DIAG.h"
#if defined(ARDUINO_ARCH_SAMD) #if defined(ARDUINO_ARCH_SAMD)
ExternalEEPROM EEPROM; 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 ; n<num; n++) {
EEPROM.get(n, b);
DIAG(F("%d %x %c\n"),n,b,isascii(b) ? b : ' ');
}
}
///////////////////////////////////////////////////////////////////////////////
EEStore *EEStore::eeStore=NULL; EEStore *EEStore::eeStore=NULL;
int EEStore::eeAddress=0; int EEStore::eeAddress=0;

View File

@ -29,6 +29,7 @@ struct EEStore{
static void advance(int); static void advance(int);
static void store(); static void store();
static void clear(); static void clear();
static void dump(int);
}; };
#endif #endif

View File

@ -1,5 +1,7 @@
/* /*
* © 2013-2016 Gregg E. Berman
* © 2020, Chris Harlow. All rights reserved. * © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
* *
* This file is part of Asbelos DCC API * This file is part of Asbelos DCC API
* *
@ -19,29 +21,42 @@
#include "Turnouts.h" #include "Turnouts.h"
#include "EEStore.h" #include "EEStore.h"
#include "PWMServoDriver.h" #include "PWMServoDriver.h"
#ifdef EESTOREDEBUG
#include "DIAG.h"
#endif
bool Turnout::activate(int n,bool state){ bool Turnout::activate(int n,bool state){
//DIAG(F("\nTurnout::activate(%d,%d)\n"),n,state); #ifdef EESTOREDEBUG
DIAG(F("\nTurnout::activate(%d,%d)\n"),n,state);
#endif
Turnout * tt=get(n); Turnout * tt=get(n);
if (tt==NULL) return false; if (tt==NULL) return false;
tt->activate(state); tt->activate(state);
if(n>0) EEPROM.put(n,tt->data.tStatus); EEStore::store();
turnoutlistHash++; turnoutlistHash++;
return true; return true;
} }
bool Turnout::isActive(int n){ bool Turnout::isActive(int n){
Turnout * tt=get(n); Turnout * tt=get(n);
if (tt==NULL) return false; if (tt==NULL) return false;
return tt->data.tStatus & STATUS_ACTIVE; return tt->data.tStatus & STATUS_ACTIVE;
} }
// activate is virtual here so that it can be overridden by a non-DCC turnout mechanism // activate is virtual here so that it can be overridden by a non-DCC turnout mechanism
void Turnout::activate(bool state) { void Turnout::activate(bool state) {
if (state) data.tStatus|=STATUS_ACTIVE; #ifdef EESTOREDEBUG
else data.tStatus &= ~STATUS_ACTIVE; DIAG(F("\nTurnout::activate(%d)\n"),state);
if (data.tStatus & STATUS_PWM) PWMServoDriver::setServo(data.tStatus & STATUS_PWMPIN, (data.inactiveAngle+(state?data.moveAngle:0))); #endif
else DCC::setAccessory(data.address,data.subAddress, 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);
EEStore::store();
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -81,6 +96,9 @@ void Turnout::load(){
else tt=create(data.id,data.address,data.subAddress); else tt=create(data.id,data.address,data.subAddress);
tt->data.tStatus=data.tStatus; tt->data.tStatus=data.tStatus;
EEStore::advance(sizeof(tt->data)); EEStore::advance(sizeof(tt->data));
#ifdef EESTOREDEBUG
tt->print(tt);
#endif
} }
} }
@ -93,6 +111,9 @@ void Turnout::store(){
EEStore::eeStore->data.nTurnouts=0; EEStore::eeStore->data.nTurnouts=0;
while(tt!=NULL){ while(tt!=NULL){
#ifdef EESTOREDEBUG
tt->print(tt);
#endif
EEPROM.put(EEStore::pointer(),tt->data); EEPROM.put(EEStore::pointer(),tt->data);
EEStore::advance(sizeof(tt->data)); EEStore::advance(sizeof(tt->data));
tt=tt->nextTurnout; tt=tt->nextTurnout;
@ -129,7 +150,19 @@ Turnout *Turnout::create(int id){
turnoutlistHash++; turnoutlistHash++;
return tt; 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; Turnout *Turnout::firstTurnout=NULL;
int Turnout::turnoutlistHash=0; //bump on every change so clients know when to refresh their lists int Turnout::turnoutlistHash=0; //bump on every change so clients know when to refresh their lists

View File

@ -49,6 +49,9 @@ class Turnout {
static Turnout *create(int id , byte pin , int activeAngle, int inactiveAngle); static Turnout *create(int id , byte pin , int activeAngle, int inactiveAngle);
static Turnout *create(int id); static Turnout *create(int id);
void activate(bool state); void activate(bool state);
#ifdef EESTOREDEBUG
void print(Turnout *tt);
#endif
}; // Turnout }; // Turnout
#endif #endif

214
WifiInboundHandler.cpp Normal file
View File

@ -0,0 +1,214 @@
#include <Arduino.h>
#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;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;
}
// 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;clientId<MAX_CLIENTS;clientId++) {
if (clientStatus[clientId]==REPLY_PENDING) {
clientPendingCIPSEND=clientId;
if (Diag::WIFI) DIAG( F("\nWiFi: [[CIPSEND=%d,%d]]"), clientId, clientStream[clientId]->available());
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;clientId<MAX_CLIENTS;clientId++) {
if (clientStatus[clientId]==CLOSE_AFTER_SEND) {
if (Diag::WIFI) DIAG(F("AT+CIPCLOSE=%d\r\n"), clientId);
StringFormatter::send(wifiStream, F("AT+CIPCLOSE=%d\r\n"), clientId);
clientStatus[clientId]=UNUSED;
return;
}
if (clientStatus[clientId]==READY_TO_PROCESS) {
processCommand(clientId);
return;
}
}
}
// This is a Finite State Automation (FSA) handling 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
}
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;
}
}

72
WifiInboundHandler.h Normal file
View File

@ -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

View File

@ -23,26 +23,75 @@
#include "DIAG.h" #include "DIAG.h"
#include "StringFormatter.h" #include "StringFormatter.h"
#include "WiThrottle.h" #include "WiThrottle.h"
#include "WifiInboundHandler.h"
const char PROGMEM READY_SEARCH[] = "\r\nready\r\n"; const char PROGMEM READY_SEARCH[] = "\r\nready\r\n";
const char PROGMEM OK_SEARCH[] = "\r\nOK\r\n"; const char PROGMEM OK_SEARCH[] = "\r\nOK\r\n";
const char PROGMEM END_DETAIL_SEARCH[] = "@ 1000"; 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 SEND_OK_SEARCH[] = "\r\nSEND OK\r\n";
const char PROGMEM IPD_SEARCH[] = "+IPD"; const char PROGMEM IPD_SEARCH[] = "+IPD";
const unsigned long LOOP_TIMEOUT = 2000; const unsigned long LOOP_TIMEOUT = 2000;
bool WifiInterface::connected = false; bool WifiInterface::connected = false;
bool WifiInterface::closeAfter = false; Stream * WifiInterface::wifiStream;
DCCEXParser WifiInterface::parser;
byte WifiInterface::loopstate = 0;
unsigned long WifiInterface::loopTimeoutStart = 0; ////////////////////////////////////////////////////////////////////////////////
int WifiInterface::datalength = 0; //
int WifiInterface::connectionId; // Figure out number of serial ports depending on hardware
byte WifiInterface::buffer[MAX_WIFI_BUFFER+1]; //
MemStream * WifiInterface::streamer; #if defined(ARDUINO_AVR_UNO)
Stream * WifiInterface::wifiStream = NULL; #define NUM_SERIAL 0
HTTP_CALLBACK WifiInterface::httpCallback = 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, bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password,
const __FlashStringHelper* hostname, int port) { const __FlashStringHelper* hostname, int port) {
@ -59,8 +108,9 @@ bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid
StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo
checkForOK(200, OK_SEARCH, true); 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")); DIAG(F("\n++ Wifi Setup %S ++\n"), connected ? F("OK") : F("FAILED"));
return connected; 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 there is, just shortcut the setup and continue to read the data as normal.
if (checkForOK(200,IPD_SEARCH, true)) { if (checkForOK(200,IPD_SEARCH, true)) {
DIAG(F("\nPreconfigured Wifi already running with data waiting\n")); 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; 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) { bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor, bool echo, bool escapeEcho) {
unsigned long startTime = millis(); unsigned long startTime = millis();
@ -244,162 +292,10 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor
return false; 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() { void WifiInterface::loop() {
if (!connected) return; if (connected) {
WiThrottle::loop();
WiThrottle::loop(); // check heartbeats WifiInboundHandler::loop();
// 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("<html><body>This is <b>not</b> a web server.<br/></body></html>"));
}
closeAfter = true;
} }
else if (buffer[0] == '<') parser.parse(streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async
else 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
} }

View File

@ -24,26 +24,26 @@
#include <Arduino.h> #include <Arduino.h>
#include <avr/pgmspace.h> #include <avr/pgmspace.h>
typedef void (*HTTP_CALLBACK)(Print *stream, byte *cmd);
class WifiInterface class WifiInterface
{ {
public: public:
static bool setup(Stream &setupStream, const __FlashStringHelper *SSSid, const __FlashStringHelper *password, static bool setup(long serial_link_speed,
const __FlashStringHelper *hostname, int port); const __FlashStringHelper *wifiESSID,
const __FlashStringHelper *wifiPassword,
const __FlashStringHelper *hostname,
const int port = 2560);
static void loop(); static void loop();
static void ATCommand(const byte *command); static void ATCommand(const byte *command);
static void setHTTPCallback(HTTP_CALLBACK callback);
private: private:
static bool setup(Stream &setupStream, const __FlashStringHelper *SSSid, const __FlashStringHelper *password,
const __FlashStringHelper *hostname, int port);
static Stream *wifiStream; static Stream *wifiStream;
static DCCEXParser parser; static DCCEXParser parser;
static bool setup2(const __FlashStringHelper *SSSid, const __FlashStringHelper *password, static bool setup2(const __FlashStringHelper *SSSid, const __FlashStringHelper *password,
const __FlashStringHelper *hostname, int port); const __FlashStringHelper *hostname, int port);
static bool checkForOK(const unsigned int timeout, const char *waitfor, bool echo, bool escapeEcho = true); 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 connected;
static bool closeAfter; static bool closeAfter;
static byte loopstate; static byte loopstate;