mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-24 08:36:14 +01:00
Compare commits
14 Commits
6a2131e934
...
6739f5fcb1
Author | SHA1 | Date | |
---|---|---|---|
|
6739f5fcb1 | ||
|
148d4d30f8 | ||
|
4c89b26c79 | ||
|
d877fc315e | ||
|
6c18226cb5 | ||
|
1c5f299b0e | ||
|
d2d7a5cd16 | ||
|
670645db4b | ||
|
a4eabf235e | ||
|
2cbcecf9e6 | ||
|
26cf28dff7 | ||
|
44351b83ae | ||
|
4e08177b7b | ||
|
04f0f56348 |
|
@ -272,3 +272,11 @@ void CommandDistributor::broadcastRaw(clientType type, char * msg) {
|
||||||
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr) {
|
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr) {
|
||||||
broadcastReply(COMMAND_TYPE, format,trackLetter, dcAddr);
|
broadcastReply(COMMAND_TYPE, format,trackLetter, dcAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CommandDistributor::broadcastRouteState(uint16_t routeId, byte state ) {
|
||||||
|
broadcastReply(COMMAND_TYPE, F("<jB %d %d>\n"),routeId,state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandDistributor::broadcastRouteCaption(uint16_t routeId, const FSH* caption ) {
|
||||||
|
broadcastReply(COMMAND_TYPE, F("<jB %d \"%S\">\n"),routeId,caption);
|
||||||
|
}
|
||||||
|
|
|
@ -58,6 +58,9 @@ public :
|
||||||
static void broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr);
|
static void broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr);
|
||||||
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
|
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
|
||||||
static void forget(byte clientId);
|
static void forget(byte clientId);
|
||||||
|
static void broadcastRouteState(uint16_t routeId,byte state);
|
||||||
|
static void broadcastRouteCaption(uint16_t routeId,const FSH * caption);
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -781,27 +781,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
TrackManager::reportCurrent(stream); // <g limit...limit>
|
TrackManager::reportCurrent(stream); // <g limit...limit>
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case HASH_KEYWORD_A: // <JA> returns automations/routes
|
case HASH_KEYWORD_A: // <JA> intercepted by EXRAIL// <JA> returns automations/routes
|
||||||
StringFormatter::send(stream, F("<jA"));
|
if (params!=1) break; // <JA>
|
||||||
if (params==1) {// <JA>
|
StringFormatter::send(stream, F("<jA>\n"));
|
||||||
#ifdef EXRAIL_ACTIVE
|
|
||||||
SENDFLASHLIST(stream,RMFT2::routeIdList)
|
|
||||||
SENDFLASHLIST(stream,RMFT2::automationIdList)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
else { // <JA id>
|
|
||||||
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;
|
return;
|
||||||
|
|
||||||
case HASH_KEYWORD_R: // <JR> returns rosters
|
case HASH_KEYWORD_R: // <JR> returns rosters
|
||||||
StringFormatter::send(stream, F("<jR"));
|
StringFormatter::send(stream, F("<jR"));
|
||||||
#ifdef EXRAIL_ACTIVE
|
#ifdef EXRAIL_ACTIVE
|
||||||
|
@ -1121,7 +1105,6 @@ bool DCCEXParser::parseS(Print *stream, int16_t params, int16_t p[])
|
||||||
bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
|
bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
|
||||||
if (params == 0)
|
if (params == 0)
|
||||||
return false;
|
return false;
|
||||||
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
|
|
||||||
switch (p[0])
|
switch (p[0])
|
||||||
{
|
{
|
||||||
#ifndef DISABLE_PROG
|
#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])); // <D ACK RETRY 2>
|
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
|
||||||
}
|
}
|
||||||
} else {
|
} 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(F("Ack diag %S"), onOff ? F("on") : F("off"));
|
||||||
Diag::ACK = onOff;
|
Diag::ACK = onOff;
|
||||||
}
|
}
|
||||||
|
@ -1229,7 +1214,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||||
IODevice::DumpAll();
|
IODevice::DumpAll();
|
||||||
else if (p[1] == HASH_KEYWORD_RESET)
|
else if (p[1] == HASH_KEYWORD_RESET)
|
||||||
IODevice::reset();
|
IODevice::reset();
|
||||||
break;
|
return true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
case HASH_KEYWORD_TT: // <D TT vpin steps activity>
|
case HASH_KEYWORD_TT: // <D TT vpin steps activity>
|
||||||
|
|
366
EXRAIL2.cpp
366
EXRAIL2.cpp
|
@ -55,22 +55,6 @@
|
||||||
#include "Turntables.h"
|
#include "Turntables.h"
|
||||||
#include "IODevice.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.
|
// 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.
|
// 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.
|
// and all others will have their locos stopped, then resumed after the pausing task resumes.
|
||||||
byte RMFT2::flags[MAX_FLAGS];
|
byte RMFT2::flags[MAX_FLAGS];
|
||||||
Print * RMFT2::LCCSerial=0;
|
Print * RMFT2::LCCSerial=0;
|
||||||
LookList * RMFT2::sequenceLookup=NULL;
|
LookList * RMFT2::routeLookup=NULL;
|
||||||
LookList * RMFT2::onThrowLookup=NULL;
|
LookList * RMFT2::onThrowLookup=NULL;
|
||||||
LookList * RMFT2::onCloseLookup=NULL;
|
LookList * RMFT2::onCloseLookup=NULL;
|
||||||
LookList * RMFT2::onActivateLookup=NULL;
|
LookList * RMFT2::onActivateLookup=NULL;
|
||||||
|
@ -100,9 +84,8 @@ LookList * RMFT2::onClockLookup=NULL;
|
||||||
LookList * RMFT2::onRotateLookup=NULL;
|
LookList * RMFT2::onRotateLookup=NULL;
|
||||||
#endif
|
#endif
|
||||||
LookList * RMFT2::onOverloadLookup=NULL;
|
LookList * RMFT2::onOverloadLookup=NULL;
|
||||||
|
byte * RMFT2::routeStateArray=nullptr;
|
||||||
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
|
const FSH * * RMFT2::routeCaptionArray=nullptr;
|
||||||
#define SKIPOP progCounter+=3
|
|
||||||
|
|
||||||
// getOperand instance version, uses progCounter from instance.
|
// getOperand instance version, uses progCounter from instance.
|
||||||
uint16_t RMFT2::getOperand(byte n) {
|
uint16_t RMFT2::getOperand(byte n) {
|
||||||
|
@ -120,6 +103,7 @@ uint16_t RMFT2::getOperand(int progCounter,byte n) {
|
||||||
LookList::LookList(int16_t size) {
|
LookList::LookList(int16_t size) {
|
||||||
m_size=size;
|
m_size=size;
|
||||||
m_loaded=0;
|
m_loaded=0;
|
||||||
|
m_chain=nullptr;
|
||||||
if (size) {
|
if (size) {
|
||||||
m_lookupArray=new int16_t[size];
|
m_lookupArray=new int16_t[size];
|
||||||
m_resultArray=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;i<m_size;i++) {
|
for (int16_t i=0;i<m_size;i++) {
|
||||||
if (m_lookupArray[i]==value) return m_resultArray[i];
|
if (m_lookupArray[i]==value) return m_resultArray[i];
|
||||||
}
|
}
|
||||||
|
return m_chain ? m_chain->find(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;i<m_size;i++)
|
||||||
|
if (m_lookupArray[i]==id)
|
||||||
|
RMFT2::startNonRecursiveTask(reason,id,m_resultArray[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LookList::stream(Print * _stream) {
|
||||||
|
for (int16_t i=0;i<m_size;i++) {
|
||||||
|
_stream->print(" ");
|
||||||
|
_stream->print(m_lookupArray[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t LookList::findPosition(int16_t value) {
|
||||||
|
for (int16_t i=0;i<m_size;i++) {
|
||||||
|
if (m_lookupArray[i]==value) return i;
|
||||||
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
int16_t LookList::size() {
|
||||||
|
return m_size;
|
||||||
|
}
|
||||||
|
|
||||||
LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||||
int progCounter;
|
int progCounter;
|
||||||
|
@ -171,7 +182,12 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||||
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
|
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
|
||||||
|
|
||||||
// create lookups
|
// create lookups
|
||||||
sequenceLookup=LookListLoader(OPCODE_ROUTE, OPCODE_AUTOMATION,OPCODE_SEQUENCE);
|
routeLookup=LookListLoader(OPCODE_ROUTE, OPCODE_AUTOMATION);
|
||||||
|
routeLookup->chain(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);
|
onThrowLookup=LookListLoader(OPCODE_ONTHROW);
|
||||||
onCloseLookup=LookListLoader(OPCODE_ONCLOSE);
|
onCloseLookup=LookListLoader(OPCODE_ONCLOSE);
|
||||||
onActivateLookup=LookListLoader(OPCODE_ONACTIVATE);
|
onActivateLookup=LookListLoader(OPCODE_ONACTIVATE);
|
||||||
|
@ -314,238 +330,15 @@ void RMFT2::setTurntableHiddenState(Turntable * tto) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
char RMFT2::getRouteType(int16_t id) {
|
char RMFT2::getRouteType(int16_t id) {
|
||||||
for (int16_t i=0;;i+=2) {
|
int16_t progCounter=routeLookup->find(id);
|
||||||
int16_t rid= GETHIGHFLASHW(routeIdList,i);
|
if (progCounter>=0) {
|
||||||
if (rid==INT16_MAX) break;
|
byte type=GET_OPCODE;
|
||||||
if (rid==id) return 'R';
|
if (type==OPCODE_ROUTE) return 'R';
|
||||||
}
|
if (type==OPCODE_AUTOMATION) return 'A';
|
||||||
for (int16_t i=0;;i+=2) {
|
|
||||||
int16_t rid= GETHIGHFLASHW(automationIdList,i);
|
|
||||||
if (rid==INT16_MAX) break;
|
|
||||||
if (rid==id) return 'A';
|
|
||||||
}
|
}
|
||||||
return 'X';
|
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) { // <D EXRAIL ON/OFF>
|
|
||||||
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) { //<L> 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("<LS x%h>\n"),getOperand(progCounter,0));
|
|
||||||
if (opcode==OPCODE_LCCX) { // long form LCC
|
|
||||||
StringFormatter::send(stream,F("<LS x%h%h%h%h>\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("<LL %d x%h%h%h:%h>\n"),
|
|
||||||
eventIndex,
|
|
||||||
getOperand(progCounter,1),
|
|
||||||
getOperand(progCounter,2),
|
|
||||||
getOperand(progCounter,3),
|
|
||||||
getOperand(progCounter,0)
|
|
||||||
);
|
|
||||||
eventIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StringFormatter::send(stream,F("<LR>\n")); // Ready to rumble
|
|
||||||
opcode=0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (paramCount==1) { // <L eventid> 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("<X>\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<MAX_FLAGS; id++) {
|
|
||||||
byte flag=flags[id];
|
|
||||||
if (flag & ~TASK_FLAG & ~SIGNAL_MASK) { // not interested in TASK_FLAG only. Already shown above
|
|
||||||
StringFormatter::send(stream,F("\nflags[%d] "),id);
|
|
||||||
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
|
|
||||||
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (compileFeatures & FEATURE_SIGNAL) {
|
|
||||||
// do the signals
|
|
||||||
// flags[n] represents the state of the nth signal in the table
|
|
||||||
for (int sigslot=0;;sigslot++) {
|
|
||||||
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
|
|
||||||
if (sigid==0) break; // end of signal list
|
|
||||||
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
|
|
||||||
StringFormatter::send(stream,F("\n%S[%d]"),
|
|
||||||
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
|
|
||||||
sigid & SIGNAL_ID_MASK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StringFormatter::send(stream,F(" *>\n"));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
switch (p[0]) {
|
|
||||||
case HASH_KEYWORD_PAUSE: // </ 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: // </ 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: // </ START [cab] route >
|
|
||||||
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) {
|
RMFT2::RMFT2(int progCtr) {
|
||||||
progCounter=progCtr;
|
progCounter=progCtr;
|
||||||
|
@ -594,7 +387,7 @@ RMFT2::~RMFT2() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void RMFT2::createNewTask(int route, uint16_t cab) {
|
void RMFT2::createNewTask(int route, uint16_t cab) {
|
||||||
int pc=sequenceLookup->find(route);
|
int pc=routeLookup->find(route);
|
||||||
if (pc<0) return;
|
if (pc<0) return;
|
||||||
RMFT2* task=new RMFT2(pc);
|
RMFT2* task=new RMFT2(pc);
|
||||||
task->loco=cab;
|
task->loco=cab;
|
||||||
|
@ -995,7 +788,7 @@ void RMFT2::loop2() {
|
||||||
}
|
}
|
||||||
|
|
||||||
case OPCODE_FOLLOW:
|
case OPCODE_FOLLOW:
|
||||||
progCounter=sequenceLookup->find(operand);
|
progCounter=routeLookup->find(operand);
|
||||||
if (progCounter<0) kill(F("FOLLOW unknown"), operand);
|
if (progCounter<0) kill(F("FOLLOW unknown"), operand);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -1005,7 +798,7 @@ void RMFT2::loop2() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
callStack[stackDepth++]=progCounter+3;
|
callStack[stackDepth++]=progCounter+3;
|
||||||
progCounter=sequenceLookup->find(operand);
|
progCounter=routeLookup->find(operand);
|
||||||
if (progCounter<0) kill(F("CALL unknown"),operand);
|
if (progCounter<0) kill(F("CALL unknown"),operand);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -1068,7 +861,7 @@ void RMFT2::loop2() {
|
||||||
|
|
||||||
case OPCODE_START:
|
case OPCODE_START:
|
||||||
{
|
{
|
||||||
int newPc=sequenceLookup->find(operand);
|
int newPc=routeLookup->find(operand);
|
||||||
if (newPc<0) break;
|
if (newPc<0) break;
|
||||||
new RMFT2(newPc);
|
new RMFT2(newPc);
|
||||||
}
|
}
|
||||||
|
@ -1076,7 +869,7 @@ void RMFT2::loop2() {
|
||||||
|
|
||||||
case OPCODE_SENDLOCO: // cab, route
|
case OPCODE_SENDLOCO: // cab, route
|
||||||
{
|
{
|
||||||
int newPc=sequenceLookup->find(getOperand(1));
|
int newPc=routeLookup->find(getOperand(1));
|
||||||
if (newPc<0) break;
|
if (newPc<0) break;
|
||||||
RMFT2* newtask=new RMFT2(newPc); // create new task
|
RMFT2* newtask=new RMFT2(newPc); // create new task
|
||||||
newtask->loco=operand;
|
newtask->loco=operand;
|
||||||
|
@ -1130,6 +923,15 @@ void RMFT2::loop2() {
|
||||||
case OPCODE_PRINT:
|
case OPCODE_PRINT:
|
||||||
printMessage(operand);
|
printMessage(operand);
|
||||||
break;
|
break;
|
||||||
|
case OPCODE_ROUTE_HIDDEN:
|
||||||
|
manageRouteState(operand,2);
|
||||||
|
break;
|
||||||
|
case OPCODE_ROUTE_INACTIVE:
|
||||||
|
manageRouteState(operand,0);
|
||||||
|
break;
|
||||||
|
case OPCODE_ROUTE_ACTIVE:
|
||||||
|
manageRouteState(operand,1);
|
||||||
|
break;
|
||||||
|
|
||||||
case OPCODE_ROUTE:
|
case OPCODE_ROUTE:
|
||||||
case OPCODE_AUTOMATION:
|
case OPCODE_AUTOMATION:
|
||||||
|
@ -1218,9 +1020,9 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||||
|
|
||||||
// Schedule any event handler for this signal change.
|
// Schedule any event handler for this signal change.
|
||||||
// Thjis will work even without a signal definition.
|
// Thjis will work even without a signal definition.
|
||||||
if (rag==SIGNAL_RED) handleEvent(F("RED"),onRedLookup,id);
|
if (rag==SIGNAL_RED) onRedLookup->handleEvent(F("RED"),id);
|
||||||
else if (rag==SIGNAL_GREEN) handleEvent(F("GREEN"), onGreenLookup,id);
|
else if (rag==SIGNAL_GREEN) onGreenLookup->handleEvent(F("GREEN"),id);
|
||||||
else handleEvent(F("AMBER"), onAmberLookup,id);
|
else onAmberLookup->handleEvent(F("AMBER"),id);
|
||||||
|
|
||||||
int16_t sigslot=getSignalSlot(id);
|
int16_t sigslot=getSignalSlot(id);
|
||||||
if (sigslot<0) return;
|
if (sigslot<0) return;
|
||||||
|
@ -1289,26 +1091,26 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||||
|
|
||||||
void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) {
|
void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) {
|
||||||
// Hunt for an ONTHROW/ONCLOSE for this turnout
|
// Hunt for an ONTHROW/ONCLOSE for this turnout
|
||||||
if (closed) handleEvent(F("CLOSE"),onCloseLookup,turnoutId);
|
if (closed) onCloseLookup->handleEvent(F("CLOSE"),turnoutId);
|
||||||
else handleEvent(F("THROW"),onThrowLookup,turnoutId);
|
else onThrowLookup->handleEvent(F("THROW"),turnoutId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void RMFT2::activateEvent(int16_t addr, bool activate) {
|
void RMFT2::activateEvent(int16_t addr, bool activate) {
|
||||||
// Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory
|
// Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory
|
||||||
if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr);
|
if (activate) onActivateLookup->handleEvent(F("ACTIVATE"),addr);
|
||||||
else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr);
|
else onDeactivateLookup->handleEvent(F("DEACTIVATE"),addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RMFT2::changeEvent(int16_t vpin, bool change) {
|
void RMFT2::changeEvent(int16_t vpin, bool change) {
|
||||||
// Hunt for an ONCHANGE for this sensor
|
// 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
|
#ifndef IO_NO_HAL
|
||||||
void RMFT2::rotateEvent(int16_t turntableId, bool change) {
|
void RMFT2::rotateEvent(int16_t turntableId, bool change) {
|
||||||
// Hunt or an ONROTATE for this turntable
|
// Hunt or an ONROTATE for this turntable
|
||||||
if (change) handleEvent(F("ROTATE"),onRotateLookup,turntableId);
|
if (change) onRotateLookup->handleEvent(F("ROTATE"),turntableId);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -1317,8 +1119,8 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) {
|
||||||
if (Diag::CMD)
|
if (Diag::CMD)
|
||||||
DIAG(F("Looking for clock event at : %d"), clocktime);
|
DIAG(F("Looking for clock event at : %d"), clocktime);
|
||||||
if (change) {
|
if (change) {
|
||||||
handleEvent(F("CLOCK"),onClockLookup,clocktime);
|
onClockLookup->handleEvent(F("CLOCK"),clocktime);
|
||||||
handleEvent(F("CLOCK"),onClockLookup,25*60+clocktime%60);
|
onClockLookup->handleEvent(F("CLOCK"),25*60+clocktime%60);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1327,16 +1129,10 @@ void RMFT2::powerEvent(int16_t track, bool overload) {
|
||||||
if (Diag::CMD)
|
if (Diag::CMD)
|
||||||
DIAG(F("Looking for Power event on track : %c"), track);
|
DIAG(F("Looking for Power event on track : %c"), track);
|
||||||
if (overload) {
|
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) {
|
void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc) {
|
||||||
// Check we dont already have a task running this handler
|
// Check we dont already have a task running this handler
|
||||||
RMFT2 * task=loopTask;
|
RMFT2 * task=loopTask;
|
||||||
|
@ -1453,3 +1249,29 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||||
break;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
24
EXRAIL2.h
24
EXRAIL2.h
|
@ -68,6 +68,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||||
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_WAITFORTT,
|
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_WAITFORTT,
|
||||||
OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC,
|
OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC,
|
||||||
OPCODE_ONOVERLOAD,
|
OPCODE_ONOVERLOAD,
|
||||||
|
OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN,
|
||||||
|
|
||||||
// OPcodes below this point are skip-nesting IF operations
|
// OPcodes below this point are skip-nesting IF operations
|
||||||
// placed here so that they may be skipped as a group
|
// 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_SIGNAL= 0x80;
|
||||||
static const byte FEATURE_LCC = 0x40;
|
static const byte FEATURE_LCC = 0x40;
|
||||||
static const byte FEATURE_ROSTER= 0x20;
|
static const byte FEATURE_ROSTER= 0x20;
|
||||||
|
static const byte FEATURE_ROUTESTATE= 0x10;
|
||||||
|
|
||||||
|
|
||||||
// Flag bits for status of hardware and TPL
|
// Flag bits for status of hardware and TPL
|
||||||
|
@ -119,13 +121,20 @@ enum thrunger: byte {
|
||||||
class LookList {
|
class LookList {
|
||||||
public:
|
public:
|
||||||
LookList(int16_t size);
|
LookList(int16_t size);
|
||||||
|
void chain(LookList* chainTo);
|
||||||
void add(int16_t lookup, int16_t result);
|
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:
|
private:
|
||||||
int16_t m_size;
|
int16_t m_size;
|
||||||
int16_t m_loaded;
|
int16_t m_loaded;
|
||||||
int16_t * m_lookupArray;
|
int16_t * m_lookupArray;
|
||||||
int16_t * m_resultArray;
|
int16_t * m_resultArray;
|
||||||
|
LookList* m_chain;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RMFT2 {
|
class RMFT2 {
|
||||||
|
@ -159,6 +168,7 @@ class LookList {
|
||||||
static const FSH * getRosterFunctions(int16_t id);
|
static const FSH * getRosterFunctions(int16_t id);
|
||||||
static const FSH * getTurntableDescription(int16_t id);
|
static const FSH * getTurntableDescription(int16_t id);
|
||||||
static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId);
|
static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId);
|
||||||
|
static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||||
|
@ -176,9 +186,7 @@ private:
|
||||||
#endif
|
#endif
|
||||||
static LookList* LookListLoader(OPCODE op1,
|
static LookList* LookListLoader(OPCODE op1,
|
||||||
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
|
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 uint16_t getOperand(int progCounter,byte n);
|
||||||
static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc);
|
|
||||||
static RMFT2 * loopTask;
|
static RMFT2 * loopTask;
|
||||||
static RMFT2 * pausingTask;
|
static RMFT2 * pausingTask;
|
||||||
void delayMe(long millisecs);
|
void delayMe(long millisecs);
|
||||||
|
@ -198,7 +206,7 @@ private:
|
||||||
static const HIGHFLASH int16_t SignalDefinitions[];
|
static const HIGHFLASH int16_t SignalDefinitions[];
|
||||||
static byte flags[MAX_FLAGS];
|
static byte flags[MAX_FLAGS];
|
||||||
static Print * LCCSerial;
|
static Print * LCCSerial;
|
||||||
static LookList * sequenceLookup;
|
static LookList * routeLookup;
|
||||||
static LookList * onThrowLookup;
|
static LookList * onThrowLookup;
|
||||||
static LookList * onCloseLookup;
|
static LookList * onCloseLookup;
|
||||||
static LookList * onActivateLookup;
|
static LookList * onActivateLookup;
|
||||||
|
@ -216,6 +224,10 @@ private:
|
||||||
static const int countLCCLookup;
|
static const int countLCCLookup;
|
||||||
static int onLCCLookup[];
|
static int onLCCLookup[];
|
||||||
static const byte compileFeatures;
|
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
|
// Local variables - exist for each instance/task
|
||||||
RMFT2 *next; // loop chain
|
RMFT2 *next; // loop chain
|
||||||
|
@ -237,4 +249,8 @@ private:
|
||||||
byte stackDepth;
|
byte stackDepth;
|
||||||
int callStack[MAX_STACK_DEPTH];
|
int callStack[MAX_STACK_DEPTH];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
|
||||||
|
#define SKIPOP progCounter+=3
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -126,6 +126,10 @@
|
||||||
#undef ROTATE
|
#undef ROTATE
|
||||||
#undef ROTATE_DCC
|
#undef ROTATE_DCC
|
||||||
#undef ROUTE
|
#undef ROUTE
|
||||||
|
#undef ROUTE_ACTIVE
|
||||||
|
#undef ROUTE_INACTIVE
|
||||||
|
#undef ROUTE_HIDDEN
|
||||||
|
#undef ROUTE_CAPTION
|
||||||
#undef SENDLOCO
|
#undef SENDLOCO
|
||||||
#undef SEQUENCE
|
#undef SEQUENCE
|
||||||
#undef SERIAL
|
#undef SERIAL
|
||||||
|
@ -267,6 +271,10 @@
|
||||||
#define ROTATE_DCC(turntable_id,position)
|
#define ROTATE_DCC(turntable_id,position)
|
||||||
#define ROSTER(cab,name,funcmap...)
|
#define ROSTER(cab,name,funcmap...)
|
||||||
#define ROUTE(id,description)
|
#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 SENDLOCO(cab,route)
|
||||||
#define SEQUENCE(id)
|
#define SEQUENCE(id)
|
||||||
#define SERIAL(msg)
|
#define SERIAL(msg)
|
||||||
|
|
291
EXRAIL2Parser.cpp
Normal file
291
EXRAIL2Parser.cpp
Normal file
|
@ -0,0 +1,291 @@
|
||||||
|
/*
|
||||||
|
* © 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS file is an extension of the RMFT2 class
|
||||||
|
// normally found in EXRAIL2.cpp
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#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) { // <D EXRAIL ON/OFF>
|
||||||
|
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) { //<L> 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("<LS x%h>\n"),getOperand(progCounter,0));
|
||||||
|
if (opcode==OPCODE_LCCX) { // long form LCC
|
||||||
|
StringFormatter::send(stream,F("<LS x%h%h%h%h>\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("<LL %d x%h%h%h:%h>\n"),
|
||||||
|
eventIndex,
|
||||||
|
getOperand(progCounter,1),
|
||||||
|
getOperand(progCounter,2),
|
||||||
|
getOperand(progCounter,3),
|
||||||
|
getOperand(progCounter,0)
|
||||||
|
);
|
||||||
|
eventIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringFormatter::send(stream,F("<LR>\n")); // Ready to rumble
|
||||||
|
opcode=0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (paramCount==1) { // <L eventid> 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
|
||||||
|
if (paramCount<1) return;
|
||||||
|
switch(p[0]) {
|
||||||
|
case HASH_KEYWORD_A: // <JA> returns automations/routes
|
||||||
|
if (paramCount==1) {// <JA>
|
||||||
|
StringFormatter::send(stream, F("<jA"));
|
||||||
|
routeLookup->stream(stream);
|
||||||
|
StringFormatter::send(stream, F(">\n"));
|
||||||
|
opcode=0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (paramCount==2) { // <JA id>
|
||||||
|
uint16_t id=p[1];
|
||||||
|
StringFormatter::send(stream,F("<jA %d %c \"%S\">\n"),
|
||||||
|
id, getRouteType(id), getRouteDescription(id));
|
||||||
|
|
||||||
|
if (compileFeatures & FEATURE_ROUTESTATE) {
|
||||||
|
// Send any non-default button states or captions
|
||||||
|
int16_t statePos=routeLookup->findPosition(id);
|
||||||
|
if (statePos>=0) {
|
||||||
|
if (routeStateArray[statePos])
|
||||||
|
StringFormatter::send(stream,F("<jB %d %d>\n"), id, routeStateArray[statePos]);
|
||||||
|
if (routeCaptionArray[statePos])
|
||||||
|
StringFormatter::send(stream,F("<jB %d \"%S\">\n"), id,routeCaptionArray[statePos]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opcode=0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
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<MAX_FLAGS; id++) {
|
||||||
|
byte flag=flags[id];
|
||||||
|
if (flag & ~TASK_FLAG & ~SIGNAL_MASK) { // not interested in TASK_FLAG only. Already shown above
|
||||||
|
StringFormatter::send(stream,F("\nflags[%d] "),id);
|
||||||
|
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
|
||||||
|
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compileFeatures & FEATURE_SIGNAL) {
|
||||||
|
// do the signals
|
||||||
|
// flags[n] represents the state of the nth signal in the table
|
||||||
|
for (int sigslot=0;;sigslot++) {
|
||||||
|
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
|
||||||
|
if (sigid==0) break; // end of signal list
|
||||||
|
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
|
||||||
|
StringFormatter::send(stream,F("\n%S[%d]"),
|
||||||
|
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
|
||||||
|
sigid & SIGNAL_ID_MASK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringFormatter::send(stream,F(" *>\n"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
switch (p[0]) {
|
||||||
|
case HASH_KEYWORD_PAUSE: // </ 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: // </ 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: // </ START [cab] route >
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -102,6 +102,14 @@ void exrailHalSetup() {
|
||||||
#define LCCX(senderid,eventid) | FEATURE_LCC
|
#define LCCX(senderid,eventid) | FEATURE_LCC
|
||||||
#undef ONLCC
|
#undef ONLCC
|
||||||
#define ONLCC(senderid,eventid) | FEATURE_LCC
|
#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
|
const byte RMFT2::compileFeatures = 0
|
||||||
#include "myAutomation.h"
|
#include "myAutomation.h"
|
||||||
|
@ -153,6 +161,12 @@ const int StringMacroTracker1=__COUNTER__;
|
||||||
#define PRINT(msg) THRUNGE(msg,thrunge_print)
|
#define PRINT(msg) THRUNGE(msg,thrunge_print)
|
||||||
#undef LCN
|
#undef LCN
|
||||||
#define LCN(msg) THRUNGE(msg,thrunge_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
|
#undef SERIAL
|
||||||
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
|
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
|
||||||
#undef SERIAL1
|
#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),
|
#define ROTATE_DCC(id,position) OPCODE_ROTATE,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(0),
|
||||||
#endif
|
#endif
|
||||||
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
|
#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 SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
|
||||||
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
|
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
|
||||||
#define SERIAL(msg) PRINT(msg)
|
#define SERIAL(msg) PRINT(msg)
|
||||||
|
|
|
@ -676,8 +676,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||||
power_sample_overload_wait *= 2;
|
power_sample_overload_wait *= 2;
|
||||||
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
|
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
|
||||||
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
|
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
|
||||||
|
#ifdef EXRAIL_ACTIVE
|
||||||
DIAG(F("Calling EXRAIL"));
|
DIAG(F("Calling EXRAIL"));
|
||||||
RMFT2::powerEvent(trackno, true); // Tell EXRAIL we have an overload
|
RMFT2::powerEvent(trackno, true); // Tell EXRAIL we have an overload
|
||||||
|
#endif
|
||||||
// power on test
|
// power on test
|
||||||
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
|
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
|
||||||
setPower(POWERMODE::ALERT);
|
setPower(POWERMODE::ALERT);
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
#include "StringFormatter.h"
|
#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.19 - Only flag 2.2.0.0-dev as broken, not 2.2.0.0
|
||||||
// 5.1.18 - TURNOUTL bugfix
|
// 5.1.18 - TURNOUTL bugfix
|
||||||
// 5.1.17 - Divide out C for config and D for diag commands
|
// 5.1.17 - Divide out C for config and D for diag commands
|
||||||
|
|
Loading…
Reference in New Issue
Block a user