1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-30 03:26:13 +01:00

Apparently working Nucleo

This commit is contained in:
Asbelos 2022-12-07 09:52:28 +00:00
parent 5a2b008367
commit 9cdabb0acf
12 changed files with 212 additions and 243 deletions

View File

@ -97,7 +97,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream
} }
void CommandDistributor::forget(byte clientId) { void CommandDistributor::forget(byte clientId) {
// keep for later if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId); if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
clients[clientId]=NONE_TYPE; clients[clientId]=NONE_TYPE;
} }
#endif #endif

View File

@ -124,52 +124,69 @@ void setup()
#endif #endif
LCD(3, F("Ready")); LCD(3, F("Ready"));
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
Diag::WIFI=true;
} }
void loop() void loop()
{ {
static bool XX=true;
if (XX) DIAG(F("loop 1"));
// The main sketch has responsibilities during loop() // The main sketch has responsibilities during loop()
// Responsibility 1: Handle DCC background processes // Responsibility 1: Handle DCC background processes
// (loco reminders and power checks) // (loco reminders and power checks)
DCC::loop(); DCC::loop();
if (XX) DIAG(F("loop 2"));
// Responsibility 2: handle any incoming commands on USB connection // Responsibility 2: handle any incoming commands on USB connection
SerialManager::loop(); SerialManager::loop();
if (XX) DIAG(F("loop 3"));
// Responsibility 3: Optionally handle any incoming WiFi traffic // Responsibility 3: Optionally handle any incoming WiFi traffic
#ifndef ARDUINO_ARCH_ESP32 #ifndef ARDUINO_ARCH_ESP32
#if WIFI_ON #if WIFI_ON
if (XX) DIAG(F("loop 4"));
WifiInterface::loop(); WifiInterface::loop();
#endif //WIFI_ON #endif //WIFI_ON
#else //ARDUINO_ARCH_ESP32 #else //ARDUINO_ARCH_ESP32
#ifndef WIFI_TASK_ON_CORE0 #ifndef WIFI_TASK_ON_CORE0
if (XX) DIAG(F("loop 5"));
WifiESP::loop(); WifiESP::loop();
#endif #endif
#endif //ARDUINO_ARCH_ESP32 #endif //ARDUINO_ARCH_ESP32
#if ETHERNET_ON #if ETHERNET_ON
if (XX) DIAG(F("loop 6"));
EthernetInterface::loop(); EthernetInterface::loop();
#endif #endif
if (XX) DIAG(F("loop 7"));
RMFT::loop(); // ignored if no automation RMFT::loop(); // ignored if no automation
if (XX) DIAG(F("loop 8"));
#if defined(LCN_SERIAL) #if defined(LCN_SERIAL)
LCN::loop(); LCN::loop();
if (XX) DIAG(F("loop 9"));
#endif #endif
LCDDisplay::loop(); // ignored if LCD not in use LCDDisplay::loop(); // ignored if LCD not in use
if (XX) DIAG(F("loop A"));
// Handle/update IO devices. // Handle/update IO devices.
IODevice::loop(); IODevice::loop();
if (XX) DIAG(F("loop B"));
Sensor::checkAll(); // Update and print changes Sensor::checkAll(); // Update and print changes
if (XX) DIAG(F("loop C"));
// Report any decrease in memory (will automatically trigger on first call) // Report any decrease in memory (will automatically trigger on first call)
static int ramLowWatermark = __INT_MAX__; // replaced on first loop static int ramLowWatermark = __INT_MAX__; // replaced on first loop
int freeNow = DCCTimer::getMinimumFreeMemory(); int freeNow = DCCTimer::getMinimumFreeMemory();
if (XX) DIAG(F("loop D"));
if (freeNow < ramLowWatermark) { if (freeNow < ramLowWatermark) {
ramLowWatermark = freeNow; ramLowWatermark = freeNow;
LCD(3,F("Free RAM=%5db"), ramLowWatermark); LCD(3,F("Free RAM=%5db"), ramLowWatermark);
} }
if (XX) DIAG(F("loop E"));
XX=false;
} }

