1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-24 00:26:13 +01:00
CommandStation-EX/DCCEXParser.cpp

823 lines
25 KiB
C++
Raw Normal View History

2020-07-03 18:35:02 +02:00
/*
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
2020-07-03 18:35:02 +02:00
*
* This file is part of CommandStation-EX
2020-07-03 18:35:02 +02:00
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "StringFormatter.h"
2020-06-04 21:55:18 +02:00
#include "DCCEXParser.h"
2020-05-25 14:38:18 +02:00
#include "DCC.h"
#include "DCCWaveform.h"
#include "Turnouts.h"
#include "Outputs.h"
#include "Sensors.h"
#include "freeMemory.h"
2020-09-20 01:59:07 +02:00
#include "GITHUB_SHA.h"
#include "version.h"
#include "EEStore.h"
2020-06-11 14:35:16 +02:00
#include "DIAG.h"
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
// To discover new keyword numbers , use the <$ YOURKEYWORD> command
2020-09-25 19:51:08 +02:00
const int HASH_KEYWORD_PROG = -29718;
const int HASH_KEYWORD_MAIN = 11339;
const int HASH_KEYWORD_JOIN = -30750;
const int HASH_KEYWORD_CABS = -11981;
const int HASH_KEYWORD_RAM = 25982;
const int HASH_KEYWORD_CMD = 9962;
const int HASH_KEYWORD_WIT = 31594;
const int HASH_KEYWORD_WIFI = -5583;
const int HASH_KEYWORD_ACK = 3113;
const int HASH_KEYWORD_ON = 2657;
const int HASH_KEYWORD_DCC = 6436;
const int HASH_KEYWORD_SLOW = -17209;
2020-09-27 13:03:46 +02:00
const int HASH_KEYWORD_PROGBOOST = -6353;
2020-10-04 21:20:13 +02:00
const int HASH_KEYWORD_EEPROM = -7168;
const int HASH_KEYWORD_LIMIT = 27413;
2020-10-30 14:00:02 +01:00
const int HASH_KEYWORD_ETHERNET = -30767;
const int HASH_KEYWORD_MAX = 16244;
const int HASH_KEYWORD_MIN = 15978;
2020-06-19 10:19:41 +02:00
2021-02-09 14:43:40 +01:00
int DCCEXParser::stashP[MAX_COMMAND_PARAMS];
2020-06-11 14:35:16 +02:00
bool DCCEXParser::stashBusy;
2020-09-25 19:51:08 +02:00
Print *DCCEXParser::stashStream = NULL;
// This is a JMRI command parser, one instance per incoming stream
2020-05-25 14:38:18 +02:00
// 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.
2020-09-25 19:51:08 +02:00
// Non-DCC things like turnouts, pins and sensors are handled in additional JMRI interface classes.
2020-06-11 14:35:16 +02:00
DCCEXParser::DCCEXParser() {}
2020-09-25 19:51:08 +02:00
void DCCEXParser::flush()
{
if (Diag::CMD)
DIAG(F("\nBuffer flush"));
bufferLength = 0;
inCommandPayload = false;
}
2020-06-11 14:35:16 +02:00
2020-09-25 19:51:08 +02:00
void DCCEXParser::loop(Stream &stream)
{
while (stream.available())
{
if (bufferLength == MAX_BUFFER)
{
flush();
}
char ch = stream.read();
if (ch == '<')
{
inCommandPayload = true;
bufferLength = 0;
buffer[0] = '\0';
}
else if (ch == '>')
{
buffer[bufferLength] = '\0';
parse(&stream, buffer, false); // Parse this allowing async responses
inCommandPayload = false;
break;
}
else if (inCommandPayload)
{
buffer[bufferLength++] = ch;
}
}
Sensor::checkAll(&stream); // Update and print changes
2020-09-25 19:51:08 +02:00
}
2021-02-09 14:43:40 +01:00
int DCCEXParser::splitValues(int result[MAX_COMMAND_PARAMS], const byte *cmd)
2020-09-25 19:51:08 +02:00
{
byte state = 1;
byte parameterCount = 0;
int runningValue = 0;
const byte *remainingCmd = cmd + 1; // skips the opcode
bool signNegative = false;
// clear all parameters in case not enough found
2021-02-09 14:43:40 +01:00
for (int i = 0; i < MAX_COMMAND_PARAMS; i++)
2020-09-25 19:51:08 +02:00
result[i] = 0;
2021-02-09 14:43:40 +01:00
while (parameterCount < MAX_COMMAND_PARAMS)
2020-09-25 19:51:08 +02:00
{
byte hot = *remainingCmd;
switch (state)
{
case 1: // skipping spaces before a param
if (hot == ' ')
break;
if (hot == '\0' || hot == '>')
return parameterCount;
state = 2;
continue;
case 2: // checking sign
signNegative = false;
runningValue = 0;
state = 3;
if (hot != '-')
continue;
signNegative = true;
break;
case 3: // building a parameter
if (hot >= '0' && hot <= '9')
{
runningValue = 10 * runningValue + (hot - '0');
break;
}
2021-01-22 00:13:08 +01:00
if (hot >= 'a' && hot <= 'z') hot=hot-'a'+'A'; // uppercase a..z
2020-09-25 19:51:08 +02:00
if (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;
}
result[parameterCount] = runningValue * (signNegative ? -1 : 1);
parameterCount++;
state = 1;
continue;
}
remainingCmd++;
}
2020-09-25 19:51:08 +02:00
return parameterCount;
}
2020-05-25 14:38:18 +02:00
2021-02-09 14:43:40 +01:00
int DCCEXParser::splitHexValues(int result[MAX_COMMAND_PARAMS], const byte *cmd)
2020-11-24 13:49:15 +01:00
{
byte state = 1;
byte parameterCount = 0;
int runningValue = 0;
const byte *remainingCmd = cmd + 1; // skips the opcode
// clear all parameters in case not enough found
2021-02-09 14:43:40 +01:00
for (int i = 0; i < MAX_COMMAND_PARAMS; i++)
2020-11-24 13:49:15 +01:00
result[i] = 0;
2021-02-09 14:43:40 +01:00
while (parameterCount < MAX_COMMAND_PARAMS)
2020-11-24 13:49:15 +01:00
{
byte hot = *remainingCmd;
switch (state)
{
case 1: // skipping spaces before a param
if (hot == ' ')
break;
if (hot == '\0' || hot == '>')
return parameterCount;
state = 2;
continue;
case 2: // checking first hex digit
runningValue = 0;
state = 3;
continue;
case 3: // building a parameter
if (hot >= '0' && hot <= '9')
{
runningValue = 16 * runningValue + (hot - '0');
break;
}
if (hot >= 'A' && hot <= 'F')
{
runningValue = 16 * runningValue + 10 + (hot - 'A');
break;
}
if (hot >= 'a' && hot <= 'f')
{
runningValue = 16 * runningValue + 10 + (hot - 'a');
break;
}
if (hot==' ' || hot=='>' || hot=='\0') {
result[parameterCount] = runningValue;
parameterCount++;
state = 1;
continue;
}
return -1; // invalid hex digit
}
remainingCmd++;
}
return parameterCount;
}
2020-09-25 19:51:08 +02:00
FILTER_CALLBACK DCCEXParser::filterCallback = 0;
FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0;
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
2020-09-25 19:51:08 +02:00
void DCCEXParser::setFilter(FILTER_CALLBACK filter)
{
filterCallback = filter;
2020-06-18 20:36:37 +02:00
}
void DCCEXParser::setRMFTFilter(FILTER_CALLBACK filter)
{
filterRMFTCallback = filter;
}
void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
{
atCommandCallback = callback;
}
2020-09-25 19:51:08 +02:00
2021-01-07 21:58:23 +01:00
// Parse an F() string
void DCCEXParser::parse(const FSH * cmd) {
2021-01-07 21:58:23 +01:00
int size=strlen_P((char *)cmd)+1;
char buffer[size];
strcpy_P(buffer,(char *)cmd);
parse(&Serial,(byte *)buffer,true);
}
2020-05-25 14:38:18 +02:00
// See documentation on DCC class for info on this section
2020-09-25 19:51:08 +02:00
void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
{
(void)EEPROM; // tell compiler not to warn this is unused
2020-09-25 19:51:08 +02:00
if (Diag::CMD)
DIAG(F("\nPARSING:%s\n"), com);
2021-02-09 14:43:40 +01:00
int p[MAX_COMMAND_PARAMS];
2020-09-25 19:51:08 +02:00
while (com[0] == '<' || com[0] == ' ')
com++; // strip off any number of < or spaces
byte params = splitValues(p, com);
byte opcode = com[0];
if (filterCallback)
filterCallback(stream, opcode, params, p);
if (filterRMFTCallback && opcode!='\0')
filterRMFTCallback(stream, opcode, params, p);
2020-09-25 19:51:08 +02:00
// Functions return from this switch if complete, break from switch implies error <X> to send
2020-09-25 19:51:08 +02:00
switch (opcode)
{
case '\0':
return; // filterCallback asked us to ignore
case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION>
{
int cab;
int tspeed;
int direction;
if (params == 4)
{ // <t REGISTER CAB SPEED DIRECTION>
cab = p[1];
tspeed = p[2];
direction = p[3];
}
else if (params == 3)
{ // <t CAB SPEED DIRECTION>
cab = p[0];
tspeed = p[1];
direction = p[2];
}
2020-09-25 19:51:08 +02:00
else
break;
2020-12-11 19:03:05 +01:00
// Convert DCC-EX protocol speed steps where
// -1=emergency stop, 0-126 as speeds
2020-09-25 19:51:08 +02:00
// to DCC 0=stop, 1= emergency stop, 2-127 speeds
if (tspeed > 126 || tspeed < -1)
break; // invalid JMRI speed code
if (tspeed < 0)
tspeed = 1; // emergency stop DCC speed
else if (tspeed > 0)
tspeed++; // map 1-126 -> 2-127
if (cab == 0 && tspeed > 1)
break; // ignore broadcasts of speed>1
if (direction < 0 || direction > 1)
break; // invalid direction code
DCC::setThrottle(cab, tspeed, direction);
if (params == 4)
StringFormatter::send(stream, F("<T %d %d %d>"), p[0], p[2], p[3]);
else
StringFormatter::send(stream, F("<O>"));
return;
}
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
if (parsef(stream, params, p))
return;
break;
2020-09-25 19:51:08 +02:00
2021-01-22 00:06:47 +01:00
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE> or <a LINEARADDRESS ACTIVATE>
{
int address;
byte subaddress;
byte activep;
if (params==2) { // <a LINEARADDRESS ACTIVATE>
address=(p[0] - 1) / 4 + 1;
subaddress=(p[0] - 1) % 4;
activep=1;
}
else if (params==3) { // <a ADDRESS SUBADDRESS ACTIVATE>
address=p[0];
subaddress=p[1];
activep=2;
}
else break; // invalid no of parameters
if (
((address & 0x01FF) != address) // invalid address (limit 9 bits )
|| ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits )
|| ((p[activep] & 0x01) != p[activep]) // invalid activate 0|1
) break;
DCC::setAccessory(address, subaddress,p[activep]==1);
}
return;
2021-01-22 00:06:47 +01:00
2020-09-25 19:51:08 +02:00
case 'T': // TURNOUT <T ...>
if (parseT(stream, params, p))
return;
break;
case 'Z': // OUTPUT <Z ...>
if (parseZ(stream, params, p))
return;
2020-05-27 10:24:56 +02:00
break;
2020-05-25 14:38:18 +02:00
2020-09-25 19:51:08 +02:00
case 'S': // SENSOR <S ...>
if (parseS(stream, params, p))
return;
break;
2020-05-25 14:38:18 +02:00
2020-09-25 19:51:08 +02:00
case 'w': // WRITE CV on MAIN <w CAB CV VALUE>
DCC::writeCVByteMain(p[0], p[1], p[2]);
return;
2020-05-25 14:38:18 +02:00
2020-09-25 19:51:08 +02:00
case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE>
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
return;
2020-05-25 14:38:18 +02:00
2020-11-24 13:49:15 +01:00
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
// Re-parse the command using a hex-only splitter
params=splitHexValues(p,com)-1; // drop REG
if (params<1) break;
{
byte packet[params];
for (int i=0;i<params;i++) {
packet[i]=(byte)p[i+1];
if (Diag::CMD) DIAG(F("packet[%d]=%d (0x%x)\n"), i, packet[i], packet[i]);
}
(opcode=='M'?DCCWaveform::mainTrack:DCCWaveform::progTrack).schedulePacket(packet,params,3);
}
return;
2020-09-25 19:51:08 +02:00
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
if (!stashCallback(stream, p))
break;
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
DCC::setLocoId(p[0],callback_Wloco, blocking);
else // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
DCC::writeCVByte(p[0], p[1], callback_W, blocking);
return;
2020-09-25 19:51:08 +02:00
case 'V': // VERIFY CV ON PROG <V CV VALUE> <V CV BIT 0|1>
if (params == 2)
{ // <V CV VALUE>
if (!stashCallback(stream, p))
break;
DCC::verifyCVByte(p[0], p[1], callback_Vbyte, blocking);
return;
2020-09-20 01:59:07 +02:00
}
2020-09-25 19:51:08 +02:00
if (params == 3)
{
if (!stashCallback(stream, p))
break;
DCC::verifyCVBit(p[0], p[1], p[2], callback_Vbit, blocking);
return;
2020-09-20 01:59:07 +02:00
}
break;
2020-09-25 19:51:08 +02:00
case 'B': // WRITE CV BIT ON PROG <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>
if (!stashCallback(stream, p))
break;
DCC::writeCVBit(p[0], p[1], p[2], callback_B, blocking);
return;
2020-09-25 19:51:08 +02:00
case 'R': // READ CV ON PROG
if (params == 3)
{ // <R CV CALLBACKNUM CALLBACKSUB>
if (!stashCallback(stream, p))
break;
DCC::readCV(p[0], callback_R, blocking);
return;
2020-09-20 01:59:07 +02:00
}
2020-09-25 19:51:08 +02:00
if (params == 0)
{ // <R> New read loco id
if (!stashCallback(stream, p))
break;
DCC::getLocoId(callback_Rloco, blocking);
return;
2020-09-20 01:59:07 +02:00
}
break;
2020-09-25 19:51:08 +02:00
case '1': // POWERON <1 [MAIN|PROG]>
case '0': // POWEROFF <0 [MAIN | PROG] >
if (params > 1)
break;
2020-06-19 10:19:41 +02:00
{
2020-09-25 19:51:08 +02:00
POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF;
DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off
if (params == 0 ||
(MotorDriver::commonFaultPin && p[0] != HASH_KEYWORD_JOIN)) // commonFaultPin prevents individual track handling
2020-09-25 19:51:08 +02:00
{
DCCWaveform::mainTrack.setPowerMode(mode);
DCCWaveform::progTrack.setPowerMode(mode);
2020-09-27 13:26:29 +02:00
if (mode == POWERMODE::OFF)
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
2020-09-25 19:51:08 +02:00
StringFormatter::send(stream, F("<p%c>"), opcode);
return;
}
switch (p[0])
{
case HASH_KEYWORD_MAIN:
DCCWaveform::mainTrack.setPowerMode(mode);
StringFormatter::send(stream, F("<p%c MAIN>"), opcode);
return;
case HASH_KEYWORD_PROG:
DCCWaveform::progTrack.setPowerMode(mode);
2020-09-27 13:26:29 +02:00
if (mode == POWERMODE::OFF)
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
2020-09-25 19:51:08 +02:00
StringFormatter::send(stream, F("<p%c PROG>"), opcode);
return;
case HASH_KEYWORD_JOIN:
DCCWaveform::mainTrack.setPowerMode(mode);
DCCWaveform::progTrack.setPowerMode(mode);
if (mode == POWERMODE::ON)
{
DCC::setProgTrackSyncMain(true);
StringFormatter::send(stream, F("<p1 JOIN>"), opcode);
}
else
StringFormatter::send(stream, F("<p0>"));
return;
}
break;
2020-06-19 10:19:41 +02:00
}
return;
2020-05-25 14:38:18 +02:00
case 'c': // SEND METER RESPONSES <c>
// <c MeterName value C/V unit min max res warn>
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>"), DCCWaveform::mainTrack.getCurrentmA(),
DCCWaveform::mainTrack.getMaxmA(), DCCWaveform::mainTrack.getTripmA());
StringFormatter::send(stream, F("<a %d>"), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available
2020-09-25 19:51:08 +02:00
return;
case 'Q': // SENSORS <Q>
Sensor::printAll(stream);
return;
2020-09-25 19:51:08 +02:00
case 's': // <s>
StringFormatter::send(stream, F("<p%d>"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>"), 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
2020-09-25 19:51:08 +02:00
// TODO Send stats of speed reminders table
return;
2020-05-25 14:38:18 +02:00
2020-09-25 19:51:08 +02:00
case 'E': // STORE EPROM <E>
2020-05-25 14:38:18 +02:00
EEStore::store();
2020-09-25 19:51:08 +02:00
StringFormatter::send(stream, F("<e %d %d %d>"), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs);
return;
2020-05-25 14:38:18 +02:00
2020-09-25 19:51:08 +02:00
case 'e': // CLEAR EPROM <e>
2020-05-25 14:38:18 +02:00
EEStore::clear();
StringFormatter::send(stream, F("<O>"));
return;
2020-05-25 14:38:18 +02:00
2020-09-25 19:51:08 +02:00
case ' ': // < >
StringFormatter::send(stream, F("\n"));
return;
2020-09-25 19:51:08 +02:00
case 'D': // < >
if (parseD(stream, params, p))
return;
2020-07-01 11:27:53 +02:00
return;
2020-07-04 21:53:44 +02:00
2020-09-25 19:51:08 +02:00
case '#': // NUMBER OF LOCOSLOTS <#>
StringFormatter::send(stream, F("<# %d>"), MAX_LOCOS);
return;
case 'F': // New command to call the new Loco Function API <F cab func 1|0>
2020-09-25 19:51:08 +02:00
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;
2020-09-25 19:51:08 +02:00
case '+': // Complex Wifi interface command (not usual parse)
if (atCommandCallback) {
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
atCommandCallback(com);
return;
}
break;
2020-09-25 19:51:08 +02:00
default: //anything else will diagnose and drop out to <X>
DIAG(F("\nOpcode=%c params=%d\n"), opcode, params);
for (int i = 0; i < params; i++)
DIAG(F("p[%d]=%d (0x%x)\n"), i, p[i], p[i]);
break;
2020-09-25 19:51:08 +02:00
} // end of opcode switch
// Any fallout here sends an <X>
2020-09-25 19:51:08 +02:00
StringFormatter::send(stream, F("<X>"));
2020-05-25 14:38:18 +02:00
}
2020-09-25 19:51:08 +02:00
bool DCCEXParser::parseZ(Print *stream, int params, int p[])
{
2020-09-25 19:51:08 +02:00
switch (params)
{
2020-09-29 23:19:02 +02:00
2020-09-25 19:51:08 +02:00
case 2: // <Z ID ACTIVATE>
{
Output *o = Output::get(p[0]);
if (o == NULL)
return false;
o->activate(p[1]);
StringFormatter::send(stream, F("<Y %d %d>"), p[0], p[1]);
}
return true;
case 3: // <Z ID PIN INVERT>
if (!Output::create(p[0], p[1], p[2], 1))
return false;
StringFormatter::send(stream, F("<O>"));
2020-09-25 19:51:08 +02:00
return true;
case 1: // <Z ID>
if (!Output::remove(p[0]))
return false;
StringFormatter::send(stream, F("<O>"));
return true;
2020-09-25 19:51:08 +02:00
case 0: // <Z> list Output definitions
2020-09-25 19:51:08 +02:00
{
bool gotone = false;
for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput)
{
gotone = true;
StringFormatter::send(stream, F("<Y %d %d %d %d>"), tt->data.id, tt->data.pin, tt->data.iFlag, tt->data.oStatus);
}
2020-09-25 19:51:08 +02:00
return gotone;
}
default:
return false;
}
}
//===================================
2020-09-25 19:51:08 +02:00
bool DCCEXParser::parsef(Print *stream, int params, int p[])
{
// JMRI sends this info in DCC message format but it's not exactly
// convenient for other processing
2020-09-25 19:51:08 +02:00
if (params == 2)
{
2020-10-13 23:08:19 +02:00
byte instructionField = p[1] & 0xE0; // 1110 0000
if (instructionField == 0x80) // 1000 0000 Function group 1
2020-09-25 19:51:08 +02:00
{
2020-10-13 23:08:19 +02:00
// Shuffle bits from order F0 F4 F3 F2 F1 to F4 F3 F2 F1 F0
2020-09-25 19:51:08 +02:00
byte normalized = (p[1] << 1 & 0x1e) | (p[1] >> 4 & 0x01);
funcmap(p[0], normalized, 0, 4);
}
2020-10-13 23:08:19 +02:00
else if (instructionField == 0xA0) // 1010 0000 Function group 2
2020-09-25 19:51:08 +02:00
{
2020-10-13 23:08:19 +02:00
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);
2020-09-25 19:51:08 +02:00
}
}
2020-09-25 19:51:08 +02:00
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;
}
2020-09-25 19:51:08 +02:00
void DCCEXParser::funcmap(int cab, byte value, byte fstart, byte fstop)
{
for (int i = fstart; i <= fstop; i++)
{
DCC::setFn(cab, i, value & 1);
value >>= 1;
}
}
//===================================
2020-09-25 19:51:08 +02:00
bool DCCEXParser::parseT(Print *stream, int params, int p[])
{
switch (params)
{
case 0: // <T> list turnout definitions
2020-09-25 19:51:08 +02:00
{
bool gotOne = false;
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
{
gotOne = true;
StringFormatter::send(stream, F("<H %d %d %d %d>"), tt->data.id, tt->data.address,
tt->data.subAddress, (tt->data.tStatus & STATUS_ACTIVE)!=0);
2020-09-25 19:51:08 +02:00
}
return gotOne; // will <X> if none found
}
2020-09-25 19:51:08 +02:00
case 1: // <T id> delete turnout
if (!Turnout::remove(p[0]))
return false;
StringFormatter::send(stream, F("<O>"));
return true;
case 2: // <T id 0|1> activate turnout
{
Turnout *tt = Turnout::get(p[0]);
if (!tt)
return false;
tt->activate(p[1]);
StringFormatter::send(stream, F("<H %d %d>"), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0);
2020-09-25 19:51:08 +02:00
}
return true;
2020-09-25 19:51:08 +02:00
case 3: // <T id addr subaddr> define turnout
if (!Turnout::create(p[0], p[1], p[2]))
return false;
StringFormatter::send(stream, F("<O>"));
return true;
2020-09-25 19:51:08 +02:00
default:
return false; // will <x>
}
}
2020-09-25 19:51:08 +02:00
bool DCCEXParser::parseS(Print *stream, int params, int p[])
{
2020-09-25 19:51:08 +02:00
switch (params)
{
case 3: // <S id pin pullup> create sensor. pullUp indicator (0=LOW/1=HIGH)
if (!Sensor::create(p[0], p[1], p[2]))
return false;
StringFormatter::send(stream, F("<O>"));
2020-09-25 19:51:08 +02:00
return true;
2020-09-25 19:51:08 +02:00
case 1: // S id> remove sensor
if (!Sensor::remove(p[0]))
return false;
StringFormatter::send(stream, F("<O>"));
return true;
case 0: // <S> list sensor definitions
if (Sensor::firstSensor == NULL)
return false;
2020-09-25 19:51:08 +02:00
for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor)
{
StringFormatter::send(stream, F("<Q %d %d %d>"), tt->data.snum, tt->data.pin, tt->data.pullUp);
}
2020-09-25 19:51:08 +02:00
return true;
default: // invalid number of arguments
break;
}
return false;
}
2020-09-25 19:51:08 +02:00
bool DCCEXParser::parseD(Print *stream, int params, int 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: // <D CABS>
DCC::displayCabList(stream);
return true;
case HASH_KEYWORD_RAM: // <D RAM>
StringFormatter::send(stream, F("\nFree memory=%d\n"), freeMemory());
break;
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX] Value>
if (params >= 3) {
if (p[1] == HASH_KEYWORD_LIMIT) {
DCCWaveform::progTrack.setAckLimit(p[2]);
StringFormatter::send(stream, F("\nAck limit=%dmA\n"), p[2]);
} else if (p[1] == HASH_KEYWORD_MIN) {
DCCWaveform::progTrack.setMinAckPulseDuration(p[2]);
StringFormatter::send(stream, F("\nAck min=%dus\n"), p[2]);
} else if (p[1] == HASH_KEYWORD_MAX) {
DCCWaveform::progTrack.setMaxAckPulseDuration(p[2]);
StringFormatter::send(stream, F("\nAck max=%dus\n"), p[2]);
}
2020-11-24 21:12:55 +01:00
} else {
StringFormatter::send(stream, F("\nAck diag %S\n"), onOff ? F("on") : F("off"));
Diag::ACK = onOff;
2020-11-24 21:12:55 +01:00
}
2020-09-25 19:51:08 +02:00
return true;
2020-09-25 19:51:08 +02:00
case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
Diag::CMD = onOff;
return true;
2020-09-25 19:51:08 +02:00
case HASH_KEYWORD_WIFI: // <D WIFI ON/OFF>
Diag::WIFI = onOff;
return true;
2020-10-30 14:00:02 +01:00
case HASH_KEYWORD_ETHERNET: // <D ETHERNET ON/OFF>
Diag::ETHERNET = onOff;
return true;
2020-09-25 19:51:08 +02:00
case HASH_KEYWORD_WIT: // <D WIT ON/OFF>
Diag::WITHROTTLE = onOff;
return true;
2020-09-27 13:03:46 +02:00
case HASH_KEYWORD_PROGBOOST:
DCC::setProgTrackBoost(true);
return true;
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
if (params >= 2)
2020-10-04 21:20:13 +02:00
EEStore::dump(p[1]);
return true;
2020-09-25 19:51:08 +02:00
default: // invalid/unknown
break;
}
return false;
}
2020-09-25 19:51:08 +02:00
// CALLBACKS must be static
2021-02-09 14:43:40 +01:00
bool DCCEXParser::stashCallback(Print *stream, int p[MAX_COMMAND_PARAMS])
2020-09-25 19:51:08 +02:00
{
if (stashBusy )
2020-09-25 19:51:08 +02:00
return false;
stashBusy = true;
stashStream = stream;
2021-02-09 14:43:40 +01:00
memcpy(stashP, p, MAX_COMMAND_PARAMS * sizeof(p[0]));
2020-09-25 19:51:08 +02:00
return true;
}
void DCCEXParser::callback_W(int result)
{
StringFormatter::send(stashStream, F("<r%d|%d|%d %d>"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
stashBusy = false;
}
2020-09-25 19:51:08 +02:00
void DCCEXParser::callback_B(int result)
{
StringFormatter::send(stashStream, F("<r%d|%d|%d %d %d>"), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1);
stashBusy = false;
}
2020-09-25 19:51:08 +02:00
void DCCEXParser::callback_Vbit(int result)
{
StringFormatter::send(stashStream, F("<v %d %d %d>"), stashP[0], stashP[1], result);
stashBusy = false;
2020-09-18 01:04:42 +02:00
}
2020-09-25 19:51:08 +02:00
void DCCEXParser::callback_Vbyte(int result)
{
StringFormatter::send(stashStream, F("<v %d %d>"), stashP[0], result);
stashBusy = false;
2020-09-18 01:04:42 +02:00
}
2020-09-25 19:51:08 +02:00
void DCCEXParser::callback_R(int result)
{
StringFormatter::send(stashStream, F("<r%d|%d|%d %d>"), stashP[1], stashP[2], stashP[0], result);
stashBusy = false;
}
2020-09-20 01:59:07 +02:00
2020-09-25 19:51:08 +02:00
void DCCEXParser::callback_Rloco(int result)
{
StringFormatter::send(stashStream, F("<r %d>"), result);
stashBusy = false;
}
void DCCEXParser::callback_Wloco(int result)
{
if (result==1) result=stashP[0]; // pick up original requested id from command
StringFormatter::send(stashStream, F("<w %d>"), result);
2020-09-25 19:51:08 +02:00
stashBusy = false;
2020-09-20 01:59:07 +02:00
}