mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-28 18:03:45 +02:00
Compare commits
14 Commits
v5.5.37-De
...
zzparser
Author | SHA1 | Date | |
---|---|---|---|
|
9bda665ad4 | ||
|
1bcc2678c2 | ||
|
502ba7a653 | ||
|
16a1ddc6e9 | ||
|
8d52cd8542 | ||
|
6087486b91 | ||
|
91e8f89fe2 | ||
|
18dcbeff31 | ||
|
16f13d9aee | ||
|
2b82e65978 | ||
|
b840aee21e | ||
|
1cce32bb2a | ||
|
570fd75b15 | ||
|
83e62c7479 |
111
CamCommands.h
Normal file
111
CamCommands.h
Normal 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);
|
||||
}
|
634
DCCEXCommands.h
Normal file
634
DCCEXCommands.h
Normal file
@@ -0,0 +1,634 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2021-2025 Herb Morton
|
||||
* © 2020-2023 Harald Barth
|
||||
* © 2020-2021 M Steve Todd
|
||||
* © 2020-2021 Fred Decker
|
||||
* © 2020-2025 Chris Harlow
|
||||
* © 2022 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
List of single character OPCODEs in use for reference.
|
||||
|
||||
When determining a new OPCODE for a new feature, refer to this list as the source of truth.
|
||||
|
||||
Once a new OPCODE is decided upon, update this list.
|
||||
|
||||
Character, Usage
|
||||
/, |EX-R| interactive commands
|
||||
-, Remove from reminder table
|
||||
=, |TM| configuration
|
||||
!, Emergency stop
|
||||
@, Reserved for future use - LCD messages to JMRI
|
||||
#, Request number of supported cabs/locos; heartbeat
|
||||
+, WiFi AT commands
|
||||
?, Reserved for future use
|
||||
0, Track power off
|
||||
1, Track power on
|
||||
a, DCC accessory control
|
||||
A, DCC extended accessory control
|
||||
b, Write CV bit on main
|
||||
B, Write CV bit
|
||||
c, Request current command
|
||||
C, configure the CS
|
||||
d,
|
||||
D, Diagnostic commands
|
||||
e, Erase EEPROM
|
||||
E, Store configuration in EEPROM
|
||||
f, Loco decoder function control (deprecated)
|
||||
F, Loco decoder function control
|
||||
g,
|
||||
G,
|
||||
h,
|
||||
H, Turnout state broadcast
|
||||
i, Server details string
|
||||
I, Turntable object command, control, and broadcast
|
||||
j, Throttle responses
|
||||
J, Throttle queries
|
||||
k, Block exit (Railcom)
|
||||
K, Block enter (Railcom)
|
||||
l, Loco speedbyte/function map broadcast
|
||||
L, Reserved for LCC interface (implemented in EXRAIL)
|
||||
m, message to throttles (broadcast output)
|
||||
m, set momentum
|
||||
M, Write DCC packet
|
||||
n, Reserved for SensorCam
|
||||
N, Reserved for Sensorcam
|
||||
o, Neopixel driver (see also IO_NeoPixel.h)
|
||||
O, Output broadcast
|
||||
p, Broadcast power state
|
||||
P, Write DCC packet
|
||||
q, Sensor deactivated
|
||||
Q, Sensor activated
|
||||
r, Broadcast address read on programming track
|
||||
R, Read CVs
|
||||
s, Display status
|
||||
S, Sensor configuration
|
||||
t, Cab/loco update command
|
||||
T, Turnout configuration/control
|
||||
u, Reserved for user commands
|
||||
U, Reserved for user commands
|
||||
v,
|
||||
V, Verify CVs
|
||||
w, Write CV on main
|
||||
W, Write CV
|
||||
x,
|
||||
X, Invalid command response
|
||||
y,
|
||||
Y, Output broadcast
|
||||
z, Direct output
|
||||
Z, Output configuration/control
|
||||
*/
|
||||
/*
|
||||
Each ZZ macro matches a command opcode and its parameters.
|
||||
Paramters in UPPER case are matched as keywords, parameters in lower case are values provided by the user.
|
||||
Its important to recognise that if the same opcode has more than one match with the same length, you must match the
|
||||
keywprds before picking up user values.
|
||||
e.g.
|
||||
ZZ(X,value1,value2)
|
||||
ZZ(X,SET,value1) This will never be matched.
|
||||
|
||||
|
||||
Use of the CHECK() macro validates a condition to be true.
|
||||
If the condition is false an error is genarated, resulting in an <X> reply.
|
||||
Commonly known parameters such as loco, cv bitvalue etc are range checked automatically.
|
||||
The REPLY( format, ...) macro sends a formatted string to the stream.
|
||||
|
||||
These macros are included into the DCCEXParser::execute function so
|
||||
stream, ringStream and other DCCEXParser variables are available in context. */
|
||||
|
||||
|
||||
|
||||
ZZBEGIN
|
||||
ZZ(#) // Request number of simultaneously supported locos
|
||||
REPLY( "<# %d>\n", MAX_LOCOS)
|
||||
ZZ(!) // Emergency stop all locos
|
||||
DCC::estopAll();
|
||||
ZZ(t,loco) // Request loco status
|
||||
CommandDistributor::broadcastLoco(DCC::lookupSpeedTable(loco,false));
|
||||
ZZ(t,loco,tspeed,direction) // Set throttle speed(0..127) and direction (0=reverse, 1=fwd)
|
||||
CHECK(setThrottle(loco,tspeed,direction))
|
||||
ZZ(t,ignore,loco,tspeed,direction) // (Deprecated) Set throttle speed and direction
|
||||
CHECK(setThrottle(loco,tspeed,direction))
|
||||
ZZ(f,loco,byte1) // (Deprecated use F) Set loco function group
|
||||
switch ( byte1 & 0b11110000) { // 1111 0000
|
||||
case 0b11100000: // 111x xxxx Function group 1 F0..F4
|
||||
case 0b11110000:
|
||||
// Shuffle bits from order F0 F4 F3 F2 F1 to F4 F3 F2 F1 F0
|
||||
return (funcmap(loco, (byte1 << 1 & 0x1e) | (byte1 >> 4 & 0x01), 0, 4));
|
||||
case 0b10110000: // 1011 xxxx Function group 2 F5..F8
|
||||
return (funcmap(loco, byte1, 5, 8));
|
||||
case 0b10100000: // 1010 xxxx Function group 3 F9..F12
|
||||
return (funcmap(loco, byte1, 9, 12));
|
||||
default:
|
||||
CHECK(false,Invalid function group)
|
||||
}
|
||||
ZZ(f,loco,group,byte2) // (Deprecated use F) Set loco function group
|
||||
if (group == 222) return (funcmap(loco, byte2, 13, 20));
|
||||
if (group == 223) return (funcmap(loco, byte2, 21, 28));
|
||||
CHECK(false,Invalid function group)
|
||||
ZZ(T) // List all turnouts
|
||||
Turnout::printAll(stream); // will <X> if none found
|
||||
ZZ(T,id) // Delete turnout
|
||||
CHECK(Turnout::remove(id))
|
||||
ZZ(T,id,X) // List turnout details
|
||||
auto tt=Turnout::get(id); CHECK(tt) tt->print(stream);
|
||||
ZZ(T,id,T) // Throw Turnout
|
||||
Turnout::setClosed(id, false);
|
||||
ZZ(T,id,C) // Close turnout#
|
||||
Turnout::setClosed(id, true);
|
||||
ZZ(T,id,value) // Close (value=0) ot Throw turnout
|
||||
Turnout::setClosed(id, value==0);
|
||||
ZZ(T,id,SERVO,vpin,closedValue,thrownValue) // Create Servo turnout
|
||||
CHECK(ServoTurnout::create(id, (VPIN)vpin, (uint16_t)closedValue, (uint16_t)thrownValue, 1))
|
||||
ZZ(T,id,VPIN,vpin) // Create pin turnout
|
||||
CHECK(VpinTurnout::create(id, vpin))
|
||||
ZZ(T,id,DCC,addr,subadd) // Create DCC turnout
|
||||
CHECK(DCCTurnout::create(id, addr, subadd))
|
||||
ZZ(T,id,DCC,linearAddr) // Create DCC turnout
|
||||
CHECK(DCCTurnout::create(id, (linearAddr-1)/4+1, (linearAddr-1)%4))
|
||||
ZZ(T,id,addr,subadd) // Create DCC turnout
|
||||
CHECK(DCCTurnout::create(id, addr, subadd))
|
||||
ZZ(T,id,vpin,closedValue,thrownValue) // Create SERVO turnout
|
||||
CHECK(ServoTurnout::create(id, (VPIN)vpin, (uint16_t)closedValue, (uint16_t)thrownValue, 1))
|
||||
ZZ(S,id,vpin,pullup) // Create Sensor
|
||||
CHECK(Sensor::create(id,vpin,pullup))
|
||||
ZZ(S,id) // Delete sensor
|
||||
CHECK(Sensor::remove(id))
|
||||
ZZ(S) // List sensors
|
||||
for (auto *tt = Sensor::firstSensor; tt; tt = tt->nextSensor) {
|
||||
REPLY("<Q %d %d %d>\n", tt->data.snum, tt->data.pin, tt->data.pullUp)
|
||||
}
|
||||
ZZ(J,M) // List stash values
|
||||
Stash::list(stream);
|
||||
ZZ(J,M,stash_id) // get stash value
|
||||
Stash::list(stream, stash_id);
|
||||
ZZ(J,M,CLEAR,ALL) // Clear all stash values
|
||||
Stash::clearAll();
|
||||
ZZ(J,M,CLEAR,stash_id) // Clear given stash
|
||||
Stash::clear(stash_id);
|
||||
ZZ(J,M,stashId,locoId) // Set stash value
|
||||
Stash::set(stashId,locoId);
|
||||
ZZ(J,M,CLEAR,ANY,locoId) // Clear all stash entries that contain locoId
|
||||
Stash::clearAny(locoId);
|
||||
ZZ(J,C) // get fastclock time
|
||||
REPLY("<jC %d>\n", CommandDistributor::retClockTime())
|
||||
ZZ(J,C,mmmm,nn) // Set fastclock time
|
||||
CommandDistributor::setClockTime(mmmm, nn, 1);
|
||||
ZZ(J,G) // FReport gauge limits
|
||||
TrackManager::reportGauges(stream);
|
||||
ZZ(J,I) // Report currents
|
||||
TrackManager::reportCurrent(stream);
|
||||
// TODO... Ask @Ash zz(J,L,display,row) // Direct current displays to LCS/OLED
|
||||
// TrackManager::reportCurrentLCD(display,row); // Track power status
|
||||
ZZ(J,A) // List Routes
|
||||
REPLY( "<jA>\n")
|
||||
ZZ(J,R) // List Roster
|
||||
REPLY("<jR")
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
SENDFLASHLIST(stream,RMFT2::rosterIdList)
|
||||
#endif
|
||||
REPLY(">\n");
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
ZZ(J,R,id) // Get roster for loco
|
||||
auto rosterName= RMFT2::getRosterName(id);
|
||||
if (!rosterName) rosterName=F("");
|
||||
auto functionNames= RMFT2::getRosterFunctions(id);
|
||||
if (!functionNames) functionNames=RMFT2::getRosterFunctions(0);
|
||||
if (!functionNames) functionNames=F("");
|
||||
REPLY("<jR %d \"%S\" \"%S\">\n",id, rosterName, functionNames)
|
||||
#endif
|
||||
ZZ(J,T) // Get turnout list
|
||||
REPLY("<jT")
|
||||
for ( auto t=Turnout::first(); t; t=t->next()) if (!t->isHidden()) REPLY(" %d",t->getId())
|
||||
REPLY(">\n");
|
||||
ZZ(J,T,id) // Get turnout state and description
|
||||
auto t=Turnout::get(id);
|
||||
if (!t || t->isHidden()) { REPLY("<jT %d X>\n",id) return true; }
|
||||
const FSH *tdesc=nullptr;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
tdesc = RMFT2::getTurnoutDescription(id);
|
||||
#endif
|
||||
if (!tdesc) tdesc = F("");
|
||||
REPLY("<jT %d %c \"%S\">\n",id,t->isThrown()?'T':'C',tdesc)
|
||||
ZZ(z,vpin) // Set pin. HIGH iv vpin positive, LOW if vpin negative
|
||||
IODevice::write(vpin,(vpin>0)?HIGH:LOW);
|
||||
ZZ(z,vpin,analog,profile,duration) // Change analog value over duration (Fade or servo move)
|
||||
IODevice::writeAnalogue(vpin,analog,profile,duration);
|
||||
ZZ(z,vpin,analog,profile) // Write analog device using profile number (Fade or servo movement)
|
||||
IODevice::writeAnalogue(vpin,analog,profile,0);
|
||||
ZZ(z,vpin,analog) // Write analog device value
|
||||
IODevice::writeAnalogue(vpin,analog,0,0);
|
||||
|
||||
// ==========================
|
||||
// Turntable - no support if no HAL
|
||||
// <I> - list all
|
||||
// <I id> - broadcast type and current position
|
||||
// <I id DCC> - create DCC - This is TBA
|
||||
// <I id steps> - operate (DCC)
|
||||
// <I id steps activity> - operate (EXTT)
|
||||
// <I id ADD position value> - add position
|
||||
// <I id EXTT i2caddress vpin home> - create EXTT
|
||||
|
||||
ZZ(I) // List all turntables
|
||||
return Turntable::printAll(stream);
|
||||
ZZ(I,id) // Broadcast turntable type and current position
|
||||
auto tto = Turntable::get(id);
|
||||
CHECK(tto,Turntable not found)
|
||||
REPLY("<I %d %d>\n", tto->isEXTT(), tto->getPosition())
|
||||
ZZ(I,id,position) // Rotate a DCC turntable
|
||||
auto tto = Turntable::get(id);
|
||||
CHECK(tto,Turntable not found)
|
||||
CHECK(!tto->isEXTT(),Turntable type incorrect)
|
||||
CHECK(tto->setPosition(id,position))
|
||||
|
||||
ZZ(I,id,DCC,home) // Create DCC turntable
|
||||
CHECK(home >=0 && home <= 3600)
|
||||
auto tto = Turntable::get(id);
|
||||
CHECK(!tto,Turntable already exists)
|
||||
CHECK(DCCTurntable::create(id))
|
||||
tto = Turntable::get(id);
|
||||
CHECK(tto)
|
||||
tto->addPosition(0, 0, home);
|
||||
REPLY("<I>\n")
|
||||
|
||||
ZZ(I,id,position,activity) // Rotate an EXTT turntable
|
||||
auto tto = Turntable::get(id);
|
||||
CHECK(tto,Turntable not found)
|
||||
CHECK(tto->isEXTT(), Turntable wrong type)
|
||||
CHECK(tto->setPosition(id, position,activity))
|
||||
|
||||
ZZ(I,id,EXTT,vpin,home) // Create an EXTT turntable
|
||||
auto tto = Turntable::get(id);
|
||||
CHECK(!tto,Turntable already exists)
|
||||
CHECK(home >= 0 && home <= 3600)
|
||||
CHECK(EXTTTurntable::create(id, (VPIN)vpin))
|
||||
tto = Turntable::get(id);
|
||||
tto->addPosition(0, 0, home);
|
||||
REPLY("<I>\n")
|
||||
|
||||
ZZ(I,id,ADD,position,value,angle) // Add turntable position
|
||||
auto tto = Turntable::get(id);
|
||||
CHECK(tto,Turntable not found)
|
||||
CHECK(position <= 48 && angle >=0 && angle <= 3600)
|
||||
tto->addPosition(id,value,angle);
|
||||
REPLY("<I>\n")
|
||||
|
||||
ZZ(Q) // List all sensors
|
||||
Sensor::printAll(stream);
|
||||
|
||||
ZZ(s) // Command station status
|
||||
REPLY("<iDCC-EX V-" VERSION " / " ARDUINO_TYPE " / %S G-" GITHUB_SHA ">\n", DCC::getMotorShieldName())
|
||||
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
|
||||
Turnout::printAll(stream); //send all Turnout states
|
||||
Sensor::printAll(stream); //send all Sensor states
|
||||
|
||||
|
||||
#ifndef DISABLE_EEPROM
|
||||
ZZ(E) // STORE EPROM
|
||||
EEStore::store();
|
||||
REPLY("<e %d %d %d>\n", EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs)
|
||||
ZZ(e) // CLEAR EPROM
|
||||
EEStore::clear();
|
||||
REPLY("<O>\n")
|
||||
#endif
|
||||
|
||||
ZZ(Z) // List Output definitions
|
||||
bool gotone = false;
|
||||
for (auto *tt = Output::firstOutput; tt ; tt = tt->nextOutput) {
|
||||
gotone = true;
|
||||
REPLY("<Y %d %d %d %d>\n",tt->data.id, tt->data.pin, tt->data.flags, tt->data.active)
|
||||
}
|
||||
CHECK(gotone,No Outputs found)
|
||||
|
||||
ZZ(Z,id,pin,iflag) // Create Output
|
||||
CHECK(id > 0 && iflag >= 0 && iflag <= 7 )
|
||||
CHECK(Output::create(id,pin,iflag, 1))
|
||||
REPLY("<O>\n")
|
||||
ZZ(Z,id,active) // Set output
|
||||
auto o = Output::get(id);
|
||||
CHECK(o,Output not found)
|
||||
o->activate(active);
|
||||
REPLY("<Y %d %d>\n", id,active)
|
||||
ZZ(Z,id) // Delete output
|
||||
CHECK(Output::remove(id))
|
||||
REPLY("<O>\n")
|
||||
|
||||
ZZ(D,ACK,ON) // Enable PROG track diagnostics
|
||||
Diag::ACK = true;
|
||||
ZZ(D,ACK,OFF) // Disable PROG track diagnostics
|
||||
Diag::ACK = false;
|
||||
ZZ(D,CABS) // Diagnostic display loco state table
|
||||
DCC::displayCabList(stream);
|
||||
ZZ(D,RAM) // Diagnostic display free RAM
|
||||
DIAG(F("Free memory=%d"), DCCTimer::getMinimumFreeMemory());
|
||||
ZZ(D,CMD,ON) // Enable command input diagnostics
|
||||
Diag::CMD = true;
|
||||
ZZ(D,CMD,OFF) // Disable command input diagnostics
|
||||
Diag::CMD = false;
|
||||
ZZ(D,RAILCOM,ON) // Enable Railcom diagnostics
|
||||
Diag::RAILCOM = true;
|
||||
ZZ(D,RAILCOM,OFF) // DIsable Railcom diagnostics
|
||||
Diag::RAILCOM = false;
|
||||
ZZ(D,WIFI,ON) // Enable Wifi diagnostics
|
||||
Diag::WIFI = true;
|
||||
ZZ(D,WIFI,OFF) // Disable Wifi diagnostics
|
||||
Diag::WIFI = false;
|
||||
ZZ(D,ETHERNET,ON) // Enable Ethernet diagnostics
|
||||
Diag::ETHERNET = true;
|
||||
ZZ(D,ETHERNET,OFF) // Disabel Ethernet diagnostics
|
||||
Diag::ETHERNET = false;
|
||||
ZZ(D,WIT,ON) // Enable Withrottle diagnostics
|
||||
Diag::WITHROTTLE = true;
|
||||
ZZ(D,WIT,OFF) // Disable Withrottle diagnostics
|
||||
Diag::WITHROTTLE = false;
|
||||
ZZ(D,LCN,ON) // Enable LCN Diagnostics
|
||||
Diag::LCN = true;
|
||||
ZZ(D,LCN,OFF) // Disabel LCN diagnostics
|
||||
Diag::LCN = false;
|
||||
ZZ(D,WEBSOCKET,ON) // Enable Websocket diagnostics
|
||||
Diag::WEBSOCKET = true;
|
||||
ZZ(D,WEBSOCKET,OFF) // Disable wensocket diagnostics
|
||||
Diag::WEBSOCKET = false;
|
||||
|
||||
#ifndef DISABLE_EEPROM
|
||||
ZZ(D,EEPROM,numentries) // Dump EEPROM contents
|
||||
EEStore::dump(numentries);
|
||||
#endif
|
||||
|
||||
|
||||
ZZ(D,ANOUT,vpin,position) // see <z vpin position>
|
||||
IODevice::writeAnalogue(vpin,position,0);
|
||||
ZZ(D,ANOUT,vpin,position,profile) // see <z vpin position profile>
|
||||
IODevice::writeAnalogue(vpin,position,profile);
|
||||
ZZ(D,SERVO,vpin,position) // Test servo
|
||||
IODevice::writeAnalogue(vpin,position,0);
|
||||
ZZ(D,SERVO,vpin,position,profile) // Test servo
|
||||
IODevice::writeAnalogue(vpin,position,profile);
|
||||
|
||||
ZZ(D,ANIN,vpin) // Display analogue input value
|
||||
DIAG(F("VPIN=%u value=%d"), vpin, IODevice::readAnalogue(vpin));
|
||||
|
||||
ZZ(D,HAL,SHOW) // Show HAL devices table
|
||||
IODevice::DumpAll();
|
||||
ZZ(D,HAL,RESET) // Reset all HAL devices
|
||||
IODevice::reset();
|
||||
ZZ(D,TT,vpin,steps) // Test turntable
|
||||
IODevice::writeAnalogue(vpin,steps,0);
|
||||
ZZ(D,TT,vpin,steps,activity) // Test turntable
|
||||
IODevice::writeAnalogue(vpin,steps,activity);
|
||||
|
||||
ZZ(C,PROGBOOST) // Configute PROG track boost
|
||||
TrackManager::progTrackBoosted=true;
|
||||
ZZ(C,RESET) // Reset and restart command station
|
||||
DCCTimer::reset();
|
||||
ZZ(C,SPEED28) // Set all DCC speed commands as 28 step to old decoders
|
||||
DCC::setGlobalSpeedsteps(28); DIAG(F("28 Speedsteps"));
|
||||
ZZ(C,SPEED128) // Set all DCC speed commands to 128 step (default)
|
||||
DCC::setGlobalSpeedsteps(128); DIAG(F("128 Speedsteps"));
|
||||
ZZ(C,RAILCOM,ON) // Enable Railcom cutout
|
||||
DIAG(F("Railcom %S"),DCCWaveform::setRailcom(true,false)?F("ON"):F("OFF"));
|
||||
ZZ(C,RAILCOM,OFF) // Disable Railcom cutout
|
||||
DIAG(F("Railcom OFF")); DCCWaveform::setRailcom(false,false);
|
||||
ZZ(C,RAILCOM,DEBUG) // Enable Railcom cutout for easy scope reading test
|
||||
DIAG(F("Railcom %S"), DCCWaveform::setRailcom(true,true)?F("ON"):F("OFF"));
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
ZZ(D,ACK,LIMIT,value) // Set ACK detection limit mA
|
||||
DCCACK::setAckLimit(value); LCD(1, F("Ack Limit=%dmA"), value);
|
||||
ZZ(D,ACK,MIN,value,MS) // Set ACK minimum duration mS
|
||||
DCCACK::setMinAckPulseDuration(value*1000L); LCD(1, F("Ack Min=%dmS"), value);
|
||||
ZZ(D,ACK,MIN,value) // Set ACK minimum duration uS
|
||||
DCCACK::setMinAckPulseDuration(value); LCD(1, F("Ack Min=%duS"), value);
|
||||
ZZ(D,ACK,MAX,value,MS) // Set ACK maximum duration mS
|
||||
DCCACK::setMaxAckPulseDuration(value*1000L); LCD(1, F("Ack Max=%dmS"), value);
|
||||
ZZ(D,ACK,MAX,value) // Set ACK maximum duration uS
|
||||
DCCACK::setMaxAckPulseDuration(value); LCD(1, F("Ack Max=%duS"), value);
|
||||
ZZ(D,ACK,RETRY,value) // Set ACK retry count
|
||||
DCCACK::setAckRetry(value); LCD(1, F("Ack Retry=%d"), value);
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
// Dirty definition tricks because the executed check needs quote separation markers
|
||||
// that should be invisible to the doc extractor.
|
||||
// The equivalent documentation will be extracted from the commented line below
|
||||
// and the matchedFormat is hand modified to the correct format which includes quotes.
|
||||
|
||||
// (documented version) ZZ(C,WIFI,"ssid","password") // reconfigure stored wifi credentials
|
||||
ZZ_nodoc(C,WIFI,ssid,password)
|
||||
CHECK(false, ssid and password must be in "quotes")
|
||||
ZZ_nodoc(C,WIFI,marker1,ssid,marker2,password)
|
||||
DCCEXParser::matchedCommandFormat=F("C,WIFI,\"ssid\",\"password\""); // for error reporting
|
||||
CHECK(marker1==0x7777 && marker2==0x7777, ssid and password must be in "quotes")
|
||||
WifiESP::setup((const char*)(com + ssid), (const char*)(com + password), WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
|
||||
#endif
|
||||
|
||||
ZZ(o,vpin) // Set neopixel on(vpin>0) or off(vpin<0)
|
||||
IODevice::write(abs(vpin),vpin>0);
|
||||
ZZ(o,vpin,count) // Set multiple neopixels on(vpin>0) or off(vpin<0)
|
||||
IODevice::writeRange(abs(vpin),vpin>0,count);
|
||||
ZZ(o,vpin,r,g,b) // Set neopixel colour
|
||||
CHECK(r>=0 && r<=0xff && g>=0 && g<=0xff && b>=0 && b<=0xff, r,g,b values range 0..255)
|
||||
IODevice::writeAnalogueRange(abs(vpin),vpin>0,r<<8 | g,b,1);
|
||||
ZZ(o,vpin,r,g,b,count) // Set multiple neopixels colour
|
||||
CHECK(r>=0 && r<=0xff && g>=0 && g<=0xff && b>=0 && b<=0xff, r,g,b values range 0..255)
|
||||
IODevice::writeAnalogueRange(abs(vpin),vpin>0,r<<8 | g,b,count);
|
||||
|
||||
ZZ(1) // Power ON all tracks
|
||||
TrackManager::setTrackPower(TRACK_ALL, POWERMODE::ON);
|
||||
ZZ(1,MAIN) // Power on MAIN track
|
||||
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::ON);
|
||||
#ifndef DISABLE_PROG
|
||||
ZZ(1,PROG) // Power on PROG track
|
||||
TrackManager::setJoin(false); TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::ON);
|
||||
ZZ(1,JOIN) // JOIN prog track to MAIN and power
|
||||
TrackManager::setJoin(true); TrackManager::setTrackPower(TRACK_MODE_MAIN|TRACK_MODE_PROG, POWERMODE::ON);
|
||||
#endif
|
||||
ZZ(1,track) // Power on given track
|
||||
TrackManager::setTrackPower(POWERMODE::ON, (byte)track-'A');
|
||||
ZZ(0) // Power off all tracks
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(TRACK_ALL, POWERMODE::OFF);
|
||||
ZZ(0,MAIN) // Power off MAIN track
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::OFF);
|
||||
ZZ(0,PROG) // Power off PROG track
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::progTrackBoosted=false;
|
||||
// todo move to TrackManager Prog track boost mode will not outlive prog track off
|
||||
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::OFF);
|
||||
ZZ(0,track) // Power off given track
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(POWERMODE::OFF, (byte)track-'a');
|
||||
|
||||
ZZ(c) // Report main track currect (Deprecated)
|
||||
TrackManager::reportObsoleteCurrent(stream);
|
||||
|
||||
ZZ(a,address,subaddress,activate) // Send DCC accessory command
|
||||
CHECK(activate==0 || activate ==1, invalid activate 0..1 )
|
||||
DCC::setAccessory(address, subaddress,activate ^ accessoryCommandReverse);
|
||||
ZZ(a,address,subaddress,activate,onoff) // Send DCC accessory command with onoff control (TODO.. numbers)
|
||||
CHECK(activate==0 || activate ==1, invalid activate 0..1 )
|
||||
CHECK(onoff>=0 && onoff<=2,invalid onoff 0..2 )
|
||||
DCC::setAccessory(address, subaddress,activate ^ accessoryCommandReverse ,onoff);
|
||||
ZZ(a,linearaddress,activate) // send dcc accessory command
|
||||
CHECK(activate==0 || activate ==1, invalid activate 0..1 )
|
||||
DCC::setAccessory((linearaddress - 1) / 4 + 1,(linearaddress - 1) % 4 ,activate ^ accessoryCommandReverse);
|
||||
ZZ(A,address,value) // Send DCC extended accessory (Aspect) command
|
||||
DCC::setExtendedAccessory(address,value);
|
||||
|
||||
ZZ(w,loco,cv,value) // POM write cv on main track
|
||||
DCC::writeCVByteMain(loco,cv,value);
|
||||
ZZ(r,loco,cv) // POM read cv on main track
|
||||
CHECK(DCCWaveform::isRailcom(),Railcom not active)
|
||||
EXPECT_CALLBACK
|
||||
DCC::readCVByteMain(loco,cv,callback_r);
|
||||
ZZ(b,loco,cv,bit,bitvalue) // POM write cv bit on main track
|
||||
DCC::writeCVBitMain(loco,cv,bit,bitvalue);
|
||||
|
||||
ZZ(m,LINEAR) // Set Momentum algorithm to linear acceleration
|
||||
DCC::linearAcceleration=true;
|
||||
ZZ(m,POWER) // Set momentum algortithm to very based on difference between current speed and throttle seting
|
||||
DCC::linearAcceleration=false;
|
||||
ZZ(m,loco,momentum) // set momentum for loco (accel and braking)
|
||||
CHECK(DCC::setMomentum(loco,momentum,momentum))
|
||||
ZZ(m,loco,accelerating,braking) // set momentum for loco
|
||||
CHECK(DCC::setMomentum(loco,accelerating,braking))
|
||||
|
||||
// todo reorder for more sensible doco.
|
||||
ZZ(W,cv,value,ignore1,ignore2) // (Deprecated) Write cv value on PROG track
|
||||
EXPECT_CALLBACK DCC::writeCVByte(cv,value, callback_W);
|
||||
ZZ(W,loco) // Write loco address on PROG track
|
||||
EXPECT_CALLBACK DCC::setLocoId(loco,callback_Wloco);
|
||||
ZZ(W,CONSIST,loco,REVERSE) // Write consist address and reverse flag on PROG track
|
||||
EXPECT_CALLBACK DCC::setConsistId(loco,true,callback_Wconsist);
|
||||
ZZ(W,CONSIST,loco) // write consist address on PROG track
|
||||
EXPECT_CALLBACK DCC::setConsistId(loco,false,callback_Wconsist);
|
||||
ZZ(W,cv,value) // Write cv value on PROG track
|
||||
EXPECT_CALLBACK DCC::writeCVByte(cv,value, callback_W);
|
||||
ZZ(W,cv,bitvalue,bit) // Write cv bit on prog track
|
||||
EXPECT_CALLBACK DCC::writeCVBit(cv,bitvalue,bit,callback_W);
|
||||
ZZ(V,cv,value) // Fast read cv with expected value
|
||||
EXPECT_CALLBACK DCC::verifyCVByte(cv,value, callback_Vbyte);
|
||||
ZZ(V,cv,bit,bitvalue) // Fast read bit with expected value
|
||||
EXPECT_CALLBACK DCC::verifyCVBit(cv,bit,bitvalue,callback_Vbit);
|
||||
ZZ(B,cv,bit,bitvalue) // Write cv bit
|
||||
EXPECT_CALLBACK DCC::writeCVBit(cv,bit,bitvalue,callback_B);
|
||||
ZZ(R,cv,ignore1,ignore2) // (Deprecated) read cv value on PROG track
|
||||
EXPECT_CALLBACK DCC::readCV(cv,callback_R);
|
||||
ZZ(R,cv) // Read cv
|
||||
EXPECT_CALLBACK DCC::verifyCVByte(cv, 0, callback_Vbyte);
|
||||
ZZ(R) // Read driveable loco id (may be long, short or consist)
|
||||
EXPECT_CALLBACK DCC::getLocoId(callback_Rloco);
|
||||
|
||||
#ifndef DISABLE_VDPY
|
||||
ZZ_nodoc(@) CommandDistributor::setVirtualLCDSerial(stream);
|
||||
REPLY( "<@ 0 0 \"DCC-EX v" VERSION "\">\n<@ 0 1 \"Lic GPLv3\">\n")
|
||||
#endif
|
||||
|
||||
ZZ(-) // Clear loco state and reminder table
|
||||
DCC::forgetAllLocos();
|
||||
ZZ(-,loco) // remove loco state amnd reminders
|
||||
DCC::forgetLoco(loco);
|
||||
ZZ(F,loco,DCCFREQ,freqvalue) // Set DC frequencey for loco
|
||||
CHECK(freqvalue>=0 && freqvalue<=3) DCC::setDCFreq(loco,freqvalue);
|
||||
ZZ(F,loco,function,onoff) // Set loco function ON/OFF
|
||||
CHECK(onoff==0 || onoff==1) DCC::setFn(loco,function,onoff);
|
||||
|
||||
// ZZ(M,ignore,d0,d1,d2,d3,d4,d5) // Send up to 5 byte DCC packet on MAIN track (all d values in hex)
|
||||
ZZ_nodoc(M,ignore,d0,d1,d2,d3,d4,d5) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3,(byte)d4,(byte)d5}; DCCWaveform::mainTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(M,ignore,d0,d1,d2,d3,d4) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3,(byte)d4}; DCCWaveform::mainTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(M,ignore,d0,d1,d2,d3) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3}; DCCWaveform::mainTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(M,ignore,d0,d1,d2) byte packet[]={(byte)d0,(byte)d1,(byte)d2}; DCCWaveform::mainTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(M,ignore,d0,d1) byte packet[]={(byte)d0,(byte)d1}; DCCWaveform::mainTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
// ZZ(P,ignore,d0,d1,d2,d3,d4,d5) // Send up to 5 byte DCC packet on PROG track (all d values in hex)
|
||||
ZZ_nodoc(P,ignore,d0,d1,d2,d3,d4,d5) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3,(byte)d4,(byte)d5}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(P,ignore,d0,d1,d2,d3,d4) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3,(byte)d4}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(P,ignore,d0,d1,d2,d3) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(P,ignore,d0,d1,d2) byte packet[]={(byte)d0,(byte)d1,(byte)d2}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
ZZ_nodoc(P,ignore,d0,d1) byte packet[]={(byte)d0,(byte)d1}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
|
||||
|
||||
ZZ(J,O) // List turntable IDs
|
||||
REPLY("<jO")
|
||||
for (auto tto=Turntable::first(); tto; tto=tto->next()) if (!tto->isHidden()) REPLY(" %d",tto->getId())
|
||||
REPLY(">\n")
|
||||
ZZ(J,O,id) // List turntable state
|
||||
auto tto=Turntable::get(id);
|
||||
if (!tto || tto->isHidden()) {REPLY("<jO %d X>\n", id) return true;}
|
||||
const FSH *todesc = nullptr;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
todesc = RMFT2::getTurntableDescription(id);
|
||||
#endif
|
||||
if (todesc == nullptr) todesc = F("");
|
||||
REPLY("<jO %d %d %d %d \"%S\">\n", id, tto->isEXTT(), tto->getPosition(), tto->getPositionCount(), todesc)
|
||||
|
||||
ZZ(J,P,id) // list turntable positions
|
||||
auto tto=Turntable::get(id);
|
||||
if (!tto || tto->isHidden()) {REPLY("<jP %d X>\n", id) return true;}
|
||||
auto posCount = tto->getPositionCount();
|
||||
if (posCount==0) {REPLY("<jP X>\n") return true;}
|
||||
|
||||
for (auto p = 0; p < posCount; p++) {
|
||||
const FSH *tpdesc = nullptr;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
tpdesc = RMFT2::getTurntablePositionDescription(id, p);
|
||||
#endif
|
||||
if (tpdesc == NULL) tpdesc = F("");
|
||||
REPLY("<jP %d %d %d \"%S\">\n", id, p, tto->getPositionAngle(p), tpdesc)
|
||||
}
|
||||
|
||||
// Track manager
|
||||
|
||||
ZZ(=) // list track manager states
|
||||
TrackManager::list(stream);
|
||||
ZZ(=,track,MAIN) // Set track to MAIN
|
||||
CHECK(TrackManager::setTrackMode(track,TRACK_MODE_MAIN))
|
||||
ZZ(=,track,MAIN_INV) // Set track to MAIN inverted polatity
|
||||
CHECK(TrackManager::setTrackMode(track,TRACK_MODE_MAIN_INV))
|
||||
ZZ(=,track,MAIN_AUTO) // Set track to MAIN with auto reversing
|
||||
CHECK(TrackManager::setTrackMode(track,TRACK_MODE_MAIN_AUTO))
|
||||
ZZ(=,track,PROG) // Set track to PROG
|
||||
CHECK(TrackManager::setTrackMode(track,TRACK_MODE_PROG))
|
||||
ZZ(=,track,OFF) // Set track power OFF
|
||||
CHECK(TrackManager::setTrackMode(track,TRACK_MODE_NONE))
|
||||
ZZ(=,track,NONE) // Set track no output
|
||||
CHECK(TrackManager::setTrackMode(track,TRACK_MODE_NONE))
|
||||
ZZ(=,track,EXT) // Set track to use external sync
|
||||
CHECK(TrackManager::setTrackMode(track,TRACK_MODE_EXT))
|
||||
|
||||
#ifdef BOOSTER_INPUT
|
||||
ZZ_nodoc(=,track,BOOST) CHECK(TrackManager::setTrackMode(track,TRACK_MODE_BOOST))
|
||||
ZZ_nodoc(=,track,BOOST_INV) CHECK(TrackManager::setTrackMode(track,TRACK_MODE_BOOST_INV))
|
||||
ZZ_nodoc(=,track,BOOST_AUTO) CHECK(TrackManager::setTrackMode(track,TRACK_MODE_BOOST_AUTO))
|
||||
#endif
|
||||
ZZ(=,track,AUTO) // Update track to auto reverse
|
||||
CHECK(TrackManager::orTrackMode(track, TRACK_MODIFIER_AUTO))
|
||||
ZZ(=,track,INV) // Update track to inverse polarity
|
||||
CHECK(TrackManager::orTrackMode(track, TRACK_MODIFIER_INV))
|
||||
ZZ(=,track,DC,loco) // Set track to DC
|
||||
CHECK(TrackManager::setTrackMode(track, TRACK_MODE_DC, loco))
|
||||
ZZ(=,track,DC_INV,loco) // Set track to DC with inverted polarity
|
||||
CHECK(TrackManager::setTrackMode(track, TRACK_MODE_DC_INV, loco))
|
||||
ZZ(=,track,DCX,loco) // Set track to DC with inverted polarity
|
||||
CHECK(TrackManager::setTrackMode(track, TRACK_MODE_DC_INV, loco))
|
||||
|
||||
ZZEND
|
1161
DCCEXParser.cpp
1161
DCCEXParser.cpp
File diff suppressed because it is too large
Load Diff
@@ -40,22 +40,18 @@ 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
|
||||
|
||||
static const FSH * matchedCommandFormat;
|
||||
static const FSH * checkFailedFormat;
|
||||
|
||||
private:
|
||||
|
||||
#ifdef DCC_ACCESSORY_COMMAND_REVERSE
|
||||
static const bool accessoryCommandReverse = true;
|
||||
#else
|
||||
static const bool accessoryCommandReverse = false;
|
||||
#endif
|
||||
static const int16_t MAX_BUFFER=50; // longest command sent in
|
||||
static int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], byte * command, bool usehex);
|
||||
|
||||
static bool parseT(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseS(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parsef(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseC(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseD(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseJM(Print * stream, int16_t params, int16_t p[]);
|
||||
#ifndef IO_NO_HAL
|
||||
static bool parseI(Print * stream, int16_t params, int16_t p[]);
|
||||
#endif
|
||||
static bool execute(byte * command, Print * stream, byte opcode, byte params, int16_t p[], RingStream * ringStream);
|
||||
|
||||
static Print * getAsyncReplyStream();
|
||||
static void commitAsyncReplyStream();
|
||||
@@ -83,6 +79,7 @@ struct DCCEXParser
|
||||
static AT_COMMAND_CALLBACK atCommandCallback;
|
||||
static bool funcmap(int16_t cab, byte value, byte fstart, byte fstop);
|
||||
static void sendFlashList(Print * stream,const int16_t flashList[]);
|
||||
static bool setThrottle(int16_t cab,int16_t tspeed,int16_t direction);
|
||||
|
||||
};
|
||||
|
||||
|
90
DCCEXParserMacros.h
Normal file
90
DCCEXParserMacros.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* © 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/>.
|
||||
*/
|
||||
|
||||
// Count the number of arguments
|
||||
#define FOR_EACH_NARG(...) FOR_EACH_NARG_HELPER(__VA_ARGS__,8,7, 6,5,4, 3, 2, 1, 0)
|
||||
#define FOR_EACH_NARG_HELPER(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
|
||||
|
||||
// Step 2: Force proper expansion (extra indirection to resolve `##`)
|
||||
#define EXPAND(x) x
|
||||
#define CONCAT(a, b) a##b
|
||||
|
||||
|
||||
#define ZZZ(_i,_arg) \
|
||||
if ( #_arg[0]<='Z' && p[_i]!=CONCAT(#_arg,_hk)) break; \
|
||||
auto _arg=p[_i]; (void) _arg;
|
||||
|
||||
// Each ZZ terminates the previous one
|
||||
#define ZPREP(op,count) return true; } if (opcode==#op[0] && params==count) for (;;) {
|
||||
#define Z1(op) ZPREP(op,0)
|
||||
#define Z2(op,_1) ZPREP(op,1) ZZZ(0,_1)
|
||||
#define Z3(op,_1,_2) ZPREP(op,2) ZZZ(0,_1) ZZZ(1,_2)
|
||||
#define Z4(op,_1,_2,_3) ZPREP(op,3) ZZZ(0,_1) ZZZ(1,_2) ZZZ(2,_3)
|
||||
#define Z5(op,_1,_2,_3,_4) ZPREP(op,4) ZZZ(0,_1) ZZZ(1,_2) ZZZ(2,_3) ZZZ(3,_4)
|
||||
#define Z6(op,_1,_2,_3,_4,_5) ZPREP(op,5) ZZZ(0,_1) ZZZ(1,_2) ZZZ(2,_3) ZZZ(3,_4) ZZZ(4,_5)
|
||||
#define Z7(op,_1,_2,_3,_4,_5,_6) ZPREP(op,6) ZZZ(0,_1) ZZZ(1,_2) ZZZ(2,_3) ZZZ(3,_4) ZZZ(4,_5) ZZZ(5,_6)
|
||||
#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 ZC1(op)
|
||||
#define ZC2(op,_1) ZZCHK(0,_1)
|
||||
#define ZC3(op,_1,_2) ZZCHK(0,_1) ZZCHK(1,_2)
|
||||
#define ZC4(op,_1,_2,_3) ZZCHK(0,_1) ZZCHK(1,_2) ZZCHK(2,_3)
|
||||
#define ZC5(op,_1,_2,_3,_4) ZZCHK(0,_1) ZZCHK(1,_2) ZZCHK(2,_3) ZZCHK(3,_4)
|
||||
#define ZC6(op,_1,_2,_3,_4,_5) ZZCHK(0,_1) ZZCHK(1,_2) ZZCHK(2,_3) ZZCHK(3,_4) ZZCHK(4,_5)
|
||||
#define ZC7(op,_1,_2,_3,_4,_5,_6) ZZCHK(0,_1) ZZCHK(1,_2) ZZCHK(2,_3) ZZCHK(3,_4) ZZCHK(4,_5) ZZCHK(5,_6)
|
||||
#define ZC8(op,_1,_2,_3,_4,_5,_6,_7) ZZCHK(0,_1) ZZCHK(1,_2) ZZCHK(2,_3) ZZCHK(3,_4) ZZCHK(4,_5) ZZCHK(5,_6) ZZCHK(6,_7)
|
||||
#define ZCRIP(count) CONCAT(ZC,count)
|
||||
|
||||
#define ZZ(...) \
|
||||
ZRIP(FOR_EACH_NARG(__VA_ARGS__))(__VA_ARGS__) \
|
||||
DCCEXParser::matchedCommandFormat = F( #__VA_ARGS__); \
|
||||
ZCRIP(FOR_EACH_NARG(__VA_ARGS__))(__VA_ARGS__)
|
||||
|
||||
|
||||
|
||||
#define ZZBEGIN if (false) {
|
||||
#define ZZEND return true; } return false;
|
||||
#define CHECK(x,...) if (!(x)) { DCCEXParser::checkFailedFormat=#__VA_ARGS__[0]?F(#__VA_ARGS__):F(#x); return false;}
|
||||
#define REPLY(format,...) StringFormatter::send(stream,F(format), ##__VA_ARGS__);
|
||||
#define EXPECT_CALLBACK CHECK(stashCallback(stream, p, ringStream))
|
||||
// helper macro to hide command from documentation extractor
|
||||
#define ZZ_nodoc ZZ
|
||||
|
||||
#define ZCHECK(_checkname,_index,_pname,_min,_max) \
|
||||
if (CONCAT(#_pname,_hk) == CONCAT(#_checkname,_hk) \
|
||||
&& (p[_index]<_min || p[_index]>_max)) CHECK(false,_checkname _min .. _max)
|
||||
|
||||
// Automatic range checks based on name of inserted parameter
|
||||
#define ZZCHK(_index,_pname)\
|
||||
ZCHECK(loco,_index,_pname,0,10239) \
|
||||
ZCHECK(track,_index,_pname,'A','H') \
|
||||
ZCHECK(cv,_index,_pname,1,255) \
|
||||
ZCHECK(value,_index,_pname,0,255) \
|
||||
ZCHECK(bit,_index,_pname,0,7) \
|
||||
ZCHECK(bitvalue,_index,_pname,0,1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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,189 @@ 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) // EXRAIL diagnostics on
|
||||
diag=1;
|
||||
ZZ(D,EXRAIL,OFF) // EXRAIL doagnostics off
|
||||
diag=0; // <D EXRAIL OFF> - turn off diagnostics
|
||||
|
||||
// This is not documented here because its an override of the one in DCCEXParserCommands.h
|
||||
ZZ_nodoc(A,address,aspect) // Send DCC extended accessory (aspect) and syncronize any signal on this address
|
||||
return signalAspectEvent(address,aspect);
|
||||
ZZ(L) // LCC/CBUS adapter introducing self
|
||||
CHECK(streamLCC(stream),no LCC/CBUS events)
|
||||
ZZ(L,eventid) // LCC incoming event
|
||||
CHECK(eventid>=0 && eventid<countLCCLookup)
|
||||
startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]);
|
||||
|
||||
ZZ(J,A) // List automation ids
|
||||
REPLY("<jA") routeLookup->stream(stream); REPLY(">\n")
|
||||
ZZ(J,A,id) // list automation details
|
||||
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) // Loco entering Block
|
||||
blockEvent(blockid,loco,true);
|
||||
ZZ(k,blockid,loco) // Loco exiting block
|
||||
blockEvent(blockid,loco,false);
|
||||
|
||||
ZZ(/) // Stream EXRAIL status
|
||||
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) // Resume all tasks
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
ZZ(/,START,route) // Start a route or sequence
|
||||
auto pc=routeLookup->find(route);
|
||||
CHECK(pc>=0,route not found)
|
||||
new RMFT2(pc,0); // no cab for route start
|
||||
|
||||
// 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,loco,route) // Start an AUTOMATION or sequence with a loco
|
||||
auto pc=routeLookup->find(route);
|
||||
CHECK(pc>=0, route not found)
|
||||
new RMFT2(pc,loco);
|
||||
|
||||
ZZ(/,KILL,ALL) // Kill all exrail tasks
|
||||
while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
|
||||
ZZ(/,KILL,taskid) // Kill specific exrail tasks
|
||||
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, task not found)
|
||||
ZZ(/,RESERVE,section) // Flag section as reserved
|
||||
CHECK(setFlag(section,SECTION_FLAG),invalid section)
|
||||
ZZ(/,FREE,section) // Free reserve on section
|
||||
CHECK(setFlag(section,0,SECTION_FLAG),invalid section)
|
||||
ZZ(/,LATCH,latch) // Set pin latch
|
||||
CHECK(setFlag(latch,LATCH_FLAG),invalid section)
|
||||
ZZ(/,UNLATCH,latch) // Removeve pin latch
|
||||
CHECK(setFlag(latch,0,LATCH_FLAG),invalid section)
|
||||
ZZ(/,RED,signal) // Set signal to Red
|
||||
doSignal(signal,SIGNAL_RED);
|
||||
ZZ(/,AMBER,signal) // set Signal to Amber
|
||||
doSignal(signal,SIGNAL_AMBER);
|
||||
ZZ(/,GREEN,signal) // Set signal to Green
|
||||
doSignal(signal,SIGNAL_GREEN);
|
||||
ZZEND
|
||||
}
|
||||
|
14
Release_Notes/AutoRefManual.html
Normal file
14
Release_Notes/AutoRefManual.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<html>
|
||||
<head>
|
||||
<script lang="javascript">
|
||||
function ZZ(header, body) {
|
||||
document.write( `<div class="html-block">
|
||||
<h2 style="font-family: monospace;"><${header}></h3>
|
||||
<p>${body}</p>
|
||||
</div>`);
|
||||
}
|
||||
</script>
|
||||
<script src="AutoRefManual.js" type="text/javascript"></script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
177
Release_Notes/AutoRefManual.js
Normal file
177
Release_Notes/AutoRefManual.js
Normal file
@@ -0,0 +1,177 @@
|
||||
ZZ('#','Request number of simultaneously supported locos');
|
||||
ZZ('!','Emergency stop all locos');
|
||||
ZZ('t loco','Request loco status');
|
||||
ZZ('t loco tspeed direction','Set throttle speed(0..127) and direction (0=reverse, 1=fwd) ');
|
||||
ZZ('t ignore loco tspeed direction','(Deprecated) Set throttle speed and direction');
|
||||
ZZ('T','List all turnouts');
|
||||
ZZ('T id','Delete turnout');
|
||||
ZZ('T id X','List turnout details');
|
||||
ZZ('T id T','Throw Turnout');
|
||||
ZZ('T id C','Close turnout#');
|
||||
ZZ('T id value','Close (value=0) ot Throw turnout');
|
||||
ZZ('T id SERVO vpin closedValue thrownValue','Create Servo turnout ');
|
||||
ZZ('T id VPIN vpin','Create pin turnout');
|
||||
ZZ('T id DCC addr subadd','Create DCC turnout ');
|
||||
ZZ('T id DCC linearAddr','Create DCC turnout');
|
||||
ZZ('T id addr subadd','Create DCC turnout');
|
||||
ZZ('T id vpin closedValue thrownValue','Create SERVO turnout');
|
||||
ZZ('S id vpin pullup','Create Sensor');
|
||||
ZZ('S id','Delete sensor');
|
||||
ZZ('S','List sensors');
|
||||
ZZ('J M','List stash values');
|
||||
ZZ('J M stash_id','get stash value');
|
||||
ZZ('J M CLEAR ALL','Clear all stash values');
|
||||
ZZ('J M CLEAR stash_id','Clear given stash');
|
||||
ZZ('J M stashId locoId','Set stash value');
|
||||
ZZ('J M CLEAR ANY locoId','Clear all stash entries that contain locoId');
|
||||
ZZ('J C','get fastclock time');
|
||||
ZZ('J C mmmm nn','Set fastclock time');
|
||||
ZZ('J G','FReport gauge limits ');
|
||||
ZZ('J I','Report currents ');
|
||||
ZZ('J L display row','Direct current displays to LCS/OLED');
|
||||
ZZ('J A','List Routes');
|
||||
ZZ('J R','List Roster');
|
||||
ZZ('J R id','Get roster for loco');
|
||||
ZZ('J T','Get turnout list ');
|
||||
ZZ('J T id','Get turnout state and description');
|
||||
ZZ('z vpin','Set pin. HIGH iv vpin positive, LOW if vpin negative ');
|
||||
ZZ('z vpin analog profile duration','Change analog value over duration (Fade or servo move)');
|
||||
ZZ('z vpin analog profile','Write analog device using profile number (Fade or servo movement)');
|
||||
ZZ('z vpin analog','Write analog device value');
|
||||
ZZ('I','List all turntables');
|
||||
ZZ('I id','Broadcast turntable type and current position ');
|
||||
ZZ('I id position','Rotate a DCC turntable');
|
||||
ZZ('I id DCC home','Create DCC turntable');
|
||||
ZZ('I id position activity','Rotate an EXTT turntable');
|
||||
ZZ('I id EXTT vpin home','Create an EXTT turntable');
|
||||
ZZ('I id ADD position value angle','Add turntable position');
|
||||
ZZ('Q','List all sensors ');
|
||||
ZZ('s','Command station status');
|
||||
ZZ('E','STORE EPROM');
|
||||
ZZ('e','CLEAR EPROM');
|
||||
ZZ('Z','List Output definitions ');
|
||||
ZZ('Z id pin iflag','Create Output');
|
||||
ZZ('Z id active','Set output ');
|
||||
ZZ('Z id','Delete output');
|
||||
ZZ('D ACK ON','Enable PROG track diagnostics');
|
||||
ZZ('D ACK OFF','Disable PROG track diagnostics');
|
||||
ZZ('D CABS','Diagnostic display loco state table');
|
||||
ZZ('D RAM','Diagnostic display free RAM');
|
||||
ZZ('D CMD ON','Enable command input diagnostics');
|
||||
ZZ('D CMD OFF','Disable command input diagnostics');
|
||||
ZZ('D RAILCOM ON','Enable Railcom diagnostics');
|
||||
ZZ('D RAILCOM OFF','DIsable Railcom diagnostics');
|
||||
ZZ('D WIFI ON','Enable Wifi diagnostics');
|
||||
ZZ('D WIFI OFF','Disable Wifi diagnostics');
|
||||
ZZ('D ETHERNET ON','Enable Ethernet diagnostics');
|
||||
ZZ('D ETHERNET OFF','Disabel Ethernet diagnostics ');
|
||||
ZZ('D WIT ON','Enable Withrottle diagnostics');
|
||||
ZZ('D WIT OFF','Disable Withrottle diagnostics ');
|
||||
ZZ('D LCN ON','Enable LCN Diagnostics');
|
||||
ZZ('D LCN OFF','Disabel LCN diagnostics');
|
||||
ZZ('D WEBSOCKET ON','Enable Websocket diagnostics ');
|
||||
ZZ('D WEBSOCKET OFF','Disable wensocket diagnostics ');
|
||||
ZZ('D EEPROM numentries','Dump EEPROM contents');
|
||||
ZZ('D ANOUT vpin position','see <z vpin position>');
|
||||
ZZ('D ANOUT vpin position profile','see <z vpin position profile>');
|
||||
ZZ('D SERVO vpin position','Test servo');
|
||||
ZZ('D SERVO vpin position profile','Test servo');
|
||||
ZZ('D ANIN vpin','Display analogue input value');
|
||||
ZZ('D HAL SHOW','Show HAL devices table');
|
||||
ZZ('D HAL RESET','Reset all HAL devices');
|
||||
ZZ('D TT vpin steps','Test turntable');
|
||||
ZZ('D TT vpin steps activity','Test turntable');
|
||||
ZZ('C PROGBOOST','Configute PROG track boost');
|
||||
ZZ('C RESET','Reset and restart command station');
|
||||
ZZ('C SPEED28','Set all DCC speed commands as 28 step to old decoders');
|
||||
ZZ('C SPEED128','Set all DCC speed commands to 128 step (default)');
|
||||
ZZ('C RAILCOM ON','Enable Railcom cutout ');
|
||||
ZZ('C RAILCOM OFF','Disable Railcom cutout');
|
||||
ZZ('C RAILCOM DEBUG','Enable Railcom cutout for easy scope reading test');
|
||||
ZZ('D ACK LIMIT value','Set ACK detection limit mA');
|
||||
ZZ('D ACK MIN value MS','Set ACK minimum duration mS');
|
||||
ZZ('D ACK MIN value','Set ACK minimum duration uS');
|
||||
ZZ('D ACK MAX value MS','Set ACK maximum duration mS');
|
||||
ZZ('D ACK MAX value','Set ACK maximum duration uS');
|
||||
ZZ('D ACK RETRY value','Set ACK retry count');
|
||||
ZZ('C WIFI "ssid" "password"','reconfigure stored wifi credentials ');
|
||||
ZZ('o vpin','Set neopixel on(vpin>0) or off(vpin<0)');
|
||||
ZZ('o vpin count','Set multiple neopixels on(vpin>0) or off(vpin<0)');
|
||||
ZZ('o vpin r g b','Set neopixel colour');
|
||||
ZZ('o vpin r g b count','Set multiple neopixels colour ');
|
||||
ZZ('1','Power ON all tracks');
|
||||
ZZ('1 MAIN','Power on MAIN track');
|
||||
ZZ('1 PROG','Power on PROG track');
|
||||
ZZ('1 JOIN','JOIN prog track to MAIN and power');
|
||||
ZZ('1 track','Power on given track');
|
||||
ZZ('0','Power off all tracks');
|
||||
ZZ('0 MAIN','Power off MAIN track');
|
||||
ZZ('0 PROG','Power off PROG track');
|
||||
ZZ('0 track','Power off given track');
|
||||
ZZ('c','Report main track currect (Deprecated)');
|
||||
ZZ('a address subaddress activate','Send DCC accessory command');
|
||||
ZZ('a address subaddress activate onoff','Send DCC accessory command with onoff control (TODO.. numbers) ');
|
||||
ZZ('a linearaddress activate','send dcc accessory command ');
|
||||
ZZ('A address value','Send DCC extended accessory (Aspect) command');
|
||||
ZZ('w loco cv value','POM write cv on main track');
|
||||
ZZ('r loco cv','POM read cv on main track');
|
||||
ZZ('b loco cv bit value','POM write cv bit on main track');
|
||||
ZZ('m LINEAR','Set Momentum algorithm to linear acceleration');
|
||||
ZZ('m POWER','Set momentum algortithm to very based on difference between current speed and throttle seting');
|
||||
ZZ('m loco momentum','set momentum for loco (accel and braking)');
|
||||
ZZ('m loco accelerating braking','set momentum for loco');
|
||||
ZZ('W cv value ignore1 ignore2','(Deprecated) Write cv value on PROG track');
|
||||
ZZ('W cab','Write loco address on PROG track');
|
||||
ZZ('W CONSIST cab REVERSE','Write consist address and reverse flag on PROG track ');
|
||||
ZZ('W CONSIST cab','write consist address on PROG track ');
|
||||
ZZ('W cv value','Write cv value on PROG track');
|
||||
ZZ('W cv value bit','Write cv bit on prog track');
|
||||
ZZ('V cv value','Fast read cv with expected value');
|
||||
ZZ('V cv bit value','Fast read bit with expected value');
|
||||
ZZ('B cv bit value','Write cv bit');
|
||||
ZZ('R cv ignore1 ignore2','(Deprecated) read cv');
|
||||
ZZ('R cv','Read cv');
|
||||
ZZ('R','Read driveable loco id (may be long, short or consist)');
|
||||
ZZ('-','Clear loco state and reminder table');
|
||||
ZZ('- loco','remove loco state amnd reminders');
|
||||
ZZ('F loco DCCFREQ value','Set DC frequencey for loco ');
|
||||
ZZ('F loco function value','Set loco function ON/OFF');
|
||||
ZZ('M ignore d0 d1 d2 d3 d4 d5','Send up to 5 byte DCC packet on MAIN track (all d values in hex)');
|
||||
ZZ('P ignore d0 d1 d2 d3 d4 d5','Send up to 5 byte DCC packet on PROG track (all d values in hex)');
|
||||
ZZ('J O','List turntable IDs');
|
||||
ZZ('J O id','List turntable state');
|
||||
ZZ('J P id','list turntable positions');
|
||||
ZZ('=','list track manager states');
|
||||
ZZ('= track MAIN','Set track to MAIN');
|
||||
ZZ('= track MAIN_INV','Set track to MAIN inverted polatity');
|
||||
ZZ('= track MAIN_AUTO','Set track to MAIN with auto reversing');
|
||||
ZZ('= track PROG','Set track to PROG');
|
||||
ZZ('= track OFF','Set track power OFF');
|
||||
ZZ('= track NONE','Set track no output');
|
||||
ZZ('= track EXT','Set track to use external sync');
|
||||
ZZ('= track AUTO','Update track to auto reverse');
|
||||
ZZ('= track INV','Update track to inverse polarity');
|
||||
ZZ('= track DC locoid','Set track to DC');
|
||||
ZZ('= track DC_INV locoid','Set track to DC with inverted polarity');
|
||||
ZZ('= track DCX locoid','Set track to DC with inverted polarity');ZZ('D EXRAIL ON','EXRAIL diagnostics on');
|
||||
ZZ('D EXRAIL OFF','EXRAIL doagnostics off');
|
||||
ZZ('L','LCC/CBUS adapter introducing self');
|
||||
ZZ('L eventid','LCC incoming event ');
|
||||
ZZ('J A','List automation ids');
|
||||
ZZ('J A id','list automation details');
|
||||
ZZ('K blockid loco','Loco entering Block');
|
||||
ZZ('k blockid loco','Loco exiting block');
|
||||
ZZ('/','Stream EXRAIL status');
|
||||
ZZ('/ PAUSE','pause all tasks ');
|
||||
ZZ('/ RESUME','Resume all tasks');
|
||||
ZZ('/ START route','Start a route or sequence');
|
||||
ZZ('/ START loco route','Start an AUTOMATION or sequence with a loco ');
|
||||
ZZ('/ KILL ALL','Kill all exrail tasks');
|
||||
ZZ('/ KILL taskid','Kill specific exrail tasks ');
|
||||
ZZ('/ RESERVE section','Flag section as reserved');
|
||||
ZZ('/ FREE section','Free reserve on section');
|
||||
ZZ('/ LATCH latch','Set pin latch');
|
||||
ZZ('/ UNLATCH latch','Removeve pin latch');
|
||||
ZZ('/ RED signal','Set signal to Red ');
|
||||
ZZ('/ AMBER signal','set Signal to Amber');
|
||||
ZZ('/ GREEN signal','Set signal to Green ');
|
15
Release_Notes/DCCEXCommands.awk
Normal file
15
Release_Notes/DCCEXCommands.awk
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/awk -f
|
||||
|
||||
{
|
||||
# Match the pattern ZZ(something) // comments
|
||||
if ($0 ~ /ZZ\([^)]*\)\s*\/\/.*/) {
|
||||
# Extract "something" and "comments"
|
||||
match($0, /ZZ\(([^)]*)\)\s*\/\/\s*(.*)/, arr);
|
||||
something = arr[1];
|
||||
comments = arr[2];
|
||||
# Replace commas in "something" with spaces
|
||||
gsub(/,/, " ", something);
|
||||
# Print in the new format as a JS call
|
||||
printf "ZZ('%s','%s');\n", something, comments;
|
||||
}
|
||||
}
|
@@ -123,6 +123,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
case 's': stream->print(va_arg(args, char*)); break;
|
||||
case 'e': printEscapes(stream,va_arg(args, char*)); break;
|
||||
case 'E': printEscapes(stream,(const FSH*)va_arg(args, char*)); break;
|
||||
case '<': printCmdFormat(stream,(const FSH*)va_arg(args, char*)); break;
|
||||
case 'S':
|
||||
{
|
||||
const FSH* flash= (const FSH*)va_arg(args, char*);
|
||||
@@ -202,15 +203,27 @@ void StringFormatter::printEscapes(Print * stream,char * input) {
|
||||
|
||||
void StringFormatter::printEscapes(Print * stream, const FSH * input) {
|
||||
|
||||
if (!stream) return;
|
||||
char* flash=(char*)input;
|
||||
for(int i=0; ; ++i) {
|
||||
char c=GETFLASH(flash+i);
|
||||
printEscape(stream,c);
|
||||
if (c=='\0') return;
|
||||
if (!stream) return;
|
||||
char* flash=(char*)input;
|
||||
for(int i=0; ; ++i) {
|
||||
char c=GETFLASH(flash+i);
|
||||
printEscape(stream,c);
|
||||
if (c=='\0') return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StringFormatter::printCmdFormat(Print * stream, const FSH * input) {
|
||||
|
||||
if (!stream) return;
|
||||
char* flash=(char*)input;
|
||||
for(int i=0; ; ++i) {
|
||||
char c=GETFLASH(flash+i);
|
||||
if (c=='\0') return;
|
||||
if (c==',') c=' ';
|
||||
stream->write(c);
|
||||
}
|
||||
}
|
||||
|
||||
void StringFormatter::printEscape( char c) {
|
||||
printEscape(&USB_SERIAL,c);
|
||||
}
|
||||
|
@@ -43,6 +43,7 @@ class StringFormatter
|
||||
|
||||
static void printEscapes(Print * serial,char * input);
|
||||
static void printEscapes(Print * serial,const FSH* input);
|
||||
static void printCmdFormat(Print * serial,const FSH* input);
|
||||
static void printEscape(Print * serial, char c);
|
||||
|
||||
// DIAG support
|
||||
|
@@ -197,7 +197,13 @@ void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
|
||||
}
|
||||
}
|
||||
|
||||
bool TrackManager::orTrackMode(byte trackToSet, TRACK_MODE mode) {
|
||||
if (trackToSet>='A' && trackToSet<='H') trackToSet-='A';
|
||||
return setTrackMode(trackToSet, track[trackToSet]->getMode() | mode);
|
||||
}
|
||||
|
||||
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
|
||||
if (trackToSet>='A' && trackToSet<='H') trackToSet-='A';
|
||||
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
|
||||
|
||||
// Remember track mode we came from for later
|
||||
@@ -385,63 +391,12 @@ void TrackManager::applyDCSpeed(byte t) {
|
||||
DCC::getThrottleFrequency(trackDCAddr[t]));
|
||||
}
|
||||
|
||||
bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[])
|
||||
bool TrackManager::list(Print *stream)
|
||||
{
|
||||
|
||||
if (params==0) { // <=> List track assignments
|
||||
FOR_EACH_TRACK(t)
|
||||
streamTrackState(stream,t);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
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]=="MAIN"_hk) // <= id MAIN>
|
||||
return setTrackMode(p[0],TRACK_MODE_MAIN);
|
||||
if (params==2 && p[1]=="MAIN_INV"_hk) // <= id MAIN_INV>
|
||||
return setTrackMode(p[0],TRACK_MODE_MAIN_INV);
|
||||
if (params==2 && p[1]=="MAIN_AUTO"_hk) // <= id MAIN_AUTO>
|
||||
return setTrackMode(p[0],TRACK_MODE_MAIN_AUTO);
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
if (params==2 && p[1]=="PROG"_hk) // <= id PROG>
|
||||
return setTrackMode(p[0],TRACK_MODE_PROG);
|
||||
#endif
|
||||
|
||||
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]=="EXT"_hk) // <= id EXT>
|
||||
return setTrackMode(p[0],TRACK_MODE_EXT);
|
||||
#ifdef BOOSTER_INPUT
|
||||
if (TRACK_MODE_BOOST != 0 && // compile time optimization
|
||||
params==2 && p[1]=="BOOST"_hk) // <= id BOOST>
|
||||
return setTrackMode(p[0],TRACK_MODE_BOOST);
|
||||
if (TRACK_MODE_BOOST_INV != 0 && // compile time optimization
|
||||
params==2 && p[1]=="BOOST_INV"_hk) // <= id BOOST_INV>
|
||||
return setTrackMode(p[0],TRACK_MODE_BOOST_INV);
|
||||
if (TRACK_MODE_BOOST_AUTO != 0 && // compile time optimization
|
||||
params==2 && p[1]=="BOOST_AUTO"_hk) // <= id BOOST_AUTO>
|
||||
return setTrackMode(p[0],TRACK_MODE_BOOST_AUTO);
|
||||
#endif
|
||||
if (params==2 && p[1]=="AUTO"_hk) // <= id AUTO>
|
||||
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODIFIER_AUTO);
|
||||
|
||||
if (params==2 && p[1]=="INV"_hk) // <= id INV>
|
||||
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODIFIER_INV);
|
||||
|
||||
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]=="DC_INV"_hk || // <= id DC_INV cab>
|
||||
p[1]=="DCX"_hk) && p[2]>0) // <= id DCX cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DC_INV,p[2]);
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const FSH* TrackManager::getModeName(TRACK_MODE tm) {
|
||||
|
@@ -72,7 +72,8 @@ class TrackManager {
|
||||
|
||||
static const int16_t MAX_TRACKS=8;
|
||||
static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0);
|
||||
static bool parseEqualSign(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool orTrackMode(byte track, TRACK_MODE mode);
|
||||
static bool list(Print * stream);
|
||||
static void loop();
|
||||
static POWERMODE getMainPower();
|
||||
static POWERMODE getProgPower();
|
||||
|
Reference in New Issue
Block a user