diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 1651771..351a18d 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -272,3 +272,11 @@ void CommandDistributor::broadcastRaw(clientType type, char * msg) { void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr) { broadcastReply(COMMAND_TYPE, format,trackLetter, dcAddr); } + +void CommandDistributor::broadcastRouteState(uint16_t routeId, byte state ) { + broadcastReply(COMMAND_TYPE, F("\n"),routeId,state); +} + +void CommandDistributor::broadcastRouteCaption(uint16_t routeId, const FSH* caption ) { + broadcastReply(COMMAND_TYPE, F("\n"),routeId,caption); +} diff --git a/CommandDistributor.h b/CommandDistributor.h index d54ef31..83bfbbd 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -58,6 +58,9 @@ public : static void broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr); template static void broadcastReply(clientType type, Targs... msg); static void forget(byte clientId); + static void broadcastRouteState(uint16_t routeId,byte state); + static void broadcastRouteCaption(uint16_t routeId,const FSH * caption); + }; 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 2e9761a..b767d17 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. @@ -86,7 +70,7 @@ RMFT2 * RMFT2::pausingTask=NULL; // Task causing a PAUSE. // and all others will have their locos stopped, then resumed after the pausing task resumes. byte RMFT2::flags[MAX_FLAGS]; Print * RMFT2::LCCSerial=0; -LookList * RMFT2::sequenceLookup=NULL; +LookList * RMFT2::routeLookup=NULL; LookList * RMFT2::onThrowLookup=NULL; LookList * RMFT2::onCloseLookup=NULL; LookList * RMFT2::onActivateLookup=NULL; @@ -100,9 +84,8 @@ LookList * RMFT2::onClockLookup=NULL; LookList * RMFT2::onRotateLookup=NULL; #endif LookList * RMFT2::onOverloadLookup=NULL; - -#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) -#define SKIPOP progCounter+=3 +byte * RMFT2::routeStateArray=nullptr; +const FSH * * RMFT2::routeCaptionArray=nullptr; // getOperand instance version, uses progCounter from instance. uint16_t RMFT2::getOperand(byte n) { @@ -120,6 +103,7 @@ uint16_t RMFT2::getOperand(int progCounter,byte n) { LookList::LookList(int16_t size) { m_size=size; m_loaded=0; + m_chain=nullptr; if (size) { m_lookupArray=new int16_t[size]; m_resultArray=new int16_t[size]; @@ -137,8 +121,35 @@ int16_t LookList::find(int16_t value) { for (int16_t i=0;ifind(value) :-1; +} +void LookList::chain(LookList * chain) { + m_chain=chain; +} +void LookList::handleEvent(const FSH* reason,int16_t id) { + // New feature... create multiple ONhandlers + for (int i=0;iprint(" "); + _stream->print(m_lookupArray[i]); + } +} + +int16_t LookList::findPosition(int16_t value) { + for (int16_t i=0;ichain(LookListLoader(OPCODE_SEQUENCE)); + 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); @@ -314,238 +330,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=sequenceLookup->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; @@ -594,7 +387,7 @@ RMFT2::~RMFT2() { } void RMFT2::createNewTask(int route, uint16_t cab) { - int pc=sequenceLookup->find(route); + int pc=routeLookup->find(route); if (pc<0) return; RMFT2* task=new RMFT2(pc); task->loco=cab; @@ -995,7 +788,7 @@ void RMFT2::loop2() { } case OPCODE_FOLLOW: - progCounter=sequenceLookup->find(operand); + progCounter=routeLookup->find(operand); if (progCounter<0) kill(F("FOLLOW unknown"), operand); return; @@ -1005,7 +798,7 @@ void RMFT2::loop2() { return; } callStack[stackDepth++]=progCounter+3; - progCounter=sequenceLookup->find(operand); + progCounter=routeLookup->find(operand); if (progCounter<0) kill(F("CALL unknown"),operand); return; @@ -1068,7 +861,7 @@ void RMFT2::loop2() { case OPCODE_START: { - int newPc=sequenceLookup->find(operand); + int newPc=routeLookup->find(operand); if (newPc<0) break; new RMFT2(newPc); } @@ -1076,7 +869,7 @@ void RMFT2::loop2() { case OPCODE_SENDLOCO: // cab, route { - int newPc=sequenceLookup->find(getOperand(1)); + int newPc=routeLookup->find(getOperand(1)); if (newPc<0) break; RMFT2* newtask=new RMFT2(newPc); // create new task newtask->loco=operand; @@ -1130,7 +923,16 @@ void RMFT2::loop2() { case OPCODE_PRINT: printMessage(operand); break; - + case OPCODE_ROUTE_HIDDEN: + manageRouteState(operand,2); + break; + case OPCODE_ROUTE_ACTIVE: + manageRouteState(operand,0); + break; + case OPCODE_ROUTE_INACTIVE: + manageRouteState(operand,1); + break; + case OPCODE_ROUTE: case OPCODE_AUTOMATION: case OPCODE_SEQUENCE: @@ -1218,9 +1020,9 @@ int16_t RMFT2::getSignalSlot(int16_t id) { // Schedule any event handler for this signal change. // Thjis will work even without a signal definition. - if (rag==SIGNAL_RED) handleEvent(F("RED"),onRedLookup,id); - else if (rag==SIGNAL_GREEN) handleEvent(F("GREEN"), onGreenLookup,id); - else handleEvent(F("AMBER"), onAmberLookup,id); + if (rag==SIGNAL_RED) onRedLookup->handleEvent(F("RED"),id); + else if (rag==SIGNAL_GREEN) onGreenLookup->handleEvent(F("GREEN"),id); + else onAmberLookup->handleEvent(F("AMBER"),id); int16_t sigslot=getSignalSlot(id); if (sigslot<0) return; @@ -1289,26 +1091,26 @@ int16_t RMFT2::getSignalSlot(int16_t id) { void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) { // Hunt for an ONTHROW/ONCLOSE for this turnout - if (closed) handleEvent(F("CLOSE"),onCloseLookup,turnoutId); - else handleEvent(F("THROW"),onThrowLookup,turnoutId); + if (closed) onCloseLookup->handleEvent(F("CLOSE"),turnoutId); + else onThrowLookup->handleEvent(F("THROW"),turnoutId); } void RMFT2::activateEvent(int16_t addr, bool activate) { // Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory - if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr); - else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr); + if (activate) onActivateLookup->handleEvent(F("ACTIVATE"),addr); + else onDeactivateLookup->handleEvent(F("DEACTIVATE"),addr); } void RMFT2::changeEvent(int16_t vpin, bool change) { // Hunt for an ONCHANGE for this sensor - if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin); + if (change) onChangeLookup->handleEvent(F("CHANGE"),vpin); } #ifndef IO_NO_HAL void RMFT2::rotateEvent(int16_t turntableId, bool change) { // Hunt or an ONROTATE for this turntable - if (change) handleEvent(F("ROTATE"),onRotateLookup,turntableId); + if (change) onRotateLookup->handleEvent(F("ROTATE"),turntableId); } #endif @@ -1317,8 +1119,8 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) { if (Diag::CMD) DIAG(F("Looking for clock event at : %d"), clocktime); if (change) { - handleEvent(F("CLOCK"),onClockLookup,clocktime); - handleEvent(F("CLOCK"),onClockLookup,25*60+clocktime%60); + onClockLookup->handleEvent(F("CLOCK"),clocktime); + onClockLookup->handleEvent(F("CLOCK"),25*60+clocktime%60); } } @@ -1327,16 +1129,10 @@ void RMFT2::powerEvent(int16_t track, bool overload) { if (Diag::CMD) DIAG(F("Looking for Power event on track : %c"), track); if (overload) { - handleEvent(F("POWER"),onOverloadLookup,track); + onOverloadLookup->handleEvent(F("POWER"),track); } } - -void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) { - int pc= handlers->find(id); - if (pc>=0) startNonRecursiveTask(reason,id,pc); -} - void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc) { // Check we dont already have a task running this handler RMFT2 * task=loopTask; @@ -1453,3 +1249,29 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { break; } } + +void RMFT2::manageRouteState(uint16_t id, byte 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) { + 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; + CommandDistributor::broadcastRouteCaption(id,caption); + } +} + diff --git a/EXRAIL2.h b/EXRAIL2.h index de14f11..a4c0e5f 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -68,6 +68,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_WAITFORTT, OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC, OPCODE_ONOVERLOAD, + OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN, // OPcodes below this point are skip-nesting IF operations // placed here so that they may be skipped as a group @@ -99,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 @@ -119,13 +121,20 @@ enum thrunger: byte { class LookList { public: LookList(int16_t size); + void chain(LookList* chainTo); void add(int16_t lookup, int16_t result); - int16_t find(int16_t value); + int16_t find(int16_t value); // finds result value + int16_t findPosition(int16_t value); // finds index + int16_t size(); + void stream(Print * _stream); + void handleEvent(const FSH* reason,int16_t id); + private: int16_t m_size; int16_t m_loaded; int16_t * m_lookupArray; - int16_t * m_resultArray; + int16_t * m_resultArray; + LookList* m_chain; }; class RMFT2 { @@ -159,7 +168,8 @@ class LookList { static const FSH * getRosterFunctions(int16_t id); static const FSH * getTurntableDescription(int16_t id); static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId); - + static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc); + private: static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]); static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ; @@ -176,9 +186,7 @@ private: #endif 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 void startNonRecursiveTask(const FSH* reason, int16_t id,int pc); static RMFT2 * loopTask; static RMFT2 * pausingTask; void delayMe(long millisecs); @@ -198,7 +206,7 @@ private: static const HIGHFLASH int16_t SignalDefinitions[]; static byte flags[MAX_FLAGS]; static Print * LCCSerial; - static LookList * sequenceLookup; + static LookList * routeLookup; static LookList * onThrowLookup; static LookList * onCloseLookup; static LookList * onActivateLookup; @@ -216,6 +224,10 @@ private: static const int countLCCLookup; static int onLCCLookup[]; static const byte compileFeatures; + static void manageRouteState(uint16_t id, byte state); + static void manageRouteCaption(uint16_t id, const FSH* caption); + static byte * routeStateArray; + static const FSH ** routeCaptionArray; // Local variables - exist for each instance/task RMFT2 *next; // loop chain @@ -237,4 +249,8 @@ private: byte stackDepth; int callStack[MAX_STACK_DEPTH]; }; + +#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) +#define SKIPOP progCounter+=3 + #endif diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index d44b244..ff8dc8d 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -126,6 +126,10 @@ #undef ROTATE #undef ROTATE_DCC #undef ROUTE +#undef ROUTE_ACTIVE +#undef ROUTE_INACTIVE +#undef ROUTE_HIDDEN +#undef ROUTE_CAPTION #undef SENDLOCO #undef SEQUENCE #undef SERIAL @@ -267,6 +271,10 @@ #define ROTATE_DCC(turntable_id,position) #define ROSTER(cab,name,funcmap...) #define ROUTE(id,description) +#define ROUTE_ACTIVE(id) +#define ROUTE_INACTIVE(id) +#define ROUTE_HIDDEN(id) +#define ROUTE_CAPTION(id,caption) #define SENDLOCO(cab,route) #define SEQUENCE(id) #define SERIAL(msg) 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 8954f6d..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" @@ -153,6 +161,12 @@ const int StringMacroTracker1=__COUNTER__; #define PRINT(msg) THRUNGE(msg,thrunge_print) #undef LCN #define LCN(msg) THRUNGE(msg,thrunge_lcn) +#undef ROUTE_CAPTION +#define ROUTE_CAPTION(id,caption) \ +case (__COUNTER__ - StringMacroTracker1) : {\ + manageRouteCaption(id,F(caption));\ + return;\ + } #undef SERIAL #define SERIAL(msg) THRUNGE(msg,thrunge_serial) #undef SERIAL1 @@ -440,6 +454,10 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; #define ROTATE_DCC(id,position) OPCODE_ROTATE,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(0), #endif #define ROUTE(id, description) OPCODE_ROUTE, V(id), +#define ROUTE_ACTIVE(id) OPCODE_ROUTE_ACTIVE,V(id), +#define ROUTE_INACTIVE(id) OPCODE_ROUTE_INACTIVE,V(id), +#define ROUTE_HIDDEN(id) OPCODE_ROUTE_HIDDEN,V(id), +#define ROUTE_CAPTION(id,caption) PRINT(caption) #define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route), #define SEQUENCE(id) OPCODE_SEQUENCE, V(id), #define SERIAL(msg) PRINT(msg) diff --git a/version.h b/version.h index 9d5a7e1..242d730 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,9 @@ #include "StringFormatter.h" -#define VERSION "5.1.19" +#define VERSION "5.1.21" +// 5.1.21 - EXRAIL invoke multiple ON handlers for same event +// 5.1.20 - EXRAIL Tidy and ROUTE_STATE, ROUTE_CAPTION // 5.1.19 - Only flag 2.2.0.0-dev as broken, not 2.2.0.0 // 5.1.18 - TURNOUTL bugfix // 5.1.17 - Divide out C for config and D for diag commands