/* * © 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 . */ /* 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 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 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("\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("\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( "\n") ZZ(J,R) // List Roster 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("\n",id, rosterName, functionNames) #endif ZZ(J,T) // Get turnout list REPLY("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("\n",id) return true; } const FSH *tdesc=nullptr; #ifdef EXRAIL_ACTIVE tdesc = RMFT2::getTurnoutDescription(id); #endif if (!tdesc) tdesc = F(""); REPLY("\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 // - list all // - broadcast type and current position // - create DCC - This is TBA // - operate (DCC) // - operate (EXTT) // - add position // - 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("\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("\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("\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("\n") ZZ(Q) // List all sensors Sensor::printAll(stream); ZZ(s) // Command station status REPLY("\n", DCC::getMotorShieldName()) CommandDistributor::broadcastPower(); // 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("\n", EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs) ZZ(e) // CLEAR EPROM EEStore::clear(); REPLY("\n") #endif ZZ(Z) // List Output definitions bool gotone = false; for (auto *tt = Output::firstOutput; tt ; tt = tt->nextOutput) { gotone = true; REPLY("\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("\n") ZZ(Z,id,active) // Set output auto o = Output::get(id); CHECK(o,Output not found) o->activate(active); REPLY("\n", id,active) ZZ(Z,id) // Delete output CHECK(Output::remove(id)) REPLY("\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 IODevice::writeAnalogue(vpin,position,0); ZZ(D,ANOUT,vpin,position,profile) // see 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("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("\n", id) return true;} const FSH *todesc = nullptr; #ifdef EXRAIL_ACTIVE todesc = RMFT2::getTurntableDescription(id); #endif if (todesc == nullptr) todesc = F(""); REPLY("\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("\n", id) return true;} auto posCount = tto->getPositionCount(); if (posCount==0) {REPLY("\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("\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