SerialCommand.cpp
/**********************************************************************
SerialCommand.cpp
COPYRIGHT (c) 2013-2016 Gregg E. Berman
Part of DCC++ BASE STATION for the Arduino
**********************************************************************/
// 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;
///////////////////////////////////////////////////////////////////////////////
void SerialCommand::init(volatile RegisterList *_mRegs, volatile RegisterList *_pRegs, CurrentMonitor *_mMonitor){
mRegs=_mRegs;
pRegs=_pRegs;
mMonitor=_mMonitor;
sprintf(commandString,"");
} // SerialCommand:SerialCommand
///////////////////////////////////////////////////////////////////////////////
void SerialCommand::process(){
char c;
#if COMM_TYPE == 0
while(INTERFACE.available()>0){ // while there is data on the serial line
c=INTERFACE.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 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
///////////////////////////////////////////////////////////////////////////////