1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-04-06 21:50:11 +02:00
CommandStation-EX/DCCEXCommands.h
2025-04-04 13:04:56 +01:00

628 lines
28 KiB
C

/*
* © 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.
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. */
// helper macro to check track letter
#define CHECKTRACK CHECK(track>='A' && track<='H', Invalid track A..H)
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
CHECK(loco>0)
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))
// todo ZZ(f,cab,byte1) CHECK(handleFunctionGroup(cab,byte1))
// todo ZZ(f,cab,byte1,byte2) CHECK(handleFunctionGroup(cab,byte1,byte2))
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);
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
CHECKTRACK
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
CHECKTRACK
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,value) // POM write cv bit on main track
// todo check values
DCC::writeCVBitMain(loco,cv,bit,value);
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 check cv values on prog track commands and 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_W4);
ZZ(W,cab) // Write loco address on PROG track
EXPECT_CALLBACK DCC::setLocoId(cab,callback_Wloco);
ZZ(W,CONSIST,cab,REVERSE) // Write consist address and reverse flag on PROG track
EXPECT_CALLBACK DCC::setConsistId(cab,true,callback_Wconsist);
ZZ(W,CONSIST,cab) // write consist address on PROG track
EXPECT_CALLBACK DCC::setConsistId(cab,false,callback_Wconsist);
ZZ(W,cv,value) // Write cv value on PROG track
EXPECT_CALLBACK DCC::writeCVByte(cv,value, callback_W);
ZZ(W,cv,value,bit) // Write cv bit on prog track
EXPECT_CALLBACK DCC::writeCVBit(cv,value,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,value) // Fast read bit with expected value
EXPECT_CALLBACK DCC::verifyCVBit(cv,bit,value,callback_Vbit);
ZZ(B,cv,bit,value) // Write cv bit
EXPECT_CALLBACK DCC::writeCVBit(cv,bit,value,callback_B);
ZZ(R,cv,ignore1,ignore2) // (Deprecated) read cv
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,value) // Set DC frequencey for loco
CHECK(value>=0 && value<=3) DCC::setDCFreq(loco,value);
ZZ(F,loco,function,value) // Set loco function ON/OFF
CHECK(value==0 || value==1) DCC::setFn(loco,function,value);
// 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
CHECKTRACK CHECK(TrackManager::setTrackMode(track,TRACK_MODE_MAIN))
ZZ(=,track,MAIN_INV) // Set track to MAIN inverted polatity
CHECKTRACK CHECK(TrackManager::setTrackMode(track,TRACK_MODE_MAIN_INV))
ZZ(=,track,MAIN_AUTO) // Set track to MAIN with auto reversing
CHECKTRACK CHECK(TrackManager::setTrackMode(track,TRACK_MODE_MAIN_AUTO))
ZZ(=,track,PROG) // Set track to PROG
CHECKTRACK CHECK(TrackManager::setTrackMode(track,TRACK_MODE_PROG))
ZZ(=,track,OFF) // Set track power OFF
CHECKTRACK CHECK(TrackManager::setTrackMode(track,TRACK_MODE_NONE))
ZZ(=,track,NONE) // Set track no output
CHECKTRACK CHECK(TrackManager::setTrackMode(track,TRACK_MODE_NONE))
ZZ(=,track,EXT) // Set track to use external sync
CHECKTRACK CHECK(TrackManager::setTrackMode(track,TRACK_MODE_EXT))
#ifdef BOOSTER_INPUT
ZZ_nodoc(=,track,BOOST) CHECKTRACK CHECK(TrackManager::setTrackMode(track,TRACK_MODE_BOOST))
ZZ_nodoc(=,track,BOOST_INV) CHECKTRACK CHECK(TrackManager::setTrackMode(track,TRACK_MODE_BOOST_INV))
ZZ_nodoc(=,track,BOOST_AUTO) CHECKTRACK) CHECK(TrackManager::setTrackMode(track,TRACK_MODE_BOOST_AUTO))
#endif
ZZ(=,track,AUTO) // Update track to auto reverse
CHECKTRACK CHECK(TrackManager::orTrackMode(track, TRACK_MODIFIER_AUTO))
ZZ(=,track,INV) // Update track to inverse polarity
CHECKTRACK CHECK(TrackManager::orTrackMode(track, TRACK_MODIFIER_INV))
ZZ(=,track,DC,locoid) // Set track to DC
CHECKTRACK CHECK(TrackManager::setTrackMode(track, TRACK_MODE_DC, locoid))
ZZ(=,track,DC_INV,locoid) // Set track to DC with inverted polarity
CHECKTRACK CHECK(TrackManager::setTrackMode(track, TRACK_MODE_DC_INV, locoid))
ZZ(=,track,DCX,locoid) // Set track to DC with inverted polarity
CHECKTRACK CHECK(TrackManager::setTrackMode(track, TRACK_MODE_DC_INV, locoid))
ZZEND