HIGHMEM + WITHROTTLE

EXRAIL HIGHMEM feature affects parser and withrottle.

Ringstream and wifi fixes

Withrottle connecting / reconnecting
This commit is contained in:
Asbelos 2022-12-16 13:14:48 +00:00
parent ad294ea17e
commit 1d61a8f3f9
19 changed files with 465 additions and 329 deletions

View File

@ -97,7 +97,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream
}
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;
}
#endif

View File

@ -41,6 +41,14 @@
#include "DCCTimer.h"
#include "EXRAIL2.h"
// This macro can't be created easily as a portable function because the
// flashlist requires a far pointer for high flash access.
#define SENDFLASHLIST(stream,flashList) \
for (int16_t i=0;;i+=sizeof(flashList[0])) { \
int16_t value=GETHIGHFLASHW(flashList,i); \
if (value==0) break; \
StringFormatter::send(stream,F(" %d"),value); \
}
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
@ -569,8 +577,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
StringFormatter::send(stream, F("<jA"));
if (params==1) {// <JA>
#ifdef EXRAIL_ACTIVE
sendFlashList(stream,RMFT2::routeIdList);
sendFlashList(stream,RMFT2::automationIdList);
SENDFLASHLIST(stream,RMFT2::routeIdList)
SENDFLASHLIST(stream,RMFT2::automationIdList)
#endif
}
else { // <JA id>
@ -589,7 +597,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case HASH_KEYWORD_R: // <JR> returns rosters
StringFormatter::send(stream, F("<jR"));
#ifdef EXRAIL_ACTIVE
if (params==1) sendFlashList(stream,RMFT2::rosterIdList);
if (params==1) {
SENDFLASHLIST(stream,RMFT2::rosterIdList)
}
else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id));
#endif
@ -634,14 +644,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
StringFormatter::send(stream, F("<X>\n"));
}
void DCCEXParser::sendFlashList(Print * stream,const int16_t flashList[]) {
for (int16_t i=0;;i++) {
int16_t value=GETFLASHW(flashList+i);
if (value==0) return;
StringFormatter::send(stream,F(" %d"),value);
}
}
bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
{

View File

@ -41,6 +41,7 @@
*/
#include <Arduino.h>
#include "defines.h"
#include "EXRAIL2.h"
#include "DCC.h"
#include "DCCWaveform.h"
@ -90,15 +91,25 @@ LookList * RMFT2::onDeactivateLookup=NULL;
LookList * RMFT2::onRedLookup=NULL;
LookList * RMFT2::onAmberLookup=NULL;
LookList * RMFT2::onGreenLookup=NULL;
#define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter)
#ifdef ARDUINO_ARCH_AVR
#define GET_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3))
#else
#define GET_OPERAND(n) GETOPW(RMFT2::RouteCode+progCounter+1+(n*3))
#define GETOPW(A) (((uint32_t)A)%2 ? GETFLASH((const byte *)A) | (GETFLASH(1+(const byte *)A)<<8) : GETFLASHW(A))
#endif
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
#define SKIPOP progCounter+=3
// getOperand instance version, uses progCounter from instance.
uint16_t RMFT2::getOperand(byte n) {
return getOperand(progCounter,n);
}
// getOperand static version, must be provided prog counter from loop etc.
uint16_t RMFT2::getOperand(int progCounter,byte n) {
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) {
m_size=size;
@ -139,12 +150,17 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
for (progCounter=0;; SKIPOP) {
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
if (opcode==op1 || opcode==op2 || opcode==op3) list->add(GET_OPERAND(0),progCounter);
if (opcode==op1 || opcode==op2 || opcode==op3) list->add(getOperand(progCounter,0),progCounter);
}
return list;
}
/* static */ void RMFT2::begin() {
DIAG(F("EXRAIL RoutCode at =%P"),RouteCode);
bool saved_diag=diag;
diag=true;
DCCEXParser::setRMFTFilter(RMFT2::ComandFilter);
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
@ -160,8 +176,8 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
// Second pass startup, define any turnouts or servos, set signals red
// add sequences onRoutines to the lookups
for (int sigpos=0;;sigpos+=4) {
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
for (int sigslot=0;;sigslot++) {
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list
doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED);
}
@ -170,7 +186,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
for (progCounter=0;; SKIPOP){
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
VPIN operand=GET_OPERAND(0);
VPIN operand=getOperand(progCounter,0);
switch (opcode) {
case OPCODE_AT:
@ -180,6 +196,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
case OPCODE_IFNOT: {
int16_t pin = (int16_t)operand;
if (pin<0) pin = -pin;
DIAG(F("EXRAIL input vpin %d"),pin);
IODevice::configureInput((VPIN)pin,true);
break;
}
@ -189,31 +206,32 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
case OPCODE_IFGTE:
case OPCODE_IFLT:
case OPCODE_DRIVE: {
DIAG(F("EXRAIL analog input vpin %d"),(VPIN)operand);
IODevice::configureAnalogIn((VPIN)operand);
break;
}
case OPCODE_TURNOUT: {
VPIN id=operand;
int addr=GET_OPERAND(1);
byte subAddr=GET_OPERAND(2);
int addr=getOperand(progCounter,1);
byte subAddr=getOperand(progCounter,2);
setTurnoutHiddenState(DCCTurnout::create(id,addr,subAddr));
break;
}
case OPCODE_SERVOTURNOUT: {
VPIN id=operand;
VPIN pin=GET_OPERAND(1);
int activeAngle=GET_OPERAND(2);
int inactiveAngle=GET_OPERAND(3);
int profile=GET_OPERAND(4);
VPIN pin=getOperand(progCounter,1);
int activeAngle=getOperand(progCounter,2);
int inactiveAngle=getOperand(progCounter,3);
int profile=getOperand(progCounter,4);
setTurnoutHiddenState(ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile));
break;
}
case OPCODE_PINTURNOUT: {
VPIN id=operand;
VPIN pin=GET_OPERAND(1);
VPIN pin=getOperand(progCounter,1);
setTurnoutHiddenState(VpinTurnout::create(id,pin));
break;
}
@ -233,20 +251,22 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS);
new RMFT2(0); // add the startup route
diag=saved_diag;
}
void RMFT2::setTurnoutHiddenState(Turnout * t) {
// turnout descriptions are in low flash F strings
t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01);
}
char RMFT2::getRouteType(int16_t id) {
for (int16_t i=0;;i++) {
int16_t rid= GETFLASHW(routeIdList+i);
for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(routeIdList,i);
if (rid==id) return 'R';
if (rid==0) break;
}
for (int16_t i=0;;i++) {
int16_t rid= GETFLASHW(automationIdList+i);
for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(automationIdList,i);
if (rid==id) return 'A';
if (rid==0) break;
}
@ -308,7 +328,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
// do the signals
// flags[n] represents the state of the nth signal in the table
for (int sigslot=0;;sigslot++) {
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigslot*4);
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
StringFormatter::send(stream,F("\n%S[%d]"),
@ -555,7 +575,7 @@ void RMFT2::loop2() {
if (delayTime!=0 && millis()-delayStart < delayTime) return;
byte opcode = GET_OPCODE;
int16_t operand = GET_OPERAND(0);
int16_t operand = getOperand(0);
// skipIf will get set to indicate a failing IF condition
bool skipIf=false;
@ -621,13 +641,13 @@ void RMFT2::loop2() {
case OPCODE_ATGTE: // wait for analog sensor>= value
timeoutFlag=false;
if (IODevice::readAnalogue(operand) >= (int)(GET_OPERAND(1))) break;
if (IODevice::readAnalogue(operand) >= (int)(getOperand(1))) break;
delayMe(50);
return;
case OPCODE_ATLT: // wait for analog sensor < value
timeoutFlag=false;
if (IODevice::readAnalogue(operand) < (int)(GET_OPERAND(1))) break;
if (IODevice::readAnalogue(operand) < (int)(getOperand(1))) break;
delayMe(50);
return;
@ -638,7 +658,7 @@ void RMFT2::loop2() {
case OPCODE_ATTIMEOUT2:
if (readSensor(operand)) break; // success without timeout
if (millis()-timeoutStart > 100*GET_OPERAND(1)) {
if (millis()-timeoutStart > 100*getOperand(1)) {
timeoutFlag=true;
break; // and drop through
}
@ -681,7 +701,7 @@ void RMFT2::loop2() {
break;
case OPCODE_POM:
if (loco) DCC::writeCVByteMain(loco, operand, GET_OPERAND(1));
if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1));
break;
case OPCODE_POWEROFF:
@ -715,11 +735,11 @@ void RMFT2::loop2() {
break;
case OPCODE_IFGTE: // do next operand if sensor>= value
skipIf=IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1));
skipIf=IODevice::readAnalogue(operand)<(int)(getOperand(1));
break;
case OPCODE_IFLT: // do next operand if sensor< value
skipIf=IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1));
skipIf=IODevice::readAnalogue(operand)>=(int)(getOperand(1));
break;
case OPCODE_IFNOT: // do next operand if sensor not set
@ -802,11 +822,11 @@ void RMFT2::loop2() {
}
case OPCODE_XFON:
DCC::setFn(operand,GET_OPERAND(1),true);
DCC::setFn(operand,getOperand(1),true);
break;
case OPCODE_XFOFF:
DCC::setFn(operand,GET_OPERAND(1),false);
DCC::setFn(operand,getOperand(1),false);
break;
case OPCODE_DCCACTIVATE: {
@ -898,7 +918,7 @@ void RMFT2::loop2() {
case OPCODE_SENDLOCO: // cab, route
{
int newPc=sequenceLookup->find(GET_OPERAND(1));
int newPc=sequenceLookup->find(getOperand(1));
if (newPc<0) break;
RMFT2* newtask=new RMFT2(newPc); // create new task
newtask->loco=operand;
@ -916,7 +936,7 @@ void RMFT2::loop2() {
case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration)
IODevice::writeAnalogue(operand,GET_OPERAND(1),GET_OPERAND(2),GET_OPERAND(3));
IODevice::writeAnalogue(operand,getOperand(1),getOperand(2),getOperand(3));
break;
case OPCODE_WAITFOR: // OPCODE_SERVO,V(pin)
@ -986,8 +1006,8 @@ void RMFT2::kill(const FSH * reason, int operand) {
}
int16_t RMFT2::getSignalSlot(int16_t id) {
for (int sigpos=0;;sigpos+=4) {
int16_t sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
for (int sigslot=0;;sigslot++) {
int16_t sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) { // end of signal list
DIAG(F("EXRAIL Signal %d not defined"), id);
return -1;
@ -997,9 +1017,10 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
// but for a servo signal it will also have SERVO_SIGNAL_FLAG set.
if ((sigid & SIGNAL_ID_MASK)!= id) continue; // keep looking
return sigpos/4; // relative slot in signals table
return sigslot; // relative slot in signals table
}
}
/* static */ void RMFT2::doSignal(int16_t id,char rag) {
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
@ -1016,11 +1037,11 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
setFlag(sigslot,rag,SIGNAL_MASK);
// Correct signal definition found, get the rag values
int16_t sigpos=sigslot*4;
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1);
VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2);
VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+3);
int16_t sigpos=sigslot*8;
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos);
VPIN redpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2);
VPIN amberpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4);
VPIN greenpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6);
if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
VPIN sigtype=sigid & ~SIGNAL_ID_MASK;
@ -1096,3 +1117,96 @@ void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
void RMFT2::printMessage2(const FSH * msg) {
DIAG(F("EXRAIL(%d) %S"),loco,msg);
}
static StringBuffer * buffer=NULL;
/* thrungeString is used to stream a HIGHFLASH string to a suitable Serial
and handle the oddities like LCD, BROADCAST and PARSE */
void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
//DIAG(F("thrunge addr=%l mode=%d id=%d"), strfar,mode,id);
Print * stream=NULL;
// Find out where the string is going
switch (mode) {
case thrunge_print:
StringFormatter::send(&Serial,F("<* EXRAIL(%d) "),loco);
stream=&Serial;
break;
case thrunge_serial: stream=&Serial; break;
case thrunge_serial1:
#ifdef SERIAL1_COMMANDS
stream=&Serial1;
#endif
break;
case thrunge_serial2:
#ifdef SERIAL2_COMMANDS
stream=&Serial2;
#endif
break;
case thrunge_serial3:
#ifdef SERIAL3_COMMANDS
stream=&Serial3;
#endif
break;
case thrunge_serial4:
#ifdef SERIAL4_COMMANDS
stream=&Serial4;
#endif
break;
case thrunge_serial5:
#ifdef SERIAL5_COMMANDS
stream=&Serial5;
#endif
break;
case thrunge_serial6:
#ifdef SERIAL6_COMMANDS
stream=&Serial6;
#endif
break;
// TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break;
case thrunge_lcn:
#if defined(LCN_SERIAL)
stream=&LCN_SERIAL;
#endif
break;
case thrunge_parse:
case thrunge_broadcast:
case thrunge_lcd:
if (!buffer) buffer=new StringBuffer();
buffer->flush();
stream=buffer;
break;
}
if (!stream) return;
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
// if mega stream it out
for (;;strfar++) {
char c=pgm_read_byte_far(strfar);
if (c=='\0') break;
stream->write(c);
}
#else
// UNO/NANO CPUs dont have high memory
// 32 bit cpus dont care anyway
stream->print((FSH *)strfar);
#endif
// and decide what to do next
switch (mode) {
case thrunge_print:
StringFormatter::send(&Serial,F(" *>\n"));
break;
// TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break;
case thrunge_parse:
DCCEXParser::parseOne(&Serial,(byte*)buffer->getString(),NULL);
break;
case thrunge_broadcast:
// TODO CommandDistributor::broadcastText(buffer->getString());
break;
case thrunge_lcd:
LCD(id,F("%s"),buffer->getString());
break;
default: break;
}
}

