diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index d79136f..96596c6 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -781,27 +781,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) TrackManager::reportCurrent(stream); // return; - 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_A: // intercepted by EXRAIL// returns automations/routes + if (params!=1) break; // + StringFormatter::send(stream, F("\n")); + return; + case HASH_KEYWORD_R: // returns rosters StringFormatter::send(stream, F(" 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off switch (p[0]) { #ifndef DISABLE_PROG @@ -1159,6 +1142,8 @@ bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) { LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // } } else { + bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off + DIAG(F("Ack diag %S"), onOff ? F("on") : F("off")); Diag::ACK = onOff; } diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 7b6107d..61e0264 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -55,22 +55,6 @@ #include "Turntables.h" #include "IODevice.h" -// Command parsing keywords -const int16_t HASH_KEYWORD_EXRAIL=15435; -const int16_t HASH_KEYWORD_ON = 2657; -const int16_t HASH_KEYWORD_START=23232; -const int16_t HASH_KEYWORD_RESERVE=11392; -const int16_t HASH_KEYWORD_FREE=-23052; -const int16_t HASH_KEYWORD_LATCH=1618; -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. @@ -103,9 +87,6 @@ LookList * RMFT2::onOverloadLookup=NULL; byte * RMFT2::routeStateArray=nullptr; const FSH * * RMFT2::routeCaptionArray=nullptr; -#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); @@ -146,6 +127,13 @@ void LookList::chain(LookList * chain) { m_chain=chain; } +void LookList::stream(Print * _stream) { + for (int16_t i=0;iprint(" "); + _stream->print(m_lookupArray[i]); + } +} + int16_t LookList::findPosition(int16_t value) { for (int16_t i=0;ichain(LookListLoader(OPCODE_SEQUENCE)); - routeStateArray=(byte *)calloc(routeLookup->size(),sizeof(byte)); - routeCaptionArray=(const FSH * *)calloc(routeLookup->size(),sizeof(const FSH *)); - + if (compileFeatures && FEATURE_ROUTESTATE) { + routeStateArray=(byte *)calloc(routeLookup->size(),sizeof(byte)); + routeCaptionArray=(const FSH * *)calloc(routeLookup->size(),sizeof(const FSH *)); + } onThrowLookup=LookListLoader(OPCODE_ONTHROW); onCloseLookup=LookListLoader(OPCODE_ONCLOSE); onActivateLookup=LookListLoader(OPCODE_ONACTIVATE); @@ -334,238 +323,15 @@ void RMFT2::setTurntableHiddenState(Turntable * tto) { #endif char RMFT2::getRouteType(int16_t id) { - for (int16_t i=0;;i+=2) { - int16_t rid= GETHIGHFLASHW(routeIdList,i); - if (rid==INT16_MAX) break; - if (rid==id) return 'R'; - } - for (int16_t i=0;;i+=2) { - int16_t rid= GETHIGHFLASHW(automationIdList,i); - if (rid==INT16_MAX) break; - if (rid==id) return 'A'; + int16_t progCounter=routeLookup->find(id); + if (progCounter>=0) { + OPCODE type=GET_OPCODE; + if (type==OPCODE_ROUTE) return 'R'; + if (type==OPCODE_AUTOMATION) return 'A'; } 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 -void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]) { - (void)stream; // avoid compiler warning if we don't access this parameter - bool reject=false; - switch(opcode) { - - case 'D': - if (p[0]==HASH_KEYWORD_EXRAIL) { // - diag = paramCount==2 && (p[1]==HASH_KEYWORD_ON || p[1]==1); - opcode=0; - } - break; - - case '/': // New EXRAIL command - reject=!parseSlash(stream,paramCount,p); - opcode=0; - break; - case 'L': - if (compileFeatures & FEATURE_LCC) { - // This entire code block is compiled out if LLC macros not used - if (paramCount==0) { // LCC adapter introducing self - LCCSerial=stream; // now we know where to send events we raise - - // loop through all possible sent events - for (int progCounter=0;; SKIPOP) { - byte opcode=GET_OPCODE; - if (opcode==OPCODE_ENDEXRAIL) break; - if (opcode==OPCODE_LCC) StringFormatter::send(stream,F("\n"),getOperand(progCounter,0)); - if (opcode==OPCODE_LCCX) { // long form LCC - StringFormatter::send(stream,F("\n"), - getOperand(progCounter,1), - getOperand(progCounter,2), - getOperand(progCounter,3), - getOperand(progCounter,0) - ); - }} - - // we stream the hex events we wish to listen to - // and at the same time build the event index looku. - - - int eventIndex=0; - for (int progCounter=0;; SKIPOP) { - byte opcode=GET_OPCODE; - if (opcode==OPCODE_ENDEXRAIL) break; - if (opcode==OPCODE_ONLCC) { - onLCCLookup[eventIndex]=progCounter; // TODO skip... - StringFormatter::send(stream,F("\n"), - eventIndex, - getOperand(progCounter,1), - getOperand(progCounter,2), - getOperand(progCounter,3), - getOperand(progCounter,0) - ); - eventIndex++; - } - } - StringFormatter::send(stream,F("\n")); // Ready to rumble - opcode=0; - break; - } - if (paramCount==1) { // LCC event arrived from adapter - int16_t eventid=p[0]; - reject=eventid<0 || eventid>=countLCCLookup; - if (!reject) startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]); - opcode=0; - } - } - break; - - default: // other commands pass through - break; - } - if (reject) { - opcode=0; - StringFormatter::send(stream,F("\n")); - } -} - -bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { - - if (paramCount==0) { // STATUS - StringFormatter::send(stream, F("<* EXRAIL STATUS")); - RMFT2 * task=loopTask; - while(task) { - StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"), - (int)(task->taskId),task->progCounter,task->loco, - task->invert?'I':' ', - task->speedo, - task->forward?'F':'R' - ); - task=task->next; - if (task==loopTask) break; - } - // Now stream the flags - for (int id=0;id\n")); - return true; - } - switch (p[0]) { - case HASH_KEYWORD_PAUSE: // - if (paramCount!=1) return false; - DCC::setThrottle(0,1,true); // pause all locos on the track - pausingTask=(RMFT2 *)1; // Impossible task address - return true; - - case HASH_KEYWORD_RESUME: // - if (paramCount!=1) return false; - pausingTask=NULL; - { - RMFT2 * task=loopTask; - while(task) { - if (task->loco) task->driveLoco(task->speedo); - task=task->next; - if (task==loopTask) break; - } - } - return true; - - - case HASH_KEYWORD_START: // - if (paramCount<2 || paramCount>3) return false; - { - int route=(paramCount==2) ? p[1] : p[2]; - uint16_t cab=(paramCount==2)? 0 : p[1]; - int pc=routeLookup->find(route); - if (pc<0) return false; - RMFT2* task=new RMFT2(pc); - task->loco=cab; - } - 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 - if (paramCount!=2 ) return false; - - switch (p[0]) { - case HASH_KEYWORD_KILL: // Kill taskid|ALL - { - if ( p[1]<0 || p[1]>=MAX_FLAGS) return false; - RMFT2 * task=loopTask; - while(task) { - if (task->taskId==p[1]) { - task->kill(F("KILL")); - return true; - } - task=task->next; - if (task==loopTask) break; - } - } - return false; - - case HASH_KEYWORD_RESERVE: // force reserve a section - return setFlag(p[1],SECTION_FLAG); - - case HASH_KEYWORD_FREE: // force free a section - return setFlag(p[1],0,SECTION_FLAG); - - case HASH_KEYWORD_LATCH: - return setFlag(p[1], LATCH_FLAG); - - case HASH_KEYWORD_UNLATCH: - return setFlag(p[1], 0, LATCH_FLAG); - - 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; - } -} - - -// This emits Routes and Automations to Withrottle -// 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. - - RMFT2::RMFT2(int progCtr) { progCounter=progCtr; @@ -1484,23 +1250,28 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { } void RMFT2::manageRouteState(uint16_t id, byte state) { - // Route state must be maintained for when new throttles connect. - // locate route id in the Routes lookup - int16_t position=routeLookup->findPosition(id); - if (position<0) return; - // set state beside it - if (routeStateArray[position]==state) return; - routeStateArray[position]=state; - CommandDistributor::broadcastRouteState(id,state); + if (compileFeatures && FEATURE_ROUTESTATE) { + // Route state must be maintained for when new throttles connect. + // locate route id in the Routes lookup + int16_t position=routeLookup->findPosition(id); + if (position<0) return; + // set state beside it + if (routeStateArray[position]==state) return; + routeStateArray[position]=state; + CommandDistributor::broadcastRouteState(id,state); + } } void RMFT2::manageRouteCaption(uint16_t id,const FSH* caption) { - // Route state must be maintained for when new throttles connect. - // locate route id in the Routes lookup - int16_t position=routeLookup->findPosition(id); - if (position<0) return; - // set state beside it - if (routeCaptionArray[position]==caption) return; - routeCaptionArray[position]=caption; - CommandDistributor::broadcastRouteCaption(id,caption); + if (compileFeatures && FEATURE_ROUTESTATE) { + // Route state must be maintained for when new throttles connect. + // locate route id in the Routes lookup + int16_t position=routeLookup->findPosition(id); + if (position<0) return; + // set state beside it + if (routeCaptionArray[position]==caption) return; + routeCaptionArray[position]=caption; + DIAG(F("rCA[%d]=%d, c=%d"),position,routeStateArray[position],caption); + CommandDistributor::broadcastRouteCaption(id,caption); + } } diff --git a/EXRAIL2.h b/EXRAIL2.h index e5db1d9..bcb517e 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -100,6 +100,7 @@ enum thrunger: byte { static const byte FEATURE_SIGNAL= 0x80; static const byte FEATURE_LCC = 0x40; static const byte FEATURE_ROSTER= 0x20; + static const byte FEATURE_ROUTESTATE= 0x10; // Flag bits for status of hardware and TPL @@ -124,7 +125,8 @@ class LookList { void add(int16_t lookup, int16_t result); int16_t find(int16_t value); // finds result value int16_t findPosition(int16_t value); // finds index - int16_t size(); + int16_t size(); + void stream(Print * _stream); private: int16_t m_size; int16_t m_loaded; @@ -224,7 +226,7 @@ private: static void manageRouteState(uint16_t id, byte state); static void manageRouteCaption(uint16_t id, const FSH* caption); static byte * routeStateArray; - static const FSH * * routeCaptionArray; + static const FSH ** routeCaptionArray; // Local variables - exist for each instance/task RMFT2 *next; // loop chain @@ -246,4 +248,8 @@ private: byte stackDepth; int callStack[MAX_STACK_DEPTH]; }; + +#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) +#define SKIPOP progCounter+=3 + #endif diff --git a/EXRAIL2Parser.cpp b/EXRAIL2Parser.cpp new file mode 100644 index 0000000..7670118 --- /dev/null +++ b/EXRAIL2Parser.cpp @@ -0,0 +1,287 @@ +/* + * © 2021 Neil McKechnie + * © 2021-2023 Harald Barth + * © 2020-2023 Chris Harlow + * © 2022-2023 Colin Murdoch + * All rights reserved. + * + * This file is part of CommandStation-EX + * + * 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 . + */ + +// THIS file is an extension of the RMFT2 class +// normally found in EXRAIL2.cpp + +#include +#include "defines.h" +#include "EXRAIL2.h" +#include "DCC.h" +// Command parsing keywords +const int16_t HASH_KEYWORD_EXRAIL=15435; +const int16_t HASH_KEYWORD_ON = 2657; +const int16_t HASH_KEYWORD_START=23232; +const int16_t HASH_KEYWORD_RESERVE=11392; +const int16_t HASH_KEYWORD_FREE=-23052; +const int16_t HASH_KEYWORD_LATCH=1618; +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; +const int16_t HASH_KEYWORD_A='A'; + +// This filter intercepts <> commands to do the following: +// - Implement RMFT specific commands/diagnostics +// - Reject/modify JMRI commands that would interfere with RMFT processing + +void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]) { + (void)stream; // avoid compiler warning if we don't access this parameter + bool reject=false; + switch(opcode) { + + case 'D': + if (p[0]==HASH_KEYWORD_EXRAIL) { // + diag = paramCount==2 && (p[1]==HASH_KEYWORD_ON || p[1]==1); + opcode=0; + } + break; + + case '/': // New EXRAIL command + reject=!parseSlash(stream,paramCount,p); + opcode=0; + break; + + case 'L': + // This entire code block is compiled out if LLC macros not used + if (!(compileFeatures & FEATURE_LCC)) return; + + if (paramCount==0) { // LCC adapter introducing self + LCCSerial=stream; // now we know where to send events we raise + + // loop through all possible sent events + for (int progCounter=0;; SKIPOP) { + byte opcode=GET_OPCODE; + if (opcode==OPCODE_ENDEXRAIL) break; + if (opcode==OPCODE_LCC) StringFormatter::send(stream,F("\n"),getOperand(progCounter,0)); + if (opcode==OPCODE_LCCX) { // long form LCC + StringFormatter::send(stream,F("\n"), + getOperand(progCounter,1), + getOperand(progCounter,2), + getOperand(progCounter,3), + getOperand(progCounter,0) + ); + }} + + // we stream the hex events we wish to listen to + // and at the same time build the event index looku. + + + int eventIndex=0; + for (int progCounter=0;; SKIPOP) { + byte opcode=GET_OPCODE; + if (opcode==OPCODE_ENDEXRAIL) break; + if (opcode==OPCODE_ONLCC) { + onLCCLookup[eventIndex]=progCounter; // TODO skip... + StringFormatter::send(stream,F("\n"), + eventIndex, + getOperand(progCounter,1), + getOperand(progCounter,2), + getOperand(progCounter,3), + getOperand(progCounter,0) + ); + eventIndex++; + } + } + StringFormatter::send(stream,F("\n")); // Ready to rumble + opcode=0; + break; + } + if (paramCount==1) { // LCC event arrived from adapter + int16_t eventid=p[0]; + reject=eventid<0 || eventid>=countLCCLookup; + if (!reject) startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]); + opcode=0; + } + break; + + case 'J': // throttle info commands + // This entire code block is compiled out if FEATURE_ROUTESTATE macros not used + if (paramCount<1 || !(compileFeatures & FEATURE_ROUTESTATE)) return; + switch(p[0]) + case HASH_KEYWORD_A: // returns automations/routes + if (paramCount==1) {// + StringFormatter::send(stream, F("stream(stream); + StringFormatter::send(stream, F(">\n")); + opcode=0; + return; + } + if (paramCount==2) { // + uint16_t id=p[1]; + StringFormatter::send(stream,F("\n"), + id, getRouteType(id), getRouteDescription(id)); + + // Send any non-default button states or captions + int16_t statePos=routeLookup->findPosition(id); + if (statePos>=0) { + if (routeStateArray[statePos]) + StringFormatter::send(stream,F("\n"), id, routeStateArray[statePos]); + if (routeCaptionArray[statePos]) + StringFormatter::send(stream,F("\n"), id,routeCaptionArray[statePos]); + } + opcode=0; + return; + } + break; + default: // other commands pass through + break; + } +} + +bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { + + if (paramCount==0) { // STATUS + StringFormatter::send(stream, F("<* EXRAIL STATUS")); + RMFT2 * task=loopTask; + while(task) { + StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"), + (int)(task->taskId),task->progCounter,task->loco, + task->invert?'I':' ', + task->speedo, + task->forward?'F':'R' + ); + task=task->next; + if (task==loopTask) break; + } + // Now stream the flags + for (int id=0;id\n")); + return true; + } + switch (p[0]) { + case HASH_KEYWORD_PAUSE: // + if (paramCount!=1) return false; + DCC::setThrottle(0,1,true); // pause all locos on the track + pausingTask=(RMFT2 *)1; // Impossible task address + return true; + + case HASH_KEYWORD_RESUME: // + if (paramCount!=1) return false; + pausingTask=NULL; + { + RMFT2 * task=loopTask; + while(task) { + if (task->loco) task->driveLoco(task->speedo); + task=task->next; + if (task==loopTask) break; + } + } + return true; + + + case HASH_KEYWORD_START: // + if (paramCount<2 || paramCount>3) return false; + { + int route=(paramCount==2) ? p[1] : p[2]; + uint16_t cab=(paramCount==2)? 0 : p[1]; + int pc=routeLookup->find(route); + if (pc<0) return false; + RMFT2* task=new RMFT2(pc); + task->loco=cab; + } + 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 + if (paramCount!=2 ) return false; + + switch (p[0]) { + case HASH_KEYWORD_KILL: // Kill taskid|ALL + { + if ( p[1]<0 || p[1]>=MAX_FLAGS) return false; + RMFT2 * task=loopTask; + while(task) { + if (task->taskId==p[1]) { + task->kill(F("KILL")); + return true; + } + task=task->next; + if (task==loopTask) break; + } + } + return false; + + case HASH_KEYWORD_RESERVE: // force reserve a section + return setFlag(p[1],SECTION_FLAG); + + case HASH_KEYWORD_FREE: // force free a section + return setFlag(p[1],0,SECTION_FLAG); + + case HASH_KEYWORD_LATCH: + return setFlag(p[1], LATCH_FLAG); + + case HASH_KEYWORD_UNLATCH: + return setFlag(p[1], 0, LATCH_FLAG); + + 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; + } +} + diff --git a/EXRAILMacros.h b/EXRAILMacros.h index e11b5db..3c58699 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -102,6 +102,14 @@ void exrailHalSetup() { #define LCCX(senderid,eventid) | FEATURE_LCC #undef ONLCC #define ONLCC(senderid,eventid) | FEATURE_LCC +#undef ROUTE_ACTIVE +#define ROUTE_ACTIVE(id) | FEATURE_ROUTESTATE +#undef ROUTE_INACTIVE +#define ROUTE_INACTIVE(id) | FEATURE_ROUTESTATE +#undef ROUTE_HIDDEN +#define ROUTE_HIDDEN(id) | FEATURE_ROUTESTATE +#undef ROUTE_CAPTION +#define ROUTE_CAPTION(id,caption) | FEATURE_ROUTESTATE const byte RMFT2::compileFeatures = 0 #include "myAutomation.h" @@ -156,7 +164,7 @@ const int StringMacroTracker1=__COUNTER__; #undef ROUTE_CAPTION #define ROUTE_CAPTION(id,caption) \ case (__COUNTER__ - StringMacroTracker1) : {\ - CommandDistributor::broadcastRouteCaption(id,F(caption));\ + manageRouteCaption(id,F(caption));\ return;\ } #undef SERIAL