diff --git a/CamCommands.h b/CamCommands.h
new file mode 100644
index 0000000..585f7e3
--- /dev/null
+++ b/CamCommands.h
@@ -0,0 +1,111 @@
+/*
+ * © 2023-2025, Barry Daniel
+ * © 2025 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 .
+ */
+
+//sensorCAM parser.cpp version 3.06 Jan 2025
+#include "DCCEXParser.h"
+#include "CamParser.h"
+#include "FSH.h"
+
+void camsend(byte camop,int16_t param1,int16_t param3) {
+ DIAG(F("CamParser: %d %c %d %d"),CAMBaseVpin,camop,param1,param3);
+ IODevice::writeAnalogue(CAMBaseVpin,param1,camop,param3);
+}
+
+
+// The CAMVPINS array will be filled by IO_EXSensorCam HAL drivers calling
+// the CamParser::addVpin() function.
+// The CAMBaseVpin is the one to be used when commands are given without a vpin.
+VPIN CamParser::CAMBaseVpin = 0; // no vpins yet known
+VPIN CamParser::CAMVPINS[] = {0,0,0,0}; // determines max # CAM's
+int CamParser::vpcount=sizeof(CAMVPINS)/sizeof(CAMVPINS[0]);
+
+
+ ZZ(N) // lists current base vpin and others available
+ DIAG(F("Cam base vpin:%d"),CAMBaseVpin);
+ for (auto i=0;i=100 || (pin=0))
+ CAMBaseVpin=(pin>=100? pin:CAMVPINS[pin];
+ DIAG(F("CAM base Vpin:%d "),pin,CAMBaseVpin);
+
+ ZZ(N,camop,p1) //send camop p1
+ CHECK(STRCHR_P((const char *)F("ABFHILMNOPQRSTUV"),camop))
+ camsend(camop,p1,999);
+
+ ZZ(N,I,p1,p2) //send camop p1 p2
+ camsend('I',p1,p2);
+ ZZ(N,J,p1,p2) //send camop p1 p2
+ camsend('J',p1,p2);
+ ZZ(N,M,p1,p2) //send camop p1 p2
+ camsend('M',p1,p2);
+ ZZ(N,N,p1,p2) //send camop p1 p2
+ camsend('N',p1,p2);
+ ZZ(N,T,p1,p2) //send camop p1 p2
+ camsend('T',p1,p2);
+ ZZ(N,vpin,rowY,colX) //send 0x80 row col
+ auto hold=CAMBaseVpin;
+ CAMBaseVpin=vpin;
+ camsend(0x80,rowY,colX);
+ CAMBaseVpin=hold;
+
+ ZZ(N,A,id,row,col)
+ CHECK(col<=316 && col>=0)
+ CHECK(row<=236 && row>=0)
+ CHECK(id<=97 && id >=0)
+ auto hold=CAMBaseVpin;
+ CAMBaseVpin=CAMBaseVpin + (id/10)*8 + id%10; //translate from pseudo octal
+ camsend(0x80,row,col)
+ CAMBaseVpin=hold;
+
+void CamParser::addVpin(VPIN pin) {
+ // called by IO_EXSensorCam starting up a camera on a vpin
+ byte slot=255;
+ for (auto i=0;i 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
-
- switch(opcode) {
-
- case 'D':
- if (p[0]=="EXRAIL"_hk) { //
- diag = paramCount==2 && (p[1]=="ON"_hk || p[1]==1);
- opcode=0;
- }
- break;
-
- case '/': // New EXRAIL command
- if (parseSlash(stream,paramCount,p)) opcode=0;
- break;
-
- case 'A': //
- if (paramCount!=2) break;
- // Ask exrail if this is just changing the aspect on a
- // predefined DCCX_SIGNAL. Because this will handle all
- // the IFRED and ONRED type issues at the same time.
- if (signalAspectEvent(p[0],p[1])) opcode=0; // all done
- break;
-
- case 'L':
- // This entire code block is compiled out if LLC macros not used
- if (!(compileFeatures & FEATURE_LCC)) return;
- static int lccProgCounter=0;
- static int lccEventIndex=0;
-
- if (paramCount==0) { // LCC adapter introducing self
- LCCSerial=stream; // now we know where to send events we raise
- opcode=0; // flag command as intercepted
-
- // loop through all possible sent/waited events
- for (int progCounter=lccProgCounter;; SKIPOP) {
- byte exrailOpcode=GET_OPCODE;
- switch (exrailOpcode) {
- case OPCODE_ENDEXRAIL:
- stream->print(F("\n")); // ready to roll
- lccProgCounter=0; // allow a second pass
- lccEventIndex=0;
- return;
-
- case OPCODE_LCC:
- StringFormatter::send(stream,F("\n"),getOperand(progCounter,0));
- SKIPOP;
- lccProgCounter=progCounter;
- return;
-
- case OPCODE_LCCX: // long form LCC
- StringFormatter::send(stream,F("\n"),
- getOperand(progCounter,1),
- getOperand(progCounter,2),
- getOperand(progCounter,3),
- getOperand(progCounter,0)
- );
- SKIPOP;SKIPOP;SKIPOP;SKIPOP;
- lccProgCounter=progCounter;
- return;
-
- case OPCODE_ACON: // CBUS ACON
- case OPCODE_ACOF: // CBUS ACOF
- StringFormatter::send(stream,F("\n"),
- exrailOpcode==OPCODE_ACOF?'1':'0',
- getOperand(progCounter,0),getOperand(progCounter,1));
- SKIPOP;SKIPOP;
- lccProgCounter=progCounter;
- return;
-
- // we stream the hex events we wish to listen to
- // and at the same time build the event index looku.
-
- case OPCODE_ONLCC:
- StringFormatter::send(stream,F("\n"),
- lccEventIndex,
- getOperand(progCounter,1),
- getOperand(progCounter,2),
- getOperand(progCounter,3),
- getOperand(progCounter,0)
- );
- SKIPOP;SKIPOP;SKIPOP;SKIPOP;
- // start on handler at next
- onLCCLookup[lccEventIndex]=progCounter;
- lccEventIndex++;
- lccProgCounter=progCounter;
- return;
-
- case OPCODE_ONACON:
- case OPCODE_ONACOF:
- StringFormatter::send(stream,F("\n"),
- lccEventIndex,
- exrailOpcode==OPCODE_ONACOF?'1':'0',
- getOperand(progCounter,0),getOperand(progCounter,1)
- );
- SKIPOP;SKIPOP;
- // start on handler at next
- onLCCLookup[lccEventIndex]=progCounter;
- lccEventIndex++;
- lccProgCounter=progCounter;
- return;
-
- default:
- break;
- }
- }
- }
- if (paramCount==1) { // LCC event arrived from adapter
- int16_t eventid=p[0];
- bool 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 "A"_hk: // returns automations/routes
- if (paramCount==1) {//
- StringFormatter::send(stream, F("stream(stream);
- StringFormatter::send(stream, F(">\n"));
- opcode=0;
- return;
- }
- if (paramCount==2) { //
- int16_t id=p[1];
- StringFormatter::send(stream,F("\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("\n"), id, routeStateArray[statePos]);
- if (routeCaptionArray[statePos])
- StringFormatter::send(stream,F("\n"), id,routeCaptionArray[statePos]);
- }
- }
- opcode=0;
- return;
- }
- break;
-
-
- case 'K': // Block enter
- case 'k': // Block exit
- if (paramCount!=2) break;
- blockEvent(p[0],p[1],opcode=='K');
- opcode=0;
- break;
-
- default: // other commands pass through
- break;
- }
-}
+ if (parseCommands(stream,opcode,paramCount,p)) opcode='\0'; // command was handled by parseCommands()
}
-bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
-
- if (paramCount==0) { // STATUS
- StringFormatter::send(stream, F("<* EXRAIL STATUS"));
- RMFT2 * task=loopTask;
+void RMFT2::streamStatus(Print * stream) {
+ REPLY("<* EXRAIL STATUS")
+ auto task=loopTask;
while(task) {
if ((compileFeatures & FEATURE_BLINK)
&& (task->blinkState==blink_high || task->blinkState==blink_low)) {
- StringFormatter::send(stream,F("\nID=%d,PC=%d,BLINK=%d"),
- (int)(task->taskId),task->progCounter,task->blinkPin
- );
+ REPLY("\nID=%d,PC=%d,BLINK=%d",(int)(task->taskId),task->progCounter,task->blinkPin)
}
else {
- StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d %c"),
- (int)(task->taskId),task->progCounter,task->loco,
- task->invert?'I':' '
- );
+ REPLY("\nID=%d,PC=%d,LOCO=%d %c",(int)(task->taskId),task->progCounter,task->loco,task->invert?'I':' ')
}
task=task->next;
if (task==loopTask) break;
@@ -221,9 +58,9 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
for (int id=0;id\n"));
- return true;
- }
- switch (p[0]) {
- case "PAUSE"_hk: // PAUSE>
- if (paramCount!=1) return false;
- { // pause all tasks
+ REPLY(" *>\n")
+}
+//
+bool RMFT2::streamLCC(Print * stream) {
+ if (!(compileFeatures & FEATURE_LCC)) return false;
+ // This function is called to stream the LCC commands to the LCC adapter.
+ LCCSerial=stream; // now we know where to send events we raise
+
+ static int lccProgCounter=0;
+ static int lccEventIndex=0;
+for (int progCounter=lccProgCounter;; SKIPOP) {
+ byte exrailOpcode=GET_OPCODE;
+ switch (exrailOpcode) {
+ case OPCODE_ENDEXRAIL:
+ stream->print(F("\n")); // ready to roll
+ lccProgCounter=0; // allow a second pass
+ lccEventIndex=0;
+ return true;
+
+ case OPCODE_LCC:
+ StringFormatter::send(stream,F("\n"),getOperand(progCounter,0));
+ SKIPOP;
+ lccProgCounter=progCounter;
+ return true;
+
+ case OPCODE_LCCX: // long form LCC
+ StringFormatter::send(stream,F("\n"),
+ getOperand(progCounter,1),
+ getOperand(progCounter,2),
+ getOperand(progCounter,3),
+ getOperand(progCounter,0)
+ );
+ SKIPOP;SKIPOP;SKIPOP;SKIPOP;
+ lccProgCounter=progCounter;
+ return true;
+
+ case OPCODE_ACON: // CBUS ACON
+ case OPCODE_ACOF: // CBUS ACOF
+ StringFormatter::send(stream,F("\n"),
+ exrailOpcode==OPCODE_ACOF?'1':'0',
+ getOperand(progCounter,0),getOperand(progCounter,1));
+ SKIPOP;SKIPOP;
+ lccProgCounter=progCounter;
+ return true;
+
+// we stream the hex events we wish to listen to
+// and at the same time build the event index looku.
+
+ case OPCODE_ONLCC:
+ StringFormatter::send(stream,F("\n"),
+ lccEventIndex,
+ getOperand(progCounter,1),
+ getOperand(progCounter,2),
+ getOperand(progCounter,3),
+ getOperand(progCounter,0)
+ );
+ SKIPOP;SKIPOP;SKIPOP;SKIPOP;
+ // start on handler at next
+ onLCCLookup[lccEventIndex]=progCounter;
+ lccEventIndex++;
+ lccProgCounter=progCounter;
+ return true;
+
+ case OPCODE_ONACON:
+ case OPCODE_ONACOF:
+ StringFormatter::send(stream,F("\n"),
+ lccEventIndex,
+ exrailOpcode==OPCODE_ONACOF?'1':'0',
+ getOperand(progCounter,0),getOperand(progCounter,1)
+ );
+ SKIPOP;SKIPOP;
+ // start on handler at next
+ onLCCLookup[lccEventIndex]=progCounter;
+ lccEventIndex++;
+ lccProgCounter=progCounter;
+ return true;
+
+ default:
+ break;
+ }
+}
+return true;
+}
+
+bool RMFT2::parseCommands(Print * stream, byte opcode, byte params, int16_t p[]) {
+
+ ZZBEGIN
+ ZZ(D,EXRAIL,ON) diag=1; // - turn on diagnostics
+ ZZ(D,EXRAIL,OFF) diag=0; // - turn off diagnostics
+ ZZ(D,EXRAIL,onoff) diag=onoff;
+ ZZ(A,address,aspect) // Aspect may intercept or be left for normal parse
+ return signalAspectEvent(address,aspect);
+ ZZ(L) //LCC adapter introducing self
+ CHECK(streamLCC(stream))
+ ZZ(L,eventid) // loop through all possible sent/waited events
+ CHECK(eventid>=0 && eventidstream(stream);
+ REPLY(">\n")
+ ZZ(J,A,id) REPLY("\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])
+ REPLY("\n", id, routeStateArray[statePos]);
+ if (routeCaptionArray[statePos])
+ REPLY("\n", id,routeCaptionArray[statePos]);
+ }
+ }
+ ZZ(K,blockid,loco) blockEvent(blockid,loco,true);
+ ZZ(k,blockid,loco) blockEvent(blockid,loco,false);
+ ZZ(/) streamStatus(stream);
+ ZZ(/,PAUSE)
+ // pause all tasks
RMFT2 * task=loopTask;
while(task) {
task->pause();
task=task->next;
if (task==loopTask) break;
}
- }
- DCC::estopAll(); // pause all locos on the track
- pausingTask=(RMFT2 *)1; // Impossible task address
- return true;
-
- case "RESUME"_hk: // RESUME>
- if (paramCount!=1) return false;
- pausingTask=NULL;
- { // resume all tasks
+ DCC::estopAll(); // pause all locos on the track
+ pausingTask=(RMFT2 *)1; // Impossible task address
+
+ ZZ(/,RESUME)
+ pausingTask=NULL;
RMFT2 * task=loopTask;
while(task) {
task->resume();
task=task->next;
if (task==loopTask) break;
}
- }
- return true;
-
-
- case "START"_hk: // 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;
- new RMFT2(pc,cab);
- }
- return true;
-
- default:
- break;
- }
+ ZZ(/,START,route)
+ auto pc=routeLookup->find(route);
+ CHECK(pc>=0)
+ new RMFT2(pc,0); // no cab for route start
- // check KILL ALL here, otherwise the next validation confuses ALL with a flag
- if (p[0]=="KILL"_hk && p[1]=="ALL"_hk) {
- 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 "KILL"_hk: // Kill taskid|ALL
- {
- if ( p[1]<0 || p[1]>=MAX_FLAGS) return false;
- RMFT2 * task=loopTask;
- while(task) {
- if (task->taskId==p[1]) {
+ ZZ(/,START,cab,route)
+ auto pc=routeLookup->find(route);
+ CHECK(pc>=0)
+ new RMFT2(pc,cab); // no cab for route start
+
+ ZZ(/,KILL,ALL) while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
+ ZZ(/,KILL,taskid)
+ CHECK(taskid>=0 && taskidtaskId==taskid) {
+ found=true;
task->kill(F("KILL"));
- return true;
+ break;
}
task=task->next;
if (task==loopTask) break;
}
- }
- return false;
-
- case "RESERVE"_hk: // force reserve a section
- return setFlag(p[1],SECTION_FLAG);
-
- case "FREE"_hk: // force free a section
- return setFlag(p[1],0,SECTION_FLAG);
-
- case "LATCH"_hk:
- return setFlag(p[1], LATCH_FLAG);
-
- case "UNLATCH"_hk:
- return setFlag(p[1], 0, LATCH_FLAG);
-
- case "RED"_hk:
- doSignal(p[1],SIGNAL_RED);
- return true;
-
- case "AMBER"_hk:
- doSignal(p[1],SIGNAL_AMBER);
- return true;
-
- case "GREEN"_hk:
- doSignal(p[1],SIGNAL_GREEN);
- return true;
-
- default:
- return false;
- }
+ CHECK(found);
+ ZZ(/,RESERVE,section) CHECK(setFlag(section,SECTION_FLAG))
+ ZZ(/,FREE,section) CHECK(setFlag(section,0,SECTION_FLAG))
+ ZZ(/,LATCH,latch) CHECK(setFlag(latch,LATCH_FLAG))
+ ZZ(/,UNLATCH,latch) CHECK(setFlag(latch,0,LATCH_FLAG))
+ ZZ(/,RED,signal) doSignal(signal,SIGNAL_RED);
+ ZZ(/,AMBER,signal) doSignal(signal,SIGNAL_AMBER);
+ ZZ(/,GREEN,signal) doSignal(signal,SIGNAL_GREEN);
+ZZEND
}
diff --git a/TrackManager.cpp b/TrackManager.cpp
index 0bf3673..0a4f60f 100644
--- a/TrackManager.cpp
+++ b/TrackManager.cpp
@@ -199,7 +199,7 @@ void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
}
bool TrackManager::orTrackMode(byte trackToSet, TRACK_MODE mode) {
- setTrackMode(trackToSet, track[trackToSet]->getMode() | mode);
+ return setTrackMode(trackToSet, track[trackToSet]->getMode() | mode);
}
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {