mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-27 01:56:14 +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 "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
10
DCC.cpp
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
12
EEStore.cpp
12
EEStore.cpp
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
53
Turnouts.cpp
53
Turnouts.cpp
|
@ -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
|
||||||
|
|
|
@ -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
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 "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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user