View File

@ -67,6 +67,12 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_IFCLOSED,OPCODE_IFTHROWN
};
enum thrunger: byte {
thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse,
thrunge_serial1, thrunge_serial2, thrunge_serial3,
thrunge_serial4, thrunge_serial5, thrunge_serial6,
thrunge_lcd, thrunge_lcn};
// Flag bits for status of hardware and TPL
@ -111,12 +117,11 @@ class LookList {
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
static const int16_t DCC_SIGNAL_FLAG=0x1000;
static const int16_t SIGNAL_ID_MASK=0x0FFF;
// Throttle Info Access functions built by exrail macros
static const byte rosterNameCount;
static const int16_t FLASH routeIdList[];
static const int16_t FLASH automationIdList[];
static const int16_t FLASH rosterIdList[];
static const int16_t HIGHFLASH routeIdList[];
static const int16_t HIGHFLASH automationIdList[];
static const int16_t HIGHFLASH rosterIdList[];
static const FSH * getRouteDescription(int16_t id);
static char getRouteType(int16_t id);
static const FSH * getTurnoutDescription(int16_t id);
@ -137,6 +142,7 @@ private:
static LookList* LookListLoader(OPCODE op1,
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
static uint16_t getOperand(int progCounter,byte n);
static RMFT2 * loopTask;
static RMFT2 * pausingTask;
void delayMe(long millisecs);
@ -148,10 +154,12 @@ private:
void kill(const FSH * reason=NULL,int operand=0);
void printMessage(uint16_t id); // Built by RMFTMacros.h
void printMessage2(const FSH * msg);
void thrungeString(uint32_t strfar, thrunger mode, byte id=0);
uint16_t getOperand(byte n);
static bool diag;
static const FLASH byte RouteCode[];
static const FLASH int16_t SignalDefinitions[];
static const HIGHFLASH byte RouteCode[];
static const HIGHFLASH int16_t SignalDefinitions[];
static byte flags[MAX_FLAGS];
static LookList * sequenceLookup;
static LookList * onThrowLookup;
@ -161,7 +169,6 @@ private:
static LookList * onRedLookup;
static LookList * onAmberLookup;
static LookList * onGreenLookup;
// Local variables - exist for each instance/task
RMFT2 *next; // loop chain

View File

@ -110,6 +110,9 @@
#undef SERIAL1
#undef SERIAL2
#undef SERIAL3
#undef SERIAL4
#undef SERIAL5
#undef SERIAL6
#undef SERVO
#undef SERVO2
#undef SERVO_TURNOUT
@ -220,6 +223,9 @@
#define SERIAL1(msg)
#define SERIAL2(msg)
#define SERIAL3(msg)
#define SERIAL4(msg)
#define SERIAL5(msg)
#define SERIAL6(msg)
#define SERVO(id,position,profile)
#define SERVO2(id,position,duration)
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)

View File

@ -73,14 +73,14 @@ void exrailHalSetup() {
#include "EXRAIL2MacroReset.h"
#undef ROUTE
#define ROUTE(id, description) id,
const int16_t FLASH RMFT2::routeIdList[]= {
const int16_t HIGHFLASH RMFT2::routeIdList[]= {
#include "myAutomation.h"
0};
// Pass 2a create throttle automation list
#include "EXRAIL2MacroReset.h"
#undef AUTOMATION
#define AUTOMATION(id, description) id,
const int16_t FLASH RMFT2::automationIdList[]= {
const int16_t HIGHFLASH RMFT2::automationIdList[]= {
#include "myAutomation.h"
0};
@ -100,30 +100,54 @@ const FSH * RMFT2::getRouteDescription(int16_t id) {
// Pass 4... Create Text sending functions
#include "EXRAIL2MacroReset.h"
const int StringMacroTracker1=__COUNTER__;
#define THRUNGE(msg,mode) \
case (__COUNTER__ - StringMacroTracker1) : {\
static const char HIGHFLASH thrunge[]=msg;\
strfar=(uint32_t)GETFARPTR(thrunge);\
tmode=mode;\
break;\
}
#undef BROADCAST
#define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break;
#define BROADCAST(msg) THRUNGE(msg,thrunge_broadcast)
#undef PARSE
#define PARSE(msg) case (__COUNTER__ - StringMacroTracker1) : DCCEXParser::parse(F(msg));break;
#define PARSE(msg) THRUNGE(msg,thrunge_parse)
#undef PRINT
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break;
#define PRINT(msg) THRUNGE(msg,thrunge_print)
#undef LCN
#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break;
#define LCN(msg) THRUNGE(msg,thrunge_lcn)
#undef SERIAL
#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break;
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
#undef SERIAL1
#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break;
#define SERIAL1(msg) THRUNGE(msg,thrunge_serial1)
#undef SERIAL2
#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break;
#define SERIAL2(msg) THRUNGE(msg,thrunge_serial2)
#undef SERIAL3
#define SERIAL3(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial3,F(msg));break;
#define SERIAL3(msg) THRUNGE(msg,thrunge_serial3)
#undef SERIAL4
#define SERIAL4(msg) THRUNGE(msg,thrunge_serial4)
#undef SERIAL5
#define SERIAL5(msg) THRUNGE(msg,thrunge_serial5)
#undef SERIAL6
#define SERIAL6(msg) THRUNGE(msg,thrunge_serial6)
#undef LCD
#define LCD(id,msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::lcd(id,F(msg));break;
#define LCD(id,msg) \
case (__COUNTER__ - StringMacroTracker1) : {\
static const char HIGHFLASH thrunge[]=msg;\
strfar=(uint32_t)GETFARPTR(thrunge);\
tmode=thrunge_lcd; \
lcdid=id;\
break;\
}
void RMFT2::printMessage(uint16_t id) {
thrunger tmode;
uint32_t strfar=0;
byte lcdid=0;
switch(id) {
#include "myAutomation.h"
default: break ;
}
if (strfar) thrungeString(strfar,tmode,lcdid);
}
@ -158,7 +182,7 @@ const byte RMFT2::rosterNameCount=0
#include "EXRAIL2MacroReset.h"
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) cabid,
const int16_t FLASH RMFT2::rosterIdList[]={
const int16_t HIGHFLASH RMFT2::rosterIdList[]={
#include "myAutomation.h"
0};
@ -198,7 +222,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
#undef VIRTUAL_SIGNAL
#define VIRTUAL_SIGNAL(id) id,0,0,0,
const FLASH int16_t RMFT2::SignalDefinitions[] = {
const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#include "myAutomation.h"
0,0,0,0 };
@ -299,6 +323,9 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
#define SERIAL1(msg) PRINT(msg)
#define SERIAL2(msg) PRINT(msg)
#define SERIAL3(msg) PRINT(msg)
#define SERIAL4(msg) PRINT(msg)
#define SERIAL5(msg) PRINT(msg)
#define SERIAL6(msg) PRINT(msg)
#define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0),
#define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L),
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
@ -323,7 +350,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
// Build RouteCode
const int StringMacroTracker2=__COUNTER__;
const FLASH byte RMFT2::RouteCode[] = {
const HIGHFLASH byte RMFT2::RouteCode[] = {
#include "myAutomation.h"
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };

65
FSH.h
View File

@ -34,42 +34,51 @@
* PROGMEM use FLASH instead
* pgm_read_byte_near use GETFLASH instead.
* pgm_read_word_near use GETFLASHW instead.
*
* Also:
* HIGHFLASH - PROGMEM forced to end of link so needs far pointers.
* GETHIGHFLASH,GETHIGHFLASHW to access them
*
*/
#include <Arduino.h>
#ifdef ARDUINO_ARCH_AVR
// AVR devices have flash memory mapped differently
// progmem can be accessed by _near functions or _far
typedef __FlashStringHelper FSH;
#define FLASH PROGMEM
#define GETFLASH(addr) pgm_read_byte_near(addr)
#if defined(ARDUINO_ARCH_MEGAAVR)
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
// AVR_MEGA memory deliberately placed at end of link may need _far functions
#define HIGHFLASH __attribute__((section(".fini2")))
#define GETFARPTR(data) pgm_get_far_address(data)
#define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset)
#define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset)
#else
// AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent
// as there is no progmem above 32kb anyway.
#define HIGHFLASH PROGMEM
#define GETFARPTR(data) ((uint32_t)(data))
#define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset))
#define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset))
#endif
#else
// Non-AVR Flat-memory devices have no need of this support so can be remapped to normal memory access
#ifdef F
#undef F
#endif
#define F(str) (str)
typedef char FSH;
#define GETFLASH(addr) (*(const unsigned char *)(addr))
#define GETFLASHW(addr) (*(const unsigned short *)(addr))
#define FLASH
#define strlen_P strlen
#define strcpy_P strcpy
#elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
typedef __FlashStringHelper FSH;
#define GETFLASH(addr) pgm_read_byte(addr)
// pgm_read_word is buggy if addr is odd but here
// we do only read well aligned addrs, the others are
// taken care about in the GET_OPERAND(n) macro in EXRAIL2.cpp.
#define GETFLASHW(addr) pgm_read_word(addr)
#ifdef FLASH
#undef FLASH
#endif
#define FLASH PROGMEM
#else // AVR and AVR compat here
typedef __FlashStringHelper FSH;
#define GETFLASH(addr) pgm_read_byte_near(addr)
#define GETFLASHW(addr) pgm_read_word_near(addr)
#define FLASH PROGMEM
#endif // flash stuff
#endif // FSH
#define F(str) (str)
typedef char FSH;
#define FLASH
#define HIGHFLASH
#define GETFARPTR(data) ((uint32_t)(data))
#define GETFLASH(addr) (*(const byte *)(addr))
#define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset))
#define GETHIGHFLASHW(data,offset) (*(const uint16_t *)(GETFARPTR(data)+offset))
#endif
#endif

View File

@ -65,6 +65,13 @@ int RingStream::availableForWrite() {
}
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.
// To save RAM we can insert a marker and the
// progmem address into the buffer instead.
@ -107,8 +114,11 @@ int RingStream::read() {
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
byte b=readRawByte();
if (b!=FLASH_INSERT_MARKER) return b;
#ifndef ARDUINO_ARCH_ESP32
// 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)
uintptr_t iFlash=0;
for (byte f=0; f<sizeof(iFlash); f++) {
@ -120,10 +130,6 @@ int RingStream::read() {
_flashInsert=reinterpret_cast<char * >( iFlash);
// and try again... so will read the first byte of the insert.
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() {
@ -189,12 +195,6 @@ bool RingStream::commit() {
_mark++;
if (_mark==_len) _mark=0;
_buffer[_mark]=lowByte(_count);
// Enable this for debugging only, it requires A LOT of RAM
//{ char s[_count+2];
// strncpy(s, (const char*)&(_buffer[_mark+1]), _count);
// s[_count]=0;
// DIAG(F("RS commit count=%d core %d \"%s\""), _count, xPortGetCoreID(), s);
//}
_ringClient = NO_CLIENT;
return true; // commit worked
}

View File

@ -27,7 +27,7 @@ class RingStream : public Print {
public:
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);
// This availableForWrite function is subverted from its original intention so that a caller

View File

@ -32,7 +32,7 @@ class StringBuffer : public Print {
private:
static const int buffer_max=64; // enough for long text msgs to throttles
int16_t _pos_write;
char _buffer[buffer_max+1];
char _buffer[buffer_max+2];
};
#endif

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*);
#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
// RingStream has special logic to handle flash strings
// 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)
((RingStream *)stream)->printFlash(flash);
else
#endif
#endif
stream->print(flash);
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 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break;
case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break;
@ -168,8 +165,8 @@ void StringFormatter::printEscape(Print * stream, char c) {
case '\r': stream->print(F("\\r")); break;
case '\0': stream->print(F("\\0")); return;
case '\t': stream->print(F("\\t")); break;
case '\\': stream->print(F("\\")); break;
default: stream->print(c);
case '\\': stream->print(F("\\\\")); break;
default: stream->write(c);
}
}

View File

@ -63,69 +63,6 @@
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) {
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
if (wt->clientid==wifiClient) return wt;
@ -135,6 +72,7 @@ WiThrottle* WiThrottle::getThrottle( int wifiClient) {
void WiThrottle::forget( byte clientId) {
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
if (wt->clientid==clientId) {
DIAG(F("Withrottle client %d dropped"),clientId);
delete wt;
break;
}
@ -159,10 +97,7 @@ WiThrottle::WiThrottle( int wificlientid) {
nextThrottle=firstThrottle;
firstThrottle= this;
clientid=wificlientid;
initSent=false; // prevent sending heartbeats before connection completed
heartBeatEnable=false; // until client turns it on
turnoutListHash = -1; // make sure turnout list is sent once
exRailSent=false;
mostRecentCab=0;
for (int loco=0;loco<MAX_MY_LOCO; loco++) myLocos[loco].throttle='\0';
}
@ -187,47 +122,17 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
heartBeat=millis();
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd);
// On first few commands, send turnout, roster and routes
if (introSent) {
if (!turnoutsSent) sendTurnouts(stream);
else if(!rosterSent) sendRoster(stream);
else if (!routesSent) sendRoutes(stream);
else if (!heartrateSent) {
heartrateSent=true;
// allow heartbeat to slow down once all metadata sent
StringFormatter::send(stream,F("*%d\nHMConnected\n"),HEARTBEAT_SECONDS);
if (initSent) {
// 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) {
// Send EX-RAIL routes list if not already sent (but not at same time as turnouts above)
exRailSent=true;
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
for (byte pass=0;pass<2;pass++) {
// first pass automations, second pass routes.
for (int ix=0;;ix++) {
int16_t id=GETFLASHW((pass?RMFT2::automationIdList:RMFT2::routeIdList)+ix);
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"),
pass?'A':'R',id,desc, pass?'4':'2');
}
}
StringFormatter::send(stream,F("\n"));
#endif
// allow heartbeat to slow down once all metadata sent
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
}
}
@ -283,32 +188,14 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
}
break;
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_PRELOAD); // return timeout value
break;
case 'M': // multithrottle
multithrottle(stream, cmd);
break;
case 'H': // send initial connection info after receiving "HU" message
if (cmd[1] == 'U') {
WiThrottle::findUniqThrottle(clientid, (char *)cmd+2);
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("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
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=GETFLASHW(RMFT2::rosterIdList+r);
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!)
StringFormatter::send(stream,F("*%d\n"), HEARTBEAT_SECONDS/2);
initSent = true;
if (cmd[1] == 'U') {
sendIntro(stream);
}
break;
case 'Q': //
@ -317,7 +204,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);
}
}
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid);
if (Diag::WITHROTTLE) DIAG(F("WiThrottle(%d) Quit"),clientid);
delete this;
break;
}
@ -378,65 +265,17 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
}
//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++) {
if (myLocos[loco].throttle=='\0' || myLocos[loco].cab == locoid) {
myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid;
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
mostRecentCab=locoid;
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
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 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;
if (myLocos[loco].throttle=='\0') {
myLocos[loco].throttle=throttleChar;
myLocos[loco].cab=locoid;
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
mostRecentCab=locoid;
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
sendFunctions(stream,loco);
//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);
@ -540,8 +379,6 @@ void WiThrottle::loop(RingStream * stream) {
// for each WiThrottle, check the heartbeat and broadcast needed
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
wt->checkHeartbeat(stream);
}
void WiThrottle::checkHeartbeat(RingStream * stream) {
@ -555,8 +392,8 @@ void WiThrottle::checkHeartbeat(RingStream * stream) {
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
//delete this;
// if it does come back, the throttle should re-acquire
delete this;
return;
}
@ -656,5 +493,120 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
DIAG(F("LocoCallback commit success"));
stashStream->commit();
CommandDistributor::broadcastPower();
}
void WiThrottle::sendIntro(Print* stream) {
introSent=true;
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("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
// set heartbeat to 2 seconds because we need to sync the metadata (1 second is too short!)
StringFormatter::send(stream,F("*%d\nHMConnecting..\n"), HEARTBEAT_PRELOAD);
}
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

@ -45,7 +45,8 @@ class WiThrottle {
~WiThrottle();
static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client
static const int HEARTBEAT_SECONDS=10; // heartbeat at 4secs to provide messaging transport
static const int HEARTBEAT_SECONDS=10; // heartbeat at 10 secs to provide messaging transport
static const int HEARTBEAT_PRELOAD=2; // request fast callback when connecting multiple messages
static const int ESTOP_SECONDS=20; // eStop if no incoming messages for more than 8secs
static WiThrottle* firstThrottle;
static int getInt(byte * cmd);
@ -61,10 +62,12 @@ class WiThrottle {
MYLOCO myLocos[MAX_MY_LOCO];
bool heartBeatEnable;
unsigned long heartBeat;
bool initSent; // valid connection established
bool exRailSent; // valid connection established
bool introSent=false;
bool turnoutsSent=false;
bool rosterSent=false;
bool routesSent=false;
bool heartrateSent=false;
uint16_t mostRecentCab;
int turnoutListHash; // used to check for changes to turnout list
bool lastPowerState; // last power state sent to this client
int DCCToWiTSpeed(int DCCSpeed);
@ -74,6 +77,11 @@ class WiThrottle {
void accessory(RingStream *, byte* cmd);
void checkHeartbeat(RingStream * stream);
void markForBroadcast2(int cab);
void sendIntro(Print * stream);
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
static RingStream * stashStream;
static WiThrottle * stashInstance;

View File

@ -66,7 +66,7 @@ void WifiInboundHandler::loop1() {
}
if (pendingCipsend) {
if (pendingCipsend && millis()-lastCIPSEND > CIPSENDgap) {
if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
pendingCipsend=false;
@ -131,11 +131,13 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
if (ch=='S') { // SEND OK probably
loopState=SKIPTOEND;
lastCIPSEND=0; // no need to wait next time
break;
}
if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND
pendingCipsend=(clientPendingCIPSEND>=0);
if (pendingCipsend) lastCIPSEND=millis(); // forces a gap to next CIPSEND
loopState=SKIPTOEND;
break;
}

View File

@ -68,7 +68,9 @@ class WifiInboundHandler {
Stream * wifiStream;
static const int INBOUND_RING = 512;
static const int OUTBOUND_RING = 2048;
static const int OUTBOUND_RING = sizeof(void*)==2?2048:8192;
static const int CIPSENDgap=100; // millis() between retries of cipsend.
RingStream * inboundRing;
RingStream * outboundRing;
@ -79,5 +81,7 @@ class WifiInboundHandler {
int clientPendingCIPSEND=-1;
int currentReplySize;
bool pendingCipsend;
uint32_t lastCIPSEND=0; // millis() of previous cipsend
};
#endif

View File

@ -344,11 +344,10 @@ void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) {
while (wifiStream->available()) stream->write(wifiStream->read());
if (stream->available()) {
int cx=stream->read();
// A newline followed by !!! is an exit
// A newline followed by ! is an exit
if (cx=='\n' || cx=='\r') startOfLine=true;
else if (startOfLine && cx=='!') break;
else startOfLine=false;
stream->write(cx);
wifiStream->write(cx);
}
}

View File

@ -149,6 +149,12 @@
#define CPU_TYPE_ERROR
#endif
// replace board type if provided by compiler
#ifdef BOARD_NAME
#undef ARDUINO_TYPE
#define ARDUINO_TYPE BOARD_NAME
#endif
////////////////////////////////////////////////////////////////////////////////
//
// WIFI_ON: All prereqs for running with WIFI are met

View File

@ -184,7 +184,7 @@ platform = ststm32
board = nucleo_f411re
framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
monitor_speed = 115200
monitor_echo = yes
@ -192,7 +192,7 @@ monitor_echo = yes
platform = teensy
board = teensy31
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@ -200,7 +200,7 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy35
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@ -208,7 +208,7 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy36
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@ -216,7 +216,7 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy40
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
@ -224,6 +224,6 @@ lib_ignore = NativeEthernet
platform = teensy
board = teensy41
framework = arduino
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore =

View File

@ -4,7 +4,10 @@
#include "StringFormatter.h"
#define VERSION "4.2.7pre1"
#define VERSION "4.2.8pre1"
// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb)
// Withrottle connect/disconnect improvements
// Report BOARD_TYPE if provided by compiler
// 4.2.7 FIX: Static IP addr
// FIX: Reuse WiThrottle list entries
// 4.2.6 FIX: Remove RAM thief