1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-12-23 21:01:25 +01:00

Compileable JMRIParser

Has Turnout, Output Sensor support.
Needs further cleaning and cross checking against spec.
May implement Daves Comms manager etc.
This commit is contained in:
Asbelos 2020-06-03 14:26:49 +01:00
parent d42589aff5
commit b89c9068ff
12 changed files with 845 additions and 321 deletions

76
EEStore.cpp Normal file
View File

@ -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;

34
EEStore.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef EEStore_h
#define EEStore_h
#include <Arduino.h>
#if defined(ARDUINO_ARCH_SAMD)
#include <SparkFun_External_EEPROM.h>
extern ExternalEEPROM EEPROM;
#else
#include <EEPROM.h>
#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

View File

@ -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 <X> 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': // <t REGISTER CAB SPEED DIRECTION>
DCC::setThrottle(p[1],p[2],p[3]);
StringParser::send(stream,F("<T %d %d %d>"), 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': // <T ID THROW>
@ -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': // <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
*/
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("<X>");
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("<X>");
break;
}
if (parseS(stream,params,p)) return;
break;
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;
#endif
/***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/
case 'w': // <w CAB CV VALUE>
@ -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("<r%d|%d|%d %d>"), 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("<r%d|%d|%d %d %d>"), 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("<r%d|%d|%d %d>"),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("<p1>"));
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("<p0>"));
break;
return;
#ifdef THIS_IS_NOT_YET_COMPLETE
/***** READ MAIN OPERATIONS TRACK CURRENT ****/
case 'c': // <c>
@ -286,11 +222,19 @@ void JMRIParser::parse(Stream & stream,const char *com) {
* returns: <a CURRENT>
* where CURRENT = 0-1024, based on exponentially-smoothed weighting scheme
*/
StringParser::send(stream,F("<a %d>"), DCCWaveform:mainTrack->getLastRead());
break;
StringParser::send(stream,F("<a %d>"), DCCWaveform::mainTrack.getLastCurrent());
return;
/***** 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(stream);
return;
/***** 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
@ -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("<T%d %d %d>", i, mainTrack->speedTable[i]>0 ? mainTrack->speedTable[i] : -mainTrack->speedTable[i], mainTrack->speedTable[i]>0 ? 1 : 0);
}
CommManager::printf("<iDCC++ BASE STATION FOR ARDUINO %s / %s: V-%s / %s %s>", "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("<iDCC-EX BASE STATION FOR ARDUINO / %s: V-%s %s/%s\n>"), 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("<e %d %d %d>", EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs);
break;
StringParser::send(stream,F("<e %d %d %d>"), 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("<O>");
break;
#endif
/***** PRINT CARRIAGE RETURN IN stream MONITOR WINDOW ****/
StringParser::send(stream, F("<O>"));
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 <X>
StringParser::send(stream, F("<X>"));
}
bool JMRIParser::parseZ(Stream & stream, int params, int p[]){
/*
* <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
*/
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("<Y %d %d>"), 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: // <T>
Turnout::showAll(stream); // verbose show
break;
case 1: // <T id>
if (!Turnout::remove(p[0])) break;
StringParser::send(stream,F("<O>"));
break;
case 2: // <T id 0|1>
if (!Turnout::activate(p[0],p[1])) return false;
Turnout::show(stream,p[0]);
break;
case 3: // <T id addr subaddr>
if (!Turnout::create(p[0],p[1],p[2])) return false;
StringParser::send(stream,F("<O>"));
break;
default:
return false; // will <x>
}
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;
}

View File

@ -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

View File

