From 6d8ca67a2b0c91d28330a455dbb1a357067881d3 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 12 Mar 2025 11:14:49 +0000 Subject: [PATCH] STASH upgrade --- DCCEXParser.cpp | 48 +++++++++++++++++++---- DCCEXParser.h | 1 + EXRAIL2.cpp | 56 ++++++++++++-------------- EXRAIL2.h | 6 ++- EXRAIL2MacroReset.h | 14 +++++++ EXRAIL2Parser.cpp | 41 +------------------ EXRAILAsserts.h | 6 +-- EXRAILMacros.h | 10 +---- Release_Notes/Stash.md | 21 ++++++++++ Stash.cpp | 89 ++++++++++++++++++++++++++++++++++++++++++ Stash.h | 39 ++++++++++++++++++ version.h | 6 ++- 12 files changed, 246 insertions(+), 91 deletions(-) create mode 100644 Release_Notes/Stash.md create mode 100644 Stash.cpp create mode 100644 Stash.h diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 4bb7fc3..12592df 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -119,6 +119,7 @@ Once a new OPCODE is decided upon, update this list. #include "version.h" #include "KeywordHasher.h" #include "CamParser.h" +#include "Stash.h" #ifdef ARDUINO_ARCH_ESP32 #include "WifiESP32.h" #endif @@ -777,7 +778,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case 'J' : // throttle info access { - if ((params<1) | (params>3)) break; // + if (params<1) break; // //if ((params<1) | (params>2)) break; // int16_t id=(params==2)?p[1]:0; switch(p[0]) { @@ -801,7 +802,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) return; case "L"_hk: // track state and mA value on display - if (params<3) break; + if (params!=3) break; TrackManager::reportCurrentLCD(p[1], p[2]); // Track power status return; @@ -810,11 +811,10 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) StringFormatter::send(stream, F("\n")); return; - case "M"_hk: // intercepted by EXRAIL - if (params>1) break; // invalid cant do - // requests stash size so say none. - StringFormatter::send(stream,F("\n")); - return; + case "M"_hk: // Stash management + if (parseJM(stream, params, p)) + return; + break; case "R"_hk: // returns rosters StringFormatter::send(stream, F(" list all stashed automations + Stash::list(stream); + return true; + + case 2: // get stash value + Stash::list(stream, p[1]); + return true; + + case 3: // + if (p[1]=="CLEAR"_hk) { + if (p[2]=="ALL"_hk) { // + Stash::clearAll(); + return true; + } + Stash::clear(p[2]); // + return true; + } + Stash::set(p[1], p[2]); // + return true; + + case 4: // + if (p[1]=="CLEAR"_hk && p[2]=="ANY"_hk) { + // + Stash::clearAny(p[3]); + return true; + } + + default: break; +} +return false; +} + // CALLBACKS must be static bool DCCEXParser::stashCallback(Print *stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream) { diff --git a/DCCEXParser.h b/DCCEXParser.h index 4c7553e..8896816 100644 --- a/DCCEXParser.h +++ b/DCCEXParser.h @@ -52,6 +52,7 @@ struct DCCEXParser static bool parsef(Print * stream, int16_t params, int16_t p[]); static bool parseC(Print * stream, int16_t params, int16_t p[]); static bool parseD(Print * stream, int16_t params, int16_t p[]); + static bool parseJM(Print * stream, int16_t params, int16_t p[]); #ifndef IO_NO_HAL static bool parseI(Print * stream, int16_t params, int16_t p[]); #endif diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 4844cde..31f8739 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -57,6 +57,7 @@ #include "Turntables.h" #include "IODevice.h" #include "EXRAILSensor.h" +#include "Stash.h" // One instance of RMFT clas is used for each "thread" in the automation. @@ -92,8 +93,7 @@ LookList * RMFT2::onBlockEnterLookup=NULL; LookList * RMFT2::onBlockExitLookup=NULL; byte * RMFT2::routeStateArray=nullptr; const FSH * * RMFT2::routeCaptionArray=nullptr; -int16_t * RMFT2::stashArray=nullptr; -int16_t RMFT2::maxStashId=0; + // getOperand instance version, uses progCounter from instance. uint16_t RMFT2::getOperand(byte n) { @@ -258,13 +258,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { IODevice::configureInput((VPIN)pin,true); break; } - case OPCODE_STASH: - case OPCODE_CLEAR_STASH: - case OPCODE_PICKUP_STASH: { - maxStashId=max(maxStashId,((int16_t)operand)); - break; - } - + case OPCODE_ATGTE: case OPCODE_ATLT: case OPCODE_IFGTE: @@ -352,13 +346,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { } SKIPOP; // include ENDROUTES opcode - if (compileFeatures & FEATURE_STASH) { - // create the stash array from the highest id found - if (maxStashId>0) stashArray=(int16_t*)calloc(maxStashId+1, sizeof(int16_t)); - //TODO check EEPROM and fetch stashArray - } - - DIAG(F("EXRAIL %db, fl=%d, stash=%d"),progCounter,MAX_FLAGS, maxStashId); + DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS); // Removed for 4.2.31 new RMFT2(0); // add the startup route diag=saved_diag; @@ -815,6 +803,10 @@ void RMFT2::loop2() { case OPCODE_IFCLOSED: skipIf=Turnout::isThrown(operand); break; + + case OPCODE_IFSTASH: + skipIf=Stash::get(operand)==0; + break; #ifndef IO_NO_HAL case OPCODE_IFTTPOSITION: // do block if turntable at this position @@ -1067,30 +1059,32 @@ void RMFT2::loop2() { break; case OPCODE_STASH: - if (compileFeatures & FEATURE_STASH) - stashArray[operand] = invert? -loco : loco; + Stash::set(operand,invert? -loco : loco); break; case OPCODE_CLEAR_STASH: - if (compileFeatures & FEATURE_STASH) - stashArray[operand] = 0; + Stash::clear(operand); break; case OPCODE_CLEAR_ALL_STASH: - if (compileFeatures & FEATURE_STASH) - for (int i=0;i<=maxStashId;i++) stashArray[operand]=0; + Stash::clearAll(); + break; + + case OPCODE_CLEAR_ANY_STASH: + if (loco) Stash::clearAny(loco); break; case OPCODE_PICKUP_STASH: - if (compileFeatures & FEATURE_STASH) { - int16_t x=stashArray[operand]; - if (x>=0) { - loco=x; - invert=false; - break; - } - loco=-x; - invert=true; + { + auto x=Stash::get(operand); + if (x>=0) { + loco=x; + invert=false; + } + else { + loco=-x; + invert=true; + } } break; diff --git a/EXRAIL2.h b/EXRAIL2.h index dfc3985..df1f5d0 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -77,6 +77,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT, OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN, OPCODE_ROUTE_DISABLED, OPCODE_STASH,OPCODE_CLEAR_STASH,OPCODE_CLEAR_ALL_STASH,OPCODE_PICKUP_STASH, + OPCODE_CLEAR_ANY_STASH, OPCODE_ONBUTTON,OPCODE_ONSENSOR, OPCODE_NEOPIXEL, OPCODE_ONBLOCKENTER,OPCODE_ONBLOCKEXIT, @@ -93,7 +94,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT, OPCODE_IFCLOSED,OPCODE_IFTHROWN, OPCODE_IFRE, OPCODE_IFLOCO, - OPCODE_IFTTPOSITION + OPCODE_IFTTPOSITION, + OPCODE_IFSTASH, }; // Ensure thrunge_lcd is put last as there may be more than one display, @@ -137,7 +139,7 @@ enum SignalType { static const byte FEATURE_LCC = 0x40; static const byte FEATURE_ROSTER= 0x20; static const byte FEATURE_ROUTESTATE= 0x10; - static const byte FEATURE_STASH = 0x08; + // spare = 0x08; static const byte FEATURE_BLINK = 0x04; static const byte FEATURE_SENSOR = 0x02; static const byte FEATURE_BLOCK = 0x01; diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 28f74c1..eaa8f93 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -46,6 +46,7 @@ #undef CALL #undef CLEAR_STASH #undef CLEAR_ALL_STASH +#undef CLEAR_ANY_STASH #undef CLOSE #undef CONFIGURE_SERVO #undef DCC_SIGNAL @@ -88,6 +89,7 @@ #undef IFRANDOM #undef IFRED #undef IFRESERVE +#undef IFSTASH #undef IFTHROWN #undef IFTIMEOUT #undef IFTTPOSITION @@ -338,6 +340,11 @@ * @brief Clears all stashed loco values */ #define CLEAR_ALL_STASH +/** +* @def CLEAR_ANY_STASH +* @brief Clears loco value from all stash entries +*/ +#define CLEAR_ANY_STASH /** * @def CLOSE(turnout_id) * @brief Close turnout by id @@ -602,6 +609,13 @@ * @param signal_id */ #define IFRED(signal_id) +/** + * @def IFSTASH(stash_id) + * @brief Checks if given stash entry has a non zero value + * @see IF + * @param stash_id + */ +#define IFSTASH(stash_id) /** * @def IFTHROWN(turnout_id) * @brief Checks if given turnout is in THROWN state diff --git a/EXRAIL2Parser.cpp b/EXRAIL2Parser.cpp index ca3b54c..a898b27 100644 --- a/EXRAIL2Parser.cpp +++ b/EXRAIL2Parser.cpp @@ -181,36 +181,7 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16 return; } break; - case "M"_hk: - // NOTE: we only need to handle valid calls here because - // DCCEXParser has to have code to handle the cases where - // exrail isnt involved anyway. - // This entire code block is compiled out if STASH macros not used - if (!(compileFeatures & FEATURE_STASH)) return; - if (paramCount==1) { // - StringFormatter::send(stream,F("\n"),maxStashId); - opcode=0; - break; - } - if (paramCount==2) { // - if (p[1]<=0 || p[1]>maxStashId) break; - StringFormatter::send(stream,F("\n"), - p[1],stashArray[p[1]]); - opcode=0; - break; - } - if (paramCount==3) { // - if (p[1]<=0 || p[1]>maxStashId) break; - stashArray[p[1]]=p[2]; - opcode=0; - break; - } - break; - - default: - break; - } - break; + case 'K': // Block enter case 'k': // Block exit @@ -223,6 +194,7 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16 break; } } +} bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { @@ -268,15 +240,6 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { slot.id); } } - - if (compileFeatures & FEATURE_STASH) { - for (int i=1;i<=maxStashId;i++) { - if (stashArray[i]) - StringFormatter::send(stream,F("\nSTASH[%d] Loco=%d"), - i, stashArray[i]); - } - } - StringFormatter::send(stream,F(" *>\n")); return true; } diff --git a/EXRAILAsserts.h b/EXRAILAsserts.h index cd74c5b..cd01ad8 100644 --- a/EXRAILAsserts.h +++ b/EXRAILAsserts.h @@ -136,11 +136,11 @@ constexpr bool unsafePin(const int16_t value, const uint16_t pos=0 ) { // check duplicate sequences #undef SEQUENCE -#define SEQUENCE(id) static_assert(seqCount(id)==1,"\n\nUSER ERROR: Duplicate ROUTE/AUTOMATION/SEQUENCE(" #id")\n"); +#define SEQUENCE(id) static_assert(seqCount(id)==1,"\n\nUSER ERROR: Duplicate ROUTE/AUTOMATION/SEQUENCE(" #id ")\n"); #undef AUTOMATION -#define AUTOMATION(id,description) static_assert(seqCount(id)==1,"\n\nUSER ERROR: Duplicate ROUTE/AUTOMATION/SEQUENCE(" #id")\n"); +#define AUTOMATION(id,description) static_assert(seqCount(id)==1,"\n\nUSER ERROR: Duplicate ROUTE/AUTOMATION/SEQUENCE(" #id ")\n"); #undef ROUTE -#define ROUTE(id,description) static_assert(seqCount(id)==1,"\n\nUSER ERROR: Duplicate ROUTE/AUTOMATION/SEQUENCE(" #id")\n"); +#define ROUTE(id,description) static_assert(seqCount(id)==1,"\n\nUSER ERROR: Duplicate ROUTE/AUTOMATION/SEQUENCE(" #id ")\n"); // check dangerous pins #define _PIN_RESERVED_ "\n\nUSER ERROR: Pin is used by Motor Shield or other critical function.\n" diff --git a/EXRAILMacros.h b/EXRAILMacros.h index d672c24..40cd5d6 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -154,14 +154,6 @@ bool exrailHalSetup() { #undef ROUTE_CAPTION #define ROUTE_CAPTION(id,caption) | FEATURE_ROUTESTATE -#undef CLEAR_STASH -#define CLEAR_STASH(id) | FEATURE_STASH -#undef CLEAR_ALL_STASH -#define CLEAR_ALL_STASH | FEATURE_STASH -#undef PICKUP_STASH -#define PICKUP_STASH(id) | FEATURE_STASH -#undef STASH -#define STASH(id) | FEATURE_STASH #undef BLINK #define BLINK(vpin,onDuty,offDuty) | FEATURE_BLINK #undef ONBUTTON @@ -435,6 +427,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; #define CALL(route) OPCODE_CALL,V(route), #define CLEAR_STASH(id) OPCODE_CLEAR_STASH,V(id), #define CLEAR_ALL_STASH OPCODE_CLEAR_ALL_STASH,V(0), +#define CLEAR_ANY_STASH OPCODE_CLEAR_ANY_STASH,V(0), #define CLOSE(id) OPCODE_CLOSE,V(id), #define CONFIGURE_SERVO(vpin,pos1,pos2,profile) #ifndef IO_NO_HAL @@ -481,6 +474,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; #define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent), #define IFRED(signal_id) OPCODE_IFRED,V(signal_id), #define IFRESERVE(block) OPCODE_IFRESERVE,V(block), +#define IFSTASH(stash_id) OPCODE_IFSTASH,V(stash_id), #define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id), #define IFTIMEOUT OPCODE_IFTIMEOUT,0,0, #ifndef IO_NO_HAL diff --git a/Release_Notes/Stash.md b/Release_Notes/Stash.md new file mode 100644 index 0000000..d374c41 --- /dev/null +++ b/Release_Notes/Stash.md @@ -0,0 +1,21 @@ +# The STASH feature of exrail. + +STASH is used for scenarios where it is helpful to relate a loco id to where it is parked. For example a fiddle yard may have 10 tracks and it's much easier for the operator to select a train to depart by using the track number, or pressing a button relating to that track, rather than by knowing the loco id which may be difficult to see. + +Automated yard parking can use the stash to determine which tracks are empty without the need for block occupancy detectors. + +Note that a negative locoid may be stashed to indicate that the loco will operate with inverted direction. For example a loco facing backwards, with the INVERT_DRECTION state may be stashed by exrail and the invert state will be restored along with the loco id when using the PICKUP_STASH. CLEAR_ANY_STASH will clear all references to the loco regardless of direction. + +The following Stash commands are available: + | EXRAIL command | Serial protocol | function | + | -------------- | --------------- | -------- | + | STASH(s) | `` | Save the current loco id in the stash array element s. | + | CLEAR_STASH(s) | `` | Sets stash array element s to zero. | + | CLEAR_ALL_STASH | `` | sets all stash entries to zero | + | CLEAR_ANY_STASH | `` | removes current loco from all stash elements | + | PICKUP_STASH(s) | N/A | sets current loco to stash element s | + | IFSTASH(s) | N/A | True if stash element s is not zero | + | N/A | `` | query all stashes (returns `` where loco is not zero) + | N/A | `` | Query loco in stash (returns ``) + + diff --git a/Stash.cpp b/Stash.cpp new file mode 100644 index 0000000..9eeebf7 --- /dev/null +++ b/Stash.cpp @@ -0,0 +1,89 @@ +/* +* © 2024 Chris Harlow + * All rights reserved. + * + * This file is part of DCC-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 . +*/ +#include "Stash.h" +#include "StringFormatter.h" + +Stash::Stash(int16_t stash_id, int16_t loco_id) { + this->stashId = stash_id; + this->locoId = loco_id; + this->next = first; + first = this; +} + +void Stash::clearAll() { + for (auto s=first;s;s=s->next) { + s->locoId = 0; + s->stashId =0; + } +} + +void Stash::clearAny(int16_t loco_id) { + auto lid=abs(loco_id); + for (auto s=first;s;s=s->next) + if (abs(s->locoId) == lid) { + s->locoId = 0; + s->stashId =0; + } +} + +void Stash::clear(int16_t stash_id) { + set(stash_id,0); +} + +int16_t Stash::get(int16_t stash_id) { + for (auto s=first;s;s=s->next) + if (s->stashId == stash_id) return s->locoId; + return 0; +} + +void Stash::set(int16_t stash_id, int16_t loco_id) { + // replace any existing stash + for (auto s=first;s;s=s->next) + if (s->stashId == stash_id) { + s->locoId=loco_id; + if (loco_id==0) s->stashId=0; // recycle + return; + } + if (loco_id==0) return; // no need to create a zero entry. + + // replace any empty stash + for (auto s=first;s;s=s->next) + if (s->locoId == 0) { + s->locoId=loco_id; + s->stashId=stash_id; + return; + } + // create a new stash + new Stash(stash_id, loco_id); +} + +void Stash::list(Print * stream, int16_t stash_id) { + bool sent=false; + for (auto s=first;s;s=s->next) + if ((s->locoId) && (stash_id==0 || s->stashId==stash_id)) { + StringFormatter::send(stream,F("\n"), + s->stashId,s->locoId); + sent=true; + } + if (!sent) StringFormatter::send(stream,F("\n"), + stash_id); +} + +Stash* Stash::first=nullptr; diff --git a/Stash.h b/Stash.h new file mode 100644 index 0000000..acbdad0 --- /dev/null +++ b/Stash.h @@ -0,0 +1,39 @@ +/* +* © 2024 Chris Harlow + * All rights reserved. + * + * This file is part of DCC-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 . +*/ +#ifndef Stash_h +#define Stash_h +#include + +class Stash { + public: + static void clear(int16_t stash_id); + static void clearAll(); + static void clearAny(int16_t loco_id); + static int16_t get(int16_t stash_id); + static void set(int16_t stash_id, int16_t loco_id); + static void list(Print * stream, int16_t stash_id=0); // id0 = LIST ALL + private: + Stash(int16_t stash_id, int16_t loco_id); + static Stash* first; + Stash* next; + int16_t stashId; + int16_t locoId; +}; +#endif diff --git a/version.h b/version.h index d90b8ce..e455cf8 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,11 @@ #include "StringFormatter.h" -#define VERSION "5.5.17" +#define VERSION "5.5.18" +// 5.5.18 - New STASH internals +// - EXRAIL IFSTASH/CLEAR_ANY_STASH +// - to clear any stash with loco id +// - See Release_Notes/Stash.md // 5.5.17 - Extensive new compile time checking in exrail scripts (duplicate sequences etc), no function change // 5.5.16 - DOXYGEN comments in EXRAIL2MacroReset.h // 5.5.15 - Support for F429ZI/F329ZI