From b89c9068ff1b02805cb804e78b9eb7772020f365 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 3 Jun 2020 14:26:49 +0100 Subject: [PATCH] Compileable JMRIParser Has Turnout, Output Sensor support. Needs further cleaning and cross checking against spec. May implement Daves Comms manager etc. --- EEStore.cpp | 76 ++++++++++++++ EEStore.h | 34 ++++++ JMRIParser.cpp | 268 +++++++++++++++++++++++++++--------------------- JMRIParser.h | 12 ++- JMRITurnout.cpp | 173 ------------------------------- JMRITurnout.h | 30 ------ Outputs.cpp | 186 +++++++++++++++++++++++++++++++++ Outputs.h | 32 ++++++ Sensors.cpp | 177 ++++++++++++++++++++++++++++++++ Sensors.h | 30 ++++++ Turnouts.cpp | 119 +++++++++++++++++++++ Turnouts.h | 29 ++++++ 12 files changed, 845 insertions(+), 321 deletions(-) create mode 100644 EEStore.cpp create mode 100644 EEStore.h delete mode 100644 JMRITurnout.cpp delete mode 100644 JMRITurnout.h create mode 100644 Outputs.cpp create mode 100644 Outputs.h create mode 100644 Sensors.cpp create mode 100644 Sensors.h create mode 100644 Turnouts.cpp create mode 100644 Turnouts.h diff --git a/EEStore.cpp b/EEStore.cpp new file mode 100644 index 0000000..fb052c9 --- /dev/null +++ b/EEStore.cpp @@ -0,0 +1,76 @@ +#include "EEStore.h" +#include "Turnouts.h" +#include "Sensors.h" +#include "Outputs.h" + + +#if defined(ARDUINO_ARCH_SAMD) +ExternalEEPROM EEPROM; +#endif + +void EEStore::init(){ +#if defined(ARDUINO_ARCH_SAMD) + EEPROM.begin(0x50); // Address for Microchip 24-series EEPROM with all three A pins grounded (0b1010000 = 0x50) +#endif + + eeStore=(EEStore *)calloc(1,sizeof(EEStore)); + + EEPROM.get(0,eeStore->data); // get eeStore data + + if(strncmp(eeStore->data.id,EESTORE_ID,sizeof(EESTORE_ID))!=0){ // check to see that eeStore contains valid DCC++ ID + sprintf(eeStore->data.id,EESTORE_ID); // if not, create blank eeStore structure (no turnouts, no sensors) and save it back to EEPROM + eeStore->data.nTurnouts=0; + eeStore->data.nSensors=0; + eeStore->data.nOutputs=0; + EEPROM.put(0,eeStore->data); + } + + reset(); // set memory pointer to first free EEPROM space + Turnout::load(); // load turnout definitions + Sensor::load(); // load sensor definitions + Output::load(); // load output definitions + +} + +/////////////////////////////////////////////////////////////////////////////// + +void EEStore::clear(){ + + sprintf(eeStore->data.id,EESTORE_ID); // create blank eeStore structure (no turnouts, no sensors) and save it back to EEPROM + eeStore->data.nTurnouts=0; + eeStore->data.nSensors=0; + eeStore->data.nOutputs=0; + EEPROM.put(0,eeStore->data); + +} + +/////////////////////////////////////////////////////////////////////////////// + +void EEStore::store(){ + reset(); + Turnout::store(); + Sensor::store(); + Output::store(); + EEPROM.put(0,eeStore->data); +} + +/////////////////////////////////////////////////////////////////////////////// + +void EEStore::advance(int n){ + eeAddress+=n; +} + +/////////////////////////////////////////////////////////////////////////////// + +void EEStore::reset(){ + eeAddress=sizeof(EEStore); +} +/////////////////////////////////////////////////////////////////////////////// + +int EEStore::pointer(){ + return(eeAddress); +} +/////////////////////////////////////////////////////////////////////////////// + +EEStore *EEStore::eeStore=NULL; +int EEStore::eeAddress=0; \ No newline at end of file diff --git a/EEStore.h b/EEStore.h new file mode 100644 index 0000000..c08fa0f --- /dev/null +++ b/EEStore.h @@ -0,0 +1,34 @@ +#ifndef EEStore_h +#define EEStore_h + +#include + +#if defined(ARDUINO_ARCH_SAMD) +#include +extern ExternalEEPROM EEPROM; +#else +#include +#endif + +#define EESTORE_ID "DCC++" + +struct EEStoreData{ + char id[sizeof(EESTORE_ID)]; + int nTurnouts; + int nSensors; + int nOutputs; +}; + +struct EEStore{ + static EEStore *eeStore; + EEStoreData data; + static int eeAddress; + static void init(); + static void reset(); + static int pointer(); + static void advance(int); + static void store(); + static void clear(); +}; + +#endif \ No newline at end of file diff --git a/JMRIParser.cpp b/JMRIParser.cpp index d7e59dd..699a22c 100644 --- a/JMRIParser.cpp +++ b/JMRIParser.cpp @@ -1,24 +1,31 @@ #include "StringParser.h" #include "JMRIParser.h" -#include "JMRITurnout.h" #include "DCC.h" #include "DCCWaveform.h" +#include "Turnouts.h" +#include "Outputs.h" +#include "Sensors.h" + +#include "EEStore.h" + +const char VERSION[]="99.666"; // 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. -// +// Non-DCC things like turnouts, pins and sensors are handled in additional JMRI interface classes. + -int JMRIParser::p[MAX_PARAMS]; // See documentation on DCC class for info on this section void JMRIParser::parse(Stream & stream,const char *com) { - StringParser::send(stream,F("\nParsing %s\n"),com); - + int p[MAX_PARAMS]; bool result; int params=StringParser::parse(com+1,p,MAX_PARAMS); - + + + // Functions return from this switch if complete, break from switch implies error to send switch(com[0]) { /***** SET ENGINE THROTTLES USING 128-STEP SPEED CONTROL ****/ @@ -26,7 +33,7 @@ void JMRIParser::parse(Stream & stream,const char *com) { case 't': // DCC::setThrottle(p[1],p[2],p[3]); StringParser::send(stream,F(""), p[0], p[2],p[3]); - break; + return; /***** OPERATE ENGINE DECODER FUNCTIONS F0-F28 ****/ @@ -35,7 +42,7 @@ void JMRIParser::parse(Stream & stream,const char *com) { else DCC::setFunction(p[0],p[1]); // TODO response? - break; + return; /***** OPERATE STATIONARY ACCESSORY DECODERS ****/ @@ -60,7 +67,7 @@ void JMRIParser::parse(Stream & stream,const char *com) { * returns: NONE */ DCC::setAccessory(p[0],p[1],p[2]); - break; + return; /***** CREATE/EDIT/REMOVE/SHOW & OPERATE A TURN-OUT ****/ case 'T': // @@ -75,92 +82,22 @@ void JMRIParser::parse(Stream & stream,const char *com) { * *** SEE ACCESSORIES.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "T" COMMAND * USED TO CREATE/EDIT/REMOVE/SHOW TURNOUT DEFINITIONS */ - JMRITurnout::parse(stream,params,p); + if (parseT(stream,params,p)) return; break; - #ifdef THIS_IS_NOT_YET_COMPLETE /***** 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; - + if (parseZ(stream,params,p)) return; + 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; - } + if (parseS(stream,params,p)) return; + 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': // @@ -175,7 +112,7 @@ void JMRIParser::parse(Stream & stream,const char *com) { */ DCC::writeCVByteMain(p[0],p[1],p[2]); - break; + return; /***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/ @@ -192,7 +129,7 @@ void JMRIParser::parse(Stream & stream,const char *com) { */ DCC::writeCVBitMain(p[0],p[1],p[2],p[3]); - break; + return; /***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON PROGRAMMING TRACK ****/ @@ -211,7 +148,7 @@ void JMRIParser::parse(Stream & stream,const char *com) { result=DCC::writeCVByte(p[0],p[1]); StringParser::send(stream,F(""), p[2], p[3],p[0],result?p[1]:-1); - break; + return; /***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON PROGRAMMING TRACK ****/ @@ -231,7 +168,7 @@ void JMRIParser::parse(Stream & stream,const char *com) { result=DCC::writeCVBit(p[0],p[1],p[2]); StringParser::send(stream,F(""), p[3],p[4], p[0],p[1],result?p[2]:-1); - break; + return; /***** READ CONFIGURATION VARIABLE BYTE FROM ENGINE DECODER ON PROGRAMMING TRACK ****/ @@ -248,7 +185,7 @@ void JMRIParser::parse(Stream & stream,const char *com) { */ StringParser::send(stream,F(""),p[1],p[2],p[0],DCC::readCV(p[0])); - break; + return; /***** TURN ON POWER FROM MOTOR SHIELD TO TRACKS ****/ @@ -261,7 +198,7 @@ void JMRIParser::parse(Stream & stream,const char *com) { DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); StringParser::send(stream,F("")); - break; + return; /***** TURN OFF POWER FROM MOTOR SHIELD TO TRACKS ****/ @@ -274,9 +211,8 @@ void JMRIParser::parse(Stream & stream,const char *com) { DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); StringParser::send(stream,F("")); - break; + return; -#ifdef THIS_IS_NOT_YET_COMPLETE /***** READ MAIN OPERATIONS TRACK CURRENT ****/ case 'c': // @@ -286,11 +222,19 @@ void JMRIParser::parse(Stream & stream,const char *com) { * returns: * where CURRENT = 0-1024, based on exponentially-smoothed weighting scheme */ - StringParser::send(stream,F(""), DCCWaveform:mainTrack->getLastRead()); - break; + StringParser::send(stream,F(""), DCCWaveform::mainTrack.getLastCurrent()); + return; + +/***** SHOW STATUS OF ALL SENSORS ****/ + + case 'Q': // + /* + * returns: the status of each sensor ID in the form (active) or (not active) + */ + Sensor::status(stream); + return; /***** READ STATUS OF DCC++ BASE STATION ****/ - case 's': // /* * returns status messages containing track power status, throttle status, turn-out status, and a version number @@ -298,18 +242,12 @@ void JMRIParser::parse(Stream & stream,const char *com) { * * 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(); + // TODO Send stats of speed reminders table + + StringParser::send(stream,F(""), BOARD_NAME, VERSION, __DATE__, __TIME__ ); - break; + // TODO send status of turnouts etc etc + return; /***** STORE SETTINGS IN EEPROM ****/ @@ -321,8 +259,8 @@ void JMRIParser::parse(Stream & stream,const char *com) { */ EEStore::store(); - CommManager::printf("", EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs); - break; + StringParser::send(stream,F(""), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs); + return; /***** CLEAR SETTINGS IN EEPROM ****/ @@ -334,21 +272,121 @@ void JMRIParser::parse(Stream & stream,const char *com) { */ EEStore::clear(); - CommManager::printf(""); - break; -#endif -/***** PRINT CARRIAGE RETURN IN stream MONITOR WINDOW ****/ + StringParser::send(stream, F("")); + return; - case ' ': // < > + case ' ': // < > /* * simply prints a carriage return - useful when interacting with Ardiuno through stream monitor window * * returns: a carriage return */ StringParser::send(stream,F("\n")); - break; - } + return; + + + } // end of opcode switch + + // Any fallout here sends an + StringParser::send(stream, F("")); } + + + + +bool JMRIParser::parseZ(Stream & stream, int params, int p[]){ + /* + * : 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 + */ + + switch (params) { + + case 2: // argument is string with id number of output followed by zero (LOW) or one (HIGH) + { + Output * o=Output::get(p[0]); + if(o==NULL) return false; + o->activate(p[1]); + StringParser::send(stream,F(""), p[0],p[1]); + } + break; + + case 3: // argument is string with id number of output followed by a pin number and invert flag + Output::create(p[0],p[1],p[2],1); + break; + + case 1: // argument is a string with id number only + Output::remove(p[0]); + break; + + case 0: // no arguments + Output::showAll(stream); // verbose show + break; + + default: + return false; + } + return true; + } + + +//=================================== +bool JMRIParser::parseT(Stream & stream, int params, int p[]) { + switch(params){ + case 0: // + Turnout::showAll(stream); // verbose show + break; + case 1: // + if (!Turnout::remove(p[0])) break; + StringParser::send(stream,F("")); + break; + case 2: // + if (!Turnout::activate(p[0],p[1])) return false; + Turnout::show(stream,p[0]); + break; + + case 3: // + if (!Turnout::create(p[0],p[1],p[2])) return false; + StringParser::send(stream,F("")); + break; + default: + return false; // will + } + + return true; +} + +bool JMRIParser::parseS(Stream & stream, int params, int p[]) { + + switch(params){ + + case 3: // argument is string with id number of sensor followed by a pin number and pullUp indicator (0=LOW/1=HIGH) + Sensor::create(p[0],p[1],p[2]); + return true; + + case 1: // argument is a string with id number only + if (Sensor::remove(p[0])) return true; + break; + + case -1: // no arguments + Sensor::show(stream); + return true; + + default: // invalid number of arguments + break; + } + return false; +} + + + diff --git a/JMRIParser.h b/JMRIParser.h index 6660d4c..72d6923 100644 --- a/JMRIParser.h +++ b/JMRIParser.h @@ -3,9 +3,15 @@ struct JMRIParser { static void parse(Stream & stream,const char * command); - + private: - static const int MAX_PARAMS=10; - static int p[MAX_PARAMS]; + + static bool parseT(Stream & stream, int params, int p[]); + static bool parseZ(Stream & stream, int params, int p[]); + static bool parseS(Stream & stream, int params, int p[]); + + static const int MAX_PARAMS=10; // longest command sent in }; + +#define BOARD_NAME "not yet configured" #endif diff --git a/JMRITurnout.cpp b/JMRITurnout.cpp deleted file mode 100644 index f4d6cad..0000000 --- a/JMRITurnout.cpp +++ /dev/null @@ -1,173 +0,0 @@ -/********************************************************************** - -Accessories.cpp -COPYRIGHT (c) 2013-2016 Gregg E. Berman - -Part of DCC++ BASE STATION for the Arduino - -**********************************************************************/ -/********************************************************************** - -DCC++ BASE STATION can keep track of the direction of any turnout that is controlled -by a DCC stationary accessory decoder. All turnouts, as well as any other DCC accessories -connected in this fashion, can always be operated using the DCC BASE STATION Accessory command: - - - -However, this general command simply sends the appropriate DCC instruction packet to the main tracks -to operate connected accessories. It does not store or retain any information regarding the current -status of that accessory. - -To have this sketch store and retain the direction of DCC-connected turnouts, as well as automatically -invoke the required command as needed, first define/edit/delete such turnouts using the following -variations of the "T" command: - - : creates a new turnout ID, with specified ADDRESS and SUBADDRESS - if turnout ID already exists, it is updated with specificed ADDRESS and SUBADDRESS - returns: if successful and if unsuccessful (e.g. out of memory) - - : deletes definition of turnout ID - returns: if successful and if unsuccessful (e.g. ID does not exist) - - : lists all defined turnouts - returns: for each defined turnout or if no turnouts defined - -where - - ID: the numeric ID (0-32767) of the turnout to control - ADDRESS: the primary address of the decoder controlling this turnout (0-511) - SUBADDRESS: the subaddress of the decoder controlling this turnout (0-3) - -Once all turnouts have been properly defined, use the command to store their definitions to EEPROM. -If you later make edits/additions/deletions to the turnout definitions, you must invoke the command if you want those -new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the command. - -To "throw" turnouts that have been defined use: - - : sets turnout ID to either the "thrown" or "unthrown" position - returns: , or if turnout ID does not exist - -where - - ID: the numeric ID (0-32767) of the turnout to control - THROW: 0 (unthrown) or 1 (thrown) - -When controlled as such, the Arduino updates and stores the direction of each Turnout in EEPROM so -that it is retained even without power. A list of the current directions of each Turnout in the form is generated -by this sketch whenever the status command is invoked. This provides an efficient way of initializing -the directions of any Turnouts being monitored or controlled by a separate interface or GUI program. - -**********************************************************************/ - -//#include "EEStore.h" -//#include -#include "JMRITurnout.h" -#include "StringParser.h" -#include "DCC.h" - -void JMRITurnout::parse(Stream & stream, int params, int p[]) { - - switch(params){ - case 0: // - showAll(stream); // verbose show - return; - case 1: // - if (!remove(p[0])) break; - StringParser::send(stream,F("")); - return; - case 2: // - if (!activate(p[0],p[1])) break; - show(stream,p[0],false); - return; - - case 3: // - if (!create(p[0],p[1],p[2])) break; - StringParser::send(stream,F("")); - break; - default: - break; // will - } - StringParser::send(stream,F("")); -} - -///////////////// ALL PRIVATE BELOW HERE ////////////////// - JMRITurnout::TurnoutEntry JMRITurnout::table[MAX_TURNOUTS]; - - bool JMRITurnout::create(int id, int add, byte subAdd){ - if (id<0 || id>=MAX_TURNOUTS || table[id].address!=0) return false; - table[id].address=add; - table[id].subAddress=subAdd; - table[id].thrown=false; - return true; -} - - -bool JMRITurnout::invalid(int id) { - return id<0 || id>=MAX_TURNOUTS || table[id].address==0; -} - -bool JMRITurnout::activate(int id,bool thrown){ - if (invalid(id)) return false; - table[id].thrown=thrown; - DCC::setAccessory(table[id].address,table[id].subAddress,table[id].thrown); - return true; -} - -bool JMRITurnout::remove(int id){ - if (invalid(id)) return false; - table[id].address=0; - return true; -} - - -bool JMRITurnout::show(Stream & stream ,int id, bool all){ - if (invalid(id)) return false; - if (all) StringParser::send(stream, F(""), - id, table[id].address, - table[id].subAddress, table[id].thrown); - else StringParser::send(stream, F(""), - id, table[id].thrown); - return true; -} - -void JMRITurnout::showAll(Stream & stream ){ - for (int id=0;iddata.nTurnouts;i++){ - EEPROM.get(EEStore::pointer(),data); - tt=create(data.id,data.address,data.subAddress); - tt->data.tStatus=data.tStatus; - tt->num=EEStore::pointer(); - EEStore::advance(sizeof(tt->data)); - } -} - -/////////////////////////////////////////////////////////////////////////////// - -void Turnout::store(){ - Turnout *tt; - - tt=firstTurnout; - EEStore::eeStore->data.nTurnouts=0; - - while(tt!=NULL){ - tt->num=EEStore::pointer(); - EEPROM.put(EEStore::pointer(),tt->data); - EEStore::advance(sizeof(tt->data)); - tt=tt->nextTurnout; - EEStore::eeStore->data.nTurnouts++; - } - -} -#endif -/////////////////////////////////////////////////////////////////////////////// diff --git a/JMRITurnout.h b/JMRITurnout.h deleted file mode 100644 index 75847c0..0000000 --- a/JMRITurnout.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef JMRITurnout_h -#define TMRITurnout_h - -#include -#include "Config.h" - -class JMRITurnout { - - public: - static void parse(Stream & stream, int params, int p[]); - - - - private: - - static void showAll(Stream & stream); - static bool show(Stream & stream ,int id, bool all); - static bool create(int id, int address, byte subAddress); - static bool remove(int id); - static bool activate(int id, bool thrown); - static bool invalid(int id); - - struct TurnoutEntry { - int address; - byte subAddress; - bool thrown; - }; - static TurnoutEntry table[MAX_TURNOUTS]; -}; -#endif diff --git a/Outputs.cpp b/Outputs.cpp new file mode 100644 index 0000000..7d72feb --- /dev/null +++ b/Outputs.cpp @@ -0,0 +1,186 @@ +/********************************************************************** + +DCC++ BASE STATION supports optional OUTPUT control of any unused Arduino Pins for custom purposes. +Pins can be activited or de-activated. The default is to set ACTIVE pins HIGH and INACTIVE pins LOW. +However, this default behavior can be inverted for any pin in which case ACTIVE=LOW and INACTIVE=HIGH. + +Definitions and state (ACTIVE/INACTIVE) for pins are retained in EEPROM and restored on power-up. +The default is to set each defined pin to active or inactive according to its restored state. +However, the default behavior can be modified so that any pin can be forced to be either active or inactive +upon power-up regardless of its previous state before power-down. + +To have this sketch utilize one or more Arduino pins as custom outputs, first define/edit/delete +output definitions using the following variation of the "Z" command: + + : creates a new output ID, with specified PIN and IFLAG values. + if output ID already exists, it is updated with specificed PIN and IFLAG. + note: output state will be immediately set to ACTIVE/INACTIVE and pin will be set to HIGH/LOW + according to IFLAG value specifcied (see below). + returns: if successful and if unsuccessful (e.g. out of memory) + + : deletes definition of output ID + returns: if successful and if unsuccessful (e.g. ID does not exist) + + : lists all defined output pins + returns: for each defined output pin or if no output pins defined + +where + + ID: the numeric ID (0-32767) of the output + PIN: the arduino pin number to use for the output + STATE: the state of the output (0=INACTIVE / 1=ACTIVE) + IFLAG: defines the operational behavior of the output based on bits 0, 1, and 2 as follows: + + IFLAG, bit 0: 0 = forward operation (ACTIVE=HIGH / INACTIVE=LOW) + 1 = inverted operation (ACTIVE=LOW / INACTIVE=HIGH) + + IFLAG, bit 1: 0 = state of pin restored on power-up to either ACTIVE or INACTIVE depending + on state before power-down; state of pin set to INACTIVE when first created + 1 = state of pin set on power-up, or when first created, to either ACTIVE of INACTIVE + depending on IFLAG, bit 2 + + IFLAG, bit 2: 0 = state of pin set to INACTIVE uponm power-up or when first created + 1 = state of pin set to ACTIVE uponm power-up or when first created + +Once all outputs have been properly defined, use the command to store their definitions to EEPROM. +If you later make edits/additions/deletions to the output definitions, you must invoke the command if you want those +new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the command. + +To change the state of outputs that have been defined use: + + : sets output ID to either ACTIVE or INACTIVE state + returns: , or if turnout ID does not exist + +where + + ID: the numeric ID (0-32767) of the turnout to control + STATE: the state of the output (0=INACTIVE / 1=ACTIVE) + +When controlled as such, the Arduino updates and stores the direction of each output in EEPROM so +that it is retained even without power. A list of the current states of each output in the form is generated +by this sketch whenever the status command is invoked. This provides an efficient way of initializing +the state of any outputs being monitored or controlled by a separate interface or GUI program. + +**********************************************************************/ + +#include "Outputs.h" +#include "EEStore.h" +#include "StringParser.h" +void Output::activate(int s){ + data.oStatus=(s>0); // if s>0, set status to active, else inactive + digitalWrite(data.pin,data.oStatus ^ bitRead(data.iFlag,0)); // set state of output pin to HIGH or LOW depending on whether bit zero of iFlag is set to 0 (ACTIVE=HIGH) or 1 (ACTIVE=LOW) + if(num>0) + EEPROM.put(num,data.oStatus); + +} + +/////////////////////////////////////////////////////////////////////////////// + +Output* Output::get(int n){ + Output *tt; + for(tt=firstOutput;tt!=NULL && tt->data.id!=n;tt=tt->nextOutput); + return(tt); +} +/////////////////////////////////////////////////////////////////////////////// + +bool Output::remove(int n){ + Output *tt,*pp=NULL; + + for(tt=firstOutput;tt!=NULL && tt->data.id!=n;pp=tt,tt=tt->nextOutput); + + if(tt==NULL) return false; + + if(tt==firstOutput) + firstOutput=tt->nextOutput; + else + pp->nextOutput=tt->nextOutput; + + free(tt); + + return true; + } + +/////////////////////////////////////////////////////////////////////////////// + +void Output::showAll(Stream & stream){ + for(Output * tt=firstOutput;tt!=NULL;tt=tt->nextOutput){ + StringParser::send(stream,F(""), tt->data.id, tt->data.pin, tt->data.iFlag, tt->data.oStatus); + } +} + +void Output::show(Stream & stream){ + for(Output * tt=firstOutput;tt!=NULL;tt=tt->nextOutput){ + StringParser::send(stream,F(""), tt->data.id, tt->data.oStatus); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Output::load(){ + struct OutputData data; + Output *tt; + + for(int i=0;idata.nOutputs;i++){ + EEPROM.get(EEStore::pointer(),data); + tt=create(data.id,data.pin,data.iFlag); + tt->data.oStatus=bitRead(tt->data.iFlag,1)?bitRead(tt->data.iFlag,2):data.oStatus; // restore status to EEPROM value is bit 1 of iFlag=0, otherwise set to value of bit 2 of iFlag + digitalWrite(tt->data.pin,tt->data.oStatus ^ bitRead(tt->data.iFlag,0)); + pinMode(tt->data.pin,OUTPUT); + tt->num=EEStore::pointer(); + EEStore::advance(sizeof(tt->data)); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Output::store(){ + Output *tt; + + tt=firstOutput; + EEStore::eeStore->data.nOutputs=0; + + while(tt!=NULL){ + tt->num=EEStore::pointer(); + EEPROM.put(EEStore::pointer(),tt->data); + EEStore::advance(sizeof(tt->data)); + tt=tt->nextOutput; + EEStore::eeStore->data.nOutputs++; + } + +} +/////////////////////////////////////////////////////////////////////////////// + +Output *Output::create(int id, int pin, int iFlag, int v){ + Output *tt; + + if(firstOutput==NULL){ + firstOutput=(Output *)calloc(1,sizeof(Output)); + tt=firstOutput; + } else if((tt=get(id))==NULL){ + tt=firstOutput; + while(tt->nextOutput!=NULL) + tt=tt->nextOutput; + tt->nextOutput=(Output *)calloc(1,sizeof(Output)); + tt=tt->nextOutput; + } + + if(tt==NULL) return tt; + + tt->data.id=id; + tt->data.pin=pin; + tt->data.iFlag=iFlag; + tt->data.oStatus=0; + + if(v==1){ + tt->data.oStatus=bitRead(tt->data.iFlag,1)?bitRead(tt->data.iFlag,2):0; // sets status to 0 (INACTIVE) is bit 1 of iFlag=0, otherwise set to value of bit 2 of iFlag + digitalWrite(tt->data.pin,tt->data.oStatus ^ bitRead(tt->data.iFlag,0)); + pinMode(tt->data.pin,OUTPUT); + } + + return(tt); + +} + +/////////////////////////////////////////////////////////////////////////////// + +Output *Output::firstOutput=NULL; diff --git a/Outputs.h b/Outputs.h new file mode 100644 index 0000000..e78d307 --- /dev/null +++ b/Outputs.h @@ -0,0 +1,32 @@ +#ifndef Outputs_h +#define Outputs_h + +#include + +struct OutputData { + uint8_t oStatus; + uint8_t id; + uint8_t pin; + uint8_t iFlag; +}; + +class Output{ + public: + void activate(int s); + static Output* get(int); + static bool remove(int); + static void load(); + static void store(); + static Output *create(int, int, int, int=0); + static void show(Stream & stream); + static void showAll(Stream & stream); + + private: + static Output *firstOutput; + int num; + struct OutputData data; + Output *nextOutput; + +}; // Output + +#endif diff --git a/Sensors.cpp b/Sensors.cpp new file mode 100644 index 0000000..dcafb45 --- /dev/null +++ b/Sensors.cpp @@ -0,0 +1,177 @@ +/********************************************************************** + +DCC++ BASE STATION supports Sensor inputs that can be connected to any Arduino Pin +not in use by this program. Sensors can be of any type (infrared, magentic, mechanical...). +The only requirement is that when "activated" the Sensor must force the specified Arduino +Pin LOW (i.e. to ground), and when not activated, this Pin should remain HIGH (e.g. 5V), +or be allowed to float HIGH if use of the Arduino Pin's internal pull-up resistor is specified. + +To ensure proper voltage levels, some part of the Sensor circuitry +MUST be tied back to the same ground as used by the Arduino. + +The Sensor code below utilizes exponential smoothing to "de-bounce" spikes generated by +mechanical switches and transistors. This avoids the need to create smoothing circuitry +for each sensor. You may need to change these parameters through trial and error for your specific sensors. + +To have this sketch monitor one or more Arduino pins for sensor triggers, first define/edit/delete +sensor definitions using the following variation of the "S" command: + + : creates a new sensor ID, with specified PIN and PULLUP + if sensor ID already exists, it is updated with specificed PIN and PULLUP + returns: if successful and if unsuccessful (e.g. out of memory) + + : deletes definition of sensor ID + returns: if successful and if unsuccessful (e.g. ID does not exist) + + : lists all defined sensors + returns: for each defined sensor or if no sensors defined + +where + + ID: the numeric ID (0-32767) of the sensor + PIN: the arduino pin number the sensor is connected to + PULLUP: 1=use internal pull-up resistor for PIN, 0=don't use internal pull-up resistor for PIN + +Once all sensors have been properly defined, use the command to store their definitions to EEPROM. +If you later make edits/additions/deletions to the sensor definitions, you must invoke the command if you want those +new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the command. + +All sensors defined as per above are repeatedly and sequentially checked within the main loop of this sketch. +If a Sensor Pin is found to have transitioned from one state to another, one of the following serial messages are generated: + + - for transition of Sensor ID from HIGH state to LOW state (i.e. the sensor is triggered) + - for transition of Sensor ID from LOW state to HIGH state (i.e. the sensor is no longer triggered) + +Depending on whether the physical sensor is acting as an "event-trigger" or a "detection-sensor," you may +decide to ignore the return and only react to triggers. + +**********************************************************************/ + + +#include "Sensors.h" +#include "EEStore.h" +#include "StringParser.h" + +/////////////////////////////////////////////////////////////////////////////// + +void Sensor::check(Stream & stream){ + Sensor *tt; + + for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ + tt->signal=tt->signal*(1.0-SENSOR_DECAY)+digitalRead(tt->data.pin)*SENSOR_DECAY; + + if(!tt->active && tt->signal<0.5){ + tt->active=true; + StringParser::send(stream,F(""), tt->data.snum); + } else if(tt->active && tt->signal>0.9){ + tt->active=false; + StringParser::send(stream,F(""), tt->data.snum); + } + } // loop over all sensors + +} // Sensor::check + +/////////////////////////////////////////////////////////////////////////////// + +Sensor *Sensor::create(int snum, int pin, int pullUp){ + Sensor *tt; + + if(firstSensor==NULL){ + firstSensor=(Sensor *)calloc(1,sizeof(Sensor)); + tt=firstSensor; + } else if((tt=get(snum))==NULL){ + tt=firstSensor; + while(tt->nextSensor!=NULL) + tt=tt->nextSensor; + tt->nextSensor=(Sensor *)calloc(1,sizeof(Sensor)); + tt=tt->nextSensor; + } + + if(tt==NULL) return tt; // problem allocating memory + + tt->data.snum=snum; + tt->data.pin=pin; + tt->data.pullUp=(pullUp==0?LOW:HIGH); + tt->active=false; + tt->signal=1; + pinMode(pin,INPUT); // set mode to input + digitalWrite(pin,pullUp); // don't use Arduino's internal pull-up resistors for external infrared sensors --- each sensor must have its own 1K external pull-up resistor + + return tt; + +} + +/////////////////////////////////////////////////////////////////////////////// + +Sensor* Sensor::get(int n){ + Sensor *tt; + for(tt=firstSensor;tt!=NULL && tt->data.snum!=n;tt=tt->nextSensor); + return tt ; +} +/////////////////////////////////////////////////////////////////////////////// + +bool Sensor::remove(int n){ + Sensor *tt,*pp=NULL; + + for(tt=firstSensor;tt!=NULL && tt->data.snum!=n;pp=tt,tt=tt->nextSensor); + + if (tt==NULL) return false; + + if(tt==firstSensor) + firstSensor=tt->nextSensor; + else + pp->nextSensor=tt->nextSensor; + + free(tt); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +void Sensor::show(Stream & stream){ + for(Sensor * tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ + StringParser::send(stream, F(""), tt->data.snum, tt->data.pin, tt->data.pullUp); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Sensor::status(Stream & stream){ + for(Sensor * tt=firstSensor;tt!=NULL;tt=tt->nextSensor){ + StringParser::send(stream,F("<%s %d>"), tt->active?"Q":"q", tt->data.snum); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Sensor::load(){ + struct SensorData data; + Sensor *tt; + + for(int i=0;idata.nSensors;i++){ + EEPROM.get(EEStore::pointer(),data); + tt=create(data.snum,data.pin,data.pullUp); + EEStore::advance(sizeof(tt->data)); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Sensor::store(){ + Sensor *tt; + + tt=firstSensor; + EEStore::eeStore->data.nSensors=0; + + while(tt!=NULL){ + EEPROM.put(EEStore::pointer(),tt->data); + EEStore::advance(sizeof(tt->data)); + tt=tt->nextSensor; + EEStore::eeStore->data.nSensors++; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +Sensor *Sensor::firstSensor=NULL; diff --git a/Sensors.h b/Sensors.h new file mode 100644 index 0000000..f3b5660 --- /dev/null +++ b/Sensors.h @@ -0,0 +1,30 @@ +#ifndef Sensor_h +#define Sensor_h + +#include "Arduino.h" + +#define SENSOR_DECAY 0.03 + +struct SensorData { + int snum; + uint8_t pin; + uint8_t pullUp; +}; + +struct Sensor{ + static Sensor *firstSensor; + SensorData data; + boolean active; + float signal; + Sensor *nextSensor; + static void load(); + static void store(); + static Sensor *create(int, int, int); + static Sensor* get(int); + static bool remove(int); + static void show(Stream & stream); + static void status(Stream & stream); + static void check(Stream & stream); +}; // Sensor + +#endif diff --git a/Turnouts.cpp b/Turnouts.cpp new file mode 100644 index 0000000..50487e3 --- /dev/null +++ b/Turnouts.cpp @@ -0,0 +1,119 @@ +#include "Turnouts.h" +#include "EEStore.h" +#include "StringParser.h" + +bool Turnout::activate(int n,bool state){ + Turnout * tt=get(n); + if (tt==NULL) return false; + tt->data.tStatus=state; + DCC::setAccessory(tt->data.address, tt->data.subAddress, tt->data.tStatus); + if(n>0) + EEPROM.put(n,tt->data.tStatus); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +Turnout* Turnout::get(int n){ + Turnout *tt; + for(tt=firstTurnout;tt!=NULL && tt->data.id!=n;tt=tt->nextTurnout); + return(tt); +} +/////////////////////////////////////////////////////////////////////////////// + +bool Turnout::remove(int n){ + Turnout *tt,*pp=NULL; + + for(tt=firstTurnout;tt!=NULL && tt->data.id!=n;pp=tt,tt=tt->nextTurnout); + + if(tt==NULL) return false; + + if(tt==firstTurnout) + firstTurnout=tt->nextTurnout; + else + pp->nextTurnout=tt->nextTurnout; + + free(tt); + return true; + +} + +/////////////////////////////////////////////////////////////////////////////// + +void Turnout::show(Stream & stream, int n){ + for(Turnout *tt=firstTurnout;tt!=NULL;tt=tt->nextTurnout){ + if (tt->data.id==n) { + StringParser::send(stream,F(""), tt->data.id, tt->data.tStatus); + return; + } + } +} + +void Turnout::showAll(Stream & stream){ + for(Turnout * tt=firstTurnout;tt!=NULL;tt=tt->nextTurnout){ + StringParser::send(stream,F(""), tt->data.id, tt->data.address, tt->data.subAddress, tt->data.tStatus); + } +} + + +/////////////////////////////////////////////////////////////////////////////// + +void Turnout::load(){ + struct TurnoutData data; + Turnout *tt; + + for(int i=0;idata.nTurnouts;i++){ + EEPROM.get(EEStore::pointer(),data); + tt=create(data.id,data.address,data.subAddress); + tt->data.tStatus=data.tStatus; + tt->num=EEStore::pointer(); + EEStore::advance(sizeof(tt->data)); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void Turnout::store(){ + Turnout *tt; + + tt=firstTurnout; + EEStore::eeStore->data.nTurnouts=0; + + while(tt!=NULL){ + tt->num=EEStore::pointer(); + EEPROM.put(EEStore::pointer(),tt->data); + EEStore::advance(sizeof(tt->data)); + tt=tt->nextTurnout; + EEStore::eeStore->data.nTurnouts++; + } + +} +/////////////////////////////////////////////////////////////////////////////// + +Turnout *Turnout::create(int id, int add, int subAdd){ + Turnout *tt; + + if(firstTurnout==NULL){ + firstTurnout=(Turnout *)calloc(1,sizeof(Turnout)); + tt=firstTurnout; + } else if((tt=get(id))==NULL){ + tt=firstTurnout; + while(tt->nextTurnout!=NULL) + tt=tt->nextTurnout; + tt->nextTurnout=(Turnout *)calloc(1,sizeof(Turnout)); + tt=tt->nextTurnout; + } + + if (tt==NULL) return(tt); + + tt->data.id=id; + tt->data.address=add; + tt->data.subAddress=subAdd; + tt->data.tStatus=0; + return(tt); + +} + +/////////////////////////////////////////////////////////////////////////////// + +Turnout *Turnout::firstTurnout=NULL; diff --git a/Turnouts.h b/Turnouts.h new file mode 100644 index 0000000..c1d12f0 --- /dev/null +++ b/Turnouts.h @@ -0,0 +1,29 @@ +#ifndef Turnouts_h +#define Turnouts_h + +#include +#include "DCC.h" + +struct TurnoutData { + uint8_t tStatus; + uint8_t subAddress; + int id; + int address; +}; + +struct Turnout{ + static Turnout *firstTurnout; + int num; + struct TurnoutData data; + Turnout *nextTurnout; + static bool activate(int n, bool state); + static Turnout* get(int); + static bool remove(int); + static void load(); + static void store(); + static Turnout *create(int, int, int); + static void show(Stream & stream, int n); + static void showAll(Stream & stream); +}; // Turnout + +#endif