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