diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 8c79652..9f2baa3 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -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 diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 8e7cd33..cbb152e 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -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(" #ifdef EXRAIL_ACTIVE - sendFlashList(stream,RMFT2::routeIdList); - sendFlashList(stream,RMFT2::automationIdList); + SENDFLASHLIST(stream,RMFT2::routeIdList) + SENDFLASHLIST(stream,RMFT2::automationIdList) #endif } else { // @@ -589,7 +597,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case HASH_KEYWORD_R: // returns rosters StringFormatter::send(stream, F("\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[]) { diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 67cfce4..e72e057 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -41,6 +41,7 @@ */ #include +#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;fsetHidden(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; + } +} + \ No newline at end of file diff --git a/EXRAIL2.h b/EXRAIL2.h index 323ae57..6e6d0ca 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -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 diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 0211e22..63fc6bd 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -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) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index d5424c6..69ffed2 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -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 }; diff --git a/FSH.h b/FSH.h index 38cc052..f4bf47e 100644 --- a/FSH.h +++ b/FSH.h @@ -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 +#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 diff --git a/IODevice.h b/IODevice.h index 942fb64..72beb9e 100644 --- a/IODevice.h +++ b/IODevice.h @@ -161,6 +161,8 @@ public: // once the GPIO port concerned has been read. void setGPIOInterruptPin(int16_t pinNumber); + // Method to check if pins will overlap before creating new device. + static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0); protected: @@ -234,9 +236,6 @@ protected: // pin low if an input changes state. int16_t _gpioInterruptPin = -1; - // Method to check if pins will overlap before creating new device. - static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0); - // Static support function for subclass creation static void addDevice(IODevice *newDevice); @@ -408,6 +407,8 @@ private: #include "IO_MCP23008.h" #include "IO_MCP23017.h" #include "IO_PCF8574.h" +#include "IO_duinoNodes.h" #include "IO_EXIOExpander.h" + #endif // iodevice_h diff --git a/IO_duinoNodes.h b/IO_duinoNodes.h new file mode 100644 index 0000000..73de1a1 --- /dev/null +++ b/IO_duinoNodes.h @@ -0,0 +1,172 @@ +/* + * © 2022, Chris Harlow. All rights reserved. + * Based on original by: Robin Simonds, Beagle Bay Inc + * + * This file is part of DCC-EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ +#ifndef IO_duinoNodes_h + #define IO_duinoNodes_h +#include +#include "defines.h" +#include "IODevice.h" + +#define DN_PIN_MASK(bit) (0x80>>(bit%8)) +#define DN_GET_BIT(x) (_pinValues[(x)/8] & DN_PIN_MASK((x)) ) +#define DN_SET_BIT(x) _pinValues[(x)/8] |= DN_PIN_MASK((x)) +#define DN_CLR_BIT(x) _pinValues[(x)/8] &= ~DN_PIN_MASK((x)) + + + +class IO_duinoNodes : public IODevice { + +public: + IO_duinoNodes(VPIN firstVpin, int nPins, + byte clockPin, byte latchPin, byte dataPin, + const byte* pinmap) : + IODevice(firstVpin, nPins) { + + _latchPin=latchPin; + _clockPin=clockPin; + _dataPin=dataPin; + _pinMap=pinmap; + _nShiftBytes=(nPins+7)/8; // rounded up to multiples of 8 bits + _pinValues=(byte*) calloc(_nShiftBytes,1); + // Connect to HAL so my _write, _read and _loop will be called as required. + IODevice::addDevice(this); + } + +// Called by HAL to start handling this device + void _begin() override { + _deviceState = DEVSTATE_NORMAL; + pinMode(_latchPin,OUTPUT); + pinMode(_clockPin,OUTPUT); + pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT); + _display(); + } + +// loop called by HAL supervisor +void _loop(unsigned long currentMicros) override { + if (_pinMap) _loopInput(currentMicros); + else if (_xmitPending) _loopOutput(); +} + +void _loopInput(unsigned long currentMicros) { + + if (currentMicros-_prevMicros < POLL_MICROS) return; // Nothing to do + _prevMicros=currentMicros; + + //set latch to HIGH to freeze & store parallel data + ArduinoPins::fastWriteDigital(_latchPin, HIGH); + delayMicroseconds(1); + //set latch to LOW to enable the data to be transmitted serially + ArduinoPins::fastWriteDigital(_latchPin, LOW); + + // stream in the bitmap using mapping order provided at constructor + for (int xmitByte=0;xmitByte<_nShiftBytes; xmitByte++) { + byte newByte=0; + for (int xmitBit=0;xmitBit<8; xmitBit++) { + ArduinoPins::fastWriteDigital(_clockPin, LOW); + delayMicroseconds(1); + bool data = ArduinoPins::fastReadDigital(_dataPin); + byte map=_pinMap[xmitBit]; + if (data) newByte |= map; + else newByte &= ~map; + ArduinoPins::fastWriteDigital(_clockPin, HIGH); + delayMicroseconds(1); + } + _pinValues[xmitByte]=newByte; + // DIAG(F("DIN %x=%x"),xmitByte, newByte); + } + } + +void _loopOutput() { + // stream out the bitmap (highest pin first) + _xmitPending=false; + ArduinoPins::fastWriteDigital(_latchPin, LOW); + for (int xmitBit=_nShiftBytes*8 -1; xmitBit>=0; xmitBit--) { + ArduinoPins::fastWriteDigital(_dataPin,DN_GET_BIT(xmitBit)); + ArduinoPins::fastWriteDigital(_clockPin,HIGH); + ArduinoPins::fastWriteDigital(_clockPin,LOW); + } + ArduinoPins::fastWriteDigital(_latchPin, HIGH); + } + + int _read(VPIN vpin) override { + int pin=vpin - _firstVpin; + bool b=DN_GET_BIT(pin); + return b?1:0; + } + + void _write(VPIN vpin, int value) override { + int pin = vpin - _firstVpin; + bool oldval=DN_GET_BIT(pin); + bool newval=value!=0; + if (newval==oldval) return; // no change + if (newval) DN_SET_BIT(pin); + else DN_CLR_BIT(pin); + _xmitPending=true; // shift register will be sent on next _loop() + } + + void _display() override { + DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"), + _pinMap?F("IN"):F("OUT"), + (int)_firstVpin, + (int)_firstVpin+_nPins-1, _nShiftBytes*8); + } + +private: + static const unsigned long POLL_MICROS=100000; // 10 / S + unsigned long _prevMicros; + int _nShiftBytes=0; + VPIN _latchPin,_clockPin,_dataPin; + byte* _pinValues; + bool _xmitPending; // Only relevant in output mode + const byte* _pinMap; // NULL in output mode +}; + +class IO_DNIN8 { +public: + static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin ) + { + // input arrives as board pin 0,7,6,5,1,2,3,4 + static const byte pinmap[8]={0x80,0x01,0x02,0x04,0x40,0x20,0x10,0x08}; + if (IODevice::checkNoOverlap(firstVpin,nPins)) + new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap); + } + +}; + +class IO_DNIN8K { +public: + static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin ) + { + // input arrives as board pin 0, 1, 2, 3, 4, 5, 6, 7 + static const byte pinmap[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; + if (IODevice::checkNoOverlap(firstVpin,nPins)) + new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap); + } +}; + +class IO_DNOU8 { +public: + static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin ) + { + if (IODevice::checkNoOverlap(firstVpin,nPins)) + new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,NULL); + } + +}; +#endif \ No newline at end of file diff --git a/MotorDrivers.h b/MotorDrivers.h index 328d5e6..c066052 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -182,7 +182,7 @@ #define STACKED_MOTOR_SHIELD F("STACKED_MOTOR_SHIELD"),\ new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN), \ - new MotorDriver( 2, 10, UNUSED_PIN, 7, A3, 2.99, 1500, UNUSED_PIN), \ - new MotorDriver( 5, 4, UNUSED_PIN, 6, A4, 2.99, 1500, UNUSED_PIN) + new MotorDriver( 2, 10, UNUSED_PIN, 7, A4, 2.99, 1500, UNUSED_PIN), \ + new MotorDriver( 5, 4, UNUSED_PIN, 6, A5, 2.99, 1500, UNUSED_PIN) // #endif diff --git a/Release_Notes/duinoNodes.md b/Release_Notes/duinoNodes.md new file mode 100644 index 0000000..64a2323 --- /dev/null +++ b/Release_Notes/duinoNodes.md @@ -0,0 +1,39 @@ +Using Lew's Duino Gear boards: + +1. DNIN8 Input + This is a shift-register implementation of a digital input collector. + Multiple DNIN8 may be connected in sequence but it is IMPORTANT that the software + configuratuion correctly represents the number of boards connected otherwise the results will be meaningless. + + Use in myAnimation.h + + HAL(IO_DNIN8, firstVpin, numPins, clockPin, latchPin, dataPin) + e.g. + HAL(IO_DNIN8, 400, 16, 40, 42, 44) + + OR Use in myHal.cpp + IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin) + + + + This will create virtaul pins 400-415 using two DNIN8 boards connected in sequence. + Vpins 400-407 will be on the first board (closest to the CS) and 408-415 on the second. + + Note: 16 pins uses two boards. You may specify a non-multiple-of-8 pins but this will be rounded up to a multiple of 8 and you must connect ONLY the number of boards that this takes. + + This example uses Arduino GPIO pins 40,42,44 as these are conveniently side-by-side on a Mega which is easier when you are using a 3 strand cable. + + The DNIN8K module works the same but you must use DNIN8K in the HAL setup instead of DNIN8. NO you cant mix 8 and 8k versions in the same string of boards but you can create another string of boards. + + + DNOU8 works the same way, + Use in myAnimation.h + + HAL(IO_DNOU8, firstVpin, numPins, clockPin, latchPin, dataPin) + e.g. + HAL(IO_DNIN8, 450, 16, 45, 47, 49) + + OR Use in myHal.cpp + IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin) + +This creates a string of input pins 450-465. Note the clock/latch/data pins must be different to any DNIN8/k pins. diff --git a/RingStream.cpp b/RingStream.cpp index 7f10728..9377a0a 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -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( 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 } diff --git a/RingStream.h b/RingStream.h index 4f51a65..b477b77 100644 --- a/RingStream.h +++ b/RingStream.h @@ -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 diff --git a/StringBuffer.h b/StringBuffer.h index fe227fb..ac42964 100644 --- a/StringBuffer.h +++ b/StringBuffer.h @@ -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 \ No newline at end of file diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 87c22e5..cc78714 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -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); } } diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 019a515..920ebd6 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -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;loconext()){ - 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;rwrite('\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\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<write(c); - } - } - StringFormatter::send(stream,F("\n")); - } - -#endif - - for(int fKey=0; fKey=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]\\["), 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<write(c); + } + } + StringFormatter::send(stream,F("\n")); + } + +#endif + + for(int fKey=0; fKey=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),myLocos[loco].throttle,LorS(locoid),locoid,fstate,fKey); + } +} \ No newline at end of file diff --git a/WiThrottle.h b/WiThrottle.h index 3fa973a..6756943 100644 --- a/WiThrottle.h +++ b/WiThrottle.h @@ -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; diff --git a/WifiInboundHandler.cpp b/WifiInboundHandler.cpp index 2a8ec28..b570527 100644 --- a/WifiInboundHandler.cpp +++ b/WifiInboundHandler.cpp @@ -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; } diff --git a/WifiInboundHandler.h b/WifiInboundHandler.h index d3410cd..08f6789 100644 --- a/WifiInboundHandler.h +++ b/WifiInboundHandler.h @@ -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 diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 347d7ef..bdc8dad 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -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); } } diff --git a/defines.h b/defines.h index c4d38a3..9ad5851 100644 --- a/defines.h +++ b/defines.h @@ -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 diff --git a/platformio.ini b/platformio.ini index bc1ef0e..5fcda00 100644 --- a/platformio.ini +++ b/platformio.ini @@ -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 = \ No newline at end of file diff --git a/version.h b/version.h index 5d36187..a17babd 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,11 @@ #include "StringFormatter.h" -#define VERSION "4.2.7pre1" +#define VERSION "4.2.9pre1" +// 4.2.9 duinoNodes support +// 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