diff --git a/DCCEX.h b/DCCEX.h index 2dc8eb7..3aa7b7a 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -49,6 +49,7 @@ #include "CommandDistributor.h" #include "TrackManager.h" #include "DCCTimer.h" +#include "KeywordHasher.h" #include "EXRAIL.h" #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index aefed4c..16f4494 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -116,6 +116,7 @@ Once a new OPCODE is decided upon, update this list. #include "EXRAIL2.h" #include "Turntables.h" #include "version.h" +#include "KeywordHasher.h" // This macro can't be created easily as a portable function because the // flashlist requires a far pointer for high flash access. @@ -126,57 +127,6 @@ Once a new OPCODE is decided upon, update this list. StringFormatter::send(stream,F(" %d"),value); \ } - -// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. -// To discover new keyword numbers , use the <$ YOURKEYWORD> command -const int16_t HASH_KEYWORD_MAIN = 11339; -const int16_t HASH_KEYWORD_CABS = -11981; -const int16_t HASH_KEYWORD_RAM = 25982; -const int16_t HASH_KEYWORD_CMD = 9962; -const int16_t HASH_KEYWORD_ACK = 3113; -const int16_t HASH_KEYWORD_ON = 2657; -const int16_t HASH_KEYWORD_DCC = 6436; -const int16_t HASH_KEYWORD_SLOW = -17209; -#ifndef DISABLE_PROG -const int16_t HASH_KEYWORD_JOIN = -30750; -const int16_t HASH_KEYWORD_PROG = -29718; -const int16_t HASH_KEYWORD_PROGBOOST = -6353; -#endif -#ifndef DISABLE_EEPROM -const int16_t HASH_KEYWORD_EEPROM = -7168; -#endif -const int16_t HASH_KEYWORD_LIMIT = 27413; -const int16_t HASH_KEYWORD_MAX = 16244; -const int16_t HASH_KEYWORD_MIN = 15978; -const int16_t HASH_KEYWORD_RESET = 26133; -const int16_t HASH_KEYWORD_RETRY = 25704; -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_TT=2688; -const int16_t HASH_KEYWORD_VPIN=-415; -const int16_t HASH_KEYWORD_A='A'; -const int16_t HASH_KEYWORD_C='C'; -const int16_t HASH_KEYWORD_G='G'; -const int16_t HASH_KEYWORD_H='H'; -const int16_t HASH_KEYWORD_I='I'; -const int16_t HASH_KEYWORD_M='M'; -const int16_t HASH_KEYWORD_O='O'; -const int16_t HASH_KEYWORD_P='P'; -const int16_t HASH_KEYWORD_R='R'; -const int16_t HASH_KEYWORD_T='T'; -const int16_t HASH_KEYWORD_X='X'; -const int16_t HASH_KEYWORD_LCN = 15137; -const int16_t HASH_KEYWORD_HAL = 10853; -const int16_t HASH_KEYWORD_SHOW = -21309; -const int16_t HASH_KEYWORD_ANIN = -10424; -const int16_t HASH_KEYWORD_ANOUT = -26399; -const int16_t HASH_KEYWORD_WIFI = -5583; -const int16_t HASH_KEYWORD_ETHERNET = -30767; -const int16_t HASH_KEYWORD_WIT = 31594; -const int16_t HASH_KEYWORD_EXTT = 8573; -const int16_t HASH_KEYWORD_ADD = 3201; - int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS]; bool DCCEXParser::stashBusy; Print *DCCEXParser::stashStream = NULL; @@ -567,20 +517,20 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::ON); } if (params==1) { - if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN> + if (p[0]=="MAIN"_hk) { // <1 MAIN> TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::ON); } #ifndef DISABLE_PROG - else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN> + else if (p[0] == "JOIN"_hk) { // <1 JOIN> TrackManager::setJoin(true); TrackManager::setTrackPower(TRACK_MODE_MAIN|TRACK_MODE_PROG, POWERMODE::ON); } - else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG> + else if (p[0]=="PROG"_hk) { // <1 PROG> TrackManager::setJoin(false); TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::ON); } #endif - else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H> + else if (p[0] >= "A"_hk && p[0] <= "H"_hk) { // <1 A-H> byte t = (p[0] - 'A'); TrackManager::setTrackPower(POWERMODE::ON, t); //StringFormatter::send(stream, F("\n"), t+'A'); @@ -600,17 +550,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::OFF); } if (params==1) { - if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN> + if (p[0]=="MAIN"_hk) { // <0 MAIN> TrackManager::setJoin(false); TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::OFF); } #ifndef DISABLE_PROG - else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG> + else if (p[0]=="PROG"_hk) { // <0 PROG> TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::OFF); } #endif - else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H> + else if (p[0] >= "A"_hk && p[0] <= "H"_hk) { // <1 A-H> byte t = (p[0] - 'A'); TrackManager::setJoin(false); TrackManager::setTrackPower(POWERMODE::OFF, t); @@ -704,7 +654,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) //if ((params<1) | (params>2)) break; // int16_t id=(params==2)?p[1]:0; switch(p[0]) { - case HASH_KEYWORD_C: // sets time and speed + case "C"_hk: // sets time and speed if (params==1) { // returns latest time int16_t x = CommandDistributor::retClockTime(); StringFormatter::send(stream, F("\n"), x); @@ -713,28 +663,28 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) CommandDistributor::setClockTime(p[1], p[2], 1); return; - case HASH_KEYWORD_G: // current gauge limits + case "G"_hk: // current gauge limits if (params>1) break; TrackManager::reportGauges(stream); // return; - case HASH_KEYWORD_I: // current values + case "I"_hk: // current values if (params>1) break; TrackManager::reportCurrent(stream); // return; - case HASH_KEYWORD_A: // intercepted by EXRAIL// returns automations/routes + case "A"_hk: // intercepted by EXRAIL// returns automations/routes if (params!=1) break; // StringFormatter::send(stream, F("\n")); return; - case HASH_KEYWORD_M: // intercepted by EXRAIL + 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 HASH_KEYWORD_R: // returns rosters + case "R"_hk: // returns rosters StringFormatter::send(stream, F("\n")); return; - case HASH_KEYWORD_T: // returns turnout list + case "T"_hk: // returns turnout list StringFormatter::send(stream, F(" for ( Turnout * t=Turnout::first(); t; t=t->next()) { @@ -780,7 +730,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) return; // No turntables without HAL support #ifndef IO_NO_HAL - case HASH_KEYWORD_O: // for (Turntable * tto=Turntable::first(); tto; tto=tto->next()) { @@ -805,7 +755,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) } } return; - case HASH_KEYWORD_P: // returns turntable position list for the turntable id + case "P"_hk: // returns turntable position list for the turntable id if (params==2) { // Turntable *tto=Turntable::get(id); if (!tto || tto->isHidden()) { @@ -972,14 +922,14 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[]) switch (p[1]) { // Turnout messages use 1=throw, 0=close. case 0: - case HASH_KEYWORD_C: + case "C"_hk: state = true; break; case 1: - case HASH_KEYWORD_T: + case "T"_hk: state= false; break; - case HASH_KEYWORD_X: + case "X"_hk: { Turnout *tt = Turnout::get(p[0]); if (tt) { @@ -996,14 +946,14 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[]) } default: // Anything else is some kind of turnout create function. - if (params == 6 && p[1] == HASH_KEYWORD_SERVO) { // + if (params == 6 && p[1] == "SERVO"_hk) { // if (!ServoTurnout::create(p[0], (VPIN)p[2], (uint16_t)p[3], (uint16_t)p[4], (uint8_t)p[5])) return false; } else - if (params == 3 && p[1] == HASH_KEYWORD_VPIN) { // + if (params == 3 && p[1] == "VPIN"_hk) { // if (!VpinTurnout::create(p[0], p[2])) return false; } else - if (params >= 3 && p[1] == HASH_KEYWORD_DCC) { + if (params >= 3 && p[1] == "DCC"_hk) { // 0<=addr<=511, 0<=subadd<=3 (like command). if (params==4 && p[2]>=0 && p[2]<512 && p[3]>=0 && p[3]<4) { // if (!DCCTurnout::create(p[0], p[2], p[3])) return false; @@ -1069,41 +1019,41 @@ bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) { switch (p[0]) { #ifndef DISABLE_PROG - case HASH_KEYWORD_PROGBOOST: + case "PROGBOOST"_hk: TrackManager::progTrackBoosted=true; return true; #endif - case HASH_KEYWORD_RESET: + case "RESET"_hk: DCCTimer::reset(); break; // and if we didnt restart - case HASH_KEYWORD_SPEED28: + case "SPEED28"_hk: DCC::setGlobalSpeedsteps(28); DIAG(F("28 Speedsteps")); return true; - case HASH_KEYWORD_SPEED128: + case "SPEED128"_hk: DCC::setGlobalSpeedsteps(128); DIAG(F("128 Speedsteps")); return true; #ifndef DISABLE_PROG - case HASH_KEYWORD_ACK: // + case "ACK"_hk: // if (params >= 3) { - if (p[1] == HASH_KEYWORD_LIMIT) { + if (p[1] == "LIMIT"_hk) { DCCACK::setAckLimit(p[2]); LCD(1, F("Ack Limit=%dmA"), p[2]); // - } else if (p[1] == HASH_KEYWORD_MIN) { + } else if (p[1] == "MIN"_hk) { DCCACK::setMinAckPulseDuration(p[2]); LCD(0, F("Ack Min=%uus"), p[2]); // - } else if (p[1] == HASH_KEYWORD_MAX) { + } else if (p[1] == "MAX"_hk) { DCCACK::setMaxAckPulseDuration(p[2]); LCD(0, F("Ack Max=%uus"), p[2]); // - } else if (p[1] == HASH_KEYWORD_RETRY) { + } else if (p[1] == "RETRY"_hk) { if (p[2] >255) p[2]=3; 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 + bool onOff = (params > 0) && (p[1] == 1 || p[1] == "ON"_hk); // dont care if other stuff or missing... just means off DIAG(F("Ack diag %S"), onOff ? F("on") : F("off")); Diag::ACK = onOff; @@ -1121,66 +1071,66 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) { if (params == 0) return false; - bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off + bool onOff = (params > 0) && (p[1] == 1 || p[1] == "ON"_hk); // dont care if other stuff or missing... just means off switch (p[0]) { - case HASH_KEYWORD_CABS: // + case "CABS"_hk: // DCC::displayCabList(stream); return true; - case HASH_KEYWORD_RAM: // + case "RAM"_hk: // DIAG(F("Free memory=%d"), DCCTimer::getMinimumFreeMemory()); return true; - case HASH_KEYWORD_CMD: // + case "CMD"_hk: // Diag::CMD = onOff; return true; #ifdef HAS_ENOUGH_MEMORY - case HASH_KEYWORD_WIFI: // + case "WIFI"_hk: // Diag::WIFI = onOff; return true; - case HASH_KEYWORD_ETHERNET: // + case "ETHERNET"_hk: // Diag::ETHERNET = onOff; return true; - case HASH_KEYWORD_WIT: // + case "WIT"_hk: // Diag::WITHROTTLE = onOff; return true; - case HASH_KEYWORD_LCN: // + case "LCN"_hk: // Diag::LCN = onOff; return true; #endif #ifndef DISABLE_EEPROM - case HASH_KEYWORD_EEPROM: // + case "EEPROM"_hk: // if (params >= 2) EEStore::dump(p[1]); return true; #endif - case HASH_KEYWORD_SERVO: // + case "SERVO"_hk: // - case HASH_KEYWORD_ANOUT: // + case "ANOUT"_hk: // IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0); - break; + return true; - case HASH_KEYWORD_ANIN: // Display analogue input value + case "ANIN"_hk: // Display analogue input value DIAG(F("VPIN=%u value=%d"), p[1], IODevice::readAnalogue(p[1])); - break; + return true; #if !defined(IO_NO_HAL) - case HASH_KEYWORD_HAL: - if (p[1] == HASH_KEYWORD_SHOW) + case "HAL"_hk: + if (p[1] == "SHOW"_hk) IODevice::DumpAll(); - else if (p[1] == HASH_KEYWORD_RESET) + else if (p[1] == "RESET"_hk) IODevice::reset(); - break; + return true; #endif - case HASH_KEYWORD_TT: // + case "TT"_hk: // IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0); - break; + return true; default: // invalid/unknown return parseC(stream, params, p); @@ -1232,7 +1182,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[]) case 3: // | - rotate to position for EX-Turntable or create DCC turntable { Turntable *tto = Turntable::get(p[0]); - if (p[1] == HASH_KEYWORD_DCC) { + if (p[1] == "DCC"_hk) { if (tto || p[2] < 0 || p[2] > 3600) return false; if (!DCCTurntable::create(p[0])) return false; Turntable *tto = Turntable::get(p[0]); @@ -1249,7 +1199,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[]) case 4: // create an EXTT turntable { Turntable *tto = Turntable::get(p[0]); - if (p[1] == HASH_KEYWORD_EXTT) { + if (p[1] == "EXTT"_hk) { if (tto || p[3] < 0 || p[3] > 3600) return false; if (!EXTTTurntable::create(p[0], (VPIN)p[2])) return false; Turntable *tto = Turntable::get(p[0]); @@ -1264,7 +1214,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[]) case 5: // add a position { Turntable *tto = Turntable::get(p[0]); - if (p[1] == HASH_KEYWORD_ADD) { + if (p[1] == "ADD"_hk) { // tto must exist, no more than 48 positions, angle 0 - 3600 if (!tto || p[2] > 48 || p[4] < 0 || p[4] > 3600) return false; tto->addPosition(p[2], p[3], p[4]); diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 7811a0d..3554f6c 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -67,6 +67,7 @@ #undef FWD #undef GREEN #undef HAL +#undef HAL_IGNORE_DEFAULTS #undef IF #undef IFAMBER #undef IFCLOSED @@ -218,6 +219,7 @@ #define FWD(speed) #define GREEN(signal_id) #define HAL(haltype,params...) +#define HAL_IGNORE_DEFAULTS #define IF(sensor_id) #define IFAMBER(signal_id) #define IFCLOSED(turnout_id) diff --git a/EXRAIL2Parser.cpp b/EXRAIL2Parser.cpp index d231342..99f0c37 100644 --- a/EXRAIL2Parser.cpp +++ b/EXRAIL2Parser.cpp @@ -28,25 +28,7 @@ #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'; -const int16_t HASH_KEYWORD_M='M'; - +#include "KeywordHasher.h" // This filter intercepts <> commands to do the following: // - Implement RMFT specific commands/diagnostics @@ -58,8 +40,8 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16 switch(opcode) { case 'D': - if (p[0]==HASH_KEYWORD_EXRAIL) { // - diag = paramCount==2 && (p[1]==HASH_KEYWORD_ON || p[1]==1); + if (p[0]=="EXRAIL"_hk) { // + diag = paramCount==2 && (p[1]=="ON"_hk || p[1]==1); opcode=0; } break; @@ -125,7 +107,7 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16 case 'J': // throttle info commands if (paramCount<1) return; switch(p[0]) { - case HASH_KEYWORD_A: // returns automations/routes + case "A"_hk: // returns automations/routes if (paramCount==1) {// StringFormatter::send(stream, F("stream(stream); @@ -152,7 +134,7 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16 return; } break; - case HASH_KEYWORD_M: + 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. @@ -236,13 +218,13 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { return true; } switch (p[0]) { - case HASH_KEYWORD_PAUSE: // + case "PAUSE"_hk: // 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: // + case "RESUME"_hk: // if (paramCount!=1) return false; pausingTask=NULL; { @@ -256,7 +238,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { return true; - case HASH_KEYWORD_START: // + case "START"_hk: // if (paramCount<2 || paramCount>3) return false; { int route=(paramCount==2) ? p[1] : p[2]; @@ -273,7 +255,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { } // check KILL ALL here, otherwise the next validation confuses ALL with a flag - if (p[0]==HASH_KEYWORD_KILL && p[1]==HASH_KEYWORD_ALL) { + if (p[0]=="KILL"_hk && p[1]=="ALL"_hk) { while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask return true; } @@ -282,7 +264,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { if (paramCount!=2 ) return false; switch (p[0]) { - case HASH_KEYWORD_KILL: // Kill taskid|ALL + case "KILL"_hk: // Kill taskid|ALL { if ( p[1]<0 || p[1]>=MAX_FLAGS) return false; RMFT2 * task=loopTask; @@ -297,27 +279,27 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { } return false; - case HASH_KEYWORD_RESERVE: // force reserve a section + case "RESERVE"_hk: // force reserve a section return setFlag(p[1],SECTION_FLAG); - case HASH_KEYWORD_FREE: // force free a section + case "FREE"_hk: // force free a section return setFlag(p[1],0,SECTION_FLAG); - case HASH_KEYWORD_LATCH: + case "LATCH"_hk: return setFlag(p[1], LATCH_FLAG); - case HASH_KEYWORD_UNLATCH: + case "UNLATCH"_hk: return setFlag(p[1], 0, LATCH_FLAG); - case HASH_KEYWORD_RED: + case "RED"_hk: doSignal(p[1],SIGNAL_RED); return true; - case HASH_KEYWORD_AMBER: + case "AMBER"_hk: doSignal(p[1],SIGNAL_AMBER); return true; - case HASH_KEYWORD_GREEN: + case "GREEN"_hk: doSignal(p[1],SIGNAL_GREEN); return true; diff --git a/EXRAILMacros.h b/EXRAILMacros.h index f79693d..8ed2b82 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -74,13 +74,81 @@ #define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10; #include "myAutomation.h" +// Pass 1d Detect sequence duplicates. +// This pass generates no runtime data or code +#include "EXRAIL2MacroReset.h" +#undef AUTOMATION +#define AUTOMATION(id, description) id, +#undef ROUTE +#define ROUTE(id, description) id, +#undef SEQUENCE +#define SEQUENCE(id) id, +constexpr int16_t compileTimeSequenceList[]={ + #include "myAutomation.h" + 0 + }; +constexpr int16_t stuffSize=sizeof(compileTimeSequenceList)/sizeof(int16_t) - 1; + + +// Compile time function to check for sequence nos. +constexpr bool hasseq(const int16_t value, const uint16_t pos=0 ) { + return pos>=stuffSize? false : + compileTimeSequenceList[pos]==value + || hasseq(value,pos+1); +} + +// Compile time function to check for duplicate sequence nos. +constexpr bool hasdup(const int16_t value, const uint16_t pos ) { + return pos>=stuffSize? false : + compileTimeSequenceList[pos]==value + || hasseq(value,pos+1) + || hasdup(compileTimeSequenceList[pos],pos+1); +} + + +static_assert(!hasdup(compileTimeSequenceList[0],1),"Duplicate SEQUENCE/ROUTE/AUTOMATION detected"); + +//pass 1s static asserts to +// - check call and follows etc for existing sequence numbers +// - check range on LATCH/UNLATCH +// This pass generates no runtime data or code +#include "EXRAIL2MacroReset.h" +#undef CALL +#define CALL(id) static_assert(hasseq(id),"Sequence not found"); +#undef FOLLOW +#define FOLLOW(id) static_assert(hasseq(id),"Sequence not found"); +#undef START +#define START(id) static_assert(hasseq(id),"Sequence not found"); +#undef SENDLOCO +#define SENDLOCO(cab,id) static_assert(hasseq(id),"Sequence not found"); +#undef LATCH +#define LATCH(id) static_assert(id>=0 && id=0 && id=0 && id=0 && id=0 && speed<128,"Speed out of valid range 0-127"); +#undef FWD +#define FWD(speed) static_assert(speed>=0 && speed<128,"Speed out of valid range 0-127"); +#undef REV +#define REV(speed) static_assert(speed>=0 && speed<128,"Speed out of valid range 0-127"); + +#include "myAutomation.h" + // Pass 1h Implements HAL macro by creating exrailHalSetup function // Also allows creating EXTurntable object #include "EXRAIL2MacroReset.h" #undef HAL #define HAL(haltype,params...) haltype::create(params); -void exrailHalSetup() { +#undef HAL_IGNORE_DEFAULTS +#define HAL_IGNORE_DEFAULTS ignore_defaults=true; +bool exrailHalSetup() { + bool ignore_defaults=false; #include "myAutomation.h" + return ignore_defaults; } // Pass 1c detect compile time featurtes @@ -396,6 +464,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup]; #define FWD(speed) OPCODE_FWD,V(speed), #define GREEN(signal_id) OPCODE_GREEN,V(signal_id), #define HAL(haltype,params...) +#define HAL_IGNORE_DEFAULTS #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), diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index a53b57a..d15db46 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202401100719Z" +#define GITHUB_SHA "devel-202401202116Z" diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h index ea56fec..9e4582f 100644 --- a/I2CManager_STM32.h +++ b/I2CManager_STM32.h @@ -110,7 +110,6 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) { // Calculate a rise time appropriate to the requested bus speed // Use 10x the rise time spec to enable integer divide of 50ns clock period uint16_t t_rise; - uint32_t ccr_freq; while (s->CR1 & I2C_CR1_STOP); // Prevents lockup by guarding further // writes to CR1 while STOP is being executed! diff --git a/IODevice.cpp b/IODevice.cpp index e811fff..99199aa 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -33,7 +33,7 @@ // Link to halSetup function. If not defined, the function reference will be NULL. extern __attribute__((weak)) void halSetup(); -extern __attribute__((weak)) void exrailHalSetup(); +extern __attribute__((weak)) bool exrailHalSetup(); //================================================================================================================== // Static methods @@ -60,34 +60,31 @@ void IODevice::begin() { halSetup(); // include any HAL devices defined in exrail. + bool ignoreDefaults=false; if (exrailHalSetup) - exrailHalSetup(); - + ignoreDefaults=exrailHalSetup(); + if (ignoreDefaults) return; + // Predefine two PCA9685 modules 0x40-0x41 if no conflicts // Allocates 32 pins 100-131 - if (checkNoOverlap(100, 16, 0x40)) { + const bool silent=true; // no message if these conflict + if (checkNoOverlap(100, 16, 0x40, silent)) { PCA9685::create(100, 16, 0x40); - } else { - DIAG(F("Default PCA9685 at I2C 0x40 disabled due to configured user device")); - } - if (checkNoOverlap(116, 16, 0x41)) { + } + + if (checkNoOverlap(116, 16, 0x41, silent)) { PCA9685::create(116, 16, 0x41); - } else { - DIAG(F("Default PCA9685 at I2C 0x41 disabled due to configured user device")); - } + } // Predefine two MCP23017 module 0x20/0x21 if no conflicts // Allocates 32 pins 164-195 - if (checkNoOverlap(164, 16, 0x20)) { + if (checkNoOverlap(164, 16, 0x20, silent)) { MCP23017::create(164, 16, 0x20); - } else { - DIAG(F("Default MCP23017 at I2C 0x20 disabled due to configured user device")); - } - if (checkNoOverlap(180, 16, 0x21)) { + } + + if (checkNoOverlap(180, 16, 0x21, silent)) { MCP23017::create(180, 16, 0x21); - } else { - DIAG(F("Default MCP23017 at I2C 0x21 disabled due to configured user device")); - } + } } // reset() function to reinitialise all devices @@ -339,7 +336,10 @@ IODevice *IODevice::findDeviceFollowing(VPIN vpin) { // returns true if pins DONT overlap with existing device // TODO: Move the I2C address reservation and checks into the I2CManager code. // That will enable non-HAL devices to reserve I2C addresses too. -bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddress) { +// Silent is used by the default setup so that there is no message if the default +// device has already been handled by the user setup. +bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, + I2CAddress i2cAddress, bool silent) { #ifdef DIAG_IO DIAG(F("Check no overlap %u %u %s"), firstPin,nPins,i2cAddress.toString()); #endif @@ -352,14 +352,14 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddres VPIN lastDevPin=firstDevPin+dev->_nPins-1; bool noOverlap= firstPin>lastDevPin || lastPin_I2CAddress==i2cAddress) { - DIAG(F("WARNING HAL Overlap. i2c Addr %s ignored."),i2cAddress.toString()); + if (!silent) DIAG(F("WARNING HAL Overlap. i2c Addr %s ignored."),i2cAddress.toString()); return false; } } diff --git a/IODevice.h b/IODevice.h index e757dc4..4d13d70 100644 --- a/IODevice.h +++ b/IODevice.h @@ -167,7 +167,8 @@ public: void setGPIOInterruptPin(int16_t pinNumber); // Method to check if pins will overlap before creating new device. - static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, I2CAddress i2cAddress=0); + static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, + I2CAddress i2cAddress=0, bool silent=false); // Method used by IODevice filters to locate slave pins that may be overlayed by their own // pin range. diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index c8bcba0..2e83eb7 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -1,5 +1,6 @@ /* * © 2022, Peter Cole. All rights reserved. + * © 2024, Harald Barth. All rights reserved. * * This file is part of EX-CommandStation * @@ -100,8 +101,14 @@ private: if (_digitalPinBytes < digitalBytesNeeded) { // Not enough space, free any existing buffer and allocate a new one if (_digitalPinBytes > 0) free(_digitalInputStates); - _digitalInputStates = (byte*) calloc(_digitalPinBytes, 1); - _digitalPinBytes = digitalBytesNeeded; + if ((_digitalInputStates = (byte*) calloc(digitalBytesNeeded, 1)) != NULL) { + _digitalPinBytes = digitalBytesNeeded; + } else { + DIAG(F("EX-IOExpander I2C:%s ERROR alloc %d bytes"), _I2CAddress.toString(), digitalBytesNeeded); + _deviceState = DEVSTATE_FAILED; + _digitalPinBytes = 0; + return; + } } } @@ -117,7 +124,16 @@ private: _analogueInputStates = (uint8_t*) calloc(analogueBytesNeeded, 1); _analogueInputBuffer = (uint8_t*) calloc(analogueBytesNeeded, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); - _analoguePinBytes = analogueBytesNeeded; + if (_analogueInputStates != NULL && + _analogueInputBuffer != NULL && + _analoguePinMap != NULL) { + _analoguePinBytes = analogueBytesNeeded; + } else { + DIAG(F("EX-IOExpander I2C:%s ERROR alloc analog pin bytes"), _I2CAddress.toString()); + _deviceState = DEVSTATE_FAILED; + _analoguePinBytes = 0; + return; + } } } } else { @@ -241,7 +257,7 @@ private: // If we're not doing anything now, check to see if a new input transfer is due. if (_readState == RDS_IDLE) { - if (currentMicros - _lastDigitalRead > _digitalRefresh && _numDigitalPins>0) { // Delay for digital read refresh + if (_numDigitalPins>0 && currentMicros - _lastDigitalRead > _digitalRefresh) { // Delay for digital read refresh // Issue new read request for digital states. As the request is non-blocking, the buffer has to // be allocated from heap (object state). _readCommandBuffer[0] = EXIORDD; @@ -249,7 +265,7 @@ private: // non-blocking read _lastDigitalRead = currentMicros; _readState = RDS_DIGITAL; - } else if (currentMicros - _lastAnalogueRead > _analogueRefresh && _numAnaloguePins>0) { // Delay for analogue read refresh + } else if (_numAnaloguePins>0 && currentMicros - _lastAnalogueRead > _analogueRefresh) { // Delay for analogue read refresh // Issue new read for analogue input states _readCommandBuffer[0] = EXIORDAN; I2CManager.read(_I2CAddress, _analogueInputBuffer, @@ -364,14 +380,14 @@ private: uint8_t _minorVer = 0; uint8_t _patchVer = 0; - uint8_t* _digitalInputStates; - uint8_t* _analogueInputStates; - uint8_t* _analogueInputBuffer; // buffer for I2C input transfers + uint8_t* _digitalInputStates = NULL; + uint8_t* _analogueInputStates = NULL; + uint8_t* _analogueInputBuffer = NULL; // buffer for I2C input transfers uint8_t _readCommandBuffer[1]; - uint8_t _digitalPinBytes = 0; // Size of allocated memory buffer (may be longer than needed) - uint8_t _analoguePinBytes = 0; // Size of allocated memory buffers (may be longer than needed) - uint8_t* _analoguePinMap; + uint8_t _digitalPinBytes = 0; // Size of allocated memory buffer (may be longer than needed) + uint8_t _analoguePinBytes = 0; // Size of allocated memory buffer (may be longer than needed) + uint8_t* _analoguePinMap = NULL; I2CRB _i2crb; enum {RDS_IDLE, RDS_DIGITAL, RDS_ANALOGUE}; // Read operation states diff --git a/KeywordHasher.h b/KeywordHasher.h new file mode 100644 index 0000000..d232bd2 --- /dev/null +++ b/KeywordHasher.h @@ -0,0 +1,57 @@ +/* + * © 2024 Vincent Hamp and Chris Harlow + * 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 . + */ + + +/* Reader be aware: + This function implements the _hk data type so that a string keyword + is hashed to the same value as the DCCEXParser uses to hash incoming + keywords. + Thus "MAIN"_hk generates exactly the same run time vakue + as const int16_t HASH_KEYWORD_MAIN=11339 +*/ +#ifndef KeywordHAsher_h +#define KeywordHasher_h + +#include +constexpr uint16_t CompiletimeKeywordHasher(const char * sv, uint16_t running=0) { + return (*sv==0) ? running : CompiletimeKeywordHasher(sv+1, + (*sv >= '0' && *sv <= '9') + ? (10*running+*sv-'0') // Numeric hash + : ((running << 5) + running) ^ *sv + ); // +} + +constexpr int16_t operator""_hk(const char * keyword, size_t len) +{ + return (int16_t) CompiletimeKeywordHasher(keyword,len*0); +} + +/* Some historical values for testing: +const int16_t HASH_KEYWORD_MAIN = 11339; +const int16_t HASH_KEYWORD_SLOW = -17209; +const int16_t HASH_KEYWORD_SPEED28 = -17064; +const int16_t HASH_KEYWORD_SPEED128 = 25816; +*/ + +static_assert("MAIN"_hk == 11339,"Keyword hasher error"); +static_assert("SLOW"_hk == -17209,"Keyword hasher error"); +static_assert("SPEED28"_hk == -17064,"Keyword hasher error"); +static_assert("SPEED128"_hk == 25816,"Keyword hasher error"); +#endif \ No newline at end of file diff --git a/TrackManager.cpp b/TrackManager.cpp index 4a501a1..da96832 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -28,6 +28,7 @@ #include "DIAG.h" #include "CommandDistributor.h" #include "DCCEXParser.h" +#include "KeywordHasher.h" // Virtualised Motor shield multi-track hardware Interface #define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++) @@ -35,21 +36,6 @@ FOR_EACH_TRACK(t) \ if (track[t]->getMode()==findmode) \ track[t]->function; -#ifndef DISABLE_PROG -const int16_t HASH_KEYWORD_PROG = -29718; -#endif -const int16_t HASH_KEYWORD_MAIN = 11339; -const int16_t HASH_KEYWORD_OFF = 22479; -const int16_t HASH_KEYWORD_NONE = -26550; -const int16_t HASH_KEYWORD_DC = 2183; -const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity -const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal -const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii. -const int16_t HASH_KEYWORD_AUTO = -5457; -#ifdef BOOSTER_INPUT -const int16_t HASH_KEYWORD_BOOST = 11269; -#endif -const int16_t HASH_KEYWORD_INV = 11857; MotorDriver * TrackManager::track[MAX_TRACKS]; int16_t TrackManager::trackDCAddr[MAX_TRACKS]; @@ -362,38 +348,38 @@ bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[]) } - p[0]-=HASH_KEYWORD_A; // convert A... to 0.... + p[0]-="A"_hk; // convert A... to 0.... if (params>1 && (p[0]<0 || p[0]>=MAX_TRACKS)) return false; - if (params==2 && p[1]==HASH_KEYWORD_MAIN) // <= id MAIN> + if (params==2 && p[1]=="MAIN"_hk) // <= id MAIN> return setTrackMode(p[0],TRACK_MODE_MAIN); #ifndef DISABLE_PROG - if (params==2 && p[1]==HASH_KEYWORD_PROG) // <= id PROG> + if (params==2 && p[1]=="PROG"_hk) // <= id PROG> return setTrackMode(p[0],TRACK_MODE_PROG); #endif - if (params==2 && (p[1]==HASH_KEYWORD_OFF || p[1]==HASH_KEYWORD_NONE)) // <= id OFF> <= id NONE> + if (params==2 && (p[1]=="OFF"_hk || p[1]=="NONE"_hk)) // <= id OFF> <= id NONE> return setTrackMode(p[0],TRACK_MODE_NONE); - if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT> + if (params==2 && p[1]=="EXT"_hk) // <= id EXT> return setTrackMode(p[0],TRACK_MODE_EXT); #ifdef BOOSTER_INPUT - if (params==2 && p[1]==HASH_KEYWORD_BOOST) // <= id BOOST> + if (params==2 && p[1]=="BOOST"_hk) // <= id BOOST> return setTrackMode(p[0],TRACK_MODE_BOOST); #endif - if (params==2 && p[1]==HASH_KEYWORD_AUTO) // <= id AUTO> + if (params==2 && p[1]=="AUTO"_hk) // <= id AUTO> return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_AUTOINV); - if (params==2 && p[1]==HASH_KEYWORD_INV) // <= id AUTO> + if (params==2 && p[1]=="INV"_hk) // <= id AUTO> return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_INV); - if (params==3 && p[1]==HASH_KEYWORD_DC && p[2]>0) // <= id DC cab> + if (params==3 && p[1]=="DC"_hk && p[2]>0) // <= id DC cab> return setTrackMode(p[0],TRACK_MODE_DC,p[2]); - if (params==3 && p[1]==HASH_KEYWORD_DCX && p[2]>0) // <= id DCX cab> + if (params==3 && p[1]=="DCX"_hk && p[2]>0) // <= id DCX cab> return setTrackMode(p[0],TRACK_MODE_DC|TRACK_MODE_INV,p[2]); return false; diff --git a/version.h b/version.h index 909d56b..e4ab5d0 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,16 @@ #include "StringFormatter.h" -#define VERSION "5.2.22ethCdf" +#define VERSION "5.2.27stm32EC" +// 5.2.27 - Bugfix: IOExpander memory allocation +// 5.2.26 - Silently ignore overridden HAL defaults +// - include HAL_IGNORE_DEFAULTS macro in EXRAIL +// 5.2.25 - Fix bug causing after working