View File

@ -95,23 +95,6 @@ LookList * RMFT2::onGreenLookup=NULL;
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) #define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
#define SKIPOP progCounter+=3 #define SKIPOP progCounter+=3
// RouteCodeFar is a far pointer to flash on anything other than a uno/nano where it is just a near pointer to flash
uint32_t RMFT2::RouteCodeFar;
uint16_t RMFT2::getOperand2(uint32_t farAddr) {
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
// AVR_MEGA memory uses far pointers
return pgm_read_word_far(farAddr);
#elif defined(ARDUINO_ARCH_AVR)
// UNO/NANO have no far memory
return pgm_read_word_near(farAddr);
#else
// other cpus dont care but may be averse to reading an int16_tr at an odd byte boundary.
const byte * op=(const byte *)farAddr;
return *op | (*(op+1) << 8);
#endif
}
// getOperand instance version, uses progCounter from instance. // getOperand instance version, uses progCounter from instance.
uint16_t RMFT2::getOperand(byte n) { uint16_t RMFT2::getOperand(byte n) {
return getOperand(progCounter,n); return getOperand(progCounter,n);
@ -119,7 +102,13 @@ uint16_t RMFT2::getOperand(byte n) {
// getOperand static version, must be provided prog counter from loop etc. // getOperand static version, must be provided prog counter from loop etc.
uint16_t RMFT2::getOperand(int progCounter,byte n) { uint16_t RMFT2::getOperand(int progCounter,byte n) {
return getOperand2(RouteCodeFar+progCounter+1+(n*3)); int offset=progCounter+1+(n*3);
if (offset&1) {
byte lsb=GETHIGHFLASH(RouteCode,offset);
byte msb=GETHIGHFLASH(RouteCode,offset+1);
return msb<<8|lsb;
}
return GETHIGHFLASHW(RouteCode,offset);
} }
LookList::LookList(int16_t size) { LookList::LookList(int16_t size) {
@ -168,8 +157,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
/* static */ void RMFT2::begin() { /* static */ void RMFT2::begin() {
RouteCodeFar=GETFARPTR(RMFT2::RouteCode); DIAG(F("EXRAIL RoutCode at =%P"),RouteCode);
DIAG(F("EXRAIL RouteAddr=%l"),RouteCodeFar);
bool saved_diag=diag; bool saved_diag=diag;
diag=true; diag=true;

View File

@ -143,7 +143,6 @@ private:
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL); OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id); static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
static uint16_t getOperand(int progCounter,byte n); static uint16_t getOperand(int progCounter,byte n);
static uint16_t getOperand2(uint32_t farAddr);
static RMFT2 * loopTask; static RMFT2 * loopTask;
static RMFT2 * pausingTask; static RMFT2 * pausingTask;
void delayMe(long millisecs); void delayMe(long millisecs);
@ -170,8 +169,6 @@ private:
static LookList * onRedLookup; static LookList * onRedLookup;
static LookList * onAmberLookup; static LookList * onAmberLookup;
static LookList * onGreenLookup; static LookList * onGreenLookup;
// RouteCodeFar is a far pointer to RouteCode flash on anything other than a uno/nano where it is just a near pointer to flash
static uint32_t RouteCodeFar;
// Local variables - exist for each instance/task // Local variables - exist for each instance/task
RMFT2 *next; // loop chain RMFT2 *next; // loop chain

View File

@ -65,6 +65,13 @@ int RingStream::availableForWrite() {
} }
size_t RingStream::printFlash(const FSH * flashBuffer) { size_t RingStream::printFlash(const FSH * flashBuffer) {
// This function does not work on a 32 bit processor where the runtime
// sometimes misrepresents the pointer size in uintptr_t.
// In any case its not really necessary in a 32 bit processor because
// we have adequate ram.
if (sizeof(void*)>2) return print(flashBuffer);
// We are about to add a PROGMEM string to the buffer. // We are about to add a PROGMEM string to the buffer.
// To save RAM we can insert a marker and the // To save RAM we can insert a marker and the
// progmem address into the buffer instead. // progmem address into the buffer instead.
@ -107,8 +114,11 @@ int RingStream::read() {
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
byte b=readRawByte(); byte b=readRawByte();
if (b!=FLASH_INSERT_MARKER) return b; if (b!=FLASH_INSERT_MARKER) return b;
#ifndef ARDUINO_ARCH_ESP32
// Detected a flash insert // Detected a flash insert
if (sizeof(void*)>2) {
DIAG(F("Detected invalid flash insert marker at pos %d"),_pos_read);
return '?';
}
// read address bytes LSB first (size depends on CPU) // read address bytes LSB first (size depends on CPU)
uintptr_t iFlash=0; uintptr_t iFlash=0;
for (byte f=0; f<sizeof(iFlash); f++) { for (byte f=0; f<sizeof(iFlash); f++) {
@ -120,10 +130,6 @@ int RingStream::read() {
_flashInsert=reinterpret_cast<char * >( iFlash); _flashInsert=reinterpret_cast<char * >( iFlash);
// and try again... so will read the first byte of the insert. // and try again... so will read the first byte of the insert.
return read(); return read();
#else
DIAG(F("Detected flash insert marker at pos %d but there should not be one"),_pos_read);
return '\0';
#endif
} }
byte RingStream::readRawByte() { byte RingStream::readRawByte() {

View File

@ -27,7 +27,7 @@ class RingStream : public Print {
public: public:
RingStream( const uint16_t len); RingStream( const uint16_t len);
static const int THIS_IS_A_RINGSTREAM=77; static const int THIS_IS_A_RINGSTREAM=777;
virtual size_t write(uint8_t b); virtual size_t write(uint8_t b);
// This availableForWrite function is subverted from its original intention so that a caller // This availableForWrite function is subverted from its original intention so that a caller

View File

@ -102,7 +102,9 @@ void SerialManager::loop() {
void SerialManager::loop2() { void SerialManager::loop2() {
while (serial->available()) { while (serial->available()) {
char ch = serial->read(); int intch=serial->read();
if (intch<0) break;
char ch = (char)intch;
if (ch == '<') { if (ch == '<') {
inCommandPayload = true; inCommandPayload = true;
bufferLength = 0; bufferLength = 0;

View File

@ -91,9 +91,6 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
{ {
const FSH* flash= (const FSH*)va_arg(args, char*); const FSH* flash= (const FSH*)va_arg(args, char*);
#ifndef ARDUINO_ARCH_ESP32
// On ESP32 the reading flashstring from rinstream code
// crashes, so don't use the flashstream hack on ESP32
#if WIFI_ON | ETHERNET_ON #if WIFI_ON | ETHERNET_ON
// RingStream has special logic to handle flash strings // RingStream has special logic to handle flash strings
// but is not implemented unless wifi or ethernet are enabled. // but is not implemented unless wifi or ethernet are enabled.
@ -101,11 +98,11 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM) if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM)
((RingStream *)stream)->printFlash(flash); ((RingStream *)stream)->printFlash(flash);
else else
#endif
#endif #endif
stream->print(flash); stream->print(flash);
break; break;
} }
case 'P': stream->print((uint32_t)va_arg(args, void*), HEX); break;
case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break; case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break;
case 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break; case 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break;
case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break; case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break;
@ -169,7 +166,7 @@ void StringFormatter::printEscape(Print * stream, char c) {
case '\0': stream->print(F("\\0")); return; case '\0': stream->print(F("\\0")); return;
case '\t': stream->print(F("\\t")); break; case '\t': stream->print(F("\\t")); break;
case '\\': stream->print(F("\\\\")); break; case '\\': stream->print(F("\\\\")); break;
default: stream->print(c); default: stream->write(c);
} }
} }

View File

@ -63,69 +63,6 @@
WiThrottle * WiThrottle::firstThrottle=NULL; WiThrottle * WiThrottle::firstThrottle=NULL;
static uint8_t xstrncmp(const char *s1, const char *s2, uint8_t n) {
if (n == 0)
return 0;
do {
if (*s1 != *s2++)
return 1;
if (*s1++ == 0)
break;
} while (--n != 0);
return 0;
}
void WiThrottle::findUniqThrottle(int id, char *u) {
WiThrottle *wtmyid = NULL;
WiThrottle *wtmyuniq = NULL;
// search 1, look for clientid match
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
if (wt->clientid == id) {
if (xstrncmp(u, wt->uniq, 16) == 0) // should be most common case
return;
wtmyid = wt;
break;
}
}
// search 2, look for string match
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
if (xstrncmp(u, wt->uniq, 16) == 0) {
wtmyuniq = wt;
break;
}
}
// analyse result of the two for loops:
if (wtmyid == NULL) { // should not happen
DIAG(F("Did not find my own wiThrottle handle"));
return;
}
// wtmyuniq == wtmyid has already returned in for loop 1
if (wtmyuniq == NULL) { // register uniq in the found id
strncpy(wtmyid->uniq, u, 16);
wtmyid->uniq[16] = '\0';
if (Diag::WITHROTTLE) DIAG(F("Client %d registered as %s"),wtmyid->clientid, wtmyid->uniq);
return;
}
// if we get here wtmyid and wtmyuniq point on objects but differnet ones
// so we need to do the copy (all other options covered above)
for(int n=0; n < MAX_MY_LOCO; n++)
wtmyid->myLocos[n] = wtmyuniq->myLocos[n];
wtmyid->heartBeatEnable = wtmyuniq->heartBeatEnable;
wtmyid->heartBeat = wtmyuniq->heartBeat;
wtmyid->initSent = wtmyuniq->initSent;
wtmyid->exRailSent = wtmyuniq->exRailSent;
wtmyid->mostRecentCab = wtmyuniq->mostRecentCab;
wtmyid->turnoutListHash = wtmyuniq->turnoutListHash;
wtmyid->lastPowerState = wtmyuniq->lastPowerState;
strncpy(wtmyid->uniq, u, 16);
wtmyid->uniq[16] = '\0';
if (Diag::WITHROTTLE)
DIAG(F("New client %d replaces old client %d as %s"), wtmyid->clientid, wtmyuniq->clientid, wtmyid->uniq);
forget(wtmyuniq->clientid); // do not use wtmyid after this
}
WiThrottle* WiThrottle::getThrottle( int wifiClient) { WiThrottle* WiThrottle::getThrottle( int wifiClient) {
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
if (wt->clientid==wifiClient) return wt; if (wt->clientid==wifiClient) return wt;
@ -135,6 +72,7 @@ WiThrottle* WiThrottle::getThrottle( int wifiClient) {
void WiThrottle::forget( byte clientId) { void WiThrottle::forget( byte clientId) {
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
if (wt->clientid==clientId) { if (wt->clientid==clientId) {
DIAG(F("Withrottle client %d dropped"),clientId);
delete wt; delete wt;
break; break;
} }
@ -159,10 +97,7 @@ WiThrottle::WiThrottle( int wificlientid) {
nextThrottle=firstThrottle; nextThrottle=firstThrottle;
firstThrottle= this; firstThrottle= this;
clientid=wificlientid; clientid=wificlientid;
initSent=false; // prevent sending heartbeats before connection completed
heartBeatEnable=false; // until client turns it on heartBeatEnable=false; // until client turns it on
turnoutListHash = -1; // make sure turnout list is sent once
exRailSent=false;
mostRecentCab=0; mostRecentCab=0;
for (int loco=0;loco<MAX_MY_LOCO; loco++) myLocos[loco].throttle='\0'; for (int loco=0;loco<MAX_MY_LOCO; loco++) myLocos[loco].throttle='\0';
} }
@ -188,53 +123,18 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
heartBeat=millis(); heartBeat=millis();
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd); if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd);
if (initSent) { // On first few commands, send turnout, roster and routes
// Send turnout list if changed since last sent (will replace list on client)
if (turnoutListHash != Turnout::turnoutlistHash) {
StringFormatter::send(stream,F("PTL"));
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
if (tt->isHidden()) continue;
int id=tt->getId();
const FSH * tdesc=NULL;
#ifdef EXRAIL_ACTIVE
tdesc=RMFT2::getTurnoutDescription(id);
#endif
char tchar=Turnout::isClosed(id)?'2':'4';
if (tdesc==NULL) // turnout with no description
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar);
else
StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar);
}
StringFormatter::send(stream,F("\n"));
turnoutListHash = Turnout::turnoutlistHash; // keep a copy of hash for later comparison
}
else if (!exRailSent) { if (!turnoutsSent) sendTurnouts(stream);
// Send EX-RAIL routes list if not already sent (but not at same time as turnouts above) else if(!rosterSent) sendRoster(stream);
exRailSent=true; else if (!routesSent) sendRoutes(stream);
#ifdef EXRAIL_ACTIVE else if (!heartrateSent) {
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL")); heartrateSent=true;
// first pass automations
for (int ix=0;;ix+=2) {
int16_t id =GETHIGHFLASHW(RMFT2::automationIdList,ix);
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[A%d}|{%S}|{4"),id,desc);
}
// second pass routes.
for (int ix=0;;ix+=2) {
int16_t id=GETHIGHFLASHW(RMFT2::routeIdList,ix);
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[R%d}|{%S}|{2"),id,desc);
}
StringFormatter::send(stream,F("\n"));
#endif
// allow heartbeat to slow down once all metadata sent // allow heartbeat to slow down once all metadata sent
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS); StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
}
} }
while (cmd[0]) { while (cmd[0]) {
switch (cmd[0]) { switch (cmd[0]) {
case '*': // heartbeat control case '*': // heartbeat control
@ -287,32 +187,20 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
} }
break; break;
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
StringFormatter::send(stream, F("*%d\n"), initSent ? HEARTBEAT_SECONDS : HEARTBEAT_SECONDS/2); // return timeout value StringFormatter::send(stream, F("*%d\n"), heartrateSent ? HEARTBEAT_SECONDS : HEARTBEAT_SECONDS/2); // return timeout value
break; break;
case 'M': // multithrottle case 'M': // multithrottle
multithrottle(stream, cmd); multithrottle(stream, cmd);
break; break;
case 'H': // send initial connection info after receiving "HU" message case 'H': // send initial connection info after receiving "HU" message
if (cmd[1] == 'U') { if (cmd[1] == 'U') {
WiThrottle::findUniqThrottle(clientid, (char *)cmd+2); StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n")); StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON); StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
int16_t cabid=GETHIGHFLASHW(RMFT2::rosterIdList,r*2);
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
}
stream->write('\n'); // end roster
#endif
// set heartbeat to 5 seconds because we need to sync the metadata (1 second is too short!) // set heartbeat to 5 seconds because we need to sync the metadata (1 second is too short!)
StringFormatter::send(stream,F("*%d\n"), HEARTBEAT_SECONDS/2); StringFormatter::send(stream,F("*%d\n"), HEARTBEAT_SECONDS/2);
initSent = true;
} }
break; break;
case 'Q': // case 'Q': //
@ -321,7 +209,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab); StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
} }
} }
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid); if (Diag::WITHROTTLE) DIAG(F("WiThrottle(%d) Quit"),clientid);
delete this; delete this;
break; break;
} }
@ -382,65 +270,17 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
} }
//use first empty "slot" on this client's list, will be added to DCC registration list //use first empty "slot" on this client's list, will be added to DCC registration list
for (int loco=0;loco<MAX_MY_LOCO;loco++) { for (int loco=0;loco<MAX_MY_LOCO;loco++) {
if (myLocos[loco].throttle=='\0') { if (myLocos[loco].throttle=='\0' || myLocos[loco].cab == locoid) {
myLocos[loco].throttle=throttleChar; myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid; myLocos[loco].cab=locoid;
myLocos[loco].functionMap=DCC::getFunctionMap(locoid); myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
mostRecentCab=locoid; mostRecentCab=locoid;
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
int fkeys=29; sendFunctions(stream,loco);
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle //speed and direction will be published at next broadcast cycle
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
#ifdef EXRAIL_ACTIVE return;
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
if (!functionNames) {
// no roster, use presets as above
}
else if (GETFLASH(functionNames)=='\0') {
// "" = Roster but no functions given
fkeys=0;
}
else {
// we have function names...
// scan names list emitting names, counting functions and
// flagging non-toggling things like horn.
myLocos[loco].functionToggles =0;
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), throttleChar,cmd[3],locoid);
fkeys=0;
bool firstchar=true;
for (int fx=0;;fx++) {
char c=GETFLASH(functionNames+fx);
if (c=='\0') {
fkeys++;
break;
}
if (c=='/') {
fkeys++;
StringFormatter::send(stream,F("]\\["));
firstchar=true;
}
else if (firstchar && c=='*') {
myLocos[loco].functionToggles |= 1UL<<fkeys;
firstchar=false;
}
else {
firstchar=false;
stream->write(c);
}
}
StringFormatter::send(stream,F("\n"));
}
#endif
for(int fKey=0; fKey<fkeys; fKey++) {
int fstate=DCC::getFn(locoid,fKey);
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey);
}
//speed and direction will be published at next broadcast cycle
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
return;
} }
} }
StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid); StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid);
@ -544,8 +384,6 @@ void WiThrottle::loop(RingStream * stream) {
// for each WiThrottle, check the heartbeat and broadcast needed // for each WiThrottle, check the heartbeat and broadcast needed
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
wt->checkHeartbeat(stream); wt->checkHeartbeat(stream);
} }
void WiThrottle::checkHeartbeat(RingStream * stream) { void WiThrottle::checkHeartbeat(RingStream * stream) {
@ -559,8 +397,8 @@ void WiThrottle::checkHeartbeat(RingStream * stream) {
heartBeat=millis(); // We have just stopped everyting, we don't need to do that again at next loop. heartBeat=millis(); // We have just stopped everyting, we don't need to do that again at next loop.
} }
} }
//haba no, not necessary the only throttle and it may come back // if it does come back, the throttle should re-acquire
//delete this; delete this;
return; return;
} }
@ -660,5 +498,110 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
DIAG(F("LocoCallback commit success")); DIAG(F("LocoCallback commit success"));
stashStream->commit(); stashStream->commit();
CommandDistributor::broadcastPower(); CommandDistributor::broadcastPower();
}
void WiThrottle::sendTurnouts(Print* stream) {
turnoutsSent=true;
StringFormatter::send(stream,F("PTL"));
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
if (tt->isHidden()) continue;
int id=tt->getId();
const FSH * tdesc=NULL;
#ifdef EXRAIL_ACTIVE
tdesc=RMFT2::getTurnoutDescription(id);
#endif
char tchar=Turnout::isClosed(id)?'2':'4';
if (tdesc==NULL) // turnout with no description
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar);
else
StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar);
}
StringFormatter::send(stream,F("\n"));
}
void WiThrottle::sendRoster(Print* stream) {
rosterSent=true;
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
int16_t cabid=GETHIGHFLASHW(RMFT2::rosterIdList,r*2);
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
}
StringFormatter::send(stream,F("\n"));
#endif
}
void WiThrottle::sendRoutes(Print* stream) {
routesSent=true;
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
// first pass automations
for (int ix=0;;ix+=2) {
int16_t id =GETHIGHFLASHW(RMFT2::automationIdList,ix);
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[A%d}|{%S}|{4"),id,desc);
}
// second pass routes.
for (int ix=0;;ix+=2) {
int16_t id=GETHIGHFLASHW(RMFT2::routeIdList,ix);
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[R%d}|{%S}|{2"),id,desc);
}
StringFormatter::send(stream,F("\n"));
#endif
}
void WiThrottle::sendFunctions(Print* stream, byte loco) {
int16_t locoid=myLocos[loco].cab;
int fkeys=29;
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
#ifdef EXRAIL_ACTIVE
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
if (!functionNames) {
// no roster, use non-exrail presets as above
}
else if (GETFLASH(functionNames)=='\0') {
// "" = Roster but no functions given
fkeys=0;
}
else {
// we have function names...
// scan names list emitting names, counting functions and
// flagging non-toggling things like horn.
myLocos[loco].functionToggles =0;
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), myLocos[loco].throttle,LorS(locoid),locoid);
fkeys=0;
bool firstchar=true;
for (int fx=0;;fx++) {
char c=GETFLASH(functionNames+fx);
if (c=='\0') {
fkeys++;
break;
}
if (c=='/') {
fkeys++;
StringFormatter::send(stream,F("]\\["));
firstchar=true;
}
else if (firstchar && c=='*') {
myLocos[loco].functionToggles |= 1UL<<fkeys;
firstchar=false;
}
else {
firstchar=false;
stream->write(c);
}
}
StringFormatter::send(stream,F("\n"));
}
#endif
for(int fKey=0; fKey<fkeys; fKey++) {
int fstate=DCC::getFn(locoid,fKey);
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),myLocos[loco].throttle,LorS(locoid),locoid,fstate,fKey);
}
} }

View File

@ -61,10 +61,11 @@ class WiThrottle {
MYLOCO myLocos[MAX_MY_LOCO]; MYLOCO myLocos[MAX_MY_LOCO];
bool heartBeatEnable; bool heartBeatEnable;
unsigned long heartBeat; unsigned long heartBeat;
bool initSent; // valid connection established bool turnoutsSent=false;
bool exRailSent; // valid connection established bool rosterSent=false;
bool routesSent=false;
bool heartrateSent=false;
uint16_t mostRecentCab; uint16_t mostRecentCab;
int turnoutListHash; // used to check for changes to turnout list
bool lastPowerState; // last power state sent to this client bool lastPowerState; // last power state sent to this client
int DCCToWiTSpeed(int DCCSpeed); int DCCToWiTSpeed(int DCCSpeed);
@ -74,6 +75,10 @@ class WiThrottle {
void accessory(RingStream *, byte* cmd); void accessory(RingStream *, byte* cmd);
void checkHeartbeat(RingStream * stream); void checkHeartbeat(RingStream * stream);
void markForBroadcast2(int cab); void markForBroadcast2(int cab);
void sendTurnouts(Print * stream);
void sendRoster(Print * stream);
void sendRoutes(Print * stream);
void sendFunctions(Print* stream, byte loco);
// callback stuff to support prog track acquire // callback stuff to support prog track acquire
static RingStream * stashStream; static RingStream * stashStream;
static WiThrottle * stashInstance; static WiThrottle * stashInstance;

View File

@ -31,6 +31,7 @@ WifiInboundHandler * WifiInboundHandler::singleton;
void WifiInboundHandler::setup(Stream * ESStream) { void WifiInboundHandler::setup(Stream * ESStream) {
singleton=new WifiInboundHandler(ESStream); singleton=new WifiInboundHandler(ESStream);
// DIAG(F("WifiInbound Setup2 %P %P"), ESStream,singleton);
} }
void WifiInboundHandler::loop() { void WifiInboundHandler::loop() {
@ -44,6 +45,7 @@ WifiInboundHandler::WifiInboundHandler(Stream * ESStream) {
inboundRing=new RingStream(INBOUND_RING); inboundRing=new RingStream(INBOUND_RING);
outboundRing=new RingStream(OUTBOUND_RING); outboundRing=new RingStream(OUTBOUND_RING);
pendingCipsend=false; pendingCipsend=false;
// DIAG(F("WifiInbound setup1 %P"), wifiStream);
} }
@ -51,11 +53,19 @@ WifiInboundHandler::WifiInboundHandler(Stream * ESStream) {
// +IPD,x,lll:data is stored in streamer[x] // +IPD,x,lll:data is stored in streamer[x]
// Other input returns // Other input returns
void WifiInboundHandler::loop1() { void WifiInboundHandler::loop1() {
static bool XX=true;
if (XX) DIAG(F("Wifi 1"));
// First handle all inbound traffic events because they will block the sending // First handle all inbound traffic events because they will block the sending
if (loop2()!=INBOUND_IDLE) return; if (loop2()!=INBOUND_IDLE) {
if (XX) DIAG(F("Wifi 2"));
return;
}
if (XX) DIAG(F("Wifi 3"));
WiThrottle::loop(outboundRing); WiThrottle::loop(outboundRing);
if (XX) DIAG(F("Wifi 4"));
XX=false;
// if nothing is already CIPSEND pending, we can CIPSEND one reply // if nothing is already CIPSEND pending, we can CIPSEND one reply
if (clientPendingCIPSEND<0) { if (clientPendingCIPSEND<0) {
clientPendingCIPSEND=outboundRing->read(); clientPendingCIPSEND=outboundRing->read();
@ -66,14 +76,13 @@ void WifiInboundHandler::loop1() {
} }
if (pendingCipsend) { if (pendingCipsend && millis()-lastCIPSEND > CIPSENDgap) {
if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize); if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize); StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
pendingCipsend=false; pendingCipsend=false;
return; return;
} }
// if something waiting to execute, we can call it // if something waiting to execute, we can call it
int clientId=inboundRing->read(); int clientId=inboundRing->read();
if (clientId>=0) { if (clientId>=0) {
@ -83,7 +92,6 @@ void WifiInboundHandler::loop1() {
for (int i=0;i<count;i++) cmd[i]=inboundRing->read(); for (int i=0;i<count;i++) cmd[i]=inboundRing->read();
cmd[count]=0; cmd[count]=0;
if (Diag::WIFI) DIAG(F("%e"),cmd); if (Diag::WIFI) DIAG(F("%e"),cmd);
CommandDistributor::parse(clientId,cmd,outboundRing); CommandDistributor::parse(clientId,cmd,outboundRing);
return; return;
} }
@ -91,16 +99,17 @@ void WifiInboundHandler::loop1() {
// This is a Finite State Automation (FSA) handling the inbound bytes from an ES AT command processor // This is a Finite State Automation (FSA) handling the inbound bytes from an ES AT command processor
WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
while (wifiStream->available()) { while (wifiStream->available()>=0) {
char ch = wifiStream->read(); int chint = wifiStream->read();
if (chint<0) break;
byte ch=(char)chint;
// echo the char to the diagnostic stream in escaped format // echo the char to the diagnostic stream in escaped format
if (Diag::WIFI) { if (Diag::WIFI) {
// DIAG(F(" %d/"), loopState); StringFormatter::printEscape((char)ch); // DIAG in disguise
StringFormatter::printEscape(ch); // DIAG in disguise
} }
switch (loopState) { switch (loopState) {
@ -131,11 +140,13 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
if (ch=='S') { // SEND OK probably if (ch=='S') { // SEND OK probably
loopState=SKIPTOEND; loopState=SKIPTOEND;
lastCIPSEND=0; // no need to wait next time
break; break;
} }
if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND
pendingCipsend=(clientPendingCIPSEND>=0); pendingCipsend=(clientPendingCIPSEND>=0);
if (pendingCipsend) lastCIPSEND=millis(); // forces a gap to next CIPSEND
loopState=SKIPTOEND; loopState=SKIPTOEND;
break; break;
} }

View File

@ -69,6 +69,7 @@ class WifiInboundHandler {
static const int INBOUND_RING = 512; static const int INBOUND_RING = 512;
static const int OUTBOUND_RING = 2048; static const int OUTBOUND_RING = 2048;
static const int CIPSENDgap=100; // millis() between retries of cipsend.
RingStream * inboundRing; RingStream * inboundRing;
RingStream * outboundRing; RingStream * outboundRing;
@ -79,5 +80,7 @@ class WifiInboundHandler {
int clientPendingCIPSEND=-1; int clientPendingCIPSEND=-1;
int currentReplySize; int currentReplySize;
bool pendingCipsend; bool pendingCipsend;
uint32_t lastCIPSEND=0; // millis() of previous cipsend
}; };
#endif #endif