@ -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:
<a ADDRESS SUBADDRESS ACTIVATE>
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 <a> command as needed, first define/edit/delete such turnouts using the following
variations of the "T" command:
<T ID ADDRESS SUBADDRESS>: creates a new turnout ID, with specified ADDRESS and SUBADDRESS
if turnout ID already exists, it is updated with specificed ADDRESS and SUBADDRESS
returns: <O> if successful and <X> if unsuccessful (e.g. out of memory)
<T ID>: deletes definition of turnout ID
returns: <O> if successful and <X> if unsuccessful (e.g. ID does not exist)
<T>: lists all defined turnouts
returns: <H ID ADDRESS SUBADDRESS THROW> for each defined turnout or <X> 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 <E> command to store their definitions to EEPROM.
If you later make edits/additions/deletions to the turnout definitions, you must invoke the <E> command if you want those
new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the <e> command.
To "throw" turnouts that have been defined use:
<T ID THROW>: sets turnout ID to either the "thrown" or "unthrown" position
returns: <H ID THROW>, or <X> 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 <H ID THROW> is generated
by this sketch whenever the <s> 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 <EEPROM.h>
#include "JMRITurnout.h"
#include "StringParser.h"
#include "DCC.h"
void JMRITurnout::parse(Stream & stream, int params, int p[]) {
switch(params){
case 0: // <T>
showAll(stream); // verbose show
return;
case 1: // <T id>
if (!remove(p[0])) break;
StringParser::send(stream,F("<O>"));
return;
case 2: // <T id 0|1>
if (!activate(p[0],p[1])) break;
show(stream,p[0],false);
return;
case 3: // <T id addr subaddr>
if (!create(p[0],p[1],p[2])) break;
StringParser::send(stream,F("<O>"));
break;
default:
break; // will <x>
}
StringParser::send(stream,F("<X>"));
}
///////////////// 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("<H %d %d %d %d>"),
id, table[id].address,
table[id].subAddress, table[id].thrown);
else StringParser::send(stream, F("<H %d %d>"),
id, table[id].thrown);
return true;
}
void JMRITurnout::showAll(Stream & stream ){
for (int id=0;id<MAX_TURNOUTS;id++) {
if (table[id].address!=0) show(stream,id,true);
}
}
///////////////////////////////////////////////////////////////////////////////
#ifdef NOT_YET_IMPLEMENTED
void Turnout::load(){
struct TurnoutData data;
Turnout *tt;
for(int i=0;i<EEStore::eeStore->data.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
///////////////////////////////////////////////////////////////////////////////

View File

@ -1,30 +0,0 @@
#ifndef JMRITurnout_h
#define TMRITurnout_h
#include <Arduino.h>
#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

186
Outputs.cpp Normal file
View File

@ -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:
<Z ID PIN IFLAG>: 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: <O> if successful and <X> if unsuccessful (e.g. out of memory)
<Z ID>: deletes definition of output ID
returns: <O> if successful and <X> if unsuccessful (e.g. ID does not exist)
<Z>: lists all defined output pins
returns: <Y ID PIN IFLAG STATE> for each defined output pin or <X> 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 <E> command to store their definitions to EEPROM.
If you later make edits/additions/deletions to the output definitions, you must invoke the <E> command if you want those
new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the <e> command.
To change the state of outputs that have been defined use:
<Z ID STATE>: sets output ID to either ACTIVE or INACTIVE state
returns: <Y ID STATE>, or <X> 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 <Y ID STATE> is generated
by this sketch whenever the <s> 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("<Y %d %d %d %d>"), 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("<Y %d %d>"), tt->data.id, tt->data.oStatus);
}
}
///////////////////////////////////////////////////////////////////////////////
void Output::load(){
struct OutputData data;
Output *tt;
for(int i=0;i<EEStore::eeStore->data.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;

32
Outputs.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef Outputs_h
#define Outputs_h
#include <Arduino.h>
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

177
Sensors.cpp Normal file
View File

@ -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:
<S ID PIN PULLUP>: creates a new sensor ID, with specified PIN and PULLUP
if sensor ID already exists, it is updated with specificed PIN and PULLUP
returns: <O> if successful and <X> if unsuccessful (e.g. out of memory)
<S ID>: deletes definition of sensor ID
returns: <O> if successful and <X> if unsuccessful (e.g. ID does not exist)
<S>: lists all defined sensors
returns: <Q ID PIN PULLUP> for each defined sensor or <X> 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 <E> command to store their definitions to EEPROM.
If you later make edits/additions/deletions to the sensor definitions, you must invoke the <E> command if you want those
new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the <e> 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:
<Q ID> - for transition of Sensor ID from HIGH state to LOW state (i.e. the sensor is triggered)
<q ID> - 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 <q ID> return and only react to <Q ID> 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("<Q %d>"), tt->data.snum);
} else if(tt->active && tt->signal>0.9){
tt->active=false;
StringParser::send(stream,F("<q %d>"), 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("<Q %d %d %d>"), 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;i<EEStore::eeStore->data.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;

30
Sensors.h Normal file
View File

@ -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

119
Turnouts.cpp Normal file
View File

@ -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("<H %d %d>"), 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("<H %d %d %d %d>"), tt->data.id, tt->data.address, tt->data.subAddress, tt->data.tStatus);
}
}
///////////////////////////////////////////////////////////////////////////////
void Turnout::load(){
struct TurnoutData data;
Turnout *tt;
for(int i=0;i<EEStore::eeStore->data.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;

29
Turnouts.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef Turnouts_h
#define Turnouts_h
#include <Arduino.h>
#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