#ifndef DISABLE_PROG
case 'P': // WRITE TRANSPARENT DCC PACKET PROG
#endif
// NOTE: this command was parsed in HEX instead of decimal
params--; // drop REG
if (params<1) break;
if (params > MAX_PACKET_SIZE) break;
{
byte packet[params];
for (int i=0;i
if (!stashCallback(stream, p, ringStream))
break;
if (params == 1) // Write new loco id (clearing consist and managing short/long)
DCC::setLocoId(p[0],callback_Wloco);
else if (params == 4) // WRITE CV ON PROG
DCC::writeCVByte(p[0], p[1], callback_W4);
else if ((params==2 || params==3 ) && p[0]=="CONSIST"_hk ) {
DCC::setConsistId(p[1],p[2]=="REVERSE"_hk,callback_Wconsist);
}
else if (params == 2) // WRITE CV ON PROG
DCC::writeCVByte(p[0], p[1], callback_W);
else
break;
return;
case 'V': // VERIFY CV ON PROG
if (params == 2)
{ //
if (!stashCallback(stream, p, ringStream))
break;
DCC::verifyCVByte(p[0], p[1], callback_Vbyte);
return;
}
if (params == 3)
{
if (!stashCallback(stream, p, ringStream))
break;
DCC::verifyCVBit(p[0], p[1], p[2], callback_Vbit);
return;
}
break;
case 'B': // WRITE CV BIT ON PROG or
if (params != 3 && params != 5)
break;
if (!stashCallback(stream, p, ringStream))
break;
DCC::writeCVBit(p[0], p[1], p[2], callback_B);
return;
case 'R': // READ CV ON PROG
if (params == 1)
{ // -- uses verify callback
if (!stashCallback(stream, p, ringStream))
break;
DCC::verifyCVByte(p[0], 0, callback_Vbyte);
return;
}
if (params == 3)
{ //
if (!stashCallback(stream, p, ringStream))
break;
DCC::readCV(p[0], callback_R);
return;
}
if (params == 0)
{ // New read loco id
if (!stashCallback(stream, p, ringStream))
break;
DCC::getLocoId(callback_Rloco);
return;
}
break;
#endif
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
{
if (params > 1) break;
if (params==0) { // All
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::ON);
}
if (params==1) {
if (p[0]=="MAIN"_hk) { // <1 MAIN>
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::ON);
}
#ifndef DISABLE_PROG
else if (p[0] == "JOIN"_hk) { // <1 JOIN>
TrackManager::setJoin(true);
TrackManager::setTrackPower(TRACK_MODE_MAIN|TRACK_MODE_PROG, POWERMODE::ON);
}
else if (p[0]=="PROG"_hk) { // <1 PROG>
TrackManager::setJoin(false);
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::ON);
}
#endif
else if (p[0] >= "A"_hk && p[0] <= "H"_hk) { // <1 A-H>
byte t = (p[0] - 'A');
TrackManager::setTrackPower(POWERMODE::ON, t);
//StringFormatter::send(stream, F("\n"), t+'A');
}
else break; // will reply
}
//TrackManager::streamTrackState(NULL,t);
return;
}
case '0': // POWEROFF <0 [MAIN | PROG] >
{
if (params > 1) break;
if (params==0) { // All
TrackManager::setJoin(false);
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::OFF);
}
if (params==1) {
if (p[0]=="MAIN"_hk) { // <0 MAIN>
TrackManager::setJoin(false);
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::OFF);
}
#ifndef DISABLE_PROG
else if (p[0]=="PROG"_hk) { // <0 PROG>
TrackManager::setJoin(false);
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::OFF);
}
#endif
else if (p[0] >= "A"_hk && p[0] <= "H"_hk) { // <1 A-H>
byte t = (p[0] - 'A');
TrackManager::setJoin(false);
TrackManager::setTrackPower(POWERMODE::OFF, t);
//StringFormatter::send(stream, F("\n"), t+'A');
}
else break; // will reply
}
return;
}
case '!': // ESTOP ALL
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
return;
#ifdef HAS_ENOUGH_MEMORY
case 'c': // SEND METER RESPONSES
// No longer useful because of multiple tracks See and
if (params>0) break;
TrackManager::reportObsoleteCurrent(stream);
return;
#endif
case 'Q': // SENSORS
Sensor::printAll(stream);
return;
case '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
return;
#ifndef DISABLE_EEPROM
case 'E': // STORE EPROM
EEStore::store();
StringFormatter::send(stream, F("\n"), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs);
return;
case 'e': // CLEAR EPROM
EEStore::clear();
StringFormatter::send(stream, F("\n"));
return;
#endif
case ' ': // < >
StringFormatter::send(stream, F("\n"));
return;
case 'C': // CONFIG
#if defined(ARDUINO_ARCH_ESP32)
// currently this only works on ESP32
#if defined(HAS_ENOUGH_MEMORY)
if (p[0] == "WIFI"_hk) { //
if (params != 5) // the 5 params 0 to 4 are (kinda): WIFI_hk 0x7777 &SSID 0x7777 &PASSWORD
break;
if (p[1] == 0x7777 && p[3] == 0x7777) {
WifiESP::setup((const char*)(com + p[2]), (const char*)(com + p[4]), WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
}
return;
}
#endif
#endif //ESP32
if (parseC(stream, params, p))
return;
break;
#ifndef DISABLE_DIAG
case 'D': // DIAG
if (parseD(stream, params, p))
return;
break;
#endif
case '=': // TRACK MANAGER CONTROL <= [params]>
if (TrackManager::parseEqualSign(stream, params, p))
return;
break;
case '#': // NUMBER OF LOCOSLOTS <#>
StringFormatter::send(stream, F("<# %d>\n"), MAX_LOCOS);
return;
case '-': // Forget Loco <- [cab]>
if (params > 1 || p[0]<0) break;
if (p[0]==0) DCC::forgetAllLocos();
else DCC::forgetLoco(p[0]);
return;
case 'F': // New command to call the new Loco Function API
if(params!=3) break;
if (p[1]=="DCFREQ"_hk) { //
if (p[2]<0 || p[2]>3) break;
DCC::setDCFreq(p[0],p[2]);
return;
}
if (Diag::CMD)
DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF"));
if (DCC::setFn(p[0], p[1], p[2] == 1)) return;
break;
#if WIFI_ON
case '+': // Complex Wifi interface command (not usual parse)
if (atCommandCallback && !ringStream) {
TrackManager::setPower(POWERMODE::OFF);
atCommandCallback((HardwareSerial *)stream,com);
return;
}
break;
#endif
case 'J' : // throttle info access
{
if ((params<1) | (params>3)) break; //
//if ((params<1) | (params>2)) break; //
int16_t id=(params==2)?p[1]:0;
switch(p[0]) {
case "C"_hk: // sets time and speed
if (params==1) { // returns latest time
int16_t x = CommandDistributor::retClockTime();
StringFormatter::send(stream, F("\n"), x);
return;
}
CommandDistributor::setClockTime(p[1], p[2], 1);
return;
case "G"_hk: // current gauge limits
if (params>1) break;
TrackManager::reportGauges(stream); //
return;
case "I"_hk: // current values
if (params>1) break;
TrackManager::reportCurrent(stream); //
return;
case "A"_hk: // intercepted by EXRAIL// returns automations/routes
if (params!=1) break; //
StringFormatter::send(stream, F("\n"));
return;
case "M"_hk: // intercepted by EXRAIL
if (params>1) break; // invalid cant do
// requests stash size so say none.
StringFormatter::send(stream,F("\n"));
return;
case "R"_hk: // returns rosters
StringFormatter::send(stream, F("\n"));
return;
case "T"_hk: // returns turnout list
StringFormatter::send(stream, F("
for ( Turnout * t=Turnout::first(); t; t=t->next()) {
if (t->isHidden()) continue;
StringFormatter::send(stream, F(" %d"),t->getId());
}
}
else { //
Turnout * t=Turnout::get(id);
if (!t || t->isHidden()) StringFormatter::send(stream, F(" %d X"),id);
else {
const FSH *tdesc = NULL;
#ifdef EXRAIL_ACTIVE
tdesc = RMFT2::getTurnoutDescription(id);
#endif
if (tdesc == NULL)
tdesc = F("");
StringFormatter::send(stream, F(" %d %c \"%S\""),
id,t->isThrown()?'T':'C',
tdesc);
}
}
StringFormatter::send(stream, F(">\n"));
return;
// No turntables without HAL support
#ifndef IO_NO_HAL
case "O"_hk: //
for (Turntable * tto=Turntable::first(); tto; tto=tto->next()) {
if (tto->isHidden()) continue;
StringFormatter::send(stream, F(" %d"),tto->getId());
}
StringFormatter::send(stream, F(">\n"));
} else { //
Turntable *tto=Turntable::get(id);
if (!tto || tto->isHidden()) {
StringFormatter::send(stream, F(" %d X>\n"), id);
} else {
uint8_t pos = tto->getPosition();
uint8_t type = tto->isEXTT();
uint8_t posCount = tto->getPositionCount();
const FSH *todesc = NULL;
#ifdef EXRAIL_ACTIVE
todesc = RMFT2::getTurntableDescription(id);
#endif
if (todesc == NULL) todesc = F("");
StringFormatter::send(stream, F(" %d %d %d %d \"%S\">\n"), id, type, pos, posCount, todesc);
}
}
return;
case "P"_hk: // returns turntable position list for the turntable id
if (params==2) { //
Turntable *tto=Turntable::get(id);
if (!tto || tto->isHidden()) {
StringFormatter::send(stream, F(" %d X>\n"), id);
} else {
uint8_t posCount = tto->getPositionCount();
const FSH *tpdesc = NULL;
for (uint8_t p = 0; p < posCount; p++) {
StringFormatter::send(stream, F("getPositionAngle(p);
#ifdef EXRAIL_ACTIVE
tpdesc = RMFT2::getTurntablePositionDescription(id, p);
#endif
if (tpdesc == NULL) tpdesc = F("");
StringFormatter::send(stream, F(" %d %d %d \"%S\""), id, p, angle, tpdesc);
StringFormatter::send(stream, F(">\n"));
}
}
} else {
StringFormatter::send(stream, F("\n"));
}
return;
#endif
default: break;
} // switch(p[1])
break; // case J
}
// No turntables without HAL support
#ifndef IO_NO_HAL
case 'I': // TURNTABLE
if (parseI(stream, params, p))
return;
break;
#endif
case '/': // implemented in EXRAIL parser
case 'L': // LCC interface implemented in EXRAIL parser
break; // Will if not intercepted by EXRAIL
#ifndef DISABLE_VDPY
case '@': // JMRI saying "give me virtual LCD msgs"
CommandDistributor::setVirtualLCDSerial(stream);
StringFormatter::send(stream,
F("<@ 0 0 \"DCC-EX v" VERSION "\">\n"
"<@ 0 1 \"Lic GPLv3 \">\n"
"<@ 0 8 \"Powered by DCC-EX \">\n"
CommandDistributor::setVirtualLCDSerial(NULL);
return;
#endif
default: //anything else will diagnose and drop out to
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);
}
break;
} // end of opcode switch
out:// Any fallout here sends an
StringFormatter::send(stream, F("\n"));
}
bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
{
switch (params)
{
case 2: //
{
Output *o = Output::get(p[0]);
if (o == NULL)
return false;
o->activate(p[1]);
StringFormatter::send(stream, F("\n"), p[0], p[1]);
}
return true;
case 3: //
if (p[0] < 0 || p[2] < 0 || p[2] > 7 )
return false;
if (!Output::create(p[0], p[1], p[2], 1))
return false;
StringFormatter::send(stream, F("\n"));
return true;
case 1: //
if (!Output::remove(p[0]))
return false;
StringFormatter::send(stream, F("\n"));
return true;
case 0: // list Output definitions
{
bool gotone = false;
for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput)
{
gotone = true;
StringFormatter::send(stream, F("\n"), tt->data.id, tt->data.pin, tt->data.flags, tt->data.active);
}
return gotone;
}
default:
return false;
}
}
//===================================
bool DCCEXParser::parsef(Print *stream, int16_t params, int16_t p[])
{
// JMRI sends this info in DCC message format but it's not exactly
// convenient for other processing
if (params == 2) {
byte instructionField = p[1] & 0xE0; // 1110 0000
if (instructionField == 0x80) { // 1000 0000 Function group 1
// Shuffle bits from order F0 F4 F3 F2 F1 to F4 F3 F2 F1 F0
byte normalized = (p[1] << 1 & 0x1e) | (p[1] >> 4 & 0x01);
return (funcmap(p[0], normalized, 0, 4));
} else if (instructionField == 0xA0) { // 1010 0000 Function group 2
if (p[1] & 0x10) // 0001 0000 Bit selects F5toF8 / F9toF12
return (funcmap(p[0], p[1], 5, 8));
else
return (funcmap(p[0], p[1], 9, 12));
}
}
if (params == 3) {
if (p[1] == 222) {
return (funcmap(p[0], p[2], 13, 20));
} else if (p[1] == 223) {
return (funcmap(p[0], p[2], 21, 28));
}
}
(void)stream; // NO RESPONSE
return false;
}
bool DCCEXParser::funcmap(int16_t cab, byte value, byte fstart, byte fstop)
{
for (int16_t i = fstart; i <= fstop; i++) {
if (! DCC::setFn(cab, i, value & 1)) return false;
value >>= 1;
}
return true;
}
//===================================
bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
{
switch (params)
{
case 0: // list turnout definitions
return Turnout::printAll(stream); // will if none found
case 1: // delete turnout
if (!Turnout::remove(p[0]))
return false;
StringFormatter::send(stream, F("\n"));
return true;
case 2: //
{
bool state = false;
switch (p[1]) {
// Turnout messages use 1=throw, 0=close.
case 0:
case "C"_hk:
state = true;
break;
case 1:
case "T"_hk:
state= false;
break;
case "X"_hk:
{
Turnout *tt = Turnout::get(p[0]);
if (tt) {
tt->print(stream);
return true;
}
return false;
}
default: // Invalid parameter
return false;
}
if (!Turnout::setClosed(p[0], state)) return false;
return true;
}
default: // Anything else is some kind of turnout create function.
if (params == 6 && p[1] == "SERVO"_hk) { //
if (!ServoTurnout::create(p[0], (VPIN)p[2], (uint16_t)p[3], (uint16_t)p[4], (uint8_t)p[5]))
return false;
} else
if (params == 3 && p[1] == "VPIN"_hk) { //
if (!VpinTurnout::create(p[0], p[2])) return false;
} else
if (params >= 3 && p[1] == "DCC"_hk) {
// 0<=addr<=511, 0<=subadd<=3 (like command).
if (params==4 && p[2]>=0 && p[2]<512 && p[3]>=0 && p[3]<4) { //
if (!DCCTurnout::create(p[0], p[2], p[3])) return false;
} else if (params==3 && p[2]>0 && p[2]<=512*4) { // , 1<=nn<=2048
// Linearaddress 1 maps onto decoder address 1/0 (not 0/0!).
if (!DCCTurnout::create(p[0], (p[2]-1)/4+1, (p[2]-1)%4)) return false;
} else
return false;
} else
if (params==3) { // legacy for DCC accessory
if (p[1]>=0 && p[1]<512 && p[2]>=0 && p[2]<4) {
if (!DCCTurnout::create(p[0], p[1], p[2])) return false;
} else
return false;
}
else
if (params==4) { // legacy for Servo
if (!ServoTurnout::create(p[0], (VPIN)p[1], (uint16_t)p[2], (uint16_t)p[3], 1)) return false;
} else
return false;
StringFormatter::send(stream, F("\n"));
return true;
}
}
bool DCCEXParser::parseS(Print *stream, int16_t params, int16_t p[])
{
switch (params)
{
case 3: // create sensor. pullUp indicator (0=LOW/1=HIGH)
if (!Sensor::create(p[0], p[1], p[2]))
return false;
StringFormatter::send(stream, F("\n"));
return true;
case 1: // S id> remove sensor
if (!Sensor::remove(p[0]))
return false;
StringFormatter::send(stream, F("\n"));
return true;
case 0: // list sensor definitions
if (Sensor::firstSensor == NULL)
return false;
for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor)
{
StringFormatter::send(stream, F("\n"), tt->data.snum, tt->data.pin, tt->data.pullUp);
}
return true;
default: // invalid number of arguments
break;
}
return false;
}
bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
(void)stream; // arg not used, maybe later?
if (params == 0)
return false;
switch (p[0])
{
#ifndef DISABLE_PROG
case "PROGBOOST"_hk:
TrackManager::progTrackBoosted=true;
return true;
#endif
case "RESET"_hk:
DCCTimer::reset();
break; // and if we didnt restart
case "SPEED28"_hk:
DCC::setGlobalSpeedsteps(28);
DIAG(F("28 Speedsteps"));
return true;
case "SPEED128"_hk:
DCC::setGlobalSpeedsteps(128);
DIAG(F("128 Speedsteps"));
return true;
#if defined(HAS_ENOUGH_MEMORY) && !defined(ARDUINO_ARCH_UNO)
case "RAILCOM"_hk:
{ //
if (params<2) return false;
bool on=false;
bool debug=false;
switch (p[1]) {
case "ON"_hk:
case 1:
on=true;
break;
case "DEBUG"_hk:
on=true;
debug=true;
break;
case "OFF"_hk:
case 0:
break;
default:
return false;
}
DIAG(F("Railcom %S")
,DCCWaveform::setRailcom(on,debug)?F("ON"):F("OFF"));
return true;
}
#endif
#ifndef DISABLE_PROG
case "ACK"_hk: //
if (params >= 3) {
long duration;
if (p[1] == "LIMIT"_hk) {
DCCACK::setAckLimit(p[2]);
LCD(1, F("Ack Limit=%dmA"), p[2]); //
} else if (p[1] == "MIN"_hk) {
if (params == 4 && p[3] == "MS"_hk)
duration = p[2] * 1000L;
else
duration = p[2];
DCCACK::setMinAckPulseDuration(duration);
LCD(0, F("Ack Min=%lus"), duration); //
} else if (p[1] == "MAX"_hk) {
if (params == 4 && p[3] == "MS"_hk) //
duration = p[2] * 1000L;
else
duration = p[2];
DCCACK::setMaxAckPulseDuration(duration);
LCD(0, F("Ack Max=%lus"), duration); //
} else if (p[1] == "RETRY"_hk) {
if (p[2] >255) p[2]=3;
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); //
}
} else {
bool onOff = (params > 0) && (p[1] == 1 || p[1] == "ON"_hk); // dont care if other stuff or missing... just means off
DIAG(F("Ack diag %S"), onOff ? F("on") : F("off"));
Diag::ACK = onOff;
}
return true;
#endif
default: // invalid/unknown
break;
}
return false;
}
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
{
if (params == 0)
return false;
bool onOff = (params > 0) && (p[1] == 1 || p[1] == "ON"_hk); // dont care if other stuff or missing... just means off
switch (p[0])
{
case "CABS"_hk: //
DCC::displayCabList(stream);
return true;
case "RAM"_hk: //
DIAG(F("Free memory=%d"), DCCTimer::getMinimumFreeMemory());
return true;
case "CMD"_hk: //
Diag::CMD = onOff;
return true;
#ifdef HAS_ENOUGH_MEMORY
case "WIFI"_hk: //
Diag::WIFI = onOff;
return true;
case "ETHERNET"_hk: //
Diag::ETHERNET = onOff;
return true;
case "WIT"_hk: //
Diag::WITHROTTLE = onOff;
return true;
case "LCN"_hk: //
Diag::LCN = onOff;
return true;
#endif
#ifndef DISABLE_EEPROM
case "EEPROM"_hk: //
if (params >= 2)
EEStore::dump(p[1]);
return true;
#endif
case "SERVO"_hk: //
case "ANOUT"_hk: //
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
return true;
case "ANIN"_hk: // Display analogue input value
DIAG(F("VPIN=%u value=%d"), p[1], IODevice::readAnalogue(p[1]));
return true;
#if !defined(IO_NO_HAL)
case "HAL"_hk:
if (p[1] == "SHOW"_hk)
IODevice::DumpAll();
else if (p[1] == "RESET"_hk)
IODevice::reset();
return true;
#endif
case "TT"_hk: //
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
return true;
default: // invalid/unknown
return parseC(stream, params, p);
}
return false;
}
// ==========================
// 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
#ifndef IO_NO_HAL
bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
{
switch (params)
{
case 0: // list turntable objects
return Turntable::printAll(stream);
case 1: // broadcast type and current position
{
Turntable *tto = Turntable::get(p[0]);
if (tto) {
bool type = tto->isEXTT();
uint8_t position = tto->getPosition();
StringFormatter::send(stream, F("\n"), type, position);
} else {
return false;
}
}
return true;
case 2: // - rotate a DCC turntable
{
Turntable *tto = Turntable::get(p[0]);
if (tto && !tto->isEXTT()) {
if (!tto->setPosition(p[0], p[1])) return false;
} else {
return false;
}
}
return true;
case 3: // | - rotate to position for EX-Turntable or create DCC turntable
{
Turntable *tto = Turntable::get(p[0]);
if (p[1] == "DCC"_hk) {
if (tto || p[2] < 0 || p[2] > 3600) return false;
if (!DCCTurntable::create(p[0])) return false;
Turntable *tto = Turntable::get(p[0]);
tto->addPosition(0, 0, p[2]);
StringFormatter::send(stream, F("\n"));
} else {
if (!tto) return false;
if (!tto->isEXTT()) return false;
if (!tto->setPosition(p[0], p[1], p[2])) return false;
}
}
return true;
case 4: // create an EXTT turntable
{
Turntable *tto = Turntable::get(p[0]);
if (p[1] == "EXTT"_hk) {
if (tto || p[3] < 0 || p[3] > 3600) return false;
if (!EXTTTurntable::create(p[0], (VPIN)p[2])) return false;
Turntable *tto = Turntable::get(p[0]);
tto->addPosition(0, 0, p[3]);
StringFormatter::send(stream, F("\n"));
} else {
return false;
}
}
return true;
case 5: // add a position
{
Turntable *tto = Turntable::get(p[0]);
if (p[1] == "ADD"_hk) {
// tto must exist, no more than 48 positions, angle 0 - 3600
if (!tto || p[2] > 48 || p[4] < 0 || p[4] > 3600) return false;
tto->addPosition(p[2], p[3], p[4]);
StringFormatter::send(stream, F("\n"));
} else {
return false;
}
}
return true;
default: // Anything else is invalid
return false;
}
}
#endif
// CALLBACKS must be static
bool DCCEXParser::stashCallback(Print *stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream)
{
if (stashBusy )
return false;
stashBusy = true;
stashStream = stream;
stashRingStream=ringStream;
if (ringStream) stashTarget= ringStream->peekTargetMark();
memcpy(stashP, p, MAX_COMMAND_PARAMS * sizeof(p[0]));
return true;
}
Print * DCCEXParser::getAsyncReplyStream() {
if (stashRingStream) {
stashRingStream->mark(stashTarget);
return stashRingStream;
}
return stashStream;
}
void DCCEXParser::commitAsyncReplyStream() {
if (stashRingStream) stashRingStream->commit();
stashBusy = false;
}
void DCCEXParser::callback_W(int16_t result)
{
StringFormatter::send(getAsyncReplyStream(),
F("\n"), stashP[0], result == 1 ? stashP[1] : -1);
commitAsyncReplyStream();
}
void DCCEXParser::callback_W4(int16_t result)
{
StringFormatter::send(getAsyncReplyStream(),
F("\n"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
commitAsyncReplyStream();
}
void DCCEXParser::callback_B(int16_t result)
{
StringFormatter::send(getAsyncReplyStream(),
F("\n"), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1);
commitAsyncReplyStream();
}
void DCCEXParser::callback_Vbit(int16_t result)
{
StringFormatter::send(getAsyncReplyStream(), F("\n"), stashP[0], stashP[1], result);
commitAsyncReplyStream();
}
void DCCEXParser::callback_Vbyte(int16_t result)
{
StringFormatter::send(getAsyncReplyStream(), F("\n"), stashP[0], result);
commitAsyncReplyStream();
}
void DCCEXParser::callback_R(int16_t result)
{
StringFormatter::send(getAsyncReplyStream(), F("\n"), stashP[1], stashP[2], stashP[0], result);
commitAsyncReplyStream();
}
void DCCEXParser::callback_Rloco(int16_t result) {
const FSH * detail;
if (result<=0) {
detail=F("\n");
} else {
bool longAddr=result & LONG_ADDR_MARKER; //long addr
if (longAddr)
result = result^LONG_ADDR_MARKER;
if (longAddr && result <= HIGHEST_SHORT_ADDR)
detail=F("\n");
else
detail=F("\n");
}
StringFormatter::send(getAsyncReplyStream(), detail, result);
commitAsyncReplyStream();
}
void DCCEXParser::callback_Wloco(int16_t result)
{
if (result==1) result=stashP[0]; // pick up original requested id from command
StringFormatter::send(getAsyncReplyStream(), F("\n"), result);
commitAsyncReplyStream();
}
void DCCEXParser::callback_Wconsist(int16_t result)
{
if (result==1) result=stashP[1]; // pick up original requested id from command
StringFormatter::send(getAsyncReplyStream(), F("\n"),
result, stashP[2]=="REVERSE"_hk ? F(" REVERSE") : F(""));
commitAsyncReplyStream();
}