Files
Last update 2 years 9 months
by
eddyluursema
SerialCommand.cpp/********************************************************************** SerialCommand.cpp COPYRIGHT (c) 2013-2016 Gregg E. Berman Part of DCC++ BASE STATION for the Arduino Adapted by Eddy Luursema October 23 2019 F1 decrease loc 3 speed with a mininum of -120 F2 stop F3 decrease loc 3 speed with a maximum of 120 **********************************************************************/ // DCC++ BASE STATION COMMUNICATES VIA THE SERIAL PORT USING SINGLE-CHARACTER TEXT COMMANDS // WITH OPTIONAL PARAMTERS, AND BRACKETED BY < AND > SYMBOLS. SPACES BETWEEN PARAMETERS // ARE REQUIRED. SPACES ANYWHERE ELSE ARE IGNORED. A SPACE BETWEEN THE SINGLE-CHARACTER // COMMAND AND THE FIRST PARAMETER IS ALSO NOT REQUIRED. // See SerialCommand::parse() below for defined text commands. #include "SerialCommand.h" #include "DCCpp_Uno.h" #include "Accessories.h" #include "Sensor.h" #include "Outputs.h" #include "EEStore.h" #include "Comm.h" extern int __heap_start, *__brkval; /////////////////////////////////////////////////////////////////////////////// char SerialCommand::commandString[MAX_COMMAND_LENGTH + 1]; volatile RegisterList *SerialCommand::mRegs; volatile RegisterList *SerialCommand::pRegs; CurrentMonitor *SerialCommand::mMonitor; //////////////Eddy////////////// const int ESCAPE = 0x1B; const int LEFTSQUAREBRACKET = 0x5B; const int TILDE = 0x7E; const int ASCIIZERO = 0x30; const int ASCIININE = 0x39; enum receiveStates {WAITESCAPE, WAITLEFTSQUARE, WAITFIRST, WAITSECOND, WAITTILDE}; receiveStates receiveState; int locSpeed = 0; int firstKey; int secondKey; char dccString[MAX_COMMAND_LENGTH + 1]; //command to send to DCC++ station //////////////Eddy////////////// /////////////////////////////////////////////////////////////////////////////// void SerialCommand::init(volatile RegisterList *_mRegs, volatile RegisterList *_pRegs, CurrentMonitor *_mMonitor) { mRegs = _mRegs; pRegs = _pRegs; mMonitor = _mMonitor; sprintf(commandString, ""); receiveState = WAITESCAPE; } // SerialCommand:SerialCommand //////////////Eddy////////////// void SerialCommand::testFunctionKeys(char c) { switch (receiveState) { case WAITESCAPE: if (c == ESCAPE) { receiveState = WAITLEFTSQUARE; } break; case WAITLEFTSQUARE: if (c == LEFTSQUAREBRACKET) { receiveState = WAITFIRST; } else { receiveState = WAITESCAPE; } break; case WAITFIRST: if ((c >= ASCIIZERO) && (c <= ASCIININE)) { receiveState = WAITSECOND; firstKey = c - 0x30; } else { receiveState = WAITESCAPE; } break; case WAITSECOND: if ((c >= ASCIIZERO) && (c <= ASCIININE)) { receiveState = WAITTILDE; secondKey = c - 0x30; } else { receiveState = WAITESCAPE; } break; case WAITTILDE: if (c == TILDE) { //we have received a coorect VT-100 command code if (firstKey == 1) { //F1 to F8 switch (secondKey) { case 1 : //F1 pressed decrease speed if (locSpeed >= MINSPEED + SPEEDSTEP) { locSpeed -= SPEEDSTEP; sprintf(dccString, "t 1 3 %d %d", abs(locSpeed), (locSpeed > 0) ? 1 : 0); } break; case 2: //F2 pressed STOP locSpeed = 0; sprintf(dccString, "t 1 3 %d %d", abs(locSpeed), (locSpeed > 0) ? 1 : 0); break; case 3: //F3 pressed increase speed if (locSpeed <= MAXSPEED - SPEEDSTEP) { locSpeed += SPEEDSTEP; sprintf(dccString, "t 1 3 %d %d", abs(locSpeed), (locSpeed > 0) ? 1 : 0); } break; case 4: //F4 POWER ON sprintf(dccString, "1"); break; case 5: //F5 POWER OFF sprintf(dccString, "0"); break; case 7: //F6 LIGHT ON sprintf(dccString, "f 3 144"); break; case 8: //F7 LIGHT OF sprintf(dccString, "f 3 128"); break; case 9: //F8 pressed set accessory adress 501 sprintf(dccString, "a 126 0 1"); break; default: sprintf(dccString, ""); } } else { if (firstKey == 2) { switch (secondKey) { case 0 : //F9 pressed set accessory sprintf(dccString, "a 126 0 0"); break; case 1 : //F10 pressed set accessory sprintf(dccString, "a 126 1 1"); break; case 3 : //F11 pressed set accessory sprintf(dccString, "a 126 1 0"); break; default: sprintf(dccString, ""); } } } //Serial.print(dccString); parse(dccString); } receiveState = WAITESCAPE; } } //////////////Eddy////////////// void SerialCommand::process() { char c; #if COMM_TYPE == 0 while (INTERFACE.available() > 0) { // while there is data on the serial line c = INTERFACE.read(); //////////////Eddy////////////// testFunctionKeys(c); //////////////Eddy////////////// if (c == '<') // start of new command sprintf(commandString, ""); else if (c == '>') // end of new command parse(commandString); else if (strlen(commandString) < MAX_COMMAND_LENGTH) // if comandString still has space, append character just read from serial line sprintf(commandString, "%s%c", commandString, c); // otherwise, character is ignored (but continue to look for '<' or '>') } // while #elif COMM_TYPE == 1 EthernetClient client = INTERFACE.available(); if (client) { while (client.connected() && client.available()) { // while there is data on the network c = client.read(); if (c == '<') // start of new command sprintf(commandString, ""); else if (c == '>') // end of new command parse(commandString); else if (strlen(commandString) < MAX_COMMAND_LENGTH) // if comandString still has space, append character just read from network sprintf(commandString, "%s%c", commandString, c); // otherwise, character is ignored (but continue to look for '<' or '>') } // while } #endif } // SerialCommand:process /////////////////////////////////////////////////////////////////////////////// void SerialCommand::parse(char *com) { switch (com[0]) { /***** SET ENGINE THROTTLES USING 128-STEP SPEED CONTROL ****/ case 't': // <t REGISTER CAB SPEED DIRECTION> /* sets the throttle for a given register/cab combination REGISTER: an internal register number, from 1 through MAX_MAIN_REGISTERS (inclusive), to store the DCC packet used to control this throttle setting CAB: the short (1-127) or long (128-10293) address of the engine decoder SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0) DIRECTION: 1=forward, 0=reverse. Setting direction when speed=0 or speed=-1 only effects directionality of cab lighting for a stopped train returns: <T REGISTER SPEED DIRECTION> */ mRegs->setThrottle(com + 1); break; /***** OPERATE ENGINE DECODER FUNCTIONS F0-F28 ****/ case 'f': // <f CAB BYTE1 [BYTE2]> /* turns on and off engine decoder functions F0-F28 (F0 is sometimes called FL) NOTE: setting requests transmitted directly to mobile engine decoder --- current state of engine functions is not stored by this program CAB: the short (1-127) or long (128-10293) address of the engine decoder To set functions F0-F4 on (=1) or off (=0): BYTE1: 128 + F1*1 + F2*2 + F3*4 + F4*8 + F0*16 BYTE2: omitted To set functions F5-F8 on (=1) or off (=0): BYTE1: 176 + F5*1 + F6*2 + F7*4 + F8*8 BYTE2: omitted To set functions F9-F12 on (=1) or off (=0): BYTE1: 160 + F9*1 +F10*2 + F11*4 + F12*8 BYTE2: omitted To set functions F13-F20 on (=1) or off (=0): BYTE1: 222 BYTE2: F13*1 + F14*2 + F15*4 + F16*8 + F17*16 + F18*32 + F19*64 + F20*128 To set functions F21-F28 on (=1) of off (=0): BYTE1: 223 BYTE2: F21*1 + F22*2 + F23*4 + F24*8 + F25*16 + F26*32 + F27*64 + F28*128 returns: NONE */ mRegs->setFunction(com + 1); break; /***** OPERATE STATIONARY ACCESSORY DECODERS ****/ case 'a': // <a ADDRESS SUBADDRESS ACTIVATE> /* turns an accessory (stationary) decoder on or off ADDRESS: the primary address of the decoder (0-511) SUBADDRESS: the subaddress of the decoder (0-3) ACTIVATE: 1=on (set), 0=off (clear) Note that many decoders and controllers combine the ADDRESS and SUBADDRESS into a single number, N, from 1 through a max of 2044, where N = (ADDRESS - 1) * 4 + SUBADDRESS + 1, for all ADDRESS>0 OR ADDRESS = INT((N - 1) / 4) + 1 SUBADDRESS = (N - 1) % 4 returns: NONE */ mRegs->setAccessory(com + 1); break; /***** CREATE/EDIT/REMOVE/SHOW & OPERATE A TURN-OUT ****/ case 'T': // <T ID THROW> /* <T ID THROW>: sets turnout ID to either the "thrown" or "unthrown" position ID: the numeric ID (0-32767) of the turnout to control THROW: 0 (unthrown) or 1 (thrown) returns: <H ID THROW> or <X> if turnout ID does not exist * *** SEE ACCESSORIES.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "T" COMMAND USED TO CREATE/EDIT/REMOVE/SHOW TURNOUT DEFINITIONS */ Turnout::parse(com + 1); break; /***** CREATE/EDIT/REMOVE/SHOW & OPERATE AN OUTPUT PIN ****/ case 'Z': // <Z ID ACTIVATE> /* <Z ID ACTIVATE>: sets output ID to either the "active" or "inactive" state ID: the numeric ID (0-32767) of the output to control ACTIVATE: 0 (active) or 1 (inactive) returns: <Y ID ACTIVATE> or <X> if output ID does not exist * *** SEE OUTPUTS.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "O" COMMAND USED TO CREATE/EDIT/REMOVE/SHOW TURNOUT DEFINITIONS */ Output::parse(com + 1); break; /***** CREATE/EDIT/REMOVE/SHOW A SENSOR ****/ case 'S': /* * *** SEE SENSOR.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "S" COMMAND USED TO CREATE/EDIT/REMOVE/SHOW SENSOR DEFINITIONS */ Sensor::parse(com + 1); break; /***** SHOW STATUS OF ALL SENSORS ****/ case 'Q': // <Q> /* returns: the status of each sensor ID in the form <Q ID> (active) or <q ID> (not active) */ Sensor::status(); break; /***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/ case 'w': // <w CAB CV VALUE> /* writes, without any verification, a Configuration Variable to the decoder of an engine on the main operations track CAB: the short (1-127) or long (128-10293) address of the engine decoder CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) VALUE: the value to be written to the Configuration Variable memory location (0-255) returns: NONE */ mRegs->writeCVByteMain(com + 1); break; /***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/ case 'b': // <b CAB CV BIT VALUE> /* writes, without any verification, a single bit within a Configuration Variable to the decoder of an engine on the main operations track CAB: the short (1-127) or long (128-10293) address of the engine decoder CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) BIT: the bit number of the Configurarion Variable regsiter to write (0-7) VALUE: the value of the bit to be written (0-1) returns: NONE */ mRegs->writeCVBitMain(com + 1); break; /***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON PROGRAMMING TRACK ****/ case 'W': // <W CV VALUE CALLBACKNUM CALLBACKSUB> /* writes, and then verifies, a Configuration Variable to the decoder of an engine on the programming track CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) VALUE: the value to be written to the Configuration Variable memory location (0-255) CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function returns: <r CALLBACKNUM|CALLBACKSUB|CV Value) where VALUE is a number from 0-255 as read from the requested CV, or -1 if verificaiton read fails */ pRegs->writeCVByte(com + 1); break; /***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON PROGRAMMING TRACK ****/ case 'B': // <B CV BIT VALUE CALLBACKNUM CALLBACKSUB> /* writes, and then verifies, a single bit within a Configuration Variable to the decoder of an engine on the programming track CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) BIT: the bit number of the Configurarion Variable memory location to write (0-7) VALUE: the value of the bit to be written (0-1) CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function returns: <r CALLBACKNUM|CALLBACKSUB|CV BIT VALUE) where VALUE is a number from 0-1 as read from the requested CV bit, or -1 if verificaiton read fails */ pRegs->writeCVBit(com + 1); break; /***** READ CONFIGURATION VARIABLE BYTE FROM ENGINE DECODER ON PROGRAMMING TRACK ****/ case 'R': // <R CV CALLBACKNUM CALLBACKSUB> /* reads a Configuration Variable from the decoder of an engine on the programming track CV: the number of the Configuration Variable memory location in the decoder to read from (1-1024) CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function returns: <r CALLBACKNUM|CALLBACKSUB|CV VALUE) where VALUE is a number from 0-255 as read from the requested CV, or -1 if read could not be verified */ pRegs->readCV(com + 1); break; /***** TURN ON POWER FROM MOTOR SHIELD TO TRACKS ****/ case '1': // <1> /* enables power from the motor shield to the main operations and programming tracks returns: <p1> */ digitalWrite(SIGNAL_ENABLE_PIN_PROG, HIGH); digitalWrite(SIGNAL_ENABLE_PIN_MAIN, HIGH); INTERFACE.print("<p1>"); break; /***** TURN OFF POWER FROM MOTOR SHIELD TO TRACKS ****/ case '0': // <0> /* disables power from the motor shield to the main operations and programming tracks returns: <p0> */ digitalWrite(SIGNAL_ENABLE_PIN_PROG, LOW); digitalWrite(SIGNAL_ENABLE_PIN_MAIN, LOW); INTERFACE.print("<p0>"); break; /***** READ MAIN OPERATIONS TRACK CURRENT ****/ case 'c': // <c> /* reads current being drawn on main operations track returns: <a CURRENT> where CURRENT = 0-1024, based on exponentially-smoothed weighting scheme */ INTERFACE.print("<a"); INTERFACE.print(int(mMonitor->current)); INTERFACE.print(">"); break; /***** READ STATUS OF DCC++ BASE STATION ****/ case 's': // <s> /* returns status messages containing track power status, throttle status, turn-out status, and a version number NOTE: this is very useful as a first command for an interface to send to this sketch in order to verify connectivity and update any GUI to reflect actual throttle and turn-out settings returns: series of status messages that can be read by an interface to determine status of DCC++ Base Station and important settings */ if (digitalRead(SIGNAL_ENABLE_PIN_PROG) == LOW) // could check either PROG or MAIN INTERFACE.print("<p0>"); else INTERFACE.print("<p1>"); for (int i = 1; i <= MAX_MAIN_REGISTERS; i++) { if (mRegs->speedTable[i] == 0) continue; INTERFACE.print("<T"); INTERFACE.print(i); INTERFACE.print(" "); if (mRegs->speedTable[i] > 0) { INTERFACE.print(mRegs->speedTable[i]); INTERFACE.print(" 1>"); } else { INTERFACE.print(-mRegs->speedTable[i]); INTERFACE.print(" 0>"); } } INTERFACE.print("<iDCC++ BASE STATION FOR ARDUINO "); INTERFACE.print(ARDUINO_TYPE); INTERFACE.print(" / "); INTERFACE.print(MOTOR_SHIELD_NAME); INTERFACE.print(": V-"); INTERFACE.print(VERSION); INTERFACE.print(" / "); INTERFACE.print(__DATE__); INTERFACE.print(" "); INTERFACE.print(__TIME__); INTERFACE.print(">"); INTERFACE.print("<N"); INTERFACE.print(COMM_TYPE); INTERFACE.print(": "); #if COMM_TYPE == 0 INTERFACE.print("SERIAL>"); #elif COMM_TYPE == 1 INTERFACE.print(Ethernet.localIP()); INTERFACE.print(">"); #endif Turnout::show(); Output::show(); break; /***** STORE SETTINGS IN EEPROM ****/ case 'E': // <E> /* stores settings for turnouts and sensors EEPROM returns: <e nTurnouts nSensors> */ EEStore::store(); INTERFACE.print("<e "); INTERFACE.print(EEStore::eeStore->data.nTurnouts); INTERFACE.print(" "); INTERFACE.print(EEStore::eeStore->data.nSensors); INTERFACE.print(" "); INTERFACE.print(EEStore::eeStore->data.nOutputs); INTERFACE.print(">"); break; /***** CLEAR SETTINGS IN EEPROM ****/ case 'e': // <e> /* clears settings for Turnouts in EEPROM returns: <O> */ EEStore::clear(); INTERFACE.print("<O>"); break; /***** PRINT CARRIAGE RETURN IN SERIAL MONITOR WINDOW ****/ case ' ': // < > /* simply prints a carriage return - useful when interacting with Ardiuno through serial monitor window returns: a carriage return */ INTERFACE.println(""); break; /// /// THE FOLLOWING COMMANDS ARE NOT NEEDED FOR NORMAL OPERATIONS AND ARE ONLY USED FOR TESTING AND DEBUGGING PURPOSES /// PLEASE SEE SPECIFIC WARNINGS IN EACH COMMAND BELOW /// /***** ENTER DIAGNOSTIC MODE ****/ case 'D': // <D> /* changes the clock speed of the chip and the pre-scaler for the timers so that you can visually see the DCC signals flickering with an LED SERIAL COMMUNICAITON WILL BE INTERUPTED ONCE THIS COMMAND IS ISSUED - MUST RESET BOARD OR RE-OPEN SERIAL WINDOW TO RE-ESTABLISH COMMS */ Serial.println("\nEntering Diagnostic Mode..."); delay(1000); bitClear(TCCR1B, CS12); // set Timer 1 prescale=8 - SLOWS NORMAL SPEED BY FACTOR OF 8 bitSet(TCCR1B, CS11); bitClear(TCCR1B, CS10); #ifdef ARDUINO_AVR_UNO // Configuration for UNO bitSet(TCCR0B, CS02); // set Timer 0 prescale=256 - SLOWS NORMAL SPEED BY A FACTOR OF 4 bitClear(TCCR0B, CS01); bitClear(TCCR0B, CS00); #else // Configuration for MEGA bitClear(TCCR3B, CS32); // set Timer 3 prescale=8 - SLOWS NORMAL SPEED BY A FACTOR OF 8 bitSet(TCCR3B, CS31); bitClear(TCCR3B, CS30); #endif CLKPR = 0x80; // THIS SLOWS DOWN SYSYEM CLOCK BY FACTOR OF 256 CLKPR = 0x08; // BOARD MUST BE RESET TO RESUME NORMAL OPERATIONS break; /***** WRITE A DCC PACKET TO ONE OF THE REGSITERS DRIVING THE MAIN OPERATIONS TRACK ****/ case 'M': // <M REGISTER BYTE1 BYTE2 [BYTE3] [BYTE4] [BYTE5]> /* writes a DCC packet of two, three, four, or five hexidecimal bytes to a register driving the main operations track FOR DEBUGGING AND TESTING PURPOSES ONLY. DO NOT USE UNLESS YOU KNOW HOW TO CONSTRUCT NMRA DCC PACKETS - YOU CAN INADVERTENTLY RE-PROGRAM YOUR ENGINE DECODER REGISTER: an internal register number, from 0 through MAX_MAIN_REGISTERS (inclusive), to write (if REGISTER=0) or write and store (if REGISTER>0) the packet BYTE1: first hexidecimal byte in the packet BYTE2: second hexidecimal byte in the packet BYTE3: optional third hexidecimal byte in the packet BYTE4: optional fourth hexidecimal byte in the packet BYTE5: optional fifth hexidecimal byte in the packet returns: NONE */ mRegs->writeTextPacket(com + 1); break; /***** WRITE A DCC PACKET TO ONE OF THE REGSITERS DRIVING THE MAIN OPERATIONS TRACK ****/ case 'P': // <P REGISTER BYTE1 BYTE2 [BYTE3] [BYTE4] [BYTE5]> /* writes a DCC packet of two, three, four, or five hexidecimal bytes to a register driving the programming track FOR DEBUGGING AND TESTING PURPOSES ONLY. DO NOT USE UNLESS YOU KNOW HOW TO CONSTRUCT NMRA DCC PACKETS - YOU CAN INADVERTENTLY RE-PROGRAM YOUR ENGINE DECODER REGISTER: an internal register number, from 0 through MAX_MAIN_REGISTERS (inclusive), to write (if REGISTER=0) or write and store (if REGISTER>0) the packet BYTE1: first hexidecimal byte in the packet BYTE2: second hexidecimal byte in the packet BYTE3: optional third hexidecimal byte in the packet BYTE4: optional fourth hexidecimal byte in the packet BYTE5: optional fifth hexidecimal byte in the packet returns: NONE */ pRegs->writeTextPacket(com + 1); break; /***** ATTEMPTS TO DETERMINE HOW MUCH FREE SRAM IS AVAILABLE IN ARDUINO ****/ case 'F': // <F> /* measure amount of free SRAM memory left on the Arduino based on trick found on the internet. Useful when setting dynamic array sizes, considering the Uno only has 2048 bytes of dynamic SRAM. Unfortunately not very reliable --- would be great to find a better method returns: <f MEM> where MEM is the number of free bytes remaining in the Arduino's SRAM */ int v; INTERFACE.print("<f"); INTERFACE.print((int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval)); INTERFACE.print(">"); break; /***** LISTS BIT CONTENTS OF ALL INTERNAL DCC PACKET REGISTERS ****/ case 'L': // <L> /* lists the packet contents of the main operations track registers and the programming track registers FOR DIAGNOSTIC AND TESTING USE ONLY */ INTERFACE.println(""); for (Register *p = mRegs->reg; p <= mRegs->maxLoadedReg; p++) { INTERFACE.print("M"); INTERFACE.print((int)(p - mRegs->reg)); INTERFACE.print(":\t"); INTERFACE.print((int)p); INTERFACE.print("\t"); INTERFACE.print((int)p->activePacket); INTERFACE.print("\t"); INTERFACE.print(p->activePacket->nBits); INTERFACE.print("\t"); for (int i = 0; i < 10; i++) { INTERFACE.print(p->activePacket->buf[i], HEX); INTERFACE.print("\t"); } INTERFACE.println(""); } for (Register *p = pRegs->reg; p <= pRegs->maxLoadedReg; p++) { INTERFACE.print("P"); INTERFACE.print((int)(p - pRegs->reg)); INTERFACE.print(":\t"); INTERFACE.print((int)p); INTERFACE.print("\t"); INTERFACE.print((int)p->activePacket); INTERFACE.print("\t"); INTERFACE.print(p->activePacket->nBits); INTERFACE.print("\t"); for (int i = 0; i < 10; i++) { INTERFACE.print(p->activePacket->buf[i], HEX); INTERFACE.print("\t"); } INTERFACE.println(""); } INTERFACE.println(""); break; } // switch }; // SerialCommand::parse ///////////////////////////////////////////////////////////////////////////////