diff --git a/DCCEXCommands.h b/DCCEXCommands.h
new file mode 100644
index 0000000..9e0dffd
--- /dev/null
+++ b/DCCEXCommands.h
@@ -0,0 +1,456 @@
+/*
+ * © 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
+ strea, ringStream and other DCCEXParser variables are available in context. */
+
+ZZBEGIN
+ZZ(#) REPLY( "<# %d>\n", MAX_LOCOS)
+ZZ(t,cab) CHECK(cab>0)
+ CommandDistributor::broadcastLoco(DCC::lookupSpeedTable(cab,false));
+ZZ(t,cab,tspeed,direction) CHECK(setThrottle(cab,tspeed,direction))
+ZZ(t,ignore,cab,tspeed,direction) CHECK(setThrottle(cab,tspeed,direction))
+// todo ZZ(f,cab,byte1) CHECK(handleFunctionGroup(cab,byte1))
+// todo ZZ(f,cab,byte1,byte2) CHECK(handleFunctionGroup(cab,byte1,byte2))
+
+ZZ(T) Turnout::printAll(stream); // will if none found
+ZZ(T,id) CHECK(Turnout::remove(id))
+ZZ(T,id,X) auto tt=Turnout::get(id); CHECK(tt) tt->print(stream);
+ZZ(T,id,T) Turnout::setClosed(id, false);
+ZZ(T,id,C) Turnout::setClosed(id, true);
+ZZ(T,id,value) Turnout::setClosed(id, value==0);
+ZZ(T,id,SERVO,pin,low,high) CHECK(ServoTurnout::create(id, (VPIN)pin, (uint16_t)low, (uint16_t)high, 1))
+ZZ(T,id,VPIN,pin) CHECK(VpinTurnout::create(id, pin))
+ZZ(T,id,DCC,addr,subadd) CHECK(DCCTurnout::create(id, addr, subadd))
+ZZ(T,id,DCC,nn) CHECK(DCCTurnout::create(id, (nn-1)/4+1, (nn-1)%4))
+ZZ(T,id,addr,subadd) CHECK(DCCTurnout::create(id, addr, subadd))
+ZZ(T,id,pin,low,high) CHECK(ServoTurnout::create(id, (VPIN)pin,low,high,1))
+ZZ(S,id,pin,pullup) CHECK(Sensor::create(id,pin,pullup))
+ZZ(S,id) CHECK(Sensor::remove(p[0]))
+ZZ(S) for (auto *tt = Sensor::firstSensor; tt; tt = tt->nextSensor) {
+ REPLY("\n", tt->data.snum, tt->data.pin, tt->data.pullUp)
+ }
+ZZ(J,M) Stash::list(stream);
+ZZ(J,M,stash_id) Stash::list(stream, stash_id);
+ZZ(J,M,CLEAR,ALL) Stash::clearAll();
+ZZ(J,M,CLEAR,stash_id) Stash::clear(stash_id);
+ZZ(J,M,stashId,locoId) Stash::set(stashId,locoId);
+ZZ(J,M,CLEAR,ANY,locoId) Stash::clearAny(locoId);
+ZZ(J,C) REPLY("\n", CommandDistributor::retClockTime())
+ZZ(J,C,mmmm,nn) CommandDistributor::setClockTime(mmmm, nn, 1);
+
+ZZ(J,G) TrackManager::reportGauges(stream); //
+ZZ(J,I) TrackManager::reportCurrent(stream); //
+ZZ(J,L,display,row) TrackManager::reportCurrentLCD(display,row); // Track power status
+ZZ(J,A) REPLY( "\n") // intercepted by EXRAIL// returns automations/routes
+#ifdef EXRAIL_ACTIVE
+ZZ(J,R) REPLY("\n");
+ZZ(J,R,id) 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)
+#else
+ZZ(J,R) REPLY("\n") // returns empty roster list
+#endif
+ZZ(J,T) // returns turnout list
+ REPLY("next()) if (!t->isHidden()) REPLY(" %d",t->getId())
+ REPLY(">\n");
+ZZ(J,T,id) auto t=Turnout::get(id);
+ if (!t || t->isHidden()) REPLY("\n",id)
+ else {
+ 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) //
+ if (vpin>0) IODevice::write(vpin,HIGH);
+ else IODevice::write(-vpin,LOW);
+ZZ(z,vpin,analog,profile,duration) IODevice::writeAnalogue(vpin,analog,profile,duration);
+ZZ(z,vpin,analog,profile) IODevice::writeAnalogue(vpin,analog,profile,0);
+ZZ(z,vpin,analog) 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) return Turntable::printAll(stream);
+
+ZZ(I,id) // broadcast type and current position
+ auto tto = Turntable::get(id);
+ CHECK(tto)
+ REPLY("\n", tto->isEXTT(), tto->getPosition())
+
+
+ZZ(I,id,position) // - rotate a DCC turntable
+ auto tto = Turntable::get(id);
+ CHECK(tto)
+ CHECK(!tto->isEXTT())
+ CHECK(tto->setPosition(id,position))
+
+ZZ(I,id,DCC,home)
+ auto tto = Turntable::get(id);
+ CHECK(tto)
+ CHECK(home >=0 && home <= 3600)
+ CHECK(DCCTurntable::create(id))
+ tto = Turntable::get(id);
+ CHECK(tto)
+ tto->addPosition(0, 0, home);
+ REPLY("\n")
+
+ZZ(I,id,position,activity)
+ auto tto = Turntable::get(id);
+ CHECK(tto)
+ CHECK(tto->isEXTT())
+ CHECK(tto->setPosition(id, position,activity))
+
+ZZ(I,id,EXTT,vpin,home) // create an EXTT turntable
+ auto tto = Turntable::get(id);
+ CHECK(!tto && 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 a position
+ auto tto = Turntable::get(p[0]);
+ // tto must exist, no more than 48 positions, angle 0 - 3600
+ CHECK(tto && position <= 48 && angle >=0 && angle <= 3600)
+ tto->addPosition(id,value,angle);
+ REPLY("\n")
+
+ ZZ(Q) Sensor::printAll(stream);
+
+ ZZ(s) // STATUS
+ REPLY("\n", F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA))
+ 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,id,active) auto o = Output::get(id);
+ CHECK(o)
+ o->activate(active);
+ REPLY("\n", id,active)
+
+ZZ(Z,id,pin,iflag) //
+ CHECK(id > 0 && iflag >= 0 && iflag <= 7 )
+ CHECK(Output::create(id,pin,iflag, 1))
+ REPLY("\n")
+ZZ(Z,id) CHECK(Output::remove(id))
+ REPLY("\n")
+
+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)
+ZZ(D,ACK,ON) Diag::ACK = true;
+ZZ(D,ACK,OFF) Diag::ACK = false;
+ZZ(D,CABS) DCC::displayCabList(stream);
+ZZ(D,RAM) DIAG(F("Free memory=%d"), DCCTimer::getMinimumFreeMemory());
+ZZ(D,CMD,ON) Diag::CMD = true;
+ZZ(D,CMD,OFF) Diag::CMD = false;
+ZZ(D,RAILCOM,ON) Diag::RAILCOM = true;
+ZZ(D,RAILCOM,OFF) Diag::RAILCOM = false;
+ZZ(D,WIFI,ON) Diag::WIFI = true;
+ZZ(D,WIFI,OFF) Diag::WIFI = false;
+ZZ(D,ETHERNET,ON) Diag::ETHERNET = true;
+ZZ(D,ETHERNET,OFF) Diag::ETHERNET = false;
+ZZ(D,WIT,ON) Diag::WITHROTTLE = true;
+ZZ(D,WIT,OFF) Diag::WITHROTTLE = false;
+ZZ(D,LCN,ON) Diag::LCN = true;
+ZZ(D,LCN,OFF) Diag::LCN = false;
+ZZ(D,WEBSOCKET,ON) Diag::WEBSOCKET = true;
+ZZ(D,WEBSOCKET,OFF) Diag::WEBSOCKET = false;
+
+#ifndef DISABLE_EEPROM
+ZZ(D,EEPROM,numentries) EEStore::dump(numentries);
+#endif
+
+
+ZZ(D,ANOUT,vpin,position) IODevice::writeAnalogue(vpin,position,0);
+ZZ(D,ANOUT,vpin,position,profile) IODevice::writeAnalogue(vpin,position,profile);
+ZZ(D,SERVO,vpin,position) IODevice::writeAnalogue(vpin,position,0);
+ZZ(D,SERVO,vpin,position,profile) 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) IODevice::DumpAll();
+ZZ(D,HAL,RESET) IODevice::reset();
+ZZ(D,TT,vpin,steps) IODevice::writeAnalogue(vpin,steps,0);
+ZZ(D,TT,vpin,steps,activity) IODevice::writeAnalogue(vpin,steps,activity);
+
+ZZ(C,PROGBOOST) TrackManager::progTrackBoosted=true;
+ZZ(C,RESET) DCCTimer::reset();
+ZZ(C,SPEED28) DCC::setGlobalSpeedsteps(28); DIAG(F("28 Speedsteps"));
+ZZ(C,SPEED128) DCC::setGlobalSpeedsteps(128); DIAG(F("128 Speedsteps"));
+ZZ(C,RAILCOM,ON) DIAG(F("Railcom %S"),DCCWaveform::setRailcom(true,false)?F("ON"):F("OFF"));
+ZZ(C,RAILCOM,OFF) DIAG(F("Railcom OFF")); DCCWaveform::setRailcom(false,false);
+ZZ(C,RAILCOM,DEBUG) DIAG(F("Railcom %S"), DCCWaveform::setRailcom(true,true)?F("ON"):F("OFF"));
+
+#ifndef DISABLE_PROG
+ZZ(D,ACK,LIMIT,value) DCCACK::setAckLimit(value); LCD(1, F("Ack Limit=%dmA"), value);
+ZZ(D,ACK,MIN,value,MS) DCCACK::setMinAckPulseDuration(value*1000L); LCD(1, F("Ack Min=%dmS"), value);
+ZZ(D,ACK,MIN,value) DCCACK::setMinAckPulseDuration(value); LCD(1, F("Ack Min=%duS"), value);
+ZZ(D,ACK,MAX,value,MS) DCCACK::setMaxAckPulseDuration(value*1000L); LCD(1, F("Ack Max=%dmS"), value);
+ZZ(D,ACK,MAX,value) DCCACK::setMaxAckPulseDuration(value); LCD(1, F("Ack Max=%duS"), value);
+ZZ(D,ACK,RETRY,value) DCCACK::setAckRetry(value); LCD(1, F("Ack Retry=%d"), value);
+#endif
+#if defined(ARDUINO_ARCH_ESP32)
+// currently this only works on ESP32
+ZZ(C,WIFI,marker1,ssid,marker2,password)
+ //
+ CHECK(marker1==0x7777 && marker2==0x7777)
+ WifiESP::setup((const char*)(com + p[2]), (const char*)(com + p[4]), WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
+#endif
+
+ZZ(o,vpin) IODevice::write(abs(vpin),vpin>0);
+ZZ(o,vpin,count) IODevice::writeRange(abs(vpin),vpin>0,count);
+ZZ(o,vpin,r,g,b) CHECK(r>-0 && r<=0xff) CHECK(g>-0 && g<=0xff) CHECK(b>-0 && b<=0xff)
+ IODevice::writeAnalogueRange(abs(vpin),vpin>0,r<<8 | g,b,1);
+ZZ(o,vpin,r,g,b,count) CHECK(r>-0 && r<=0xff) CHECK(g>-0 && g<=0xff) CHECK(b>-0 && b<=0xff)
+ IODevice::writeAnalogueRange(abs(vpin),vpin>0,r<<8 | g,b,count);
+
+ZZ(1) TrackManager::setTrackPower(TRACK_ALL, POWERMODE::ON);
+ZZ(1,MAIN) TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::ON);
+#ifndef DISABLE_PROG
+ZZ(1,PROG) TrackManager::setJoin(false); TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::ON);
+ZZ(1,JOIN) TrackManager::setJoin(true); TrackManager::setTrackPower(TRACK_MODE_MAIN|TRACK_MODE_PROG, POWERMODE::ON);
+#endif
+ZZ(1,letter) CHECK(letter>='A' && letter<='H') TrackManager::setTrackPower(POWERMODE::ON, (byte)letter-'A');
+
+ZZ(0) TrackManager::setJoin(false); TrackManager::setTrackPower(TRACK_ALL, POWERMODE::OFF);
+ZZ(0,MAIN)TrackManager::setJoin(false); TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::OFF);
+ZZ(0,PROG) 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,letter) CHECK(letter>='A' && letter <='H')
+ TrackManager::setJoin(false);
+ TrackManager::setTrackPower(POWERMODE::OFF, (byte)letter-'a');
+
+ZZ(!) DCC::estopAll(); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
+ZZ(c) TrackManager::reportObsoleteCurrent(stream);
+
+ZZ(a,address,subaddress,activate) DCC::setAccessory(address, subaddress,activate ^ accessoryCommandReverse);
+ZZ(a,address,subaddress,activate,onoff) CHECK(onoff>=0 && onoff<-2)
+ DCC::setAccessory(address, subaddress,activate ^ accessoryCommandReverse ,onoff);
+ZZ(a,linearaddress,activate)
+ DCC::setAccessory((linearaddress - 1) / 4 + 1,(linearaddress - 1) % 4 ,activate ^ accessoryCommandReverse);
+ZZ(A,address,value) DCC::setExtendedAccessory(address,value);
+
+ZZ(w,cab,cv,value) DCC::writeCVByteMain(p[0], p[1], p[2]);
+ZZ(r,cab,cv)
+ CHECK(DCCWaveform::isRailcom())
+ EXPECT_CALLBACK
+ DCC::readCVByteMain(cab,cv,callback_r);
+ ZZ(b,cab,cv,bit,value) DCC::writeCVBitMain(cab,cv,bit,value);
+ ZZ(m,LINEAR) DCC::linearAcceleration=true;
+ ZZ(m,POWER) DCC::linearAcceleration=false;
+ ZZ(m,cab,momentum) CHECK(DCC::setMomentum(cab,momentum,momentum))
+ ZZ(m,cab,momentum,braking) CHECK(DCC::setMomentum(cab,momentum,braking))
+
+ ZZ(W,cv,value,ignore1,ignore2) EXPECT_CALLBACK DCC::writeCVByte(cv,value, callback_W4);
+ZZ(W,cab) EXPECT_CALLBACK DCC::setLocoId(cab,callback_Wloco);
+ZZ(W,CONSIST,cab,REVERSE) EXPECT_CALLBACK DCC::setConsistId(cab,true,callback_Wconsist);
+ZZ(W,CONSIST,cab) EXPECT_CALLBACK DCC::setConsistId(cab,false,callback_Wconsist);
+ZZ(W,cv,value) EXPECT_CALLBACK DCC::writeCVByte(cv,value, callback_W);
+ZZ(W,cv,value,bit) EXPECT_CALLBACK DCC::writeCVBit(cv,value,bit,callback_W);
+ZZ(V,cv,value) EXPECT_CALLBACK DCC::verifyCVByte(cv,value, callback_Vbyte);
+ZZ(V,cv,bit,value) EXPECT_CALLBACK DCC::verifyCVBit(cv,bit,value,callback_Vbit);
+
+ZZ(B,cv,bit,value) EXPECT_CALLBACK DCC::writeCVBit(cv,bit,value,callback_B);
+ZZ(R,cv,ignore1,ignore2) EXPECT_CALLBACK DCC::readCV(cv,callback_R);
+ZZ(R,cv) EXPECT_CALLBACK DCC::verifyCVByte(cv, 0, callback_Vbyte);
+ZZ(R) EXPECT_CALLBACK DCC::getLocoId(callback_Rloco);
+
+#ifndef DISABLE_VDPY
+ZZ(@) CommandDistributor::setVirtualLCDSerial(stream);
+ REPLY( "<@ 0 0 \"DCC-EX v" VERSION "\">\n<@ 0 1 \"Lic GPLv3\">\n")
+#endif
+
+ZZ(-) DCC::forgetAllLocos();
+ZZ(-,cab) DCC::forgetLoco(cab);
+ZZ(F,cab,DCCFREQ,value) CHECK(value>=0 && value<=3) DCC::setDCFreq(cab,value);
+ZZ(F,cab,function,value) CHECK(value==0 || value==1) DCC::setFn(cab,function,value);
+
+
+ZZ(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(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(M,ignore,d0,d1,d2,d3) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3}; DCCWaveform::mainTrack.schedulePacket(packet,sizeof(packet),3);
+ZZ(M,ignore,d0,d1,d2) byte packet[]={(byte)d0,(byte)d1,(byte)d2}; DCCWaveform::mainTrack.schedulePacket(packet,sizeof(packet),3);
+ZZ(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) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3,(byte)d4,(byte)d5}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
+ZZ(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(P,ignore,d0,d1,d2,d3) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
+ZZ(P,ignore,d0,d1,d2) byte packet[]={(byte)d0,(byte)d1,(byte)d2}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
+ZZ(P,ignore,d0,d1) byte packet[]={(byte)d0,(byte)d1}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
+
+ZZ(J,O) REPLY("next()) if (!tto->isHidden()) REPLY(" %d",tto->getId())
+ REPLY(">\n")
+ZZ(J,O,id) 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) 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)
+ }
+
+
+ZZEND
diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp
index c9f4bcc..d2f6ba3 100644
--- a/DCCEXParser.cpp
+++ b/DCCEXParser.cpp
@@ -141,11 +141,11 @@ Print *DCCEXParser::stashStream = NULL;
RingStream *DCCEXParser::stashRingStream = NULL;
byte DCCEXParser::stashTarget=0;
-// This is a JMRI command parser.
+// This is a DCC-EX command parser.
// It doesnt know how the string got here, nor how it gets back.
// It knows nothing about hardware or tracks... it just parses strings and
// calls the corresponding DCC api.
-// Non-DCC things like turnouts, pins and sensors are handled in additional JMRI interface classes.
+// Non-DCC things like turnouts, pins and sensors are handled in additional interface classes.
int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], byte *cmd, bool usehex)
@@ -177,7 +177,6 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], byte *cmd,
continue;
case 2: // checking sign or quoted string
-#ifdef HAS_ENOUGH_MEMORY
if (hot == '"') {
// this inserts an extra parameter 0x7777 in front
// of each string parameter as a marker that can
@@ -191,7 +190,6 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], byte *cmd,
state = 4;
break;
}
-#endif
signNegative = false;
runningValue = 0;
state = 3;
@@ -213,7 +211,6 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], byte *cmd,
}
if (hot=='_' || (hot >= 'A' && hot <= 'Z'))
{
- // Since JMRI got modified to send keywords in some rare cases, we need this
// Super Kluge to turn keywords into a hash value that can be recognised later
runningValue = ((runningValue << 5) + runningValue) ^ hot;
break;
@@ -222,7 +219,6 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], byte *cmd,
parameterCount++;
state = 1;
continue;
-#ifdef HAS_ENOUGH_MEMORY
case 4: // skipover text
if (hot == '\0') // We did run to end of buffer without finding the "
return -1;
@@ -231,7 +227,6 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], byte *cmd,
state = 1;
}
break;
-#endif
}
remainingCmd++;
}
@@ -378,352 +373,16 @@ bool DCCEXParser::funcmap(int16_t cab, byte value, byte fstart, byte fstop)
return true;
}
+#ifdef DCC_ACCESSORY_COMMAND_REVERSE
+ const bool accessoryCommandReverse = true;
+#else
+ const bool accessoryCommandReverse = false;
+#endif
+
+// Having broken the command into opcode and parameters, we now execute the command
+// The actual commands and their parameter mappings are in DCCEXCommands.h
bool DCCEXParser::execute(byte * com,Print *stream, byte opcode,byte params, int16_t p[], RingStream * ringStream) {
-bool accessoryCommandReverse = false; // TODO
-ZZBEGIN
-ZZ(#) StringFormatter::send(stream, F("<# %d>\n"), MAX_LOCOS);
-ZZ(t,cab) CHECK(cab>0)
- CommandDistributor::broadcastLoco(DCC::lookupSpeedTable(cab,false));
-ZZ(t,cab,tspeed,direction) CHECK(setThrottle(cab,tspeed,direction))
-ZZ(t,ignore,cab,tspeed,direction) CHECK(setThrottle(cab,tspeed,direction))
-// todo ZZ(f,cab,byte1) CHECK(handleFunctionGroup(cab,byte1))
-// todo ZZ(f,cab,byte1,byte2) CHECK(handleFunctionGroup(cab,byte1,byte2))
-
-ZZ(T) Turnout::printAll(stream); // will if none found
-ZZ(T,id) CHECK(Turnout::remove(id))
-ZZ(T,id,X) auto tt=Turnout::get(id); CHECK(tt) tt->print(stream);
-ZZ(T,id,T) Turnout::setClosed(id, false);
-ZZ(T,id,C) Turnout::setClosed(id, true);
-ZZ(T,id,value) Turnout::setClosed(id, value==0);
-ZZ(T,id,SERVO,pin,low,high) CHECK(ServoTurnout::create(id, (VPIN)pin, (uint16_t)low, (uint16_t)high, 1))
-ZZ(T,id,VPIN,pin) CHECK(VpinTurnout::create(id, pin))
-ZZ(T,id,DCC,addr,subadd) CHECK(DCCTurnout::create(id, addr, subadd))
-ZZ(T,id,DCC,nn) CHECK(DCCTurnout::create(id, (nn-1)/4+1, (nn-1)%4))
-ZZ(T,id,addr,subadd) CHECK(DCCTurnout::create(id, addr, subadd))
-ZZ(T,id,pin,low,high) CHECK(ServoTurnout::create(id, (VPIN)pin,low,high,1))
-ZZ(S,id,pin,pullup) CHECK(Sensor::create(id,pin,pullup))
-ZZ(S,id) CHECK(Sensor::remove(p[0]))
-ZZ(S) for (auto *tt = Sensor::firstSensor; tt; tt = tt->nextSensor) {
- StringFormatter::send(stream, F("\n"), tt->data.snum, tt->data.pin, tt->data.pullUp);
- }
-ZZ(J,M) Stash::list(stream);
-ZZ(J,M,stash_id) Stash::list(stream, stash_id);
-ZZ(J,M,CLEAR,ALL) Stash::clearAll();
-ZZ(J,M,CLEAR,stash_id) Stash::clear(stash_id);
-ZZ(J,M,stashId,locoId) Stash::set(stashId,locoId);
-ZZ(J,M,CLEAR,ANY,locoId) Stash::clearAny(locoId);
-ZZ(J,C) StringFormatter::send(stream, F("\n"), CommandDistributor::retClockTime());
-ZZ(J,C,mmmm,nn) CommandDistributor::setClockTime(mmmm, nn, 1);
-
-ZZ(J,G) TrackManager::reportGauges(stream); //
-ZZ(J,I) TrackManager::reportCurrent(stream); //
-ZZ(J,L,display,row) TrackManager::reportCurrentLCD(display,row); // Track power status
-ZZ(J,A) StringFormatter::send(stream, F("\n")); // intercepted by EXRAIL// returns automations/routes
-ZZ(J,R) StringFormatter::send(stream, F("\n"));
-ZZ(J,R,id) auto rosterName= RMFT2::getRosterName(id);
- if (!rosterName) rosterName=F("");
- auto functionNames= RMFT2::getRosterFunctions(id);
- if (!functionNames) functionNames=RMFT2::getRosterFunctions(0);
- if (!functionNames) functionNames=F("");
- StringFormatter::send(stream,F(" %d \"%S\" \"%S\">\n"),
- id, rosterName, functionNames);
-ZZ(J,T) // returns turnout list
- StringFormatter::send(stream, F("next()) {
- if (t->isHidden()) continue;
- StringFormatter::send(stream, F(" %d"),t->getId());
- }
-
- StringFormatter::send(stream, F(">\n"));
-ZZ(J,T,id) auto t=Turnout::get(id);
- if (!t || t->isHidden()) StringFormatter::send(stream, F(" %d X"),id);
- else {
- const FSH *tdesc = RMFT2::getTurnoutDescription(id);
- if (!tdesc) tdesc = F("");
- StringFormatter::send(stream, F("\n"),
- id,t->isThrown()?'T':'C',
- tdesc);
- }
-ZZ(z,vpin) //
- if (vpin>0) IODevice::write(vpin,HIGH);
- else IODevice::write(-vpin,LOW);
-ZZ(z,vpin,analog,profile,duration) IODevice::writeAnalogue(vpin,analog,profile,duration);
-ZZ(z,vpin,analog,profile) IODevice::writeAnalogue(vpin,analog,profile,0);
-ZZ(z,vpin,analog) 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) return Turntable::printAll(stream);
-
-ZZ(I,id) // broadcast type and current position
- auto tto = Turntable::get(id);
- CHECK(tto)
- StringFormatter::send(stream, F("\n"), tto->isEXTT(), tto->getPosition());
-
-
-ZZ(I,id,position) // - rotate a DCC turntable
- auto tto = Turntable::get(id);
- CHECK(tto)
- CHECK(!tto->isEXTT())
- CHECK(tto->setPosition(id,position))
-
-ZZ(I,id,DCC,home)
- auto tto = Turntable::get(id);
- CHECK(tto)
- CHECK(home >=0 && home <= 3600)
- CHECK(DCCTurntable::create(id))
- tto = Turntable::get(id);
- CHECK(tto)
- tto->addPosition(0, 0, home);
- StringFormatter::send(stream, F("\n"));
-
-ZZ(I,id,position,activity)
- auto tto = Turntable::get(id);
- CHECK(tto)
- CHECK(tto->isEXTT())
- CHECK(tto->setPosition(id, position,activity))
-
-ZZ(I,id,EXTT,vpin,home) // create an EXTT turntable
- auto tto = Turntable::get(id);
- CHECK(!tto && home >= 0 && home <= 3600)
- CHECK(EXTTTurntable::create(id, (VPIN)vpin))
- tto = Turntable::get(id);
- tto->addPosition(0, 0, home);
- StringFormatter::send(stream, F("\n"));
-
-
-ZZ(I,id,ADD,position,value,angle) // add a position
- auto tto = Turntable::get(p[0]);
- // tto must exist, no more than 48 positions, angle 0 - 3600
- CHECK(tto && position <= 48 && angle >=0 && angle <= 3600)
- tto->addPosition(id,value,angle);
- StringFormatter::send(stream, F("\n"));
-
- ZZ(Q) Sensor::printAll(stream);
-
- ZZ(s) // STATUS
- StringFormatter::send(stream, F("\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
- 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();
- StringFormatter::send(stream, F("\n"), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs);
-
- ZZ(e) // CLEAR EPROM
- EEStore::clear();
- StringFormatter::send(stream, F("\n"));
-
-#endif
-
-ZZ(Z,id,active) auto o = Output::get(id);
- CHECK(o)
- o->activate(active);
- StringFormatter::send(stream, F("\n"), id,active);
-
-ZZ(Z,id,pin,iflag) //
- CHECK(id > 0 && iflag >= 0 && iflag <= 7 )
- CHECK(Output::create(id,pin,iflag, 1))
- StringFormatter::send(stream, F("\n"));
-ZZ(Z,id) CHECK(Output::remove(id))
- StringFormatter::send(stream, F("\n"));
-
-ZZ(Z) // list Output definitions
- bool gotone = false;
- for (auto *tt = Output::firstOutput; tt ; tt = tt->nextOutput){
- gotone = true;
- StringFormatter::send(stream, F("\n"),
- tt->data.id, tt->data.pin, tt->data.flags, tt->data.active);
- }
- CHECK(gotone)
-ZZ(D,ACK,ON) Diag::ACK = true;
-ZZ(D,ACK,OFF) Diag::ACK = false;
-ZZ(D,CABS) DCC::displayCabList(stream);
-ZZ(D,RAM) DIAG(F("Free memory=%d"), DCCTimer::getMinimumFreeMemory());
-ZZ(D,CMD,ON) Diag::CMD = true;
-ZZ(D,CMD,OFF) Diag::CMD = false;
-ZZ(D,RAILCOM,ON) Diag::RAILCOM = true;
-ZZ(D,RAILCOM,OFF) Diag::RAILCOM = false;
-ZZ(D,WIFI,ON) Diag::WIFI = true;
-ZZ(D,WIFI,OFF) Diag::WIFI = false;
-ZZ(D,ETHERNET,ON) Diag::ETHERNET = true;
-ZZ(D,ETHERNET,OFF) Diag::ETHERNET = false;
-ZZ(D,WIT,ON) Diag::WITHROTTLE = true;
-ZZ(D,WIT,OFF) Diag::WITHROTTLE = false;
-ZZ(D,LCN,ON) Diag::LCN = true;
-ZZ(D,LCN,OFF) Diag::LCN = false;
-ZZ(D,WEBSOCKET,ON) Diag::WEBSOCKET = true;
-ZZ(D,WEBSOCKET,OFF) Diag::WEBSOCKET = false;
-
-#ifndef DISABLE_EEPROM
-ZZ(D,EEPROM,numentries) EEStore::dump(numentries);
-#endif
-
-
-ZZ(D,ANOUT,vpin,position) IODevice::writeAnalogue(vpin,position,0);
-ZZ(D,ANOUT,vpin,position,profile) IODevice::writeAnalogue(vpin,position,profile);
-ZZ(D,SERVO,vpin,position) IODevice::writeAnalogue(vpin,position,0);
-ZZ(D,SERVO,vpin,position,profile) 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) IODevice::DumpAll();
-ZZ(D,HAL,RESET) IODevice::reset();
-ZZ(D,TT,vpin,steps) IODevice::writeAnalogue(vpin,steps,0);
-ZZ(D,TT,vpin,steps,activity) IODevice::writeAnalogue(vpin,steps,activity);
-
-ZZ(C,PROGBOOST) TrackManager::progTrackBoosted=true;
-ZZ(C,RESET) DCCTimer::reset();
-ZZ(C,SPEED28) DCC::setGlobalSpeedsteps(28); DIAG(F("28 Speedsteps"));
-ZZ(C,SPEED128) DCC::setGlobalSpeedsteps(128); DIAG(F("128 Speedsteps"));
-ZZ(C,RAILCOM,ON) DIAG(F("Railcom %S"),DCCWaveform::setRailcom(true,false)?F("ON"):F("OFF"));
-ZZ(C,RAILCOM,OFF) DIAG(F("Railcom OFF")); DCCWaveform::setRailcom(false,false);
-ZZ(C,RAILCOM,DEBUG) DIAG(F("Railcom %S"), DCCWaveform::setRailcom(true,true)?F("ON"):F("OFF"));
-
-#ifndef DISABLE_PROG
-ZZ(D,ACK,LIMIT,value) DCCACK::setAckLimit(value); LCD(1, F("Ack Limit=%dmA"), value);
-ZZ(D,ACK,MIN,value,MS) DCCACK::setMinAckPulseDuration(value*1000L); LCD(1, F("Ack Min=%dmS"), value);
-ZZ(D,ACK,MIN,value) DCCACK::setMinAckPulseDuration(value); LCD(1, F("Ack Min=%duS"), value);
-ZZ(D,ACK,MAX,value,MS) DCCACK::setMaxAckPulseDuration(value*1000L); LCD(1, F("Ack Max=%dmS"), value);
-ZZ(D,ACK,MAX,value) DCCACK::setMaxAckPulseDuration(value); LCD(1, F("Ack Max=%duS"), value);
-ZZ(D,ACK,RETRY,value) DCCACK::setAckRetry(value); LCD(1, F("Ack Retry=%d"), value);
-#endif
-#if defined(ARDUINO_ARCH_ESP32)
-// currently this only works on ESP32
-ZZ(C,WIFI,marker1,ssid,marker2,password)
- //
- CHECK(marker1==0x7777 && marker2==0x7777)
- WifiESP::setup((const char*)(com + p[2]), (const char*)(com + p[4]), WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
-#endif
-
-ZZ(o,vpin) IODevice::write(abs(vpin),vpin>0);
-ZZ(o,vpin,count) IODevice::writeRange(abs(vpin),vpin>0,count);
-ZZ(o,vpin,r,g,b) CHECK(r>-0 && r<=0xff) CHECK(g>-0 && g<=0xff) CHECK(b>-0 && b<=0xff)
- IODevice::writeAnalogueRange(abs(vpin),vpin>0,r<<8 | g,b,1);
-ZZ(o,vpin,r,g,b,count) CHECK(r>-0 && r<=0xff) CHECK(g>-0 && g<=0xff) CHECK(b>-0 && b<=0xff)
- IODevice::writeAnalogueRange(abs(vpin),vpin>0,r<<8 | g,b,count);
-
-ZZ(1) TrackManager::setTrackPower(TRACK_ALL, POWERMODE::ON);
-ZZ(1,MAIN) TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::ON);
-#ifndef DISABLE_PROG
-ZZ(1,PROG) TrackManager::setJoin(false); TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::ON);
-ZZ(1,JOIN) TrackManager::setJoin(true); TrackManager::setTrackPower(TRACK_MODE_MAIN|TRACK_MODE_PROG, POWERMODE::ON);
-#endif
-ZZ(1,letter) CHECK(letter>='A' && letter<='H') TrackManager::setTrackPower(POWERMODE::ON, (byte)letter-'A');
-
-ZZ(0) TrackManager::setJoin(false); TrackManager::setTrackPower(TRACK_ALL, POWERMODE::OFF);
-ZZ(0,MAIN)TrackManager::setJoin(false); TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::OFF);
-ZZ(0,PROG) 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,letter) CHECK(letter>='A' && letter <='H')
- TrackManager::setJoin(false);
- TrackManager::setTrackPower(POWERMODE::OFF, (byte)letter-'a');
-
-ZZ(!) DCC::estopAll(); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
-ZZ(c) TrackManager::reportObsoleteCurrent(stream);
-
-ZZ(a,address,subaddress,activate) DCC::setAccessory(address, subaddress,activate ^ accessoryCommandReverse);
-ZZ(a,address,subaddress,activate,onoff) CHECK(onoff>=0 && onoff<-2)
- DCC::setAccessory(address, subaddress,activate ^ accessoryCommandReverse ,onoff);
-ZZ(a,linearaddress,activate)
- DCC::setAccessory((linearaddress - 1) / 4 + 1,(linearaddress - 1) % 4 ,activate ^ accessoryCommandReverse);
-ZZ(A,address,value) DCC::setExtendedAccessory(address,value);
-
-ZZ(w,cab,cv,value) DCC::writeCVByteMain(p[0], p[1], p[2]);
-ZZ(r,cab,cv)
- CHECK(DCCWaveform::isRailcom())
- EXPECT_CALLBACK
- DCC::readCVByteMain(cab,cv,callback_r);
- ZZ(b,cab,cv,bit,value) DCC::writeCVBitMain(cab,cv,bit,value);
- ZZ(m,LINEAR) DCC::linearAcceleration=true;
- ZZ(m,POWER) DCC::linearAcceleration=false;
- ZZ(m,cab,momentum) CHECK(DCC::setMomentum(cab,momentum,momentum))
- ZZ(m,cab,momentum,braking) CHECK(DCC::setMomentum(cab,momentum,braking))
-
- ZZ(W,cv,value,ignore1,ignore2) EXPECT_CALLBACK DCC::writeCVByte(cv,value, callback_W4);
-ZZ(W,cab) EXPECT_CALLBACK DCC::setLocoId(cab,callback_Wloco);
-ZZ(W,CONSIST,cab,REVERSE) EXPECT_CALLBACK DCC::setConsistId(cab,true,callback_Wconsist);
-ZZ(W,CONSIST,cab) EXPECT_CALLBACK DCC::setConsistId(cab,false,callback_Wconsist);
-ZZ(W,cv,value) EXPECT_CALLBACK DCC::writeCVByte(cv,value, callback_W);
-ZZ(W,cv,value,bit) EXPECT_CALLBACK DCC::writeCVBit(cv,value,bit,callback_W);
-ZZ(V,cv,value) EXPECT_CALLBACK DCC::verifyCVByte(cv,value, callback_Vbyte);
-ZZ(V,cv,bit,value) EXPECT_CALLBACK DCC::verifyCVBit(cv,bit,value,callback_Vbit);
-
-ZZ(B,cv,bit,value) EXPECT_CALLBACK DCC::writeCVBit(cv,bit,value,callback_B);
-ZZ(R,cv,ignore1,ignore2) EXPECT_CALLBACK DCC::readCV(cv,callback_R);
-ZZ(R,cv) EXPECT_CALLBACK DCC::verifyCVByte(cv, 0, callback_Vbyte);
-ZZ(R) EXPECT_CALLBACK DCC::getLocoId(callback_Rloco);
-
-#ifndef DISABLE_VDPY
-ZZ(@) CommandDistributor::setVirtualLCDSerial(stream);
- StringFormatter::send(stream,
- F("<@ 0 0 \"DCC-EX v" VERSION "\">\n"
- "<@ 0 1 \"Lic GPLv3\">\n"));
-#endif
-
-ZZ(-) DCC::forgetAllLocos();
-ZZ(-,cab) DCC::forgetLoco(cab);
-ZZ(F,cab,DCCFREQ,value) CHECK(value>=0 && value<=3) DCC::setDCFreq(cab,value);
-ZZ(F,cab,function,value) CHECK(value==0 || value==1) DCC::setFn(cab,function,value);
-
-
-ZZ(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(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(M,ignore,d0,d1,d2,d3) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3}; DCCWaveform::mainTrack.schedulePacket(packet,sizeof(packet),3);
-ZZ(M,ignore,d0,d1,d2) byte packet[]={(byte)d0,(byte)d1,(byte)d2}; DCCWaveform::mainTrack.schedulePacket(packet,sizeof(packet),3);
-ZZ(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) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3,(byte)d4,(byte)d5}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
-ZZ(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(P,ignore,d0,d1,d2,d3) byte packet[]={(byte)d0,(byte)d1,(byte)d2,(byte)d3}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
-ZZ(P,ignore,d0,d1,d2) byte packet[]={(byte)d0,(byte)d1,(byte)d2}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
-ZZ(P,ignore,d0,d1) byte packet[]={(byte)d0,(byte)d1}; DCCWaveform::progTrack.schedulePacket(packet,sizeof(packet),3);
-
-ZZ(J,O) StringFormatter::send(stream, F("next()) {
- if (!tto->isHidden()) StringFormatter::send(stream, F(" %d"),tto->getId());
- }
- StringFormatter::send(stream, F(">\n"));
-ZZ(J,O,id) auto tto=Turntable::get(id);
- if (!tto || tto->isHidden()) {StringFormatter::send(stream, F("\n"), id); return true;}
- const FSH *todesc = nullptr;
-#ifdef EXRAIL_ACTIVE
- todesc = RMFT2::getTurntableDescription(id);
-#endif
- if (todesc == nullptr) todesc = F("");
- StringFormatter::send(stream, F("\n"), id, tto->isEXTT(), tto->getPosition(), tto->getPositionCount(), todesc);
-
-ZZ(J,P,id) auto tto=Turntable::get(id);
- if (!tto || tto->isHidden()) {StringFormatter::send(stream, F("\n"), id); return true;}
- auto posCount = tto->getPositionCount();
- if (posCount==0) {StringFormatter::send(stream, F("\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("");
- StringFormatter::send(stream, F("\n"), id, p, tto->getPositionAngle(p), tpdesc);
- }
-
-
-ZZEND
-
+ #include "DCCEXCommands.h"
}
// CALLBACKS must be static
diff --git a/DCCEXParserMacros.h b/DCCEXParserMacros.h
index d1c8604..ed27bb3 100644
--- a/DCCEXParserMacros.h
+++ b/DCCEXParserMacros.h
@@ -29,5 +29,6 @@
#define ZZBEGIN if (false) {
#define ZZEND return true; } return false;
#define CHECK(x) if (!(x)) return false;
+#define REPLY(format,...) StringFormatter::send(stream,F(format), ##__VA_ARGS__);
#define EXPECT_CALLBACK CHECK(stashCallback(stream, p, ringStream))