diff --git a/StringParser.cpp b/StringParser.cpp new file mode 100644 index 0000000..16ada64 --- /dev/null +++ b/StringParser.cpp @@ -0,0 +1,414 @@ +#include "StringParser.h" +#include "DCC.h" +#include "DCCWaveform.h" +#include "DIAG.h" +// This is a JMRI command parser +// 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. +// + + int StringParser::p[MAX_PARAMS]; + +// See documentation on DCC class for info on this section +void StringParser::parse(const char *com) { + int params; + bool result; + switch(com[0]) { + +/***** SET ENGINE THROTTLES USING 128-STEP SPEED CONTROL ****/ + + case 't': // + parse(com,4); + DCC::setThrottle(p[1],p[2],p[3]); + DIAG(F(""), p[0], p[2],p[3]); + break; + +/***** OPERATE ENGINE DECODER FUNCTIONS F0-F28 ****/ + + case 'f': // + params=parse(com,3); + if (params==3) DCC::setFunction(p[0],p[1],p[2]); + else DCC::setFunction(p[0],p[1]); + + // TODO response? + break; + +/***** OPERATE STATIONARY ACCESSORY DECODERS ****/ + + case 'a': // + /* + * 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 + */ + parse(com,3); + DCC::setAccessory(p[0],p[1],p[2]); + break; +#ifdef THIS_IS_NOT_YET_COMPLETE +/***** CREATE/EDIT/REMOVE/SHOW & OPERATE A TURN-OUT ****/ + + case 'T': // + /* + * : 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: or 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 + */ + + int n,s,m; + Turnout *t; + + switch(sscanf(com+1,"%d %d %d",&n,&s,&m)){ + + case 2: // argument is string with id number of turnout followed by zero (not thrown) or one (thrown) + t=Turnout::get(n); + if(t!=NULL) + t->activate(s, (DCC*) mainTrack); + else + CommManager::printf(""); + break; + + case 3: // argument is string with id number of turnout followed by an address and subAddress + Turnout::create(n,s,m,1); + break; + + case 1: // argument is a string with id number only + Turnout::remove(n); + break; + + case -1: // no arguments + Turnout::show(1); // verbose show + break; + } + + break; + +/***** CREATE/EDIT/REMOVE/SHOW & OPERATE AN OUTPUT PIN ****/ + + case 'Z': // + /* + * : 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: or 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 + */ + + int on,os,om; + Output* o; + + switch(sscanf(com+1,"%d %d %d",&on,&os,&om)){ + + case 2: // argument is string with id number of output followed by zero (LOW) or one (HIGH) + o=Output::get(on); + if(o!=NULL) + o->activate(os); + else + CommManager::printf(""); + break; + + case 3: // argument is string with id number of output followed by a pin number and invert flag + Output::create(on,os,om,1); + break; + + case 1: // argument is a string with id number only + Output::remove(on); + break; + + case -1: // no arguments + Output::show(1); // verbose show + break; + } + + break; + +/***** CREATE/EDIT/REMOVE/SHOW A SENSOR ****/ + + case 'S': + + int sn,ss,sm; + + switch(sscanf(com+1,"%d %d %d",&sn,&ss,&sm)){ + + case 3: // argument is string with id number of sensor followed by a pin number and pullUp indicator (0=LOW/1=HIGH) + Sensor::create(sn,ss,sm,1); + break; + + case 1: // argument is a string with id number only + Sensor::remove(sn); + break; + + case -1: // no arguments + Sensor::show(); + break; + + case 2: // invalid number of arguments + CommManager::printf(""); + break; + } + + break; + +/***** SHOW STATUS OF ALL SENSORS ****/ + + case 'Q': // + /* + * returns: the status of each sensor ID in the form (active) or (not active) + */ + Sensor::status(); + break; + +#endif +/***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/ + + case 'w': // + /* + * 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 + */ + parse(com,3); + DCC::writeCVByteMain(p[0],p[1],p[2]); + break; + +/***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/ + + case 'b': // + /* + * 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 + */ + parse(com,4); + DCC::writeCVBitMain(p[0],p[1],p[2],p[3]); + break; + +/***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON PROGRAMMING TRACK ****/ + + case 'W': // + /* + * 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: "), p[2], p[3],p[0],result?p[1]:-1); + break; + +/***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON PROGRAMMING TRACK ****/ + + case 'B': // + /* + * 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: "), p[3],p[4], p[0],p[1],result?p[2]:-1); + break; + +/***** READ CONFIGURATION VARIABLE BYTE FROM ENGINE DECODER ON PROGRAMMING TRACK ****/ + + case 'R': // + /* + * 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: "),p[1],p[2],p[0],DCC::readCV(p[0])); + 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: + */ + DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); + DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); + DIAG(F("")); + 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: + */ + DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); + DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); + DIAG(F("")); + break; + +#ifdef THIS_IS_NOT_YET_COMPLETE +/***** READ MAIN OPERATIONS TRACK CURRENT ****/ + + case 'c': // + /* + * reads current being drawn on main operations track + * + * returns: + * where CURRENT = 0-1024, based on exponentially-smoothed weighting scheme + */ + DIAG(F(""), DCCWaveform:mainTrack->getLastRead()); + break; + +/***** READ STATUS OF DCC++ BASE STATION ****/ + + case '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 + */ + // mainTrack->showStatus(); + for(int i=1;i<=mainTrack->numDev;i++){ + if(mainTrack->speedTable[i]==0) + continue; + CommManager::printf("", i, mainTrack->speedTable[i]>0 ? mainTrack->speedTable[i] : -mainTrack->speedTable[i], mainTrack->speedTable[i]>0 ? 1 : 0); + } + CommManager::printf("", "SAMD21 Command Station", BOARD_NAME, VERSION, __DATE__, __TIME__); + CommManager::showInitInfo(); + Turnout::show(); + Output::show(); + + break; + +/***** STORE SETTINGS IN EEPROM ****/ + + case 'E': // + /* + * stores settings for turnouts and sensors EEPROM + * + * returns: + */ + + EEStore::store(); + CommManager::printf("", EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs); + break; + +/***** CLEAR SETTINGS IN EEPROM ****/ + + case 'e': // + /* + * clears settings for Turnouts in EEPROM + * + * returns: + */ + + EEStore::clear(); + CommManager::printf(""); + break; +#endif +/***** 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 + */ + DIAG(F("\n")); + break; + } +} + +int StringParser::parse(const char * com, byte pcount) { + byte state=1; + byte parameterCount=0; + int runningValue=0; + const char * remainingCmd=com; + bool signNegative=false; + while(parameterCount') return parameterCount; + switch (state) { + + case 1: // skipping spaces before a param + if (hot==' ') break; + continue; + + case 2: // checking sign + signNegative=false; + runningValue=0; + if (hot=='-') { + signNegative=true; + break; + } + state=3; + continue; + case 3: // building a parameter + if (hot>='0' || hot<='9') { + runningValue=10*runningValue+(hot-'0'); + break; + } + p[parameterCount] = runningValue * (signNegative ?-1:1); + parameterCount++; + state=1; + break; + + } + remainingCmd++; + } + return parameterCount; +} diff --git a/StringParser.h b/StringParser.h new file mode 100644 index 0000000..8fcf069 --- /dev/null +++ b/StringParser.h @@ -0,0 +1,17 @@ +#ifndef CommParser_h +#define CommParser_h + +#include + +const int MAX_PARAMS=8; +struct StringParser +{ + static void init(); + static void parse(const char *); + + private: + static int parse(const char *, byte); + static int p[MAX_PARAMS]; +}; + +#endif