1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-04-04 12:40:12 +02:00
This commit is contained in:
Asbelos 2025-04-02 20:40:39 +01:00
parent 16f13d9aee
commit 18dcbeff31
6 changed files with 283 additions and 262 deletions

111
CamCommands.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*/
//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<vpcount;i++){
if (CAMVPINS[i]==0) break;
DIAG(F("EXSensorCam #%d vpin %d"),i+1,CAMVPINS[i]);
}
ZZ(N,V) // show version
camsend('^',0,999);
ZZ(N,F) //
camsend(']',0,999);
ZZ(N,Q) //
camsend('Q',0,10);
ZZ(N,camop) //
CHECK(STRCHR_P((const char *)F("EGMRW"),camop))
camsend(camop,0,999);
ZZ(N,C,pin) // change CAM base vpin or cam number from list
CHECK(pin>=100 || (pin<vpcount && 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<vpcount && slot==255;i++) {
if (CAMVPINS[i]==0) {
slot=i;
CAMVPINS[slot]=pin;
}
}
if (slot==255) {
DIAG(F("No more than %d cameras supported"),vpcount);
return;
}
if (slot==0) CAMBaseVpin=pin;
DIAG(F("CamParser Registered cam #%dvpin %d"),slot+1,pin);
// tell the DCCEXParser that we wish to filter commands
DCCEXParser::setCamParserFilter(&parse);
}

View File

@ -40,10 +40,10 @@ struct DCCEXParser
static void setCamParserFilter(FILTER_CALLBACK filter);
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
static const int MAX_COMMAND_PARAMS=10; // Must not exceed this
private:
static const FSH * matchedCommandFormat;
static const FSH * checkFailedFormat;
private:
#ifdef DCC_ACCESSORY_COMMAND_REVERSE
static const bool accessoryCommandReverse = true;
#else

View File

@ -24,11 +24,11 @@
#define Z8(op,_1,_2,_3,_4,_5,_6,_7) ZPREP(op,7) ZZZ(0,_1) ZZZ(1,_2) ZZZ(2,_3) ZZZ(3,_4) ZZZ(4,_5) ZZZ(5,_6) ZZZ(6,_7)
#define ZRIP(count) CONCAT(Z,count)
#define ZZ(...) ZRIP(FOR_EACH_NARG(__VA_ARGS__))(__VA_ARGS__) matchedCommandFormat = F( #__VA_ARGS__);
#define ZZ(...) ZRIP(FOR_EACH_NARG(__VA_ARGS__))(__VA_ARGS__) DCCEXParser::matchedCommandFormat = F( #__VA_ARGS__);
#define ZZBEGIN if (false) {
#define ZZEND return true; } return false;
#define CHECK(x) if (!(x)) { checkFailedFormat=F(#x); return false;}
#define CHECK(x) if (!(x)) { DCCEXParser::checkFailedFormat=F(#x); return false;}
#define REPLY(format,...) StringFormatter::send(stream,F(format), ##__VA_ARGS__);
#define EXPECT_CALLBACK CHECK(stashCallback(stream, p, ringStream))

View File

@ -214,8 +214,13 @@ class LookList {
private:
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
static bool parseCommands(Print * stream, byte opcode, byte params, int16_t p[]);
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
static void streamFlags(Print* stream);
static void streamStatus(Print * stream);
static bool streamLCC(Print * stream);
static bool setFlag(VPIN id,byte onMask, byte OffMask=0);
static bool getFlag(VPIN id,byte mask);
static int16_t progtrackLocoId;

View File

@ -29,190 +29,27 @@
#include "EXRAIL2.h"
#include "DCC.h"
#include "KeywordHasher.h"
#include "DCCEXParser.h"
#include "DCCEXParserMacros.h"
// 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
switch(opcode) {
case 'D':
if (p[0]=="EXRAIL"_hk) { // <D EXRAIL ON/OFF>
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': // <A address aspect>
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) { //<L> 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("<LR>\n")); // ready to roll
lccProgCounter=0; // allow a second pass
lccEventIndex=0;
return;
case OPCODE_LCC:
StringFormatter::send(stream,F("<LS x%h>\n"),getOperand(progCounter,0));
SKIPOP;
lccProgCounter=progCounter;
return;
case 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)
);
SKIPOP;SKIPOP;SKIPOP;SKIPOP;
lccProgCounter=progCounter;
return;
case OPCODE_ACON: // CBUS ACON
case OPCODE_ACOF: // CBUS ACOF
StringFormatter::send(stream,F("<LS x%c%h%h>\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("<LL %d x%h%h%h:%h>\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("<LL %d x%c%h%h>\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) { // <L eventid> 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: // <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>
int16_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;
case 'K': // <K blockid loco> Block enter
case 'k': // <k blockid loco> 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<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"));
REPLY("\nflags[%d] ",id);
if (flag & SECTION_FLAG) REPLY(" RESERVED");
if (flag & LATCH_FLAG) REPLY(" LATCHED");
}
}
@ -235,108 +72,176 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
if (slot.type==sigtypeNoMoreSignals) break; // end of signal list
if (slot.type==sigtypeContinuation) continue; // continueation of previous line
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this ids
StringFormatter::send(stream,F("\n%S[%d]"),
REPLY("\n%S[%d]",
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
slot.id);
slot.id)
}
}
StringFormatter::send(stream,F(" *>\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("<LR>\n")); // ready to roll
lccProgCounter=0; // allow a second pass
lccEventIndex=0;
return true;
case OPCODE_LCC:
StringFormatter::send(stream,F("<LS x%h>\n"),getOperand(progCounter,0));
SKIPOP;
lccProgCounter=progCounter;
return true;
case 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)
);
SKIPOP;SKIPOP;SKIPOP;SKIPOP;
lccProgCounter=progCounter;
return true;
case OPCODE_ACON: // CBUS ACON
case OPCODE_ACOF: // CBUS ACOF
StringFormatter::send(stream,F("<LS x%c%h%h>\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("<LL %d x%h%h%h:%h>\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("<LL %d x%c%h%h>\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; // <D EXRAIL ON> - turn on diagnostics
ZZ(D,EXRAIL,OFF) diag=0; // <D EXRAIL OFF> - 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 && eventid<countLCCLookup)
startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]);
ZZ(J,A) REPLY("<jA")
routeLookup->stream(stream);
REPLY(">\n")
ZZ(J,A,id) REPLY("<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])
REPLY("<jB %d %d>\n", id, routeStateArray[statePos]);
if (routeCaptionArray[statePos])
REPLY("<jB %d \"%S\">\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 && taskid<MAX_FLAGS)
auto task=loopTask;
bool found=false;
while(task) {
if (task->taskId==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
}

View File

@ -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) {