diff --git a/DCCEXCommands.h b/DCCEXCommands.h index d7e16e4..ed54381 100644 --- a/DCCEXCommands.h +++ b/DCCEXCommands.h @@ -113,80 +113,120 @@ 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. */ + 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) +// helper macro to hide command from documentation extractor +#define ZZ_nodoc ZZ 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)) +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) 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(id)) -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(" 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"); -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); +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 - 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); +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 @@ -198,238 +238,335 @@ ZZ(z,vpin,analog) IODevice::writeAnalogue(vpin,analog,0,0); // - 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()) +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) - auto tto = Turntable::get(id); - CHECK(tto) +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) +ZZ(I,id,position,activity) // Rotate an EXTT turntable auto tto = Turntable::get(id); - CHECK(tto) - CHECK(tto->isEXTT()) + 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 +ZZ(I,id,EXTT,vpin,home) // Create an EXTT turntable auto tto = Turntable::get(id); - CHECK(!tto && home >= 0 && home <= 3600) + 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(I,id,ADD,position,value,angle) // add a position - auto tto = Turntable::get(id); - // 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) // List all sensors + Sensor::printAll(stream); - ZZ(Q) Sensor::printAll(stream); - - ZZ(s) // STATUS - REPLY("\n", F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)) + 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 + ZZ(E) // STORE EPROM EEStore::store(); REPLY("\n", EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs) - - ZZ(e) // CLEAR EPROM + 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) // 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) // - 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,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(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; +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) EEStore::dump(numentries); +ZZ(D,EEPROM,numentries) // Dump EEPROM contents + 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,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 +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(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) 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")); +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) 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); +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) -// currently this only works on ESP32 -ZZ(C,WIFI,marker1,ssid,marker2,password) - // - CHECK(marker1==0x7777 && marker2==0x7777) - WifiESP::setup((const char*)(com + ssid), (const char*)(com + password), WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP); + // 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) 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(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) TrackManager::setTrackPower(TRACK_ALL, POWERMODE::ON); -ZZ(1,MAIN) TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::ON); +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) 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); +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,letter) CHECK(letter>='A' && letter<='H') TrackManager::setTrackPower(POWERMODE::ON, (byte)letter-'A'); +ZZ(1,track) // Power on given track + CHECKTRACK + TrackManager::setTrackPower(POWERMODE::ON, (byte)track-'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(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(!) DCC::estopAll(); // this broadcasts speed 1(estop) and sets all reminders to speed 1. -ZZ(c) TrackManager::reportObsoleteCurrent(stream); +ZZ(c) // Report main track currect (Deprecated) + 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(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,cab,cv,value) DCC::writeCVByteMain(cab,cv,value); -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,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)) - 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); + // 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) 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); +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(@) CommandDistributor::setVirtualLCDSerial(stream); +ZZ_nodoc(@) 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(-) // 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(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); +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 @@ -438,7 +575,8 @@ ZZ(J,O,id) auto tto=Turntable::get(id); if (todesc == nullptr) todesc = F(""); REPLY("\n", id, tto->isEXTT(), tto->getPosition(), tto->getPositionCount(), todesc) -ZZ(J,P,id) auto tto=Turntable::get(id); +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;} @@ -453,24 +591,38 @@ ZZ(J,P,id) auto tto=Turntable::get(id); } // Track manager -ZZ(=) TrackManager::list(stream); -ZZ(=,track,MAIN) CHECK(track>='A' && track<='H') CHECK(TrackManager::setTrackMode(track,TRACK_MODE_MAIN)) -ZZ(=,track,MAIN_INV) CHECK(track>='A' && track<='H') CHECK(TrackManager::setTrackMode(track,TRACK_MODE_MAIN_INV)) -ZZ(=,track,MAIN_AUTO) CHECK(track>='A' && track<='H') CHECK(TrackManager::setTrackMode(track,TRACK_MODE_MAIN_AUTO)) -ZZ(=,track,PROG) CHECK(track>='A' && track<='H') CHECK(TrackManager::setTrackMode(track,TRACK_MODE_PROG)) -ZZ(=,track,OFF) CHECK(track>='A' && track<='H') CHECK(TrackManager::setTrackMode(track,TRACK_MODE_NONE)) -ZZ(=,track,NONE) CHECK(track>='A' && track<='H') CHECK(TrackManager::setTrackMode(track,TRACK_MODE_NONE)) -ZZ(=,track,EXT) CHECK(track>='A' && track<='H') CHECK(TrackManager::setTrackMode(track,TRACK_MODE_EXT)) + +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(=,track,BOOST) CHECK(TRACK_MODE_BOOST) CHECK(track>='A' && track<='H') CHECK(TrackManager::setTrackMode(track,TRACK_MODE_BOOST)) -ZZ(=,track,BOOST_INV) CHECK(TRACK_MODE_BOOST_INV) CHECK(track>='A' && track<='H') CHECK(TrackManager::setTrackMode(track,TRACK_MODE_BOOST_INV)) -ZZ(=,track,BOOST_AUTO) CHECK(TRACK_MODE_BOOST_AUTO) CHECK(track>='A' && track<='H') CHECK(TrackManager::setTrackMode(track,TRACK_MODE_BOOST_AUTO)) +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) CHECK(track>='A' && track<='H') CHECK(TrackManager::orTrackMode(track, TRACK_MODIFIER_AUTO)) -ZZ(=,track,INV) CHECK(track>='A' && track<='H') CHECK(TrackManager::orTrackMode(track, TRACK_MODIFIER_INV)) -ZZ(=,track,DC,locoid) CHECK(track>='A' && track<='H') CHECK(TrackManager::setTrackMode(track, TRACK_MODE_DC, locoid)) -ZZ(=,track,DC_INV,locoid) CHECK(track>='A' && track<='H') CHECK(TrackManager::setTrackMode(track, TRACK_MODE_DC_INV, locoid)) -ZZ(=,track,DCX,locoid) CHECK(track>='A' && track<='H') CHECK(TrackManager::setTrackMode(track, TRACK_MODE_DC_INV, locoid)) +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 diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index b4ed8fa..66f949f 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -314,7 +314,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) // Because of check above we are now inside byte size params = splitnum; - + matchedCommandFormat = nullptr; + checkFailedFormat = nullptr; + if (filterCallback) filterCallback(stream, opcode, params, p); if (filterRMFTCallback && opcode!='\0') @@ -324,21 +326,25 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) if (opcode=='\0') return; // filterCallback asked us to ignore - matchedCommandFormat = F("none"); - checkFailedFormat = matchedCommandFormat; if (execute(com,stream, opcode, params, p, ringStream)) return; - // TODO magnificent diagnostics - StringFormatter::send(stream, F("\n")); - DIAG(F("Command format <%<> failed CHECK(%S)"), matchedCommandFormat, checkFailedFormat); - if (opcode >= ' ' && opcode <= '~') { - DIAG(F("Opcode=%c params=%d"), opcode, params); - for (int i = 0; i < params; i++) - DIAG(F("p[%d]=%d (0x%x)"), i, p[i], p[i]); - } else { - DIAG(F("Unprintable %x"), opcode); - } + StringFormatter::send(stream, F("\n")); // respond to caller with error + // Extended diagnostics + StringFormatter::send(USB_SERIAL, F("<*")); + if (matchedCommandFormat) { + // A command format was matched but a check failed + StringFormatter::send(USB_SERIAL,F(" Command format <%<> failed CHECK(%S)\n <"), matchedCommandFormat, checkFailedFormat); + } + else { + // No command format matched, so we have no idea what the command was + StringFormatter::send(USB_SERIAL,F(" Unrecognized command: <")); + } + if (opcode >= ' ' && opcode <= '~') StringFormatter::send(USB_SERIAL,F( "%c"), opcode); + else StringFormatter::send(USB_SERIAL,F("0x%x"), opcode); + + for (int i = 0; i < params; i++) StringFormatter::send(USB_SERIAL,F(" %d"),p[i]); + StringFormatter::send(USB_SERIAL,F("> *>\n")); } bool DCCEXParser::setThrottle(int16_t cab,int16_t tspeed,int16_t direction) { diff --git a/DCCEXParserMacros.h b/DCCEXParserMacros.h index 6eee29a..d6d258c 100644 --- a/DCCEXParserMacros.h +++ b/DCCEXParserMacros.h @@ -28,7 +28,8 @@ #define ZZBEGIN if (false) { #define ZZEND return true; } return false; -#define CHECK(x) if (!(x)) { DCCEXParser::checkFailedFormat=F(#x); return false;} +//#define CHECK(x) if (!(x)) { DCCEXParser::checkFailedFormat=F(#x); return false;} +#define CHECK(x,...) if (!(x)) { DCCEXParser::checkFailedFormat=#__VA_ARGS__[0]?F(#__VA_ARGS__):F(#x); return false;} #define REPLY(format,...) StringFormatter::send(stream,F(format), ##__VA_ARGS__); #define EXPECT_CALLBACK CHECK(stashCallback(stream, p, ringStream)) diff --git a/EXRAIL2Parser.cpp b/EXRAIL2Parser.cpp index b583f8d..fbd600e 100644 --- a/EXRAIL2Parser.cpp +++ b/EXRAIL2Parser.cpp @@ -170,14 +170,12 @@ bool RMFT2::parseCommands(Print * stream, byte opcode, byte params, int16_t p[]) ZZ(A,address,aspect) // Aspect may intercept or be left for normal parse return signalAspectEvent(address,aspect); ZZ(L) //LCC adapter introducing self - CHECK(streamLCC(stream)) + CHECK(streamLCC(stream),no LCC/CBUS events) ZZ(L,eventid) // loop through all possible sent/waited events CHECK(eventid>=0 && eventidstream(stream); - REPLY(">\n") + ZZ(J,A) REPLY("stream(stream); REPLY(">\n") ZZ(J,A,id) REPLY("\n",id, getRouteType(id), getRouteDescription(id)); if (compileFeatures & FEATURE_ROUTESTATE) { // Send any non-default button states or captions @@ -213,12 +211,12 @@ bool RMFT2::parseCommands(Print * stream, byte opcode, byte params, int16_t p[]) } ZZ(/,START,route) auto pc=routeLookup->find(route); - CHECK(pc>=0) + CHECK(pc>=0,route not found) new RMFT2(pc,0); // no cab for route start ZZ(/,START,cab,route) auto pc=routeLookup->find(route); - CHECK(pc>=0) + CHECK(pc>=0, route not found) new RMFT2(pc,cab); // no cab for route start ZZ(/,KILL,ALL) while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask @@ -236,10 +234,10 @@ bool RMFT2::parseCommands(Print * stream, byte opcode, byte params, int16_t p[]) if (task==loopTask) break; } CHECK(found); - ZZ(/,RESERVE,section) CHECK(setFlag(section,SECTION_FLAG)) - ZZ(/,FREE,section) CHECK(setFlag(section,0,SECTION_FLAG)) - ZZ(/,LATCH,latch) CHECK(setFlag(latch,LATCH_FLAG)) - ZZ(/,UNLATCH,latch) CHECK(setFlag(latch,0,LATCH_FLAG)) + ZZ(/,RESERVE,section) CHECK(setFlag(section,SECTION_FLAG),invalid section) + ZZ(/,FREE,section) CHECK(setFlag(section,0,SECTION_FLAG),invalid section) + ZZ(/,LATCH,latch) CHECK(setFlag(latch,LATCH_FLAG),invalid section) + ZZ(/,UNLATCH,latch) CHECK(setFlag(latch,0,LATCH_FLAG),invalid section) ZZ(/,RED,signal) doSignal(signal,SIGNAL_RED); ZZ(/,AMBER,signal) doSignal(signal,SIGNAL_AMBER); ZZ(/,GREEN,signal) doSignal(signal,SIGNAL_GREEN); diff --git a/Release_Notes/DCCEXCommands.awk b/Release_Notes/DCCEXCommands.awk new file mode 100644 index 0000000..a3d8141 --- /dev/null +++ b/Release_Notes/DCCEXCommands.awk @@ -0,0 +1,15 @@ +#!/usr/bin/awk -f + +{ + # Match the pattern ZZ(something) // comments + if ($0 ~ /ZZ\([^)]*\)\s*\/\/.*/) { + # Extract "something" and "comments" + match($0, /ZZ\(([^)]*)\)\s*\/\/\s*(.*)/, arr); + something = arr[1]; + comments = arr[2]; + # Replace commas in "something" with spaces + gsub(/,/, " ", something); + # Print in the new format as a JS call + printf "ZZ('%s','%s');\n", something, comments; + } +} diff --git a/TrackManager.cpp b/TrackManager.cpp index 0a4f60f..57b0428 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -199,7 +199,8 @@ void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { } bool TrackManager::orTrackMode(byte trackToSet, TRACK_MODE mode) { - return setTrackMode(trackToSet, track[trackToSet]->getMode() | mode); + if (trackToSet>='A' && trackToSet<='H') trackToSet-='A'; + return setTrackMode(trackToSet, track[trackToSet]->getMode() | mode); } bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {