From 977802f160102002a9f96872208cfab99620a0d2 Mon Sep 17 00:00:00 2001 From: Fred Date: Tue, 3 May 2022 16:53:33 -0400 Subject: [PATCH 01/10] Servo signal (#227) Prepping for version 4.1 SERVO_SIGNAL definition in EXRAIL SERVO_SIGNAL(vpin, redpos, amberpos, greenpos) use RED/AMBER/GREEN as for led signals. * SIGNALH, ATGTE, ATLT UNTESTED * Automatic ALIAS(name) and _ in keywords * EXRAIL FORGET current loco * EXRAIL * EXRAIL VIRTUAL_TURNOUT * Cleanup version.h * Update version.h (#223) Rewrite & Updated the 4.0.0 Section * fix * Incoming LCN turnout throw. * KILLALL macro and DIAGNOSTIC messages when KILL command used. * EXRAIL PARSE * Rebuild throttle info getters UNTESTED... create different methods to obtain throttle info without being withrottle specific. Also implements turnout description of "*" as hidden. * J command parsing JA JR JT commands parsed EXRAIL sets hidden turnout state HIDDEN description macro Turnouts hidden flag bit UNO seems OK, MEGA UNTESTED * Assist notes draft & syntax tweaks * Throttle notes * uno memory saver * JA JR and * Subtle corrections * Update version.h * I2C code corrections Corrections to I2C code: 1) I2CManager_Mega4809.h: Correct bitwise 'and' to logical 'and' - no impact. 2) I2CManager_Wire.h: Ensure that error codes from Wire subsystem are passed back to caller in queueRequest(). * RAG Ifs and cmds * IF block perf/memory * Allow negative route ids. * correct GREEN keyword * Update version.h * myFilter auto detect * Update version.h * fix weak ref to myFilter * ACK defaults now 50-2000-20000 * Update version.h * Improved SIGNALs startup and diagnostics * Update IO_PCA9685.cpp * Allow turnout id 0 * Position servo pin used as GPIO * NoPowerOff LEDS * CALLBACK parameter optional for Write * WRITE CV ON PROG Callback parameters are now optional on PROG * Updated CV read command Equivalent to uses the verify callback. Co-authored-by: Asbelos Co-authored-by: Kcsmith0708 Co-authored-by: Neil McKechnie Co-authored-by: Ash-4 <81280775+Ash-4@users.noreply.github.com> --- DCC.cpp | 5 +- DCC.h | 5 +- DCCEXParser.cpp | 123 +++++++++++++- DCCEXParser.h | 2 + DCCWaveform.h | 6 +- EXRAIL2.cpp | 279 ++++++++++++++++++++----------- EXRAIL2.h | 66 +++++--- EXRAIL2MacroReset.h | 30 +++- EXRAILMacros.h | 127 +++++++++----- I2CManager_Mega4809.h | 2 +- I2CManager_Wire.h | 14 +- IO_PCA9685.cpp | 16 +- LCN.cpp | 6 +- Release_Notes/ThrottleAssists.md | 75 +++++++++ Turnouts.h | 12 +- WiThrottle.cpp | 49 ++++-- version.h | 22 ++- 17 files changed, 634 insertions(+), 205 deletions(-) create mode 100644 Release_Notes/ThrottleAssists.md diff --git a/DCC.cpp b/DCC.cpp index 6674fb9..1c53456 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -647,7 +647,7 @@ byte DCC::cv2(int cv) { return lowByte(cv); } -int DCC::lookupSpeedTable(int locoId) { +int DCC::lookupSpeedTable(int locoId, bool autoCreate) { // determine speed reg for this loco int firstEmpty = MAX_LOCOS; int reg; @@ -655,6 +655,9 @@ int DCC::lookupSpeedTable(int locoId) { if (speedTable[reg].loco == locoId) break; if (speedTable[reg].loco == 0 && firstEmpty == MAX_LOCOS) firstEmpty = reg; } + + // return -1 if not found and not auto creating + if (reg== MAX_LOCOS && !autoCreate) return -1; if (reg == MAX_LOCOS) reg = firstEmpty; if (reg >= MAX_LOCOS) { DIAG(F("Too many locos")); diff --git a/DCC.h b/DCC.h index fbeb603..5d9064c 100644 --- a/DCC.h +++ b/DCC.h @@ -128,7 +128,6 @@ public: static void forgetLoco(int cab); // removes any speed reminders for this loco static void forgetAllLocos(); // removes all speed reminders static void displayCabList(Print *stream); - static FSH *getMotorShieldName(); static inline void setGlobalSpeedsteps(byte s) { globalSpeedsteps = s; @@ -148,7 +147,8 @@ public: unsigned long functions; }; static LOCO speedTable[MAX_LOCOS]; - + static int lookupSpeedTable(int locoId, bool autoCreate=true); + private: static byte joinRelay; static byte loopStatus; @@ -162,7 +162,6 @@ private: static byte cv1(byte opcode, int cv); static byte cv2(int cv); - static int lookupSpeedTable(int locoId); static void issueReminders(); static void callback(int value); diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index f84c123..2dfac84 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -37,6 +37,7 @@ #include "CommandDistributor.h" #include "EEStore.h" #include "DIAG.h" +#include "EXRAIL2.h" #include //////////////////////////////////////////////////////////////////////////////// @@ -74,8 +75,10 @@ const int16_t HASH_KEYWORD_SPEED28 = -17064; const int16_t HASH_KEYWORD_SPEED128 = 25816; const int16_t HASH_KEYWORD_SERVO=27709; const int16_t HASH_KEYWORD_VPIN=-415; -const int16_t HASH_KEYWORD_C=67; -const int16_t HASH_KEYWORD_T=84; +const int16_t HASH_KEYWORD_A='A'; +const int16_t HASH_KEYWORD_C='C'; +const int16_t HASH_KEYWORD_R='R'; +const int16_t HASH_KEYWORD_T='T'; const int16_t HASH_KEYWORD_LCN = 15137; const int16_t HASH_KEYWORD_HAL = 10853; const int16_t HASH_KEYWORD_SHOW = -21309; @@ -91,7 +94,7 @@ Print *DCCEXParser::stashStream = NULL; RingStream *DCCEXParser::stashRingStream = NULL; byte DCCEXParser::stashTarget=0; -// This is a JMRI command parser, one instance per incoming stream +// This is a JMRI command parser. // It doesnt know how the string got here, nor how it gets back. // It knows nothing about hardware or tracks... it just parses strings and // calls the corresponding DCC api. @@ -145,7 +148,7 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte runningValue = 16 * runningValue + (hot - 'A' + 10); break; } - if (hot >= 'A' && hot <= 'Z') + if (hot=='_' || (hot >= 'A' && hot <= 'Z')) { // Since JMRI got modified to send keywords in some rare cases, we need this // Super Kluge to turn keywords into a hash value that can be recognised later @@ -162,9 +165,12 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte return parameterCount; } -FILTER_CALLBACK DCCEXParser::filterCallback = 0; +extern __attribute__((weak)) void myFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]); +FILTER_CALLBACK DCCEXParser::filterCallback = myFilter; FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0; AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0; + +// deprecated void DCCEXParser::setFilter(FILTER_CALLBACK filter) { filterCallback = filter; @@ -214,10 +220,23 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) return; // filterCallback asked us to ignore case 't': // THROTTLE { + if (params==1) { // display state + + int16_t slot=DCC::lookupSpeedTable(p[0],false); + if (slot>=0) { + DCC::LOCO * sp=&DCC::speedTable[slot]; + StringFormatter::send(stream,F("\n"), + sp->loco,slot,sp->speedCode,sp->functions); + } + else // send dummy state speed 0 fwd no functions. + StringFormatter::send(stream,F("\n"),p[0]); + return; + } + int16_t cab; int16_t tspeed; int16_t direction; - + if (params == 4) { // cab = p[1]; @@ -333,7 +352,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) break; if (params == 1) // Write new loco id (clearing consist and managing short/long) DCC::setLocoId(p[0],callback_Wloco); - else // WRITE CV ON PROG + else if (params == 4) // WRITE CV ON PROG + DCC::writeCVByte(p[0], p[1], callback_W4); + else // WRITE CV ON PROG DCC::writeCVByte(p[0], p[1], callback_W); return; @@ -361,6 +382,13 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) return; case 'R': // READ CV ON PROG + if (params == 1) + { // -- uses verify callback + if (!stashCallback(stream, p, ringStream)) + break; + DCC::verifyCVByte(p[0], p[1], callback_Vbyte); + return; + } if (params == 3) { // if (!stashCallback(stream, p, ringStream)) @@ -500,6 +528,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) DCC::setFn(p[0], p[1], p[2] == 1); return; +#if WIFI_ON case '+': // Complex Wifi interface command (not usual parse) if (atCommandCallback && !ringStream) { DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); @@ -508,6 +537,69 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) return; } break; +#endif + + case 'J' : // throttle info access + { + if ((params<1) | (params>2)) break; // + int16_t id=(params==2)?p[1]:0; + switch(p[0]) { + case HASH_KEYWORD_A: // returns automations/routes + StringFormatter::send(stream, F(" +#ifdef EXRAIL_ACTIVE + sendFlashList(stream,RMFT2::routeIdList); + sendFlashList(stream,RMFT2::automationIdList); +#endif + } + else { // + StringFormatter::send(stream,F(" %d %c \"%S\""), + id, +#ifdef EXRAIL_ACTIVE + RMFT2::getRouteType(id), // A/R + RMFT2::getRouteDescription(id) +#else + 'X',F("") +#endif + ); + } + StringFormatter::send(stream, F(">\n")); + return; + case HASH_KEYWORD_R: // returns rosters + StringFormatter::send(stream, F("\n")); + return; + case HASH_KEYWORD_T: // returns turnout list + StringFormatter::send(stream, F(" + for ( Turnout * t=Turnout::first(); t; t=t->next()) { + if (t->isHidden()) continue; + StringFormatter::send(stream, F(" %d"),t->getId()); + } + } + else { // + Turnout * t=Turnout::get(id); + if (!t || t->isHidden()) StringFormatter::send(stream, F(" %d X"),id); + else StringFormatter::send(stream, F(" %d %c \"%S\""), + id,t->isThrown()?'T':'C', +#ifdef EXRAIL_ACTIVE + RMFT2::getTurnoutDescription(id) +#else + F("") +#endif + ); + } + StringFormatter::send(stream, F(">\n")); + return; + default: break; + } // switch(p[1]) + break; // case J + } default: //anything else will diagnose and drop out to DIAG(F("Opcode=%c params=%d"), opcode, params); @@ -521,6 +613,14 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) 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[]) { @@ -856,7 +956,14 @@ void DCCEXParser::commitAsyncReplyStream() { void DCCEXParser::callback_W(int16_t result) { StringFormatter::send(getAsyncReplyStream(), - F("\n"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1); + F("\n"), stashP[0], result == 1 ? stashP[1] : -1); + commitAsyncReplyStream(); +} + +void DCCEXParser::callback_W4(int16_t result) +{ + StringFormatter::send(getAsyncReplyStream(), + F("\n"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1); commitAsyncReplyStream(); } diff --git a/DCCEXParser.h b/DCCEXParser.h index 5761eb0..d9fe0e1 100644 --- a/DCCEXParser.h +++ b/DCCEXParser.h @@ -60,6 +60,7 @@ struct DCCEXParser static int16_t stashP[MAX_COMMAND_PARAMS]; static bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream); static void callback_W(int16_t result); + static void callback_W4(int16_t result); static void callback_B(int16_t result); static void callback_R(int16_t result); static void callback_Rloco(int16_t result); @@ -70,6 +71,7 @@ struct DCCEXParser static FILTER_CALLBACK filterRMFTCallback; static AT_COMMAND_CALLBACK atCommandCallback; static void funcmap(int16_t cab, byte value, byte fstart, byte fstop); + static void sendFlashList(Print * stream,const int16_t flashList[]); }; diff --git a/DCCWaveform.h b/DCCWaveform.h index 14f2f11..3838b8e 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -157,7 +157,7 @@ class DCCWaveform { volatile bool ackPending; volatile bool ackDetected; int ackThreshold; - int ackLimitmA = 60; + int ackLimitmA = 50; int ackMaxCurrent; unsigned long ackCheckStart; // millis unsigned int ackCheckDuration; // millis @@ -165,8 +165,8 @@ class DCCWaveform { unsigned int ackPulseDuration; // micros unsigned long ackPulseStart; // micros - unsigned int minAckPulseDuration = 4000; // micros - unsigned int maxAckPulseDuration = 8500; // micros + unsigned int minAckPulseDuration = 2000; // micros + unsigned int maxAckPulseDuration = 20000; // micros volatile static uint8_t numAckGaps; volatile static uint8_t numAckSamples; diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 127a1ab..60c66c6 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -40,7 +40,6 @@ T2. Extend to >64k */ - #include #include "EXRAIL2.h" #include "DCC.h" @@ -63,7 +62,11 @@ const int16_t HASH_KEYWORD_UNLATCH=1353; const int16_t HASH_KEYWORD_PAUSE=-4142; const int16_t HASH_KEYWORD_RESUME=27609; const int16_t HASH_KEYWORD_KILL=5218; +const int16_t HASH_KEYWORD_ALL=3457; const int16_t HASH_KEYWORD_ROUTES=-3702; +const int16_t HASH_KEYWORD_RED=26099; +const int16_t HASH_KEYWORD_AMBER=18713; +const int16_t HASH_KEYWORD_GREEN=-31493; // One instance of RMFT clas is used for each "thread" in the automation. // Each thread manages a loco on a journey through the layout, and/or may manage a scenery automation. @@ -166,14 +169,10 @@ int16_t LookList::find(int16_t value) { // Second pass startup, define any turnouts or servos, set signals red // add sequences onRoutines to the lookups - for (int sigpos=0;;sigpos+=3) { - VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos); - if (redpin==0) break; // end of signal list - VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1); - VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2); - IODevice::write(redpin,true); - if (amberpin) IODevice::write(amberpin,false); - IODevice::write(greenpin,false); + for (int sigpos=0;;sigpos+=4) { + VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos); + if (sigid==0) break; // end of signal list + doSignal(sigid & (~ SERVO_SIGNAL_FLAG) & (~ACTIVE_HIGH_SIGNAL_FLAG), SIGNAL_RED); } for (progCounter=0;; SKIPOP){ @@ -196,7 +195,7 @@ int16_t LookList::find(int16_t value) { VPIN id=operand; int addr=GET_OPERAND(1); byte subAddr=GET_OPERAND(2); - DCCTurnout::create(id,addr,subAddr); + setTurnoutHiddenState(DCCTurnout::create(id,addr,subAddr)); break; } @@ -206,14 +205,14 @@ int16_t LookList::find(int16_t value) { int activeAngle=GET_OPERAND(2); int inactiveAngle=GET_OPERAND(3); int profile=GET_OPERAND(4); - ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile); + setTurnoutHiddenState(ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile)); break; } case OPCODE_PINTURNOUT: { VPIN id=operand; VPIN pin=GET_OPERAND(1); - VpinTurnout::create(id,pin); + setTurnoutHiddenState(VpinTurnout::create(id,pin)); break; } @@ -257,6 +256,23 @@ int16_t LookList::find(int16_t value) { new RMFT2(0); // add the startup route } +void RMFT2::setTurnoutHiddenState(Turnout * t) { + 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); + if (rid==id) return 'R'; + if (rid==0) break; + } + for (int16_t i=0;;i++) { + int16_t rid= GETFLASHW(automationIdList+i); + if (rid==id) return 'A'; + if (rid==0) break; + } + return 'X'; +} // This filter intercepts <> commands to do the following: // - Implement RMFT specific commands/diagnostics // - Reject/modify JMRI commands that would interfere with RMFT processing @@ -346,32 +362,30 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { } return true; - case HASH_KEYWORD_ROUTES: // JMRI withrottle support - if (paramCount>1) return false; - StringFormatter::send(stream,F("")); - return true; - default: break; } - + + // check KILL ALL here, otherwise the next validation confuses ALL with a flag + if (p[0]==HASH_KEYWORD_KILL && p[1]==HASH_KEYWORD_ALL) { + while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask + return true; + } + // all other / commands take 1 parameter 0 to MAX_FLAGS-1 - if (paramCount!=2 || p[1]<0 || p[1]>=MAX_FLAGS) return false; switch (p[0]) { - case HASH_KEYWORD_KILL: // Kill taskid + case HASH_KEYWORD_KILL: // Kill taskid|ALL { RMFT2 * task=loopTask; while(task) { - if (task->taskId==p[1]) { - delete task; - return true; - } - task=task->next; - if (task==loopTask) break; + if (task->taskId==p[1]) { + task->kill(F("KILL")); + return true; + } + task=task->next; + if (task==loopTask) break; } } return false; @@ -391,6 +405,18 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { case HASH_KEYWORD_UNLATCH: setFlag(p[1], 0, LATCH_FLAG); return true; + + case HASH_KEYWORD_RED: + doSignal(p[1],SIGNAL_RED); + return true; + + case HASH_KEYWORD_AMBER: + doSignal(p[1],SIGNAL_AMBER); + return true; + + case HASH_KEYWORD_GREEN: + doSignal(p[1],SIGNAL_GREEN); + return true; default: return false; @@ -402,11 +428,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { // Automations are given a state to set the button to "handoff" which implies // handing over the loco to the automation. // Routes are given "Set" buttons and do not cause the loco to be handed over. -void RMFT2::emitWithrottleRouteList(Print* stream) { - StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL")); - emitWithrottleDescriptions(stream); - StringFormatter::send(stream,F("\n")); -} + RMFT2::RMFT2(int progCtr) { @@ -428,7 +450,7 @@ RMFT2::RMFT2(int progCtr) { invert=false; timeoutFlag=false; stackDepth=0; - onTurnoutId=0; // Not handling an ONTHROW/ONCLOSE + onTurnoutId=-1; // Not handling an ONTHROW/ONCLOSE // chain into ring of RMFTs if (loopTask==NULL) { @@ -493,28 +515,21 @@ bool RMFT2::skipIfBlock() { while (nest > 0) { SKIPOP; byte opcode = GET_OPCODE; - switch(opcode) { - case OPCODE_ENDEXRAIL: - kill(F("missing ENDIF"), nest); - return false; - case OPCODE_IF: - case OPCODE_IFCLOSED: - case OPCODE_IFGTE: - case OPCODE_IFLT: - case OPCODE_IFNOT: - case OPCODE_IFRANDOM: - case OPCODE_IFRESERVE: - case OPCODE_IFTHROWN: - case OPCODE_IFTIMEOUT: - nest++; - break; - case OPCODE_ENDIF: - nest--; - break; - case OPCODE_ELSE: - // if nest==1 then this is the ELSE for the IF we are skipping - if (nest==1) nest=0; // cause loop exit and return after ELSE - break; + // all other IF type commands increase the nesting level + if (opcode>IF_TYPE_OPCODES) nest++; + else switch(opcode) { + case OPCODE_ENDEXRAIL: + kill(F("missing ENDIF"), nest); + return false; + + case OPCODE_ENDIF: + nest--; + break; + + case OPCODE_ELSE: + // if nest==1 then this is the ELSE for the IF we are skipping + if (nest==1) nest=0; // cause loop exit and return after ELSE + break; default: break; } @@ -542,6 +557,10 @@ void RMFT2::loop2() { byte opcode = GET_OPCODE; int16_t operand = GET_OPERAND(0); + + // skipIf will get set to indicate a failing IF condition + bool skipIf=false; + // if (diag) DIAG(F("RMFT2 %d %d"),opcode,operand); // Attention: Returning from this switch leaves the program counter unchanged. // This is used for unfinished waits for timers or sensors. @@ -570,6 +589,13 @@ void RMFT2::loop2() { driveLoco(operand); break; + case OPCODE_FORGET: + if (loco!=0) { + DCC::forgetLoco(loco); + loco=0; + } + break; + case OPCODE_INVERT_DIRECTION: invert= !invert; driveLoco(speedo); @@ -594,6 +620,18 @@ void RMFT2::loop2() { delayMe(50); return; + case OPCODE_ATGTE: // wait for analog sensor>= value + timeoutFlag=false; + if (IODevice::readAnalogue(operand) >= (int)(GET_OPERAND(1))) break; + delayMe(50); + return; + + case OPCODE_ATLT: // wait for analog sensor < value + timeoutFlag=false; + if (IODevice::readAnalogue(operand) < (int)(GET_OPERAND(1))) break; + delayMe(50); + return; + case OPCODE_ATTIMEOUT1: // ATTIMEOUT(vpin,timeout) part 1 timeoutStart=millis(); timeoutFlag=false; @@ -609,7 +647,7 @@ void RMFT2::loop2() { return; case OPCODE_IFTIMEOUT: // do next operand if timeout flag set - if (!timeoutFlag) if (!skipIfBlock()) return; + skipIf=!timeoutFlag; break; case OPCODE_AFTER: // waits for sensor to hit and then remain off for 0.5 seconds. (must come after an AT operation) @@ -661,40 +699,52 @@ void RMFT2::loop2() { break; case OPCODE_IF: // do next operand if sensor set - if (!readSensor(operand)) if (!skipIfBlock()) return; + skipIf=!readSensor(operand); break; case OPCODE_ELSE: // skip to matching ENDIF - if (!skipIfBlock()) return; + skipIf=true; break; case OPCODE_IFGTE: // do next operand if sensor>= value - if (IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1))) if (!skipIfBlock()) return; + skipIf=IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1)); break; case OPCODE_IFLT: // do next operand if sensor< value - if (IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1))) if (!skipIfBlock()) return; + skipIf=IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1)); break; case OPCODE_IFNOT: // do next operand if sensor not set - if (readSensor(operand)) if (!skipIfBlock()) return; + skipIf=readSensor(operand); break; case OPCODE_IFRANDOM: // do block on random percentage - if ((int16_t)random(100)>=operand) if (!skipIfBlock()) return; + skipIf=(int16_t)random(100)>=operand; break; case OPCODE_IFRESERVE: // do block if we successfully RERSERVE if (!getFlag(operand,SECTION_FLAG)) setFlag(operand,SECTION_FLAG); - else if (!skipIfBlock()) return; + else skipIf=true; + break; + + case OPCODE_IFRED: // do block if signal as expected + skipIf=!isSignal(operand,SIGNAL_RED); + break; + + case OPCODE_IFAMBER: // do block if signal as expected + skipIf=!isSignal(operand,SIGNAL_AMBER); + break; + + case OPCODE_IFGREEN: // do block if signal as expected + skipIf=!isSignal(operand,SIGNAL_GREEN); break; case OPCODE_IFTHROWN: - if (Turnout::isClosed(operand)) if (!skipIfBlock()) return; + skipIf=Turnout::isClosed(operand); break; case OPCODE_IFCLOSED: - if (!Turnout::isClosed(operand)) if (!skipIfBlock()) return; + skipIf=Turnout::isThrown(operand); break; case OPCODE_ENDIF: @@ -717,15 +767,15 @@ void RMFT2::loop2() { break; case OPCODE_RED: - doSignal(operand,true,false,false); + doSignal(operand,SIGNAL_RED); break; case OPCODE_AMBER: - doSignal(operand,false,true,false); + doSignal(operand,SIGNAL_AMBER); break; case OPCODE_GREEN: - doSignal(operand,false,false,true); + doSignal(operand,SIGNAL_GREEN); break; case OPCODE_FON: @@ -787,7 +837,11 @@ void RMFT2::loop2() { case OPCODE_ENDEXRAIL: kill(); return; - + + case OPCODE_KILLALL: + while(loopTask) loopTask->kill(F("KILLALL")); + return; + case OPCODE_JOIN: DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); @@ -890,6 +944,8 @@ void RMFT2::loop2() { kill(F("INVOP"),operand); } // Falling out of the switch means move on to the next opcode + // but if we are skipping a false IF or else + if (skipIf) if (!skipIfBlock()) return; SKIPOP; } @@ -917,26 +973,65 @@ void RMFT2::kill(const FSH * reason, int operand) { delete this; } -/* static */ void RMFT2::doSignal(VPIN id,bool red, bool amber, bool green) { - //if (diag) DIAG(F(" dosignal %d"),id); - for (int sigpos=0;;sigpos+=3) { - VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos); - //if (diag) DIAG(F("red=%d"),redpin); - if (redpin==0) { - DIAG(F("EXRAIL Signal %d not defined"), id); - return; // signal not found - } - if (redpin==id) { - VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1); - VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2); - //if (diag) DIAG(F("signal %d %d %d"),redpin,amberpin,greenpin); - // If amberpin is zero, synthesise amber from red+green - IODevice::write(redpin,red || (amber && (amberpin==0))); - if (amberpin) IODevice::write(amberpin,amber); - if (greenpin) IODevice::write(greenpin,green || (amber && (amberpin==0))); - return; - } +int16_t RMFT2::getSignalSlot(VPIN id) { + for (int sigpos=0;;sigpos+=4) { + VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos); + if (sigid==0) { // end of signal list + DIAG(F("EXRAIL Signal %d not defined"), id); + return -1; + } + // sigid is the signal id used in RED/AMBER/GREEN macro + // for a LED signal it will be same as redpin + // but for a servo signal it will also have SERVO_SIGNAL_FLAG set. + + if ((sigid & ~SERVO_SIGNAL_FLAG & ~ACTIVE_HIGH_SIGNAL_FLAG)!= id) continue; // keep looking + return sigpos/4; // relative slot in signals table + } +} +/* static */ void RMFT2::doSignal(VPIN id,char rag) { + if (diag) DIAG(F(" doSignal %d %x"),id,rag); + int16_t sigslot=getSignalSlot(id); + if (sigslot<0) return; + + // keep track of signal state + 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); + if (diag) DIAG(F("signal %d %d %d %d"),sigid,redpin,amberpin,greenpin); + + if (sigid & SERVO_SIGNAL_FLAG) { + // A servo signal, the pin numbers are actually servo positions + // Note, setting a signal to a zero position has no effect. + int16_t servopos= rag==SIGNAL_RED? redpin: (rag==SIGNAL_GREEN? greenpin : amberpin); + if (diag) DIAG(F("sigA %d %d"),id,servopos); + if (servopos!=0) IODevice::writeAnalogue(id,servopos,PCA9685::Bounce); + return; } + + // LED or similar 3 pin signal + // If amberpin is zero, synthesise amber from red+green + const byte SIMAMBER=0x00; + if (rag==SIGNAL_AMBER && (amberpin==0)) rag=SIMAMBER; // special case this func only + + // Manage invert (HIGH on) pins + bool aHigh=sigid & ACTIVE_HIGH_SIGNAL_FLAG; + + // set the three pins + if (redpin) IODevice::write(redpin,(rag==SIGNAL_RED || rag==SIMAMBER)^aHigh); + if (amberpin) IODevice::write(amberpin,(rag==SIGNAL_AMBER)^aHigh); + if (greenpin) IODevice::write(greenpin,(rag==SIGNAL_GREEN || rag==SIMAMBER)^aHigh); + return; +} + +/* static */ bool RMFT2::isSignal(VPIN id,char rag) { + int16_t sigslot=getSignalSlot(id); + if (sigslot<0) return false; + return (flags[sigslot] & SIGNAL_MASK) == rag; } void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) { @@ -983,8 +1078,4 @@ void RMFT2::printMessage2(const FSH * msg) { DIAG(F("EXRAIL(%d) %S"),loco,msg); } -// This is called by emitRouteDescriptions to emit a withrottle description for a route or autoomation. -void RMFT2::emitRouteDescription(Print * stream, char type, int id, const FSH * description) { - StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"), - type,id,description, type=='R'?'2':'4'); -} + diff --git a/EXRAIL2.h b/EXRAIL2.h index 3114511..b44ce6c 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -22,6 +22,7 @@ #define EXRAIL2_H #include "FSH.h" #include "IODevice.h" +#include "Turnouts.h" // The following are the operation codes (or instructions) for a kind of virtual machine. // Each instruction is normally 3 bytes long with an operation code followed by a parameter. @@ -33,33 +34,48 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION, OPCODE_RESERVE,OPCODE_FREE, OPCODE_AT,OPCODE_AFTER,OPCODE_AUTOSTART, - OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,OPCODE_IFTIMEOUT, + OPCODE_ATGTE,OPCODE_ATLT, + OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2, OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET, - OPCODE_IF,OPCODE_IFNOT,OPCODE_ENDIF,OPCODE_IFRANDOM,OPCODE_IFRESERVE, - OPCODE_IFCLOSED, OPCODE_IFTHROWN,OPCODE_ELSE, + OPCODE_ENDIF,OPCODE_ELSE, OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT, OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF, OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE, OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR, OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN, OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM, - OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO, + OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET, OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON, OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT, OPCODE_PRINT,OPCODE_DCCACTIVATE, - OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,OPCODE_IFGTE,OPCODE_IFLT, - OPCODE_ROSTER, - OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,OPCODE_ENDTASK,OPCODE_ENDEXRAIL + OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE, + OPCODE_ROSTER,OPCODE_KILLALL, + OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE, + OPCODE_ENDTASK,OPCODE_ENDEXRAIL, + + // OPcodes below this point are skip-nesting IF operations + // placed here so that they may be skipped as a group + // see skipIfBlock() + IF_TYPE_OPCODES, // do not move this... + OPCODE_IFRED,OPCODE_IFAMBER,OPCODE_IFGREEN, + OPCODE_IFGTE,OPCODE_IFLT, + OPCODE_IFTIMEOUT, + OPCODE_IF,OPCODE_IFNOT, + OPCODE_IFRANDOM,OPCODE_IFRESERVE, + OPCODE_IFCLOSED, OPCODE_IFTHROWN }; // Flag bits for status of hardware and TPL static const byte SECTION_FLAG = 0x80; - static const byte LATCH_FLAG = 0x40; - static const byte TASK_FLAG = 0x20; - static const byte SPARE_FLAG = 0x10; - static const byte COUNTER_MASK= 0x0F; + static const byte LATCH_FLAG = 0x40; + static const byte TASK_FLAG = 0x20; + static const byte SPARE_FLAG = 0x10; + static const byte SIGNAL_MASK = 0x0C; + static const byte SIGNAL_RED = 0x08; + static const byte SIGNAL_AMBER = 0x0C; + static const byte SIGNAL_GREEN = 0x04; static const byte MAX_STACK_DEPTH=4; @@ -86,14 +102,23 @@ class LookList { RMFT2(int route, uint16_t cab); ~RMFT2(); static void readLocoCallback(int16_t cv); - static void emitWithrottleRouteList(Print* stream); static void createNewTask(int route, uint16_t cab); static void turnoutEvent(int16_t id, bool closed); static void activateEvent(int16_t addr, bool active); - static void emitTurnoutDescription(Print* stream,int16_t id); - static const byte rosterNameCount; - static void emitWithrottleRoster(Print * stream); - static const FSH * getRosterFunctions(int16_t cabid); + static const int16_t SERVO_SIGNAL_FLAG=0x4000; + static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000; + + // 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 FSH * getRouteDescription(int16_t id); + static char getRouteType(int16_t id); + static const FSH * getTurnoutDescription(int16_t id); + static const FSH * getRosterName(int16_t id); + static const FSH * getRosterFunctions(int16_t id); + private: static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]); static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ; @@ -101,10 +126,10 @@ private: static void setFlag(VPIN id,byte onMask, byte OffMask=0); static bool getFlag(VPIN id,byte mask); static int16_t progtrackLocoId; - static void doSignal(VPIN id,bool red, bool amber, bool green); - static void emitRouteDescription(Print * stream, char type, int id, const FSH * description); - static void emitWithrottleDescriptions(Print * stream); - + static void doSignal(VPIN id,char rag); + static bool isSignal(VPIN id,char rag); + static int16_t getSignalSlot(VPIN id); + static void setTurnoutHiddenState(Turnout * t); static RMFT2 * loopTask; static RMFT2 * pausingTask; void delayMe(long millisecs); @@ -128,6 +153,7 @@ private: static LookList * onActivateLookup; static LookList * onDeactivateLookup; + // Local variables - exist for each instance/task RMFT2 *next; // loop chain int progCounter; // Byte offset of next route opcode in ROUTES table diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 6a28b7f..dde5ea2 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -29,6 +29,8 @@ #undef ALIAS #undef AMBER #undef AT +#undef ATGTE +#undef ATLT #undef ATTIMEOUT #undef AUTOMATION #undef AUTOSTART @@ -52,20 +54,25 @@ #undef FOFF #undef FOLLOW #undef FON +#undef FORGET #undef FREE #undef FWD #undef GREEN #undef IF +#undef IFAMBER #undef IFCLOSED +#undef IFGREEN #undef IFGTE #undef IFLT #undef IFNOT #undef IFRANDOM +#undef IFRED #undef IFRESERVE #undef IFTHROWN #undef IFTIMEOUT #undef INVERT_DIRECTION #undef JOIN +#undef KILLALL #undef LATCH #undef LCD #undef LCN @@ -75,6 +82,7 @@ #undef ONDEACTIVATEL #undef ONCLOSE #undef ONTHROW +#undef PARSE #undef PAUSE #undef PIN_TURNOUT #undef PRINT @@ -99,9 +107,11 @@ #undef SERVO #undef SERVO2 #undef SERVO_TURNOUT +#undef SERVO_SIGNAL #undef SET #undef SETLOCO #undef SIGNAL +#undef SIGNALH #undef SPEED #undef START #undef STOP @@ -109,6 +119,7 @@ #undef TURNOUT #undef UNJOIN #undef UNLATCH +#undef VIRTUAL_TURNOUT #undef WAITFOR #undef XFOFF #undef XFON @@ -117,11 +128,13 @@ #define ACTIVATE(addr,subaddr) #define ACTIVATEL(addr) #define AFTER(sensor_id) -#define ALIAS(name,value) +#define ALIAS(name,value...) #define AMBER(signal_id) #define AT(sensor_id) +#define ATGTE(sensor_id,value) +#define ATLT(sensor_id,value) #define ATTIMEOUT(sensor_id,timeout_ms) -#define AUTOMATION(id, description) +#define AUTOMATION(id,description) #define AUTOSTART #define BROADCAST(msg) #define CALL(route) @@ -142,21 +155,26 @@ #define FADE(pin,value,ms) #define FOFF(func) #define FOLLOW(route) -#define FON(func) +#define FON(func) +#define FORGET #define FREE(blockid) #define FWD(speed) #define GREEN(signal_id) #define IF(sensor_id) +#define IFAMBER(signal_id) #define IFCLOSED(turnout_id) +#define IFGREEN(signal_id) #define IFGTE(sensor_id,value) #define IFLT(sensor_id,value) #define IFNOT(sensor_id) #define IFRANDOM(percent) +#define IFRED(signal_id) #define IFTHROWN(turnout_id) #define IFRESERVE(block) #define IFTIMEOUT #define INVERT_DIRECTION #define JOIN +#define KILLALL #define LATCH(sensor_id) #define LCD(row,msg) #define LCN(msg) @@ -169,6 +187,7 @@ #define PAUSE #define PIN_TURNOUT(id,pin,description...) #define PRINT(msg) +#define PARSE(msg) #define POM(cv,value) #define POWEROFF #define POWERON @@ -179,7 +198,7 @@ #define RESUME #define RETURN #define REV(speed) -#define ROUTE(id, description) +#define ROUTE(id,description) #define ROSTER(cab,name,funcmap...) #define SENDLOCO(cab,route) #define SEQUENCE(id) @@ -189,10 +208,12 @@ #define SERIAL3(msg) #define SERVO(id,position,profile) #define SERVO2(id,position,duration) +#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos) #define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) #define SET(pin) #define SETLOCO(loco) #define SIGNAL(redpin,amberpin,greenpin) +#define SIGNALH(redpin,amberpin,greenpin) #define SPEED(speed) #define START(route) #define STOP @@ -200,6 +221,7 @@ #define TURNOUT(id,addr,subaddr,description...) #define UNJOIN #define UNLATCH(sensor_id) +#define VIRTUAL_TURNOUT(id,description...) #define WAITFOR(pin) #define XFOFF(cab,func) #define XFON(cab,func) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index aa8c971..eb56794 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -49,28 +49,53 @@ // CAUTION: The macros below are multiple passed over myAutomation.h + +// helper macro for turnout descriptions, creates NULL for missing description +#define O_DESC(id, desc) case id: return ("" desc)[0]?F("" desc):NULL; +// helper macro for turnout description as HIDDEN +#define HIDDEN "\x01" + // Pass 1 Implements aliases #include "EXRAIL2MacroReset.h" #undef ALIAS -#define ALIAS(name,value) const int name=value; +#define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10; #include "myAutomation.h" -// Pass 2 convert descriptions to withrottle format emitter function +// Pass 2 create throttle route list #include "EXRAIL2MacroReset.h" #undef ROUTE -#define ROUTE(id, description) emitRouteDescription(stream,'R',id,F(description)); -#undef AUTOMATION -#define AUTOMATION(id, description) emitRouteDescription(stream,'A',id,F(description)); -void RMFT2::emitWithrottleDescriptions(Print * stream) { - (void)stream; +#define ROUTE(id, description) id, +const int16_t FLASH 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[]= { + #include "myAutomation.h" + 0}; + +// Pass 3 Create route descriptions: +#undef ROUTE +#define ROUTE(id, description) case id: return F(description); +#undef AUTOMATION +#define AUTOMATION(id, description) case id: return F(description); +const FSH * RMFT2::getRouteDescription(int16_t id) { + switch(id) { + #include "myAutomation.h" + default: break; + } + return F(""); } -// Pass 3... Create Text sending functions +// Pass 4... Create Text sending functions #include "EXRAIL2MacroReset.h" const int StringMacroTracker1=__COUNTER__; #undef BROADCAST #define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break; +#undef PARSE +#define PARSE(msg) case (__COUNTER__ - StringMacroTracker1) : DCCEXParser::parse(F(msg));break; #undef PRINT #define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break; #undef LCN @@ -94,64 +119,75 @@ void RMFT2::printMessage(uint16_t id) { } -// Pass 4: Turnout descriptions (optional) +// Pass 5: Turnout descriptions (optional) #include "EXRAIL2MacroReset.h" #undef TURNOUT -#define TURNOUT(id,addr,subaddr,description...) case id: desc=F("" description); break; +#define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description) #undef PIN_TURNOUT -#define PIN_TURNOUT(id,pin,description...) case id: desc=F("" description); break; +#define PIN_TURNOUT(id,pin,description...) O_DESC(id,description) #undef SERVO_TURNOUT -#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) case id: desc=F("" description); break; +#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) O_DESC(id,description) +#undef VIRTUAL_TURNOUT +#define VIRTUAL_TURNOUT(id,description...) O_DESC(id,description) -void RMFT2::emitTurnoutDescription(Print* stream,int16_t turnoutid) { - const FSH * desc=F(""); +const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) { switch (turnoutid) { #include "myAutomation.h" - default: break; + default:break; } - if (GETFLASH(desc)=='\0') desc=F("%d"); - StringFormatter::send(stream,desc,turnoutid); + return NULL; } -// Pass 5: Roster names (count) +// Pass 6: Roster IDs (count) #include "EXRAIL2MacroReset.h" #undef ROSTER #define ROSTER(cabid,name,funcmap...) +1 - const byte RMFT2::rosterNameCount=0 - #include "myAutomation.h" - ; - -// Pass 6: Roster names emitter + #include "myAutomation.h" + ; + +// Pass 6: Roster IDs #include "EXRAIL2MacroReset.h" #undef ROSTER -#define ROSTER(cabid,name,funcmap...) StringFormatter::send(stream,(FSH *)format,F(name),cabid,cabid<128?'S':'L'); -void RMFT2::emitWithrottleRoster(Print * stream) { - static const char format[] FLASH ="]\\[%S}|{%d}|{%c"; - (void)format; - StringFormatter::send(stream,F("RL%d"), rosterNameCount); - #include "myAutomation.h" - stream->write('\n'); -} +#define ROSTER(cabid,name,funcmap...) cabid, +const int16_t FLASH RMFT2::rosterIdList[]={ + #include "myAutomation.h" + 0}; -// Pass 7: functions getter +// Pass 7: Roster names getter #include "EXRAIL2MacroReset.h" #undef ROSTER +#define ROSTER(cabid,name,funcmap...) case cabid: return F(name); +const FSH * RMFT2::getRosterName(int16_t id) { + switch(id) { + #include "myAutomation.h" + default: break; + } + return F(""); +} + +// Pass to get roster functions +#undef ROSTER #define ROSTER(cabid,name,funcmap...) case cabid: return F("" funcmap); -const FSH * RMFT2::getRosterFunctions(int16_t cabid) { - switch(cabid) { +const FSH * RMFT2::getRosterFunctions(int16_t id) { + switch(id) { #include "myAutomation.h" - default: return NULL; - } -} + default: break; + } + return F(""); +} // Pass 8 Signal definitions #include "EXRAIL2MacroReset.h" #undef SIGNAL -#define SIGNAL(redpin,amberpin,greenpin) redpin,amberpin,greenpin, +#define SIGNAL(redpin,amberpin,greenpin) redpin,redpin,amberpin,greenpin, +#undef SIGNALH +#define SIGNALH(redpin,amberpin,greenpin) redpin | RMFT2::ACTIVE_HIGH_SIGNAL_FLAG,redpin,amberpin,greenpin, +#undef SERVO_SIGNAL +#define SERVO_SIGNAL(vpin,redval,amberval,greenval) vpin | RMFT2::SERVO_SIGNAL_FLAG,redval,amberval,greenval, const FLASH int16_t RMFT2::SignalDefinitions[] = { #include "myAutomation.h" - 0,0,0 }; + 0,0,0,0 }; // Last Pass : create main routes table // Only undef the macros, not dummy them. @@ -166,9 +202,11 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define ACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1 | 1), #define ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1), #define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id), -#define ALIAS(name,value) +#define ALIAS(name,value...) #define AMBER(signal_id) OPCODE_AMBER,V(signal_id), #define AT(sensor_id) OPCODE_AT,V(sensor_id), +#define ATGTE(sensor_id,value) OPCODE_ATGTE,V(sensor_id),OPCODE_PAD,V(value), +#define ATLT(sensor_id,value) OPCODE_ATLT,V(sensor_id),OPCODE_PAD,V(value), #define ATTIMEOUT(sensor_id,timeout) OPCODE_ATTIMEOUT1,0,0,OPCODE_ATTIMEOUT2,V(sensor_id),OPCODE_PAD,V(timeout/100L), #define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id), #define AUTOSTART OPCODE_AUTOSTART,0,0, @@ -192,20 +230,25 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define FOFF(func) OPCODE_FOFF,V(func), #define FOLLOW(route) OPCODE_FOLLOW,V(route), #define FON(func) OPCODE_FON,V(func), +#define FORGET OPCODE_FORGET,0,0, #define FREE(blockid) OPCODE_FREE,V(blockid), #define FWD(speed) OPCODE_FWD,V(speed), #define GREEN(signal_id) OPCODE_GREEN,V(signal_id), #define IF(sensor_id) OPCODE_IF,V(sensor_id), +#define IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id), #define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id), +#define IFGREEN(signal_id) OPCODE_IFGREEN,V(signal_id), #define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value), #define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value), #define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id), #define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent), +#define IFRED(signal_id) OPCODE_IFRED,V(signal_id), #define IFRESERVE(block) OPCODE_IFRESERVE,V(block), #define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id), #define IFTIMEOUT OPCODE_IFTIMEOUT,0,0, #define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0, #define JOIN OPCODE_JOIN,0,0, +#define KILLALL OPCODE_KILLALL,0,0, #define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id), #define LCD(id,msg) PRINT(msg) #define LCN(msg) PRINT(msg) @@ -221,6 +264,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define POWEROFF OPCODE_POWEROFF,0,0, #define POWERON OPCODE_POWERON,0,0, #define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2), +#define PARSE(msg) PRINT(msg) #define READ_LOCO OPCODE_READ_LOCO1,0,0,OPCODE_READ_LOCO2,0,0, #define RED(signal_id) OPCODE_RED,V(signal_id), #define RESERVE(blockid) OPCODE_RESERVE,V(blockid), @@ -238,10 +282,12 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define SERIAL3(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) #define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile), #define SET(pin) OPCODE_SET,V(pin), #define SETLOCO(loco) OPCODE_SETLOCO,V(loco), #define SIGNAL(redpin,amberpin,greenpin) +#define SIGNALH(redpin,amberpin,greenpin) #define SPEED(speed) OPCODE_SPEED,V(speed), #define START(route) OPCODE_START,V(route), #define STOP OPCODE_SPEED,V(0), @@ -249,6 +295,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr), #define UNJOIN OPCODE_UNJOIN,0,0, #define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id), +#define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0), #define WAITFOR(pin) OPCODE_WAITFOR,V(pin), #define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func), #define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func), diff --git a/I2CManager_Mega4809.h b/I2CManager_Mega4809.h index a8254a9..0b8e8ca 100644 --- a/I2CManager_Mega4809.h +++ b/I2CManager_Mega4809.h @@ -72,7 +72,7 @@ void I2CManagerClass::I2C_sendStart() { bytesToReceive = currentRequest->readLen; // If anything to send, initiate write. Otherwise initiate read. - if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) & !bytesToSend)) + if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1; else TWI0.MADDR = (currentRequest->i2cAddress << 1) | 0; diff --git a/I2CManager_Wire.h b/I2CManager_Wire.h index 26deb74..87152e7 100644 --- a/I2CManager_Wire.h +++ b/I2CManager_Wire.h @@ -94,22 +94,24 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea /*************************************************************************** * Function to queue a request block and initiate operations. * - * For the Wire version, this executes synchronously, but the status is - * returned in the I2CRB as for the asynchronous version. + * For the Wire version, this executes synchronously. + * The read/write/write_P functions return I2C_STATUS_OK always, and the + * completion status of the operation is in the request block, as for + * the non-blocking version. ***************************************************************************/ void I2CManagerClass::queueRequest(I2CRB *req) { switch (req->operation) { case OPERATION_READ: - req->status = read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req); + read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req); break; case OPERATION_SEND: - req->status = write(req->i2cAddress, req->writeBuffer, req->writeLen, req); + write(req->i2cAddress, req->writeBuffer, req->writeLen, req); break; case OPERATION_SEND_P: - req->status = write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req); + write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req); break; case OPERATION_REQUEST: - req->status = read(req->i2cAddress, req->readBuffer, req->readLen, req->writeBuffer, req->writeLen, req); + read(req->i2cAddress, req->readBuffer, req->readLen, req->writeBuffer, req->writeLen, req); break; } } diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index 009ff63..55065b7 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -114,7 +114,6 @@ void PCA9685::_begin() { // Device-specific write function, invoked from IODevice::write(). // For this function, the configured profile is used. void PCA9685::_write(VPIN vpin, int value) { - if (_deviceState == DEVSTATE_FAILED) return; #ifdef DIAG_IO DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value); #endif @@ -125,7 +124,10 @@ void PCA9685::_write(VPIN vpin, int value) { if (s != NULL) { // Use configured parameters _writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration); - } // else { /* ignorethe request */ } + } else { + /* simulate digital pin on PWM */ + _writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0); + } } // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). @@ -139,11 +141,11 @@ void PCA9685::_write(VPIN vpin, int value) { // 4 (Bounce) Servo 'bounces' at extremes. // void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) { - if (_deviceState == DEVSTATE_FAILED) return; #ifdef DIAG_IO - DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d"), - vpin, value, profile, duration); + DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif + if (_deviceState == DEVSTATE_FAILED) return; int pin = vpin - _firstVpin; if (value > 4095) value = 4095; else if (value < 0) value = 0; @@ -153,10 +155,10 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t dur // Servo pin not configured, so configure now using defaults s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1); if (s == NULL) return; // Check for memory allocation failure - s->activePosition = 0; + s->activePosition = 4095; s->inactivePosition = 0; s->currentPosition = value; - s->profile = Instant; // Use instant profile (but not this time) + s->profile = Instant | NoPowerOff; // Use instant profile (but not this time) } // Animated profile. Initiate the appropriate action. diff --git a/LCN.cpp b/LCN.cpp index 16b3f3f..efb49ff 100644 --- a/LCN.cpp +++ b/LCN.cpp @@ -50,7 +50,11 @@ void LCN::loop() { if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch); if (!Turnout::exists(id)) LCNTurnout::create(id); Turnout::setClosedStateOnly(id,ch=='t'); - Turnout::turnoutlistHash++; // signals ED update of turnout data + id = 0; + } + else if (ch == 'y' || ch == 'Y') { // Turnout opcodes + if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch); + Turnout::setClosed(id,ch=='y'); id = 0; } else if (ch == 'S' || ch == 's') { diff --git a/Release_Notes/ThrottleAssists.md b/Release_Notes/ThrottleAssists.md new file mode 100644 index 0000000..9d979c9 --- /dev/null +++ b/Release_Notes/ThrottleAssists.md @@ -0,0 +1,75 @@ +Throttle Assist updates for versiuon 4.? + +Chris Harlow April 2022 + +There are a number of additional throttle information commands that have been implemented to assist throttle authors to obtain information from the Command Station in order to implement turnout, route/automation and roster features which are already found in the Withrottle implementations. +These commands are new and not overlapped with the existing commands which are probabaly due to be obsoleted as they are over complex and unfit for purpose. + +Turnouts: + +The conventional turnout definition commands and the `````` responses do not contain information about the turnout description which may have been provided in an EXRAIL script. A turnout description is much more user friendly than T123 and having a list helps the throttle UI build a suitable set of buttons. + +`````` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state. +e.g. response `````` + +````` requests info on turnout 17. +e.g. response `````` or `````` +(T=thrown, C=closed) +or `````` indicating turnout description not given. +or `````` indicating turnout unknown (or possibly hidden.) + +Note: It is still the throttles responsibility to monitor the status broadcasts. + (TBD I'm thinking that the existing broadcast is messy and needs cleaning up) + However, I'm not keen on dynamically created/deleted turnouts so I have no intention of providing a command that indicates the turnout list has been updated since the throttle started. + Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "description" will NOT show up in these commands. + + + Automations/Routes + + A throttle need to know which EXRAIL Automations and Routes it can show the user. + + `````` Returns a list of Automations/Routes + e.g. `````` + Indicates route/automation ids. + Information on each route needs to be obtained by + `````` + returns e.g. `````` for a route + or `````` for an automation. + or `````` for id not found + + Whats the difference: + A Route is just a call to an EXRAIL ROUTE, traditionally to set some turnouts or signals but can be used to perform any kind of EXRAIL function... but its not expecting to know the loco. + Thus a route can be triggered by sending in for example ``````. + + An Automation is a handoff of the last accessed loco id to an EXRAIL AUTOMATION which would typically drive the loco away. + Thus an Automation expects a start command with a cab id + e.g. `````` + + + Roster Information: + The `````` command requests a list of cab ids from the roster. + e.g. responding `````` + or for none. + + Each Roster entry had a name and function map obtained by: + `````` reply like ``` + + Refer to EXRAIL ROSTER command for function map format. + + + Obtaining throttle status. + `````` Requests a deliberate update on the cab speed/functions in the same format as the cab broadcast. + `````` + Note that a slot of -1 indicates that the cab is not in the reminders table and this comand will not reserve a slot until such time as the cab is throttled. + + + COMMANDS TO AVOID + + `````` Use `````` + `````` Just drop the slot number + `````` other than `````` + `````` + `````` + + + diff --git a/Turnouts.h b/Turnouts.h index e394c85..3d1f0bc 100644 --- a/Turnouts.h +++ b/Turnouts.h @@ -3,7 +3,7 @@ * © 2021 M Steve Todd * © 2021 Fred Decker * © 2020-2021 Harald Barth - * © 2020-2021 Chris Harlow + * © 2020-2022 Chris Harlow * © 2013-2016 Gregg E. Berman * All rights reserved. * @@ -61,7 +61,8 @@ protected: struct { bool closed : 1; bool _rfu: 2; - uint8_t turnoutType : 5; + bool hidden: 1; + uint8_t turnoutType : 4; }; uint8_t flags; }; @@ -83,6 +84,7 @@ protected: _turnoutData.id = id; _turnoutData.turnoutType = turnoutType; _turnoutData.closed = closed; + _turnoutData.hidden=false; add(this); } @@ -104,11 +106,11 @@ protected: * Static functions */ - static Turnout *get(uint16_t id); static void add(Turnout *tt); public: + static Turnout *get(uint16_t id); /* * Static data */ @@ -120,6 +122,8 @@ public: */ inline bool isClosed() { return _turnoutData.closed; }; inline bool isThrown() { return !_turnoutData.closed; } + inline bool isHidden() { return _turnoutData.hidden; } + inline void setHidden(bool h) { _turnoutData.hidden=h; } inline bool isType(uint8_t type) { return _turnoutData.turnoutType == type; } inline uint16_t getId() { return _turnoutData.id; } inline Turnout *next() { return _nextTurnout; } @@ -169,7 +173,7 @@ public: #endif static void printAll(Print *stream) { for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout) - StringFormatter::send(stream, F("\n"),tt->getId(), tt->isThrown()); + if (!tt->isHidden()) StringFormatter::send(stream, F("\n"),tt->getId(), tt->isThrown()); } diff --git a/WiThrottle.cpp b/WiThrottle.cpp index d91745c..60bdf7d 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -119,14 +119,17 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { 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(); - StringFormatter::send(stream,F("]\\[%d}|{"), id); -#ifdef EXRAIL_ACTIVE - RMFT2::emitTurnoutDescription(stream,id); -#else - StringFormatter::send(stream,F("%d"), id); -#endif - StringFormatter::send(stream,F("}|{%c"), Turnout::isClosed(id)?'2':'4'); + 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 @@ -136,7 +139,18 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { // Send EX-RAIL routes list if not already sent (but not at same time as turnouts above) exRailSent=true; #ifdef EXRAIL_ACTIVE - RMFT2::emitWithrottleRouteList(stream); + 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); @@ -205,9 +219,19 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { 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"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); + + // Send the roster #ifdef EXRAIL_ACTIVE - RMFT2::emitWithrottleRoster(stream); -#endif + StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount); + for (int16_t r=0;rwrite('\n'); // end roster +#endif + + // set heartbeat to 1 second because we need to sync the metadata StringFormatter::send(stream,F("*1\n")); initSent = true; @@ -231,11 +255,14 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { int WiThrottle::getInt(byte * cmd) { int i=0; + bool negate=cmd[0]=='-'; + if (negate) cmd++; while (cmd[0]>='0' && cmd[0]<='9') { i=i*10 + (cmd[0]-'0'); cmd++; } - return i; + if (negate) i=0-i; + return i ; } int WiThrottle::getLocoId(byte * cmd) { diff --git a/version.h b/version.h index 014eeec..352ba8e 100644 --- a/version.h +++ b/version.h @@ -3,8 +3,26 @@ #include "StringFormatter.h" -#define VERSION "4.0.1" -// 4.0.1 EXRAIL BROADCAST("msg") + +#define VERSION "4.0.2" +// 4.0.2 EXRAIL additions: +// ACK defaults set to 50mA LIMIT, 2000uS MIN, 20000uS MAX +// myFilter automatic detection (no need to call setFilter) +// FIX negative route ids in WIthrottle problem. +// IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id) +// commands +// command to obtain current throttle settings +// JA, JR, JT commands to obtain route, roster and turnout descriptions +// HIDDEN turnouts +// PARSE <> commands in EXRAIL +// VIRTUAL_TURNOUT +// and KILLALL command to stop all tasks. +// FORGET forgets the current loco in DCC reminder tables. +// Servo signals (SERVO_SIGNAL) +// High-On signal pins (SIGNALH) +// Wait for analog value (ATGTE, ATLT) +// 4.0.1 Small EXRAIL updates +// EXRAIL BROADCAST("msg") // EXRAIL POWERON // 4.0.0 Major functional and non-functional changes. // Engine Driver "DriveAway" feature enhancement From 2fd7a31ae4242ee96e46ecee0cb25ce52c3a5527 Mon Sep 17 00:00:00 2001 From: Fred Date: Tue, 3 May 2022 21:06:24 -0400 Subject: [PATCH 02/10] Update version.h --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 352ba8e..bea474f 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.0.2" +#define VERSION "4.1.0" // 4.0.2 EXRAIL additions: // ACK defaults set to 50mA LIMIT, 2000uS MIN, 20000uS MAX // myFilter automatic detection (no need to call setFilter) From a614a616fafd5410177f3595e61871b66b498318 Mon Sep 17 00:00:00 2001 From: Ash-4 <81280775+Ash-4@users.noreply.github.com> Date: Wed, 4 May 2022 13:44:12 -0500 Subject: [PATCH 03/10] struct TurnoutData to enable EEPROM from v 4.0 --- Turnouts.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Turnouts.h b/Turnouts.h index 3d1f0bc..709ab6c 100644 --- a/Turnouts.h +++ b/Turnouts.h @@ -61,8 +61,8 @@ protected: struct { bool closed : 1; bool _rfu: 2; - bool hidden: 1; uint8_t turnoutType : 4; + bool hidden: 1; }; uint8_t flags; }; From 2d3794724654056403e98e5edc6427ef233f5b9e Mon Sep 17 00:00:00 2001 From: Ash-4 <81280775+Ash-4@users.noreply.github.com> Date: Wed, 4 May 2022 14:33:08 -0500 Subject: [PATCH 04/10] Update version.h --- version.h | 1 - 1 file changed, 1 deletion(-) diff --git a/version.h b/version.h index 9d5fedd..bea474f 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,6 @@ #include "StringFormatter.h" - #define VERSION "4.1.0" // 4.0.2 EXRAIL additions: // ACK defaults set to 50mA LIMIT, 2000uS MIN, 20000uS MAX From 589336eac317fe63be784fe5d42d8cf081f80236 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 7 May 2022 08:47:34 +0200 Subject: [PATCH 05/10] better bugfix for bitfield in turnout struct --- Turnouts.h | 6 +++--- version.h | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Turnouts.h b/Turnouts.h index 709ab6c..b181709 100644 --- a/Turnouts.h +++ b/Turnouts.h @@ -60,9 +60,9 @@ protected: union { struct { bool closed : 1; - bool _rfu: 2; - uint8_t turnoutType : 4; - bool hidden: 1; + bool hidden : 1; + bool _rfu : 1; + uint8_t turnoutType : 5; }; uint8_t flags; }; diff --git a/version.h b/version.h index bea474f..7890e6b 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,10 @@ #include "StringFormatter.h" -#define VERSION "4.1.0" +#define VERSION "4.1.1rc1" +// 4.1.1 Bugfix: preserve turnout format +// 4.1.0 ... +// // 4.0.2 EXRAIL additions: // ACK defaults set to 50mA LIMIT, 2000uS MIN, 20000uS MAX // myFilter automatic detection (no need to call setFilter) From 357560b2269e736846f2a4959d51df74a55fada5 Mon Sep 17 00:00:00 2001 From: Ash-4 <81280775+Ash-4@users.noreply.github.com> Date: Sat, 7 May 2022 10:51:00 -0500 Subject: [PATCH 06/10] Update version.h Space character needed after 4.1.1 for JMRI parsing. JMRI applies updated functions based on the version. --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 7890e6b..d50178d 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.1.1rc1" +#define VERSION "4.1.1 rc1" // 4.1.1 Bugfix: preserve turnout format // 4.1.0 ... // From ff73a608745427a4f5790bb957d7f1f30774a924 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 13 May 2022 16:18:47 +0200 Subject: [PATCH 07/10] Parse strings with more than one command () correct --- DCCEXParser.cpp | 16 +++++++++++++++- DCCEXParser.h | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 2dfac84..b262600 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -195,7 +195,21 @@ void DCCEXParser::parse(const FSH * cmd) { // See documentation on DCC class for info on this section -void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) +void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) { + // This function can get stings of the form "" or "C OMM AND" + // found is true first after the leading "<" has been passed + bool found = (com[0] != '<'); + for (byte *c=com; c[0] != '\0'; c++) { + if (found) { + parseOne(stream, c, ringStream); + found=false; + } + if (c[0] == '<') + found = true; + } +} + +void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) { #ifndef DISABLE_EEPROM (void)EEPROM; // tell compiler not to warn this is unused diff --git a/DCCEXParser.h b/DCCEXParser.h index d9fe0e1..797eab0 100644 --- a/DCCEXParser.h +++ b/DCCEXParser.h @@ -33,6 +33,7 @@ struct DCCEXParser static void parse(Print * stream, byte * command, RingStream * ringStream); static void parse(const FSH * cmd); + static void parseOne(Print * stream, byte * command, RingStream * ringStream); static void setFilter(FILTER_CALLBACK filter); static void setRMFTFilter(FILTER_CALLBACK filter); static void setAtCommandCallback(AT_COMMAND_CALLBACK filter); From 632d777fe72850f17843945fbfc6af2740c0d6a9 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 13 May 2022 16:21:15 +0200 Subject: [PATCH 08/10] version --- version.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/version.h b/version.h index d50178d..cc95b56 100644 --- a/version.h +++ b/version.h @@ -4,8 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.1.1 rc1" +#define VERSION "4.1.1 rc2" // 4.1.1 Bugfix: preserve turnout format +// Bugfix: parse multiple commands in one buffer string correct // 4.1.0 ... // // 4.0.2 EXRAIL additions: From 506b65d0eaf0d0d58abd46657df0a630a4334e59 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 18 May 2022 17:44:41 +0100 Subject: [PATCH 09/10] Fix command for signals --- EXRAIL2.cpp | 18 ++++++++++++++---- version.h | 3 ++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 60c66c6..6e8b0a3 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -320,12 +320,22 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { // Now stream the flags for (int id=0;id\n")); return true; } diff --git a/version.h b/version.h index cc95b56..846ad3a 100644 --- a/version.h +++ b/version.h @@ -4,9 +4,10 @@ #include "StringFormatter.h" -#define VERSION "4.1.1 rc2" +#define VERSION "4.1.1 rc3" // 4.1.1 Bugfix: preserve turnout format // Bugfix: parse multiple commands in one buffer string correct +// Bugfix: command signal status in Exrail // 4.1.0 ... // // 4.0.2 EXRAIL additions: From ebebd0dc1114f1bb4a0bac3e8138aff1c8af9aeb Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 19 May 2022 09:03:28 +0100 Subject: [PATCH 10/10] Improved display and loop time for signals. --- EXRAIL2.cpp | 21 +++++++++++---------- EXRAIL2.h | 1 + 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 6e8b0a3..541e2fb 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -172,7 +172,7 @@ int16_t LookList::find(int16_t value) { for (int sigpos=0;;sigpos+=4) { VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos); if (sigid==0) break; // end of signal list - doSignal(sigid & (~ SERVO_SIGNAL_FLAG) & (~ACTIVE_HIGH_SIGNAL_FLAG), SIGNAL_RED); + doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED); } for (progCounter=0;; SKIPOP){ @@ -327,14 +327,15 @@ 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 id=0;id\n")); return true; @@ -994,7 +995,7 @@ int16_t RMFT2::getSignalSlot(VPIN id) { // for a LED signal it will be same as redpin // but for a servo signal it will also have SERVO_SIGNAL_FLAG set. - if ((sigid & ~SERVO_SIGNAL_FLAG & ~ACTIVE_HIGH_SIGNAL_FLAG)!= id) continue; // keep looking + if ((sigid & SIGNAL_ID_MASK)!= id) continue; // keep looking return sigpos/4; // relative slot in signals table } } diff --git a/EXRAIL2.h b/EXRAIL2.h index b44ce6c..3b9b874 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -107,6 +107,7 @@ class LookList { static void activateEvent(int16_t addr, bool active); static const int16_t SERVO_SIGNAL_FLAG=0x4000; static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000; + static const int16_t SIGNAL_ID_MASK=0x0FFF; // Throttle Info Access functions built by exrail macros static const byte rosterNameCount;