mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-12-27 22:31:24 +01:00
Merge branch 'master' of https://github.com/mstevetodd/CommandStation-EX
This commit is contained in:
commit
e112be7087
54
CommandDistributor.cpp
Normal file
54
CommandDistributor.cpp
Normal 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
20
CommandDistributor.h
Normal 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
|
@ -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
|
||||
|
10
DCC.cpp
10
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("<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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
12
EEStore.cpp
12
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 ; n<num; n++) {
|
||||
EEPROM.get(n, b);
|
||||
DIAG(F("%d %x %c\n"),n,b,isascii(b) ? b : ' ');
|
||||
}
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EEStore *EEStore::eeStore=NULL;
|
||||
int EEStore::eeAddress=0;
|
||||
|
@ -29,6 +29,7 @@ struct EEStore{
|
||||
static void advance(int);
|
||||
static void store();
|
||||
static void clear();
|
||||
static void dump(int);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
53
Turnouts.cpp
53
Turnouts.cpp
@ -1,5 +1,7 @@
|
||||
/*
|
||||
* © 2013-2016 Gregg E. Berman
|
||||
* © 2020, Chris Harlow. All rights reserved.
|
||||
* © 2020, Harald Barth.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
@ -19,29 +21,42 @@
|
||||
#include "Turnouts.h"
|
||||
#include "EEStore.h"
|
||||
#include "PWMServoDriver.h"
|
||||
#ifdef EESTOREDEBUG
|
||||
#include "DIAG.h"
|
||||
#endif
|
||||
|
||||
bool Turnout::activate(int n,bool state){
|
||||
//DIAG(F("\nTurnout::activate(%d,%d)\n"),n,state);
|
||||
bool Turnout::activate(int n,bool state){
|
||||
#ifdef EESTOREDEBUG
|
||||
DIAG(F("\nTurnout::activate(%d,%d)\n"),n,state);
|
||||
#endif
|
||||
Turnout * tt=get(n);
|
||||
if (tt==NULL) return false;
|
||||
tt->activate(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
|
||||
|
@ -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
|
||||
|
214
WifiInboundHandler.cpp
Normal file
214
WifiInboundHandler.cpp
Normal 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
72
WifiInboundHandler.h
Normal 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
|
@ -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("<html><body>This is <b>not</b> a web server.<br/></body></html>"));
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -24,26 +24,26 @@
|
||||
#include <Arduino.h>
|
||||
#include <avr/pgmspace.h>
|
||||
|
||||
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;
|
||||
|
Loading…
Reference in New Issue
Block a user