case 'P': // WRITE TRANSPARENT DCC PACKET PROG
// NOTE: this command was parsed in HEX instead of decimal
params--; // drop REG
if (params<1) 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 // WRITE CV ON PROG
DCC::writeCVByte(p[0], p[1], callback_W);
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
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], p[1], 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;
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
{
bool main=false;
bool prog=false;
bool join=false;
if (params > 1) break;
if (params==0 || MotorDriver::commonFaultPin) { // <1> or tracks can not be handled individually
main=true;
prog=true;
}
if (params==1) {
if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
main=true;
prog=true;
join=true;
}
else if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
main=true;
}
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
prog=true;
}
else break; // will reply
}
if (main) TrackManager::setMainPower(POWERMODE::ON);
if (prog) TrackManager::setProgPower(POWERMODE::ON);
TrackManager::setJoin(join);
CommandDistributor::broadcastPower();
return;
}
case '0': // POWEROFF <0 [MAIN | PROG] >
{
bool main=false;
bool prog=false;
if (params > 1) break;
if (params==0 || MotorDriver::commonFaultPin) { // <0> or tracks can not be handled individually
main=true;
prog=true;
}
if (params==1) {
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
main=true;
}
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
prog=true;
}
else break; // will reply
}
if (main) TrackManager::setMainPower(POWERMODE::OFF);
if (prog) {
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
TrackManager::setProgPower(POWERMODE::OFF);
}
TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
return;
}
case '!': // ESTOP ALL
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
return;
case 'c': // SEND METER RESPONSES
// No longer supported because of multiple tracks
break;
case 'Q': // SENSORS
Sensor::printAll(stream);
return;
case 's': //
StringFormatter::send(stream, F("\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
Turnout::printAll(stream); //send all Turnout states
Output::printAll(stream); //send all Output states
Sensor::printAll(stream); //send all Sensor states
// TODO Send stats of speed reminders table
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 'D': // < >
if (parseD(stream, params, p))
return;
return;
case '=': // <= Track manager control >
if (TrackManager::parseJ(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 (Diag::CMD)
DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF"));
DCC::setFn(p[0], p[1], p[2] == 1);
return;
#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>2)) break; //
int16_t id=(params==2)?p[1]:0;
switch(p[0]) {
case HASH_KEYWORD_A: // returns automations/routes
StringFormatter::send(stream, F("
#ifdef EXRAIL_ACTIVE
sendFlashList(stream,RMFT2::routeIdList);
sendFlashList(stream,RMFT2::automationIdList);
#endif
}
else { //
StringFormatter::send(stream,F(" %d %c \"%S\""),
id,
#ifdef EXRAIL_ACTIVE
RMFT2::getRouteType(id), // A/R
RMFT2::getRouteDescription(id)
#else
'X',F("")
#endif
);
}
StringFormatter::send(stream, F(">\n"));
return;
case HASH_KEYWORD_R: // returns rosters
StringFormatter::send(stream, F("\n"));
return;
case HASH_KEYWORD_T: // 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 StringFormatter::send(stream, F(" %d %c \"%S\""),
id,t->isThrown()?'T':'C',
#ifdef EXRAIL_ACTIVE
RMFT2::getTurnoutDescription(id)
#else
F("")
#endif
);
}
StringFormatter::send(stream, F(">\n"));
return;
default: break;
} // switch(p[1])
break; // case J
}
default: //anything else will diagnose and drop out to
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]);
break;
} // end of opcode switch
// Any fallout here sends an
StringFormatter::send(stream, F("\n"));
}
void DCCEXParser::sendFlashList(Print * stream,const int16_t flashList[]) {
for (int16_t i=0;;i++) {
int16_t value=GETFLASHW(flashList+i);
if (value==0) return;
StringFormatter::send(stream,F(" %d"),value);
}
}
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);
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
funcmap(p[0], p[1], 5, 8);
else
funcmap(p[0], p[1], 9, 12);
}
}
if (params == 3)
{
if (p[1] == 222)
funcmap(p[0], p[2], 13, 20);
else if (p[1] == 223)
funcmap(p[0], p[2], 21, 28);
}
(void)stream; // NO RESPONSE
return true;
}
void DCCEXParser::funcmap(int16_t cab, byte value, byte fstart, byte fstop)
{
for (int16_t i = fstart; i <= fstop; i++)
{
DCC::setFn(cab, i, value & 1);
value >>= 1;
}
}
//===================================
bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
{
switch (params)
{
case 0: // list turnout definitions
{
bool gotOne = false;
for (Turnout *tt = Turnout::first(); tt != NULL; tt = tt->next())
{
gotOne = true;
tt->print(stream);
}
return gotOne; // 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 HASH_KEYWORD_C:
state = true;
break;
case 1:
case HASH_KEYWORD_T:
state= false;
break;
default:
return false; // Invalid parameter
}
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] == HASH_KEYWORD_SERVO) { //
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] == HASH_KEYWORD_VPIN) { //
if (!VpinTurnout::create(p[0], p[2])) return false;
} else
if (params >= 3 && p[1] == HASH_KEYWORD_DCC) {
// 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::parseD(Print *stream, int16_t params, int16_t p[])
{
if (params == 0)
return false;
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
switch (p[0])
{
case HASH_KEYWORD_CABS: //
DCC::displayCabList(stream);
return true;
case HASH_KEYWORD_RAM: //
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory());
break;
case HASH_KEYWORD_ACK: //
if (params >= 3) {
if (p[1] == HASH_KEYWORD_LIMIT) {
DCCACK::setAckLimit(p[2]);
LCD(1, F("Ack Limit=%dmA"), p[2]); //
} else if (p[1] == HASH_KEYWORD_MIN) {
DCCACK::setMinAckPulseDuration(p[2]);
LCD(0, F("Ack Min=%uus"), p[2]); //
} else if (p[1] == HASH_KEYWORD_MAX) {
DCCACK::setMaxAckPulseDuration(p[2]);
LCD(0, F("Ack Max=%uus"), p[2]); //
} else if (p[1] == HASH_KEYWORD_RETRY) {
if (p[2] >255) p[2]=3;
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); //
}
} else {
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off"));
Diag::ACK = onOff;
}
return true;
case HASH_KEYWORD_CMD: //
Diag::CMD = onOff;
return true;
#ifdef HAS_ENOUGH_MEMORY
case HASH_KEYWORD_WIFI: //
Diag::WIFI = onOff;
return true;
case HASH_KEYWORD_ETHERNET: //
Diag::ETHERNET = onOff;
return true;
case HASH_KEYWORD_WIT: //
Diag::WITHROTTLE = onOff;
return true;
case HASH_KEYWORD_LCN: //
Diag::LCN = onOff;
return true;
#endif
case HASH_KEYWORD_PROGBOOST:
TrackManager::progTrackBoosted=true;
return true;
case HASH_KEYWORD_RESET:
{
#ifdef HAS_AVR_WDT
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
delay(50); // wait for the prescaller time to expire
#else
ESP.restart();
#endif
break; // and if we didnt restart
}
#ifndef DISABLE_EEPROM
case HASH_KEYWORD_EEPROM: //
if (params >= 2)
EEStore::dump(p[1]);
return true;
#endif
case HASH_KEYWORD_SPEED28:
DCC::setGlobalSpeedsteps(28);
StringFormatter::send(stream, F("28 Speedsteps"));
return true;
case HASH_KEYWORD_SPEED128:
DCC::setGlobalSpeedsteps(128);
StringFormatter::send(stream, F("128 Speedsteps"));
return true;
case HASH_KEYWORD_SERVO: //
case HASH_KEYWORD_ANOUT: //
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
break;
case HASH_KEYWORD_ANIN: // Display analogue input value
DIAG(F("VPIN=%d value=%d"), p[1], IODevice::readAnalogue(p[1]));
break;
#if !defined(IO_MINIMAL_HAL)
case HASH_KEYWORD_HAL:
if (p[1] == HASH_KEYWORD_SHOW)
IODevice::DumpAll();
break;
#endif
default: // invalid/unknown
break;
}
return false;
}
// 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();
}