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