1013 lines
32 KiB
C++
1013 lines
32 KiB
C++
|
|
/**
|
|
* @file ecos.ino
|
|
* @brief ECoS protocol support for PacoMouseCYD throttle.
|
|
* @author F. Cañada
|
|
* @date 2025-2026
|
|
* @copyright https://usuaris.tinet.cat/fmco/
|
|
*
|
|
* This file contains functions to communicate with ECoS/CS1 command stations,
|
|
* including sending and receiving messages, requesting locomotive and system status,
|
|
* and handling ECoS-specific operations.
|
|
*
|
|
* This software and associated files are a DIY project that is not intended for commercial use.
|
|
* This software uses libraries with different licenses, follow all their different terms included.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
|
|
*
|
|
* Sources are only provided for building and uploading to the device.
|
|
* You are not allowed to modify the source code or fork/publish this project.
|
|
* Commercial use is forbidden.
|
|
*/
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// API Documentation
|
|
////////////////////////////////////////////////////////////
|
|
/**
|
|
* @brief Sends a message to the ECoS command station.
|
|
* @param buf The message buffer to send.
|
|
*/
|
|
void sendMsgECOS(char *buf);
|
|
|
|
/**
|
|
* @brief Requests various views and information from the ECoS command station.
|
|
*/
|
|
void requestViews();
|
|
|
|
/**
|
|
* @brief Requests the power status from the ECoS command station.
|
|
*/
|
|
void getStatusECoS();
|
|
|
|
/**
|
|
* @brief Requests the list of locomotives from the ECoS command station.
|
|
*/
|
|
void requestLocoList();
|
|
|
|
/**
|
|
* @brief Requests information about a specific locomotive from the ECoS command station.
|
|
* @param ID The ECoS locomotive ID.
|
|
*/
|
|
void infoLocomotoraECoS(unsigned int ID);
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// End API Documentation
|
|
////////////////////////////////////////////////////////////
|
|
|
|
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
|
|
|
|
This software and associated files are a DIY project that is not intended for commercial use.
|
|
This software uses libraries with different licenses, follow all their different terms included.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
|
|
|
|
Sources are only provided for building and uploading to the device.
|
|
You are not allowed to modify the source code or fork/publish this project.
|
|
Commercial use is forbidden.
|
|
*/
|
|
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// ***** ECoS SOPORTE *****
|
|
////////////////////////////////////////////////////////////
|
|
|
|
void sendMsgECOS (char *buf) {
|
|
bool recvAnswer;
|
|
uint32_t timeoutECoS;
|
|
Client.write(buf);
|
|
#ifdef DEBUG
|
|
Serial.print(F("Send: "));
|
|
Serial.println(buf);
|
|
#endif
|
|
timeoutECoS = millis();
|
|
recvAnswer = false;
|
|
while ((millis() - timeoutECoS < 50) && (!recvAnswer)) // wait answer for 50ms
|
|
recvAnswer = ECoSProcess();
|
|
}
|
|
|
|
|
|
void requestViews () {
|
|
snprintf(cmd, 64, "get(%d, info)\n", ID_ECOS); // ECoS info
|
|
sendMsgECOS(cmd);
|
|
snprintf(cmd, 64, "request(%d, view)\n", ID_ECOS); // ECoS manager (power)
|
|
sendMsgECOS(cmd);
|
|
snprintf(cmd, 64, "request(%d, view)\n", ID_LOKMANAGER); // Lok manager
|
|
sendMsgECOS(cmd);
|
|
snprintf(cmd, 64, "request(%d, view)\n", ID_S88FEEDBACK); // S88 feedback
|
|
sendMsgECOS(cmd);
|
|
getStatusECoS();
|
|
}
|
|
|
|
void getStatusECoS() {
|
|
snprintf(cmd, 64, "get(%d, status)\n", ID_ECOS); // Power status
|
|
sendMsgECOS(cmd);
|
|
}
|
|
|
|
void requestLocoList() {
|
|
snprintf(cmd, 64, "queryObjects(%d, addr, name)\n", ID_LOKMANAGER); // only address and name, protocol not needed at the moment
|
|
sendMsgECOS(cmd);
|
|
}
|
|
|
|
|
|
void infoLocomotoraECoS (unsigned int ID) {
|
|
byte n;
|
|
if (ID > 999) {
|
|
bitClear(locoData[myLocoData].mySteps, 3);
|
|
snprintf(cmd, 64, "request(%d, view, control[events])\n", ID); // View Locomotive data updates
|
|
sendMsgECOS(cmd);
|
|
snprintf(cmd, 64, "get(%d, speed, dir, speedstep)\n", ID); // Speed, dir & steps
|
|
sendMsgECOS(cmd);
|
|
for (n = 0; n < 29; n++) {
|
|
snprintf(cmd, 64, "get(%d, func[%d])\n", ID, n); // Functions
|
|
sendMsgECOS(cmd);
|
|
if (appVer > 2)
|
|
snprintf(cmd, 64, "get(%d, funcicon[%d])\n", ID, n); // Icon functions
|
|
else
|
|
snprintf(cmd, 64, "get(%d, funcsymbol[%d])\n", ID, n); // Icon functions
|
|
sendMsgECOS(cmd);
|
|
}
|
|
setTimer (TMR_FNC_ECOS, 10, TMR_ONESHOT);
|
|
}
|
|
}
|
|
|
|
|
|
void releaseLocoECoS() {
|
|
if (locoData[myLocoData].myLocoID) {
|
|
snprintf(cmd, 64, "release(%d, control)\n", locoData[myLocoData].myLocoID); // release control
|
|
sendMsgECOS(cmd);
|
|
snprintf(cmd, 64, "release(%d, view)\n", locoData[myLocoData].myLocoID); // release updates
|
|
sendMsgECOS(cmd);
|
|
}
|
|
}
|
|
|
|
void checkControlLoco() {
|
|
if (bitRead(locoData[myLocoData].mySteps, 3)) {
|
|
bitClear(locoData[myLocoData].mySteps, 3);
|
|
snprintf(cmd, 64, "request(%d, control[events], force)\n", locoData[myLocoData].myLocoID); // force control
|
|
sendMsgECOS(cmd);
|
|
}
|
|
}
|
|
|
|
|
|
void changeDirectionECoS() {
|
|
checkControlLoco();
|
|
snprintf(cmd, 64, "set(%d, dir[%d])\n", locoData[myLocoData].myLocoID, (locoData[myLocoData].myDir & 0x80) ? 0 : 1); // direction (1=rückwärts).
|
|
sendMsgECOS(cmd);
|
|
}
|
|
|
|
|
|
void locoOperationSpeedECoS() {
|
|
checkControlLoco();
|
|
snprintf(cmd, 64, "set(%d, speed[%d])\n", locoData[myLocoData].myLocoID, locoData[myLocoData].mySpeed); // Speed
|
|
sendMsgECOS(cmd);
|
|
}
|
|
|
|
byte getCurrentStepECoS() {
|
|
return (mySpeedStep);
|
|
}
|
|
|
|
void funcOperationsECoS (byte fnc) {
|
|
byte stat;
|
|
checkControlLoco();
|
|
stat = (bitRead(locoData[myLocoData].myFunc.Bits, fnc)) ? 1 : 0;
|
|
snprintf(cmd, 64, "set(%d, func[%d,%d])\n", locoData[myLocoData].myLocoID, fnc, stat); // Function
|
|
sendMsgECOS(cmd);
|
|
}
|
|
|
|
|
|
void setAccessoryECoS(unsigned int FAdr, int pair, bool activate) {
|
|
char pos;
|
|
if (activate) {
|
|
pos = (pair > 0) ? 'g' : 'r';
|
|
snprintf(cmd, 64, "set(%d, switch[%d%c])\n", ID_SWMANAGER, FAdr, pos); // Directly setting a port.
|
|
sendMsgECOS(cmd);
|
|
}
|
|
}
|
|
|
|
void resumeOperationsECoS () {
|
|
snprintf(cmd, 64, "set(%d, go)\n", ID_ECOS); // Track on
|
|
sendMsgECOS(cmd);
|
|
}
|
|
|
|
|
|
void emergencyOffECoS() {
|
|
snprintf(cmd, 64, "set(%d, stop)\n", ID_ECOS); // Track off
|
|
sendMsgECOS(cmd);
|
|
}
|
|
|
|
void showTrkECoS() {
|
|
if (csStatus > 0) {
|
|
stopTimer (TMR_POWER);
|
|
iconData[ICON_POWER].color = COLOR_GREEN;
|
|
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
|
|
newEvent(OBJ_ICON, ICON_POWER, EVNT_DRAW);
|
|
if (isWindow(WIN_STA_PLAY)) {
|
|
fncData[FNC_STA_RAYO].state = false;
|
|
newEvent(OBJ_FNC, FNC_STA_RAYO, EVNT_DRAW);
|
|
}
|
|
if (isWindow(WIN_ALERT)) {
|
|
switch (errType) {
|
|
case ERR_SERV:
|
|
case ERR_STOP:
|
|
case ERR_CV:
|
|
closeWindow(WIN_ALERT);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
iconData[ICON_POWER].color = COLOR_RED; // Power off
|
|
setTimer (TMR_POWER, 5, TMR_PERIODIC); // Flash power icon
|
|
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
|
|
newEvent(OBJ_ICON, ICON_POWER, EVNT_DRAW);
|
|
if (isWindow(WIN_STA_PLAY)) {
|
|
fncData[FNC_STA_RAYO].state = true;
|
|
newEvent(OBJ_FNC, FNC_STA_RAYO, EVNT_DRAW);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void readCVECoS (unsigned int adr, byte stepPrg) {
|
|
if (!modeProg) { // Read only in Direct mode
|
|
snprintf(cmd, 64, "request(%d, view)\n", ID_PRGMANAGER); // Programming manager
|
|
sendMsgECOS(cmd);
|
|
requestedCV = true;
|
|
snprintf(cmd, 64, "set(%d, mode[readdccdirect], cv[%d])\n", ID_PRGMANAGER, adr);
|
|
sendMsgECOS(cmd);
|
|
}
|
|
progStepCV = stepPrg;
|
|
}
|
|
|
|
|
|
void writeCVECoS (unsigned int adr, unsigned int data, byte stepPrg) {
|
|
if (modeProg) {
|
|
snprintf(cmd, 64, "request(%d, view)\n", ID_POMMANAGER); // PoM Programming manager
|
|
sendMsgECOS(cmd);
|
|
snprintf(cmd, 64, "set(%d, mode[writedccpomloco], addr[%d], cv[%d,%d])\n", ID_POMMANAGER, locoData[myLocoData].myAddr.address, adr, data); // Operations Mode Programming byte mode write request
|
|
}
|
|
else {
|
|
snprintf(cmd, 64, "request(%d,view)\n", ID_PRGMANAGER); // Programming manager
|
|
sendMsgECOS(cmd);
|
|
snprintf(cmd, 64, "set(%d, mode[writedccdirect], cv[%d,%d])\n", ID_PRGMANAGER, adr, data);
|
|
}
|
|
requestedCV = true;
|
|
sendMsgECOS(cmd);
|
|
progStepCV = stepPrg;
|
|
DEBUG_MSG("Write CV%d = %d", adr, data);
|
|
}
|
|
|
|
|
|
void exitProgrammingECoS() {
|
|
if (requestedCV) {
|
|
snprintf(cmd, 64, "release(%d,view)\n", ID_PRGMANAGER); // Programming manager
|
|
sendMsgECOS(cmd);
|
|
snprintf(cmd, 64, "release(%d, view)\n", ID_POMMANAGER); // PoM Programming manager
|
|
sendMsgECOS(cmd);
|
|
requestedCV = false;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// ***** ECoS DECODE *****
|
|
////////////////////////////////////////////////////////////
|
|
|
|
bool ECoSProcess() {
|
|
char chr;
|
|
bool rcvMsg;
|
|
rcvMsg = false;
|
|
while (Client.available()) {
|
|
chr = Client.read();
|
|
if (chr == '\n') {
|
|
if (inputLength > 0) {
|
|
inputBuffer[inputLength] = 0;
|
|
rcvMsg = ECoSDecode();
|
|
}
|
|
inputLength = 0;
|
|
}
|
|
else {
|
|
inputBuffer[inputLength++] = chr;
|
|
if (inputLength >= BUF_LNG) // line too long, discard
|
|
inputLength = 0;
|
|
}
|
|
}
|
|
yield();
|
|
if (progFinished) { // fin de lectura/programacion CV
|
|
progFinished = false;
|
|
endProg();
|
|
}
|
|
return rcvMsg;
|
|
}
|
|
|
|
|
|
//============= Scanning ==================
|
|
|
|
// Simplified scanning based on C compiler method. https://github.com/DoctorWkt/acwj
|
|
|
|
char getChar () { // get a chr form input buffer
|
|
return inputBuffer[posFile++];
|
|
}
|
|
|
|
|
|
void putBack(char c) { // Put back an unwanted character
|
|
putBackChr = c;
|
|
posFile--; // set reading position back
|
|
}
|
|
|
|
|
|
char next() { // Get the next character from the input stream.
|
|
char c;
|
|
if (putBackChr) { // Use the character put back if there is one
|
|
c = putBackChr;
|
|
putBackChr = 0;
|
|
posFile++;
|
|
}
|
|
else
|
|
c = getChar(); // Read from input stream
|
|
return c;
|
|
}
|
|
|
|
|
|
char skip() { // Skip past input that we don't need to deal with, i.e. whitespace, newlines. Return the first character we do need to deal with.
|
|
char c;
|
|
c = next();
|
|
while (' ' == c || '\t' == c || '\n' == c || '\r' == c) {
|
|
c = next();
|
|
}
|
|
return (c);
|
|
}
|
|
|
|
|
|
void discardLine() { // ignore rest of the line
|
|
putBackChr = 0;
|
|
inputBuffer[posFile] = 0;
|
|
}
|
|
|
|
|
|
int scanInt(char c) { // Scan and return an integer literal value from the input stream.
|
|
int val;
|
|
val = 0;
|
|
while (isDigit(c)) { // Convert each character into an int value
|
|
val = val * 10 + (c - '0');
|
|
c = next();
|
|
}
|
|
putBack(c); // We hit a non-integer character, put it back.
|
|
return val;
|
|
}
|
|
|
|
|
|
int scanIdent(int c, char *buf, int lim) { // Scan an identifier from the input file and store it in buf[]. Return the identifier's length
|
|
int i = 0;
|
|
|
|
while (isalpha(c) || isdigit(c) || (c == '-') || (c == '_')) { // Allow digits, alpha and underscore -
|
|
if (lim - 1 == i) { // Error if we hit the identifier length limit, else append to buf[] and get next character
|
|
return (0);
|
|
} else if (i < lim - 1) {
|
|
buf[i++] = c;
|
|
}
|
|
c = next();
|
|
}
|
|
putBack(c); // We hit a non-valid character, put it back. NUL-terminate the buf[] and return the length
|
|
buf[i] = '\0';
|
|
return (i);
|
|
}
|
|
|
|
|
|
int scanStr (char *buf) { // Scan in a string literal from the input file, and store it in buf[]. Return the length of the string.
|
|
int i, c;
|
|
for (i = 0; i < TEXTLEN - 1; i++) { // Loop while we have enough buffer space
|
|
// Get the next char and append to buf
|
|
// Return when we hit the ending double quote
|
|
if ((c = next()) == '"') {
|
|
buf[i] = 0;
|
|
return (i);
|
|
}
|
|
buf[i] = c;
|
|
}
|
|
// Ran out of buf[] space
|
|
return (0);
|
|
}
|
|
|
|
|
|
int ident2HEX (char *buf, int *value) { // convert an idetifier (xNNNN) to hex value
|
|
int val, n;
|
|
char c;
|
|
val = 0;
|
|
n = 1;
|
|
if (buf[0] == 'x') {
|
|
while (buf[n]) {
|
|
c = buf[n++];
|
|
val *= 16;
|
|
if (isDigit(c)) {
|
|
val += (c - '0');
|
|
}
|
|
else {
|
|
c &= 0xDF;
|
|
val += ((c - 'A') + 10);
|
|
}
|
|
}
|
|
*value = val;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Given a word from the input, return the matching keyword token number or 0 if it's not a keyword.
|
|
// Switch on the first letter so that we don't have to waste time strcmp()ing against all the keywords.
|
|
int keyword(char *s) {
|
|
switch (*s) {
|
|
case 'R':
|
|
if (!strcmp(s, "REPLY"))
|
|
return (T_REPLY);
|
|
break;
|
|
case 'E':
|
|
if (!strcmp(s, "END"))
|
|
return (T_END);
|
|
if (!strcmp(s, "EVENT"))
|
|
return (T_EVENT);
|
|
if (!strcmp(s, "ECoS2"))
|
|
return (T_ECOS2);
|
|
if (!strcmp(s, "ECoS"))
|
|
return (T_ECOS);
|
|
break;
|
|
case 'O':
|
|
if (!strcmp(s, "OK"))
|
|
return (T_OK);
|
|
break;
|
|
case 'C':
|
|
if (!strcmp(s, "CONTROL_LOST"))
|
|
return (T_LOST);
|
|
if (!strcmp(s, "CentralStation"))
|
|
return (T_CS1);
|
|
break;
|
|
case 'G':
|
|
if (!strcmp(s, "GO"))
|
|
return (T_GO);
|
|
break;
|
|
case 'S':
|
|
if (!strcmp(s, "STOP"))
|
|
return (T_STOP);
|
|
if (!strcmp(s, "SHUTDOWN"))
|
|
return (T_SHUTDWN);
|
|
break;
|
|
case 'A':
|
|
if (!strcmp(s, "ApplicationVersion"))
|
|
return (T_APPV);
|
|
break;
|
|
case 'a':
|
|
if (!strcmp(s, "addr"))
|
|
return (T_ADDR);
|
|
break;
|
|
case 'c':
|
|
if (!strcmp(s, "control"))
|
|
return (T_CONTROL);
|
|
if (!strcmp(s, "cv"))
|
|
return (T_CV);
|
|
break;
|
|
case 'd':
|
|
if (!strcmp(s, "dir"))
|
|
return (T_DIR);
|
|
break;
|
|
case 'e':
|
|
if (!strcmp(s, "error"))
|
|
return (T_ERROR);
|
|
break;
|
|
case 'f':
|
|
if (!strcmp(s, "funcsymbol")) // ECoS replies 'funcsymbol'
|
|
return (T_FSYMBOL);
|
|
if (!strcmp(s, "funcsymb")) // CS1 replies 'funcsymb'
|
|
return (T_FSYMB);
|
|
if (!strcmp(s, "funcicon"))
|
|
return (T_FICON);
|
|
if (!strcmp(s, "func"))
|
|
return (T_FUNC);
|
|
if (!strcmp(s, "force"))
|
|
return (T_FORCE);
|
|
break;
|
|
case 'g':
|
|
if (!strcmp(s, "get"))
|
|
return (T_GET);
|
|
break;
|
|
case 'm':
|
|
if (!strcmp(s, "msg"))
|
|
return (T_MSG);
|
|
break;
|
|
case 'n':
|
|
if (!strcmp(s, "name"))
|
|
return (T_NAME);
|
|
break;
|
|
case 'o':
|
|
if (!strcmp(s, "ok"))
|
|
return (T_CVOK);
|
|
if (!strcmp(s, "other"))
|
|
return (T_OTHER);
|
|
break;
|
|
case 'p':
|
|
if (!strcmp(s, "protocol"))
|
|
return (T_PROT);
|
|
break;
|
|
case 'q':
|
|
if (!strcmp(s, "queryObjects"))
|
|
return (T_QOBJ);
|
|
break;
|
|
case 'r':
|
|
if (!strcmp(s, "request"))
|
|
return (T_REQ);
|
|
if (!strcmp(s, "release"))
|
|
return (T_RELEASE);
|
|
break;
|
|
case 's':
|
|
if (!strcmp(s, "status2"))
|
|
return (T_STATUS2);
|
|
if (!strcmp(s, "status"))
|
|
return (T_STATUS);
|
|
if (!strcmp(s, "state"))
|
|
return (T_STATE);
|
|
if (!strcmp(s, "speedstep"))
|
|
return (T_STEPS);
|
|
if (!strcmp(s, "speed"))
|
|
return (T_SPEED);
|
|
if (!strcmp(s, "set"))
|
|
return (T_SET);
|
|
if (!strcmp(s, "switch"))
|
|
return (T_SWITCH);
|
|
break;
|
|
case 'v':
|
|
if (!strcmp(s, "view"))
|
|
return (T_VIEW);
|
|
break;
|
|
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
|
|
//=========== Lexical ===============
|
|
|
|
int scan(struct token *t) { // Scan and return the next token found in the input. Return 1 if token valid, 0 if no tokens left.
|
|
char c;
|
|
|
|
c = skip(); // Skip whitespace
|
|
switch (c) { // Determine the token based on the input character
|
|
case 0: // EOF
|
|
return (0);
|
|
case '(':
|
|
t->token = T_LPARENT;
|
|
t->intvalue = posFile;
|
|
break;
|
|
case ')':
|
|
t->token = T_RPARENT;
|
|
t->intvalue = posFile;
|
|
break;
|
|
case '[':
|
|
t->token = T_LBRACKET;
|
|
t->intvalue = posFile;
|
|
break;
|
|
case ']':
|
|
t->token = T_RBRACKET;
|
|
t->intvalue = posFile;
|
|
break;
|
|
case '<':
|
|
t->token = T_START;
|
|
t->intvalue = posFile;
|
|
break;
|
|
case '>':
|
|
t->token = T_ENDB;
|
|
t->intvalue = posFile;
|
|
break;
|
|
case ',':
|
|
t->token = T_COMMA;
|
|
t->intvalue = posFile;
|
|
break;
|
|
case '#':
|
|
t->token = T_NULL;
|
|
t->intvalue = posFile;
|
|
discardLine();
|
|
break;
|
|
case '"':
|
|
scanStr(Text);
|
|
t->token = T_STRLIT;
|
|
break;
|
|
default:
|
|
if (isDigit(c)) { // If it's a digit, scan the literal integer value in
|
|
t->intvalue = scanInt(c);
|
|
t->token = T_INTLIT;
|
|
break;
|
|
}
|
|
if (isAlpha(c)) {
|
|
scanIdent(c, Text, TEXTLEN);
|
|
tokenType = keyword(Text);
|
|
if (tokenType) {
|
|
t->token = tokenType;
|
|
break;
|
|
}
|
|
t->token = T_IDENT; // Not a recognised keyword, so it must be an identifier
|
|
t->intvalue = Text[0];
|
|
break;
|
|
}
|
|
return (0);
|
|
break;
|
|
}
|
|
//DEBUG_MSG("Token found: %s", tokstr[T.token]);
|
|
return (1); // We found a token
|
|
}
|
|
|
|
//----------------------------------------------------------
|
|
|
|
bool ECoSDecode() {
|
|
bool endMsg;
|
|
#ifdef DEBUG
|
|
Serial.print(F("Recv: "));
|
|
Serial.println(inputBuffer);
|
|
#endif
|
|
posFile = 0;
|
|
putBackChr = 0;
|
|
endMsg = false;
|
|
while (scan(&T)) {
|
|
switch (msgDecodePhase) {
|
|
case MSG_WAIT: // wait '<' for start of msg
|
|
if (T.token == T_START)
|
|
msgDecodePhase = MSG_START;
|
|
break;
|
|
case MSG_START:
|
|
switch (T.token) {
|
|
case T_REPLY: // receiving a REPLY
|
|
msgDecodePhase = MSG_REPLY;
|
|
break;
|
|
case T_EVENT: // receiving an EVENT
|
|
msgDecodePhase = MSG_EVENT;
|
|
break;
|
|
case T_END: // receiving END
|
|
msgDecodePhase = MSG_END;
|
|
break;
|
|
default:
|
|
msgDecodePhase = MSG_WAIT; // others not supported
|
|
break;
|
|
}
|
|
idManager = 0;
|
|
break;
|
|
case MSG_END:
|
|
if (T.token == T_INTLIT) { // get the error code
|
|
errCode = T.intvalue;
|
|
discardLine();
|
|
msgDecodePhase = MSG_WAIT; // discard rest of message
|
|
switch (errCode) {
|
|
case 25: // NERROR_NOCONTROL
|
|
bitSet(locoData[myLocoData].mySteps, 3);
|
|
break;
|
|
case 15: // NERROR_UNKNOWNID
|
|
if (requestedCV) {
|
|
if (progStepCV != PRG_IDLE) {
|
|
CVdata = 0x0600;
|
|
progFinished = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
DEBUG_MSG("END Error code: %d", errCode);
|
|
}
|
|
endMsg = true;
|
|
break;
|
|
case MSG_EVENT:
|
|
if (T.token == T_INTLIT) // get the event manager
|
|
idManager = T.intvalue;
|
|
discardLine();
|
|
msgDecodePhase = MSG_EVENTBODY; // discard rest of line
|
|
DEBUG_MSG("Manager ID: %d", idManager);
|
|
if ((idManager == ID_PRGMANAGER) || (idManager == ID_POMMANAGER)) {
|
|
lastNumValue = 0x0600;
|
|
}
|
|
break;
|
|
case MSG_REPLY: // <REPLY get(1,status)>
|
|
idCommand = T.token; // get ...
|
|
scan(&T); // (
|
|
scan(&T); // id manager
|
|
if (T.token == T_INTLIT) {
|
|
idManager = T.intvalue;
|
|
//DEBUG_MSG("Reply: %s id: %d", tokstr[idCommand], idManager);
|
|
if ((idManager == ID_LOKMANAGER) && (idCommand == T_QOBJ)) { // list of loks
|
|
numLoks = 0;
|
|
initLocos();
|
|
DEBUG_MSG("Roster list cleared");
|
|
}
|
|
|
|
/*
|
|
if ((idManager == ID_SWMANAGER) && (idCommand == T_GET)) { // info of turnout <REPLY get(11, switch[12g])>
|
|
//DEBUG_MSG("Switch manager get");
|
|
scan(&T); // ,
|
|
scan(&T); // switch
|
|
if (T.token == T_SWITCH) {
|
|
//DEBUG_MSG("Get turnout");
|
|
scan(&T); // [
|
|
scan(&T); // 12g
|
|
if (T.token == T_INTLIT) // 12
|
|
scan(&T);
|
|
if (T.token == T_IDENT) { // Text is "g" or could be "DCC12g"
|
|
lastTxtChar = Text[strlen(Text) - 1];
|
|
DEBUG_MSG("Info turnout pos: %s - %c", Text, lastTxtChar);
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
discardLine();
|
|
msgDecodePhase = MSG_REPLYBODY;
|
|
break;
|
|
case MSG_EVENTBODY:
|
|
switch (T.token) {
|
|
case T_START:
|
|
msgDecodePhase = MSG_START; // End of body
|
|
break;
|
|
case T_INTLIT:
|
|
switch (idManager) { // id
|
|
case ID_ECOS: // ECoS events
|
|
decodeEventECoS();
|
|
break;
|
|
case ID_PRGMANAGER: // Programming events
|
|
case ID_POMMANAGER:
|
|
decodeEventCV(); // decodes CV answer
|
|
break;
|
|
default:
|
|
if (idManager == locoData[myLocoData].myLocoID) { // current loco events
|
|
if (T.intvalue == locoData[myLocoData].myLocoID)
|
|
decodeReplyLoco();
|
|
}
|
|
else {
|
|
/*
|
|
if ((idManager >= ID_S88FEEDBACK) && (idManager < (ID_S88FEEDBACK + 32))) // S88 events
|
|
decodeEventS88Feedback();
|
|
else
|
|
*/
|
|
discardLine();
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
default: // other types of start of line not supported
|
|
discardLine();
|
|
break;
|
|
}
|
|
break;
|
|
case MSG_REPLYBODY:
|
|
switch (T.token) {
|
|
case T_START:
|
|
msgDecodePhase = MSG_START; // End of body
|
|
break;
|
|
case T_INTLIT:
|
|
switch (idManager) { // id
|
|
case ID_ECOS: // decodes answer to: get(1,...) / request
|
|
if (idCommand == T_GET) {
|
|
decodeEventECoS();
|
|
}
|
|
else
|
|
discardLine();
|
|
break;
|
|
case ID_LOKMANAGER: // decodes answer to: queryObjects(10,...)
|
|
decodeLokManager();
|
|
break;
|
|
case ID_SWMANAGER:
|
|
if (idCommand == T_GET) { // decodes answer to: get(11,..)
|
|
//decodeSwitchManager();
|
|
}
|
|
else
|
|
discardLine();
|
|
break;
|
|
default:
|
|
/*
|
|
if ((idManager >= ID_S88FEEDBACK) && (idManager < (ID_S88FEEDBACK + 32)))
|
|
decodeEventS88Feedback();
|
|
*/
|
|
if (idManager == locoData[myLocoData].myLocoID)
|
|
decodeReplyLoco(); // decodes answer to: get(1xxx,...) / set..
|
|
else
|
|
discardLine();
|
|
break;
|
|
}
|
|
break;
|
|
default: // other types of start of line not supported
|
|
discardLine();
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return endMsg;
|
|
}
|
|
|
|
|
|
void decodeEventECoS () {
|
|
if (T.intvalue == ID_ECOS) {
|
|
scan(&T);
|
|
switch (T.token) {
|
|
case T_STATUS: // 1 status[GO]
|
|
scan(&T); // [
|
|
scan(&T); // GO / STOP / SHUTDOWN
|
|
if (T.token == T_GO) {
|
|
csStatus = 1;
|
|
showTrkECoS();
|
|
DEBUG_MSG("Power On");
|
|
}
|
|
else {
|
|
csStatus = 0;
|
|
showTrkECoS();
|
|
DEBUG_MSG("Power Off");
|
|
}
|
|
break;
|
|
case T_APPV: // 1 ApplicationVersion[2.0.4]
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_INTLIT) {
|
|
appVer = T.intvalue;
|
|
DEBUG_MSG("Version: %d", appVer)
|
|
}
|
|
break;
|
|
/*
|
|
case T_CS1: // 1 CentralStation
|
|
case T_ECOS: // 1 ECoS
|
|
case T_ECOS2: // 1 ECoS2
|
|
typeCmdStation = T.token;
|
|
DEBUG_MSG("CS Type: %s", tokstr[typeCmdStation]);
|
|
break;
|
|
*/
|
|
}
|
|
discardLine();
|
|
}
|
|
}
|
|
|
|
|
|
void decodeEventCV() {
|
|
while (scan(&T)) {
|
|
switch (T.token) {
|
|
case T_INTLIT:
|
|
lastNumValue = T.intvalue;
|
|
break;
|
|
case T_CVOK:
|
|
CVdata = lastNumValue;
|
|
progFinished = true;
|
|
discardLine();
|
|
break;
|
|
case T_ERROR:
|
|
CVdata = 0x0600;
|
|
progFinished = true;
|
|
discardLine();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void parseLokAddrName(int pos) {
|
|
scan(&T); // 1003 addr[78] name["W. K"]
|
|
switch (T.token) {
|
|
case T_ADDR:
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_INTLIT)
|
|
locoData[pos].myAddr.address = T.intvalue;
|
|
scan(&T);
|
|
DEBUG_MSG("Addr: %d", locoData[pos].myAddr.address);
|
|
break;
|
|
case T_NAME:
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_STRLIT)
|
|
snprintf(locoData[pos].myName, NAME_LNG + 1, "%s", Text);
|
|
scan(&T);
|
|
DEBUG_MSG("Name: %s", locoData[pos].myName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void decodeLokManager () {
|
|
int id, fnc, num;
|
|
switch (idCommand) {
|
|
case T_QOBJ:
|
|
if (numLoks < LOCOS_IN_STACK) {
|
|
locoData[numLoks].myLocoID = T.intvalue; // 1003 addr[78] name["W. K"]
|
|
DEBUG_MSG("ID: %d", locoData[numLoks].myLocoID);
|
|
parseLokAddrName(numLoks); // addr
|
|
parseLokAddrName(numLoks); // name
|
|
pushLoco(locoData[numLoks].myLocoID); //.myAddr.address);
|
|
numLoks++;
|
|
}
|
|
discardLine();
|
|
break;
|
|
default:
|
|
discardLine();
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void decodeReplyLoco() {
|
|
int numFunc;
|
|
scan(&T);
|
|
switch (T.token) { // <REPLY get(1xxx,... )> / <EVENT 1xxx>
|
|
case T_MSG: // 1018 msg[CONTROL_LOST]
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_LOST) {
|
|
bitSet(locoData[myLocoData].mySteps, 3);
|
|
DEBUG_MSG("Lost control of %d", locoData[myLocoData].myLocoID);
|
|
}
|
|
break;
|
|
case T_CONTROL: // 1000 control[other]
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_OTHER) {
|
|
bitSet(locoData[myLocoData].mySteps, 3);
|
|
DEBUG_MSG("Control %d by other", locoData[myLocoData].myLocoID);
|
|
}
|
|
break;
|
|
case T_SPEED: // 1000 speed[6]
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_INTLIT) {
|
|
locoData[myLocoData].mySpeed = T.intvalue;
|
|
updateSpeedHID();
|
|
DEBUG_MSG("Speed: %d", T.intvalue);
|
|
}
|
|
break;
|
|
case T_STEPS: // 1000 speedstep[2]
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_INTLIT) {
|
|
mySpeedStep = T.intvalue;
|
|
DEBUG_MSG("Steps: %d", T.intvalue);
|
|
}
|
|
break;
|
|
case T_DIR: // 1000 dir[0]
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_INTLIT) {
|
|
locoData[myLocoData].myDir = (T.intvalue > 0) ? 0 : 0x80;
|
|
updateSpeedDir();
|
|
DEBUG_MSG("Dir: %d", T.intvalue);
|
|
}
|
|
break;
|
|
case T_FUNC: // 1015 func[0, 0]
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_INTLIT) {
|
|
numFunc = T.intvalue;
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_INTLIT) {
|
|
if (T.intvalue > 0)
|
|
bitSet(locoData[myLocoData].myFunc.Bits, numFunc);
|
|
else
|
|
bitClear(locoData[myLocoData].myFunc.Bits, numFunc);
|
|
DEBUG_MSG("Func%d: %d", numFunc, T.intvalue);
|
|
updateFuncState(isWindow(WIN_THROTTLE));
|
|
}
|
|
}
|
|
break;
|
|
/*
|
|
case T_FSYMBOL: // 1015 funcsymbol[0,2]
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_INTLIT) {
|
|
numFunc = T.intvalue;
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_INTLIT) {
|
|
locoData[myLocoData].myFuncIcon[numFunc] = FunktionsTastenSymbole[T.intvalue & 0x7F] << 1;
|
|
DEBUG_MSG("Func: %d, Icon: %d", numFunc, T.intvalue)
|
|
}
|
|
}
|
|
break;
|
|
*/
|
|
case T_FSYMB: // 1015 funcsymb[0,2]
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_INTLIT) {
|
|
numFunc = T.intvalue;
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_INTLIT) {
|
|
locoData[myLocoData].myFuncIcon[numFunc] = FunktionsTastenSymboleCS1[T.intvalue & 0x3F] << 1;
|
|
DEBUG_MSG("Func: %d, Icon: %d", numFunc, T.intvalue)
|
|
}
|
|
}
|
|
break;
|
|
case T_FICON: // 1000 funcicon[0,3,light]
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_INTLIT) {
|
|
numFunc = T.intvalue;
|
|
scan(&T);
|
|
scan(&T);
|
|
if (T.token == T_INTLIT) {
|
|
locoData[myLocoData].myFuncIcon[numFunc] = FunktionsTastenSymbole[T.intvalue & 0x7F] << 1;
|
|
DEBUG_MSG("Func: %d, Icon: %d", numFunc, T.intvalue)
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
discardLine();
|
|
}
|