1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-03-15 02:23:08 +01:00

Restructure Turnout class.

Turnout class split into a base class for common code and specific subclasses for Servo, DCC, VPIN and LCN turnouts.
Interface further narrowed to reduce direct access to member variables.
Turnout creation command handling has been moved into the DCCEXParser class.
Turnout function and parameter names changed to make the Throw and Close functionality explicit.
Turnout commands <T id C> (close) and <T id T> (throw) added.
This commit is contained in:
Neil McKechnie 2021-08-19 21:22:59 +01:00
parent 776a098a72
commit fd36ca2b92
6 changed files with 645 additions and 468 deletions

View File

@ -57,6 +57,7 @@ const int16_t HASH_KEYWORD_RESET = 26133;
const int16_t HASH_KEYWORD_SPEED28 = -17064; const int16_t HASH_KEYWORD_SPEED28 = -17064;
const int16_t HASH_KEYWORD_SPEED128 = 25816; const int16_t HASH_KEYWORD_SPEED128 = 25816;
const int16_t HASH_KEYWORD_SERVO=27709; const int16_t HASH_KEYWORD_SERVO=27709;
const int16_t HASH_KEYWORD_VPIN=-415;
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS]; int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
bool DCCEXParser::stashBusy; bool DCCEXParser::stashBusy;
@ -658,7 +659,7 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
case 0: // <T> list turnout definitions case 0: // <T> list turnout definitions
{ {
bool gotOne = false; bool gotOne = false;
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout) for (Turnout *tt = Turnout::first(); tt != NULL; tt = tt->next())
{ {
gotOne = true; gotOne = true;
tt->print(stream); tt->print(stream);
@ -672,15 +673,60 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
StringFormatter::send(stream, F("<O>\n")); StringFormatter::send(stream, F("<O>\n"));
return true; return true;
case 2: // <T id 0|1> turnout 0=CLOSE,1=THROW case 2: // <T id 0|1|T|C>
if (p[1]>1 || p[1]<0 ) return false; switch (p[1]) {
if (!Turnout::setClosed(p[0],p[1]==0)) return false; #ifdef TURNOUT_LEGACY_BEHAVIOUR
// turnout 1 or T=THROW, 0 or C=CLOSE
case 1: case 0x54: // 1 or T
if (!Turnout::setClosed(p[0], false)) return false;
break;
case 0: case 0x43: // 0 or C
if (!Turnout::setClosed(p[0], true)) return false;
break;
#else
// turnout 0 or T=THROW,1 or C=CLOSE
case 0: case 0x54: // 0 or T
if (!Turnout::setClosed(p[0], false)) return false;
break;
case 1: case 0x43: // 1 or C
if (!Turnout::setClosed(p[0], true)) return false;
break;
#endif
default:
return false;
}
// Send acknowledgement to caller, and to Serial.
StringFormatter::send(stream, F("<H %d %d>\n"), p[0], p[1]); StringFormatter::send(stream, F("<H %d %d>\n"), p[0], p[1]);
if (stream != &Serial) StringFormatter::send(Serial, F("<H %d %d>\n"), p[0], p[1]);
return true; return true;
default: // Anything else is handled by Turnout class. default: // Anything else is some kind of create function.
if (!Turnout::create(p[0], params-1, &p[1])) if (p[1] == HASH_KEYWORD_SERVO) { // <T id SERVO n n n n>
if (params == 6) {
if (!ServoTurnout::create(p[0], (VPIN)p[2], (uint16_t)p[3], (uint16_t)p[4], (uint8_t)p[5]))
return false; return false;
} else
return false;
} else
if (p[1] == HASH_KEYWORD_VPIN) { // <T id VPIN n>
if (params==3) {
if (VpinTurnout::create(p[0], p[2])) return false;
} else
return false;
} else
if (p[1]==HASH_KEYWORD_DCC) {
if (params==4 && p[2]>0 && p[2]<=512 && p[3]>=0 && p[3]<4) { // <T id DCC n n>
if (!DCCTurnout::create(p[0], p[2], p[3])) return false;
} else if (params==3 && p[2]>0 && p[2]<=512*4) { // <T id DCC nn>
if (!DCCTurnout::create(p[0], (p[2]-1)/4+1, (p[2]-1)%4)) return false;
} else
return false;
} else if (params==3) { // <T id n n> for DCC or LCN
if (!DCCTurnout::create(p[0], p[1], p[2])) return false;
}
else if (params==3) { // legacy <T id n n n> for Servo
if (!ServoTurnout::create(p[0], (VPIN)p[1], (uint16_t)p[2], (uint16_t)p[3], 1)) return false;
}
StringFormatter::send(stream, F("<O>\n")); StringFormatter::send(stream, F("<O>\n"));
return true; return true;
} }
@ -797,7 +843,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
StringFormatter::send(stream, F("128 Speedsteps")); StringFormatter::send(stream, F("128 Speedsteps"));
return true; return true;
case HASH_KEYWORD_SERVO: case HASH_KEYWORD_SERVO: // <D SERVO vpin position [profile]>
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0); IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
break; break;

View File

@ -48,8 +48,7 @@ void LCN::loop() {
} }
else if (ch == 't' || ch == 'T') { // Turnout opcodes else if (ch == 't' || ch == 'T') { // Turnout opcodes
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch); if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
Turnout * tt = Turnout::get(id); if (!Turnout::exists(id)) LCNTurnout::create(id);
if (!tt) tt=Turnout::createLCN(id);
Turnout::setClosedStateOnly(id,ch=='t'); Turnout::setClosedStateOnly(id,ch=='t');
Turnout::turnoutlistHash++; // signals ED update of turnout data Turnout::turnoutlistHash++; // signals ED update of turnout data
id = 0; id = 0;

View File

@ -77,7 +77,7 @@ byte RMFT2::flags[MAX_FLAGS];
VPIN id=GET_OPERAND(0); VPIN id=GET_OPERAND(0);
int addr=GET_OPERAND(1); int addr=GET_OPERAND(1);
byte subAddr=GET_OPERAND(2); byte subAddr=GET_OPERAND(2);
Turnout::createDCC(id,addr,subAddr); DCCTurnout::create(id,addr,subAddr);
continue; continue;
} }
@ -87,14 +87,14 @@ byte RMFT2::flags[MAX_FLAGS];
int activeAngle=GET_OPERAND(2); int activeAngle=GET_OPERAND(2);
int inactiveAngle=GET_OPERAND(3); int inactiveAngle=GET_OPERAND(3);
int profile=GET_OPERAND(4); int profile=GET_OPERAND(4);
Turnout::createServo(id,pin,activeAngle,inactiveAngle,profile); ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile);
continue; continue;
} }
if (opcode==OPCODE_PINTURNOUT) { if (opcode==OPCODE_PINTURNOUT) {
int16_t id=GET_OPERAND(0); int16_t id=GET_OPERAND(0);
VPIN pin=GET_OPERAND(1); VPIN pin=GET_OPERAND(1);
Turnout::createVpin(id,pin); VpinTurnout::create(id,pin);
continue; continue;
} }
// other opcodes are not needed on this pass // other opcodes are not needed on this pass

View File

@ -1,4 +1,5 @@
/* /*
* © 2021 Restructured Neil McKechnie
* © 2013-2016 Gregg E. Berman * © 2013-2016 Gregg E. Berman
* © 2020, Chris Harlow. All rights reserved. * © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth. * © 2020, Harald Barth.
@ -19,17 +20,12 @@
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>. * along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/ */
// >>>>>> ATTENTION: This class requires major cleaning.
// The public interface has been narrowed to avoid the ambuguity of "activated".
//#define EESTOREDEBUG
#include "defines.h" #include "defines.h"
#include "Turnouts.h"
#include "EEStore.h" #include "EEStore.h"
#include "StringFormatter.h" #include "StringFormatter.h"
#include "RMFT2.h" #include "RMFT2.h"
#include "Turnouts.h"
#ifdef EESTOREDEBUG #ifdef EESTOREDEBUG
#include "DIAG.h" #include "DIAG.h"
#endif #endif
@ -39,370 +35,150 @@ const int16_t HASH_KEYWORD_SERVO=27709;
const int16_t HASH_KEYWORD_DCC=6436; const int16_t HASH_KEYWORD_DCC=6436;
const int16_t HASH_KEYWORD_VPIN=-415; const int16_t HASH_KEYWORD_VPIN=-415;
enum unit8_t {
TURNOUT_DCC = 1,
TURNOUT_SERVO = 2,
TURNOUT_VPIN = 3,
TURNOUT_LCN = 4,
};
/*
* Protected static data
*/
Turnout *Turnout::_firstTurnout = 0;
/*
* Public static data
*/
int Turnout::turnoutlistHash = 0;
/*
* Protected static functions
*/
/////////////////////////////////////////////////////////////////////////////// Turnout *Turnout::get(uint16_t id) {
// Static function to print all Turnout states to stream in form "<H id state>" // Find turnout object from list.
for (Turnout *tt = _firstTurnout; tt != NULL; tt = tt->_nextTurnout)
void Turnout::printAll(Print *stream){ if (tt->_turnoutData.id == id) return tt;
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout) return NULL;
StringFormatter::send(stream, F("<H %d %d>\n"), tt->data.id, tt->data.active);
} // Turnout::printAll
///////////////////////////////////////////////////////////////////////////////
// Object method to print configuration of one Turnout to stream, in one of the following forms:
// <H id SERVO vpin activePos inactivePos profile state>
// <H id LCN state>
// <H id VPIN vpin state>
// <H id DCC address subAddress state>
void Turnout::print(Print *stream){
uint8_t state = ((data.active) != 0);
uint8_t type = data.type;
switch (type) {
case TURNOUT_LCN:
// LCN Turnout
StringFormatter::send(stream, F("<H %d LCN %d>\n"), data.id, state);
break;
case TURNOUT_DCC:
// DCC Turnout
StringFormatter::send(stream, F("<H %d DCC %d %d %d>\n"), data.id,
(((data.dccAccessoryData.address-1) >> 2)+1), ((data.dccAccessoryData.address-1) & 3), state);
break;
case TURNOUT_VPIN:
// VPIN Digital output
StringFormatter::send(stream, F("<H %d VPIN %d %d>\n"), data.id, data.vpinData.vpin, state);
break;
case TURNOUT_SERVO:
// Servo Turnout
StringFormatter::send(stream, F("<H %d SERVO %d %d %d %d %d>\n"), data.id, data.servoData.vpin,
data.servoData.activePosition, data.servoData.inactivePosition, data.servoData.profile, state);
break;
default:
break;
}
} }
// Add new turnout to end of chain
// Public interface to turnout throw/close void Turnout::add(Turnout *tt) {
bool Turnout::setClosed(int id, bool closed) { if (!_firstTurnout)
// hides the internal activate argument to a single place _firstTurnout = tt;
return activate(id, closed? false: true ); /// Needs cleaning up else {
// Find last object on chain
Turnout *ptr = _firstTurnout;
for ( ; ptr->_nextTurnout!=0; ptr=ptr->_nextTurnout) {}
// Line new object to last object.
ptr->_nextTurnout = tt;
} }
bool Turnout::isClosed(int id) {
// hides the internal activate argument to a single place
return !isActive(id); /// Needs cleaning up
}
int Turnout::getId() {
return data.id;
}
///////////////////////////////////////////////////////////////////////////////
// Static function to activate/deactivate Turnout with ID 'n'.
// Returns false if turnout not found.
bool Turnout::activate(int n, bool state){
Turnout * tt=get(n);
if (!tt) return false;
tt->activate(state);
turnoutlistHash++; turnoutlistHash++;
return true;
} }
/////////////////////////////////////////////////////////////////////////////// // Remove nominated turnout from turnout linked list and delete the object.
// Static function to check if the Turnout with ID 'n' is activated or not. bool Turnout::remove(uint16_t id) {
// Returns false if turnout not found.
bool Turnout::isActive(int n){
Turnout * tt=get(n);
if (!tt) return false;
return tt->isActive();
}
///////////////////////////////////////////////////////////////////////////////
// Object function to check the status of Turnout is activated or not.
bool Turnout::isActive() {
return data.active;
}
///////////////////////////////////////////////////////////////////////////////
// Object method to activate or deactivate the Turnout.
// activate is virtual here so that it can be overridden by a non-DCC turnout mechanism
void Turnout::activate(bool state) {
#ifdef EESTOREDEBUG
DIAG(F("Turnout::activate(%d)"),state);
#endif
if (data.type == TURNOUT_LCN) {
// A LCN turnout is transmitted to the LCN master.
LCN::send('T', data.id, state);
return; // The tStatus will be updated by a message from the LCN master, later.
}
data.active = state;
switch (data.type) {
case TURNOUT_DCC:
DCC::setAccessory((((data.dccAccessoryData.address-1) >> 2) + 1),
((data.dccAccessoryData.address-1) & 3), state);
break;
case TURNOUT_SERVO:
#ifndef IO_NO_HAL
IODevice::write(data.servoData.vpin, state);
#endif
break;
case TURNOUT_VPIN:
IODevice::write(data.vpinData.vpin, state);
break;
}
// Save state if stored in EEPROM
if (EEStore::eeStore->data.nTurnouts > 0 && num > 0)
EEPROM.put(num, data.tStatus);
#if defined(RMFT_ACTIVE)
RMFT2::turnoutEvent(data.id, !state);
#endif
}
///////////////////////////////////////////////////////////////////////////////
// Static function to find Turnout object specified by ID 'n'. Return NULL if not found.
Turnout* Turnout::get(int n){
Turnout *tt;
for(tt=firstTurnout;tt!=NULL && tt->data.id!=n;tt=tt->nextTurnout);
return(tt);
}
///////////////////////////////////////////////////////////////////////////////
// Static function to delete Turnout object specified by ID 'n'. Return false if not found.
bool Turnout::remove(int n){
Turnout *tt,*pp=NULL; Turnout *tt,*pp=NULL;
for(tt=firstTurnout;tt!=NULL && tt->data.id!=n;pp=tt,tt=tt->nextTurnout); for(tt=_firstTurnout; tt!=NULL && tt->_turnoutData.id!=id; pp=tt, tt=tt->_nextTurnout) {}
if (tt == NULL) return false; if (tt == NULL) return false;
if(tt==firstTurnout) if (tt == _firstTurnout)
firstTurnout=tt->nextTurnout; _firstTurnout = tt->_nextTurnout;
else else
pp->nextTurnout=tt->nextTurnout; pp->_nextTurnout = tt->_nextTurnout;
delete (ServoTurnout *)tt;
free(tt);
turnoutlistHash++; turnoutlistHash++;
return true; return true;
} }
///////////////////////////////////////////////////////////////////////////////
// Static function to load all Turnout definitions from EEPROM
// TODO: Consider transmitting the initial state of the DCC/LCN turnout here.
// (already done for servo turnouts and VPIN turnouts).
/*
* Public static functions
*/
bool Turnout::isClosed(uint16_t id) {
Turnout *tt = get(id);
if (tt)
return tt->isClosed();
else
return false;
}
// Static activate function is invoked from close(), throw() etc. to perform the
// common parts of the turnout operation. Code which is specific to a turnout
// type should be placed in the polymorphic virtual function activate(bool) which is
// called from here.
bool Turnout::activate(uint16_t id, bool closeFlag) {
#ifdef EESTOREDEBUG
if (closeFlag)
DIAG(F("Turnout::close(%d)"), id);
else
DIAG(F("Turnout::throw(%d)"), id);
#endif
Turnout *tt = Turnout::get(id);
if (!tt) return false;
bool ok = tt->activate(closeFlag);
// Write new closed/thrown state to EEPROM if required. Note that eepromAddress
// is always zero for LCN turnouts.
if (EEStore::eeStore->data.nTurnouts > 0 && tt->_eepromAddress)
EEPROM.put(tt->_eepromAddress, tt->_turnoutData.closed);
#if defined(RMFT_ACTIVE)
// TODO: Check that the inversion is correct here!
RMFT2::turnoutEvent(id, !closeFlag);
#endif
return ok;
}
// Load all turnout objects
void Turnout::load() { void Turnout::load() {
struct TurnoutData data;
Turnout *tt=NULL;
for (uint16_t i=0; i<EEStore::eeStore->data.nTurnouts; i++) { for (uint16_t i=0; i<EEStore::eeStore->data.nTurnouts; i++) {
// Retrieve data Turnout::loadTurnout();
EEPROM.get(EEStore::pointer(), data);
int lastKnownState = data.active;
switch (data.type) {
case TURNOUT_DCC:
tt=createDCC(data.id, ((data.dccAccessoryData.address-1)>>2)+1, (data.dccAccessoryData.address-1)&3); // DCC-based turnout
break;
case TURNOUT_LCN:
// LCN turnouts are created when the remote device sends a message.
break;
case TURNOUT_SERVO:
tt=createServo(data.id, data.servoData.vpin,
data.servoData.activePosition, data.servoData.inactivePosition, data.servoData.profile, lastKnownState);
break;
case TURNOUT_VPIN:
tt=createVpin(data.id, data.vpinData.vpin, lastKnownState); // VPIN-based turnout
break;
default:
tt=NULL;
}
if (tt) tt->num = EEStore::pointer() + offsetof(TurnoutData, tStatus); // Save pointer to tStatus byte within EEPROM
// Advance by the actual size of the individual turnout struct.
EEStore::advance(data.size);
#ifdef EESTOREDEBUG
if (tt) print(tt);
#endif
} }
} }
/////////////////////////////////////////////////////////////////////////////// // Save all turnout objects
// Static function to store all Turnout definitions to EEPROM
void Turnout::store() { void Turnout::store() {
Turnout *tt;
tt=firstTurnout;
EEStore::eeStore->data.nTurnouts=0; EEStore::eeStore->data.nTurnouts=0;
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout) {
while(tt!=NULL){ tt->save();
// LCN turnouts aren't saved to EEPROM
if (tt->data.type != TURNOUT_LCN) {
#ifdef EESTOREDEBUG
print(tt);
#endif
tt->num = EEStore::pointer() + offsetof(TurnoutData, tStatus); // Save pointer to tstatus byte within EEPROM
EEPROM.put(EEStore::pointer(),tt->data);
EEStore::advance(tt->data.size);
EEStore::eeStore->data.nTurnouts++; EEStore::eeStore->data.nTurnouts++;
} }
tt=tt->nextTurnout;
}
} }
/////////////////////////////////////////////////////////////////////////////// // Load one turnout from EEPROM
// Static function for creating a DCC-controlled Turnout. Turnout *Turnout::loadTurnout () {
Turnout *tt;
// Read turnout type from EEPROM
struct TurnoutData turnoutData;
int eepromAddress = EEStore::pointer(); // Address of byte containing the _closed flag.
EEPROM.get(EEStore::pointer(), turnoutData);
EEStore::advance(sizeof(turnoutData));
Turnout *Turnout::createDCC(int id, uint16_t add, uint8_t subAdd){ switch (turnoutData.turnoutType) {
if (add > 511 || subAdd > 3) return NULL; case TURNOUT_SERVO:
Turnout *tt=create(id); // Servo turnout
if (!tt) return(tt); tt = ServoTurnout::load(&turnoutData);
tt->data.type = TURNOUT_DCC; break;
tt->data.size = sizeof(tt->data.header) + sizeof(tt->data.dccAccessoryData); case TURNOUT_DCC:
tt->data.active = 0; // DCC Accessory turnout
tt->data.dccAccessoryData.address = ((add-1) << 2) + subAdd + 1; tt = DCCTurnout::load(&turnoutData);
return(tt); break;
case TURNOUT_VPIN:
// VPIN turnout
tt = VpinTurnout::load(&turnoutData);
break;
}
if (!tt) {
// Save EEPROM address in object. Note that LCN turnouts always have eepromAddress of zero.
tt->_eepromAddress = eepromAddress;
add(tt);
} }
/////////////////////////////////////////////////////////////////////////////// #ifdef EESTOREDEBUG
// Static function for creating a LCN-controlled Turnout. printAll(&Serial);
Turnout *Turnout::createLCN(int id, uint8_t state) {
Turnout *tt=create(id);
if (!tt) return(tt);
tt->data.type = TURNOUT_LCN;
tt->data.size = sizeof(tt->data.header) + sizeof(tt->data.lcnData);
tt->data.active = (state != 0);
return(tt);
}
///////////////////////////////////////////////////////////////////////////////
// Static function for associating a Turnout id with a virtual pin in IODevice space.
// The actual creation and configuration of the pin must be done elsewhere,
// e.g. in mySetup.cpp during startup of the CS.
Turnout *Turnout::createVpin(int id, VPIN vpin, uint8_t state){
if (vpin > VPIN_MAX) return NULL;
Turnout *tt=create(id);
if(!tt) return(tt);
tt->data.type = TURNOUT_VPIN;;
tt->data.size = sizeof(tt->data.header) + sizeof(tt->data.vpinData);
tt->data.active = (state != 0);
tt->data.vpinData.vpin = vpin;
IODevice::write(vpin, state); // Set initial state of output.
return(tt);
}
///////////////////////////////////////////////////////////////////////////////
// Method for creating a Servo Turnout, e.g. connected to PCA9685 PWM device.
Turnout *Turnout::createServo(int id, VPIN vpin, uint16_t activePosition, uint16_t inactivePosition, uint8_t profile, uint8_t state){
#ifndef IO_NO_HAL
if (activePosition > 511 || inactivePosition > 511 || profile > 4) return NULL;
Turnout *tt=create(id);
if (!tt) return(tt);
if (tt->data.type != TURNOUT_SERVO) tt->data.active = (state != 0); // Retain current state if it's an existing servo turnout.
tt->data.type = TURNOUT_SERVO;
tt->data.size = sizeof(tt->data.header) + sizeof(tt->data.servoData);
tt->data.servoData.vpin = vpin;
tt->data.servoData.activePosition = activePosition;
tt->data.servoData.inactivePosition = inactivePosition;
tt->data.servoData.profile = profile;
// Configure PWM interface device
int deviceParams[] = {(int)activePosition, (int)inactivePosition, profile, tt->data.active};
if (!IODevice::configure(vpin, IODevice::CONFIGURE_SERVO, 4, deviceParams)) {
remove(id);
return NULL;
}
return(tt);
#else
(void)id; (void)vpin; (void)activePosition; (void)inactivePosition; (void)profile; (void)state; // avoid compiler warnings
return NULL;
#endif #endif
}
///////////////////////////////////////////////////////////////////////////////
// Support for <T id SERVO pin activepos inactive pos profile>
// and <T id DCC address subaddress>
// and <T id VPIN pin>
Turnout *Turnout::create(int id, int params, int16_t p[]) {
if (p[0] == HASH_KEYWORD_SERVO) { // <T id SERVO n n n n>
if (params == 5)
return createServo(id, (VPIN)p[1], (uint16_t)p[2], (uint16_t)p[3], (uint8_t)p[4]);
else
return NULL;
} else
if (p[0] == HASH_KEYWORD_VPIN) { // <T id VPIN n>
if (params==2)
return createVpin(id, p[1]);
else
return NULL;
} else
if (p[0]==HASH_KEYWORD_DCC) {
if (params==3 && p[1]>0 && p[1]<=512 && p[2]>=0 && p[2]<4) // <T id DCC n n>
return createDCC(id, p[1], p[2]);
else if (params==2 && p[1]>0 && p[1]<=512*4) // <T id DCC nn>
return createDCC(id, (p[1]-1)/4+1, (p[1]-1)%4);
else
return NULL;
} else if (params==2) { // <T id n n> for DCC or LCN
return createDCC(id, p[0], p[1]);
}
else if (params==3) { // legacy <T id n n n> for Servo
return createServo(id, (VPIN)p[0], (uint16_t)p[1], (uint16_t)p[2]);
}
return NULL;
}
///////////////////////////////////////////////////////////////////////////////
// Create basic Turnout object. The details of what sort of object it is
// controlling are not set here.
Turnout *Turnout::create(int id){
Turnout *tt=get(id);
if (tt==NULL) {
tt=(Turnout *)calloc(1,sizeof(Turnout));
if (!tt) return (tt);
tt->nextTurnout=firstTurnout;
firstTurnout=tt;
tt->data.id=id;
}
turnoutlistHash++;
return tt; return tt;
} }
///////////////////////////////////////////////////////////////////////////////
//
// Object method to print debug info about the state of a Turnout object
//
#ifdef EESTOREDEBUG
void Turnout::print(Turnout *tt) {
tt->print(StringFormatter::diagSerial);
}
#endif
///////////////////////////////////////////////////////////////////////////////
Turnout *Turnout::firstTurnout=NULL;
int Turnout::turnoutlistHash=0; //bump on every change so clients know when to refresh their lists

View File

@ -1,4 +1,6 @@
/* /*
* © 2021 Restructured Neil McKechnie
* © 2013-2016 Gregg E. Berman
* © 2020, Chris Harlow. All rights reserved. * © 2020, Chris Harlow. All rights reserved.
* *
* This file is part of Asbelos DCC API * This file is part of Asbelos DCC API
@ -17,109 +19,464 @@
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>. * along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/ */
/*
* Turnout data is stored in a structure whose length depends on the
* type of turnout. There is a common header of 3 bytes, followed by
* 2 bytes for DCC turnout, 5 bytes for servo turnout, 2 bytes for a
* VPIN turnout, or zero bytes for an LCN turnout.
* The variable length allows the limited space in EEPROM to be used effectively.
*/
#ifndef Turnouts_h //#define EESTOREDEBUG
#define Turnouts_h #include "defines.h"
#include "EEStore.h"
#include "StringFormatter.h"
#include "RMFT2.h"
#ifdef EESTOREDEBUG
#include "DIAG.h"
#endif
#include <Arduino.h>
#include "DCC.h" #include "DCC.h"
#include "LCN.h" #include "LCN.h"
#include "IODevice.h"
// Turnout type definitions
enum {
TURNOUT_DCC = 1,
TURNOUT_SERVO = 2,
TURNOUT_VPIN = 3,
TURNOUT_LCN = 4,
};
const byte STATUS_ACTIVE=0x80; // Flag as activated in tStatus field /*************************************************************************************
const byte STATUS_TYPE = 0x7f; // Mask for turnout type in tStatus field * Turnout - Base class for turnouts.
*
*************************************************************************************/
// The struct 'header' is used to determine the length of the class Turnout {
// overlaid data so must be at least as long as the anonymous fields it protected:
// is overlaid with. /*
* Object data
*/
// The TurnoutData struct contains data common to all turnout types, that
// is written to EEPROM when the turnout is saved.
// The first byte of this struct contains the 'closed' flag which is
// updated whenever the turnout changes from thrown to closed and
// vice versa. If the turnout has been saved, then this byte is rewritten
// when changed in RAM. The 'closed' flag must be located in the first byte.
struct TurnoutData { struct TurnoutData {
// Header common to all turnouts bool closed : 1;
union { bool _rfu: 2;
struct { uint8_t turnoutType : 5;
int id; uint16_t id;
uint8_t tStatus; } _turnoutData; // 3 bytes
uint8_t size;
} header;
struct { // Address in eeprom of first byte of the _turnoutData struct (containing the closed flag).
int id; // Set to zero if the object has not been saved in EEPROM, e.g. for newly created Turnouts, and
union { // for all LCN turnouts.
uint8_t tStatus; uint16_t _eepromAddress = 0;
struct {
uint8_t active: 1; // Pointer to next turnout on linked list.
uint8_t type: 5; Turnout *_nextTurnout = 0;
uint8_t :2;
/*
* Constructor
*/
Turnout(uint16_t id, uint8_t turnoutType, bool closed) {
_turnoutData.id = id;
_turnoutData.turnoutType = turnoutType;
_turnoutData.closed = closed;
add(this);
}
/*
* Static data
*/
static Turnout *_firstTurnout;
static int _turnoutlistHash;
/*
* Virtual functions
*/
virtual bool activate(bool close) = 0; // Mandatory in subclass
virtual void save() {}
/*
* Static functions
*/
static Turnout *get(uint16_t id);
static void add(Turnout *tt);
public:
/*
* Static data
*/
static int turnoutlistHash;
/*
* Public base class functions
*/
inline bool isClosed() { return _turnoutData.closed; };
inline bool isThrown() { return !_turnoutData.closed; }
inline bool isType(uint8_t type) { return _turnoutData.turnoutType == type; }
inline uint16_t getId() { return _turnoutData.id; }
inline Turnout *next() { return _nextTurnout; }
/*
* Virtual functions
*/
virtual void print(Print *stream) {}
virtual ~Turnout() {} // Destructor
/*
* Public static functions
*/
inline static bool exists(uint16_t id) { return get(id) != 0; }
static bool remove(uint16_t id);
static bool isClosed(uint16_t id);
inline static bool isThrown(uint16_t id) {
return !isClosed(id);
}
static bool activate(uint16_t id, bool closeFlag);
inline static bool setClosed(uint16_t id) {
return activate(id, true);
}
inline static bool setThrown(uint16_t id) {
return activate(id, false);
}
inline static bool setClosed(uint16_t id, bool close) {
return activate(id, close);
}
static bool setClosedStateOnly(uint16_t id, bool close) {
Turnout *tt = get(id);
if (tt) return false;
tt->_turnoutData.closed = close;
return true;
}
inline static Turnout *first() { return _firstTurnout; }
// Load all turnout definitions.
static void load();
// Load one turnout definition
static Turnout *loadTurnout();
// Save all turnout definitions
static void store();
static void printAll(Print *stream) {
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout)
tt->print(stream);
}
}; };
/*************************************************************************************
* ServoTurnout - Turnout controlled by servo device.
*
*************************************************************************************/
class ServoTurnout : public Turnout {
private:
// ServoTurnoutData contains data specific to this subclass that is
// written to EEPROM when the turnout is saved.
struct ServoTurnoutData {
VPIN vpin;
uint16_t closedPosition : 12;
uint16_t thrownPosition : 12;
uint8_t profile;
} _servoTurnoutData; // 6 bytes
public:
// Constructor
ServoTurnout(uint16_t id, VPIN vpin, uint16_t thrownPosition, uint16_t closedPosition, uint8_t profile, bool closed = true) :
Turnout(id, TURNOUT_SERVO, closed)
{
_servoTurnoutData.vpin = vpin;
_servoTurnoutData.thrownPosition = thrownPosition;
_servoTurnoutData.closedPosition = closedPosition;
_servoTurnoutData.profile = profile;
}
// Create function
static Turnout *create(uint16_t id, VPIN vpin, uint16_t thrownPosition, uint16_t closedPosition, uint8_t profile, bool closed = true) {
#ifndef IO_NO_HAL
Turnout *tt = get(id);
if (!tt) {
// Object already exists, check if it is usable
if (tt->isType(TURNOUT_SERVO)) {
// Yes, so set parameters
ServoTurnout *st = (ServoTurnout *)tt;
st->_servoTurnoutData.vpin = vpin;
st->_servoTurnoutData.thrownPosition = thrownPosition;
st->_servoTurnoutData.closedPosition = closedPosition;
st->_servoTurnoutData.profile = profile;
// Don't touch the _closed parameter, retain the original value.
// We don't really need to do the following, since a call to IODevice::_writeAnalogue
// will provide all the data that is required!
// int params[] = {(int)thrownPosition, (int)closedPosition, profile, closed};
// IODevice::configure(vpin, IODevice::CONFIGURE_SERVO, 4, params);
// Set position to saved position
IODevice::writeAnalogue(vpin, closed ? closedPosition : thrownPosition, PCA9685::Instant);
return tt;
} else {
// Incompatible object, delete and recreate
remove(id);
}
}
tt = (Turnout *)new ServoTurnout(id, vpin, thrownPosition, closedPosition, profile, closed);
IODevice::writeAnalogue(vpin, closed ? closedPosition : thrownPosition, PCA9685::Instant);
return tt;
#else
return NULL;
#endif
}
bool activate(bool close) override {
#ifndef IO_NO_HAL
IODevice::writeAnalogue(_servoTurnoutData.vpin,
close ? _servoTurnoutData.closedPosition : _servoTurnoutData.thrownPosition, _servoTurnoutData.profile);
_turnoutData.closed = close;
#endif
return true;
}
void save() override {
// Write turnout definition and current position to EEPROM
// First write common servo data, then
// write the servo-specific data
EEPROM.put(EEStore::pointer(), _turnoutData);
EEStore::advance(sizeof(_turnoutData));
EEPROM.put(EEStore::pointer(), _servoTurnoutData);
EEStore::advance(sizeof(_servoTurnoutData));
}
void print(Print *stream) override {
StringFormatter::send(stream, F("<H %d SERVO %d %d %d %d %d>\n"), _turnoutData.id, _servoTurnoutData.vpin,
_servoTurnoutData.thrownPosition, _servoTurnoutData.closedPosition, _servoTurnoutData.profile, _turnoutData.closed);
}
// Load a Servo turnout definition from EEPROM. The common Turnout data has already been read at this point.
static Turnout *load(struct TurnoutData *turnoutData) {
ServoTurnoutData servoTurnoutData;
// Read class-specific data from EEPROM
EEPROM.get(EEStore::pointer(), servoTurnoutData);
EEStore::advance(sizeof(servoTurnoutData));
// Create new object
ServoTurnout *tt = new ServoTurnout(turnoutData->id, servoTurnoutData.vpin, servoTurnoutData.thrownPosition,
servoTurnoutData.closedPosition, servoTurnoutData.profile, turnoutData->closed);
return tt;
}
}; };
uint8_t size; // set to actual total length of used structure
}; /*************************************************************************************
}; * DCCTurnout - Turnout controlled by DCC Accessory Controller.
// Turnout-type-specific structure elements, different length depending *
// on turnout type. This allows the data to be packed efficiently *************************************************************************************/
// in the EEPROM. class DCCTurnout : public Turnout {
union { private:
struct { // DCCTurnoutData contains data specific to this subclass that is
// written to EEPROM when the turnout is saved.
struct DCCTurnoutData {
// DCC address (Address in bits 15-2, subaddress in bits 1-0 // DCC address (Address in bits 15-2, subaddress in bits 1-0
uint16_t address; // CS currently supports linear address 1-2048 uint16_t address; // CS currently supports linear address 1-2048
// That's DCC accessory address 1-512 and subaddress 0-3. // That's DCC accessory address 1-512 and subaddress 0-3.
} dccAccessoryData; } _dccTurnoutData; // 2 bytes
struct {
VPIN vpin;
uint16_t activePosition : 12; // 0-4095
uint16_t inactivePosition : 12; // 0-4095
uint8_t profile;
} servoData;
struct {
} lcnData;
struct {
VPIN vpin;
} vpinData;
};
};
class Turnout {
public: public:
static Turnout *firstTurnout; // Constructor
static int turnoutlistHash; DCCTurnout(uint16_t id, uint16_t address, uint8_t subAdd) :
Turnout *nextTurnout; Turnout(id, TURNOUT_DCC, false)
static Turnout* get(int); {
static bool remove(int); _dccTurnoutData.address = ((address-1) << 2) + subAdd + 1;
static bool isClosed(int); }
static bool setClosed(int n, bool closed); // return false if not found.
static void setClosedStateOnly(int n, bool closed); // Create function
int getId(); static Turnout *create(uint16_t id, uint16_t add, uint8_t subAdd) {
static void load(); Turnout *tt = get(id);
static void store(); if (!tt) {
static Turnout *createServo(int id , VPIN vpin , uint16_t activeAngle, uint16_t inactiveAngle, uint8_t profile=1, uint8_t initialState=0); // Object already exists, check if it is usable
static Turnout *createVpin(int id, VPIN vpin, uint8_t initialState=0); if (tt->isType(TURNOUT_DCC)) {
static Turnout *createDCC(int id, uint16_t address, uint8_t subAddress); // Yes, so set parameters<T>
static Turnout *createLCN(int id, uint8_t initialState=0); DCCTurnout *dt = (DCCTurnout *)tt;
static Turnout *create(int id, int params, int16_t p[]); dt->_dccTurnoutData.address = ((add-1) << 2) + subAdd + 1;
static Turnout *create(int id); // Don't touch the _closed parameter, retain the original value.
static void printAll(Print *); return tt;
void print(Print *stream); } else {
#ifdef EESTOREDEBUG // Incompatible object, delete and recreate
static void print(Turnout *tt); remove(id);
#endif }
private: }
int num; // EEPROM address of tStatus in TurnoutData struct, or zero if not stored. tt = (Turnout *)new DCCTurnout(id, add, subAdd);
TurnoutData data; return tt;
static bool activate(int n, bool thrown); }
static bool isActive(int);
bool isActive(); bool activate(bool close) override {
void activate(bool state); DCC::setAccessory((((_dccTurnoutData.address-1) >> 2) + 1),
void setActive(bool state); ((_dccTurnoutData.address-1) & 3), close);
}; // Turnout _turnoutData.closed = close;
return true;
}
void save() override {
// Write turnout definition and current position to EEPROM
// First write common servo data, then
// write the servo-specific data
EEPROM.put(EEStore::pointer(), _turnoutData);
EEStore::advance(sizeof(_turnoutData));
EEPROM.put(EEStore::pointer(), _dccTurnoutData);
EEStore::advance(sizeof(_dccTurnoutData));
}
void print(Print *stream) override {
StringFormatter::send(stream, F("<H %d DCC %d %d %d>\n"), _turnoutData.id,
(((_dccTurnoutData.address-1) >> 2)+1), ((_dccTurnoutData.address-1) & 3), _turnoutData.closed);
}
// Load a DCC turnout definition from EEPROM. The common Turnout data has already been read at this point.
static Turnout *load(struct TurnoutData *turnoutData) {
DCCTurnoutData dccTurnoutData;
// Read class-specific data from EEPROM
EEPROM.get(EEStore::pointer(), dccTurnoutData);
EEStore::advance(sizeof(dccTurnoutData));
// Create new object
DCCTurnout *tt = new DCCTurnout(turnoutData->id, (((dccTurnoutData.address-1) >> 2)+1), ((dccTurnoutData.address-1) & 3));
return tt;
}
};
/*************************************************************************************
* VpinTurnout - Turnout controlled through a HAL vpin.
*
*************************************************************************************/
class VpinTurnout : public Turnout {
private:
// VpinTurnoutData contains data specific to this subclass that is
// written to EEPROM when the turnout is saved.
struct VpinTurnoutData {
VPIN vpin;
} _vpinTurnoutData; // 2 bytes
public:
// Constructor
VpinTurnout(uint16_t id, VPIN vpin, bool closed=true) :
Turnout(id, TURNOUT_VPIN, closed)
{
_vpinTurnoutData.vpin = vpin;
}
// Create function
static Turnout *create(uint16_t id, VPIN vpin, bool closed=true) {
Turnout *tt = get(id);
if (!tt) {
// Object already exists, check if it is usable
if (tt->isType(TURNOUT_VPIN)) {
// Yes, so set parameters
VpinTurnout *vt = (VpinTurnout *)tt;
vt->_vpinTurnoutData.vpin = vpin;
// Don't touch the _closed parameter, retain the original value.
return tt;
} else {
// Incompatible object, delete and recreate
remove(id);
}
}
tt = (Turnout *)new VpinTurnout(id, vpin, closed);
return tt;
}
bool activate(bool close) override {
IODevice::write(_vpinTurnoutData.vpin, close);
_turnoutData.closed = close;
return true;
}
void save() override {
// Write turnout definition and current position to EEPROM
// First write common servo data, then
// write the servo-specific data
EEPROM.put(EEStore::pointer(), _turnoutData);
EEStore::advance(sizeof(_turnoutData));
EEPROM.put(EEStore::pointer(), _vpinTurnoutData);
EEStore::advance(sizeof(_vpinTurnoutData));
}
void print(Print *stream) override {
StringFormatter::send(stream, F("<H %d VPIN %d %d>\n"), _turnoutData.id,
_vpinTurnoutData.vpin, _turnoutData.closed);
}
// Load a VPIN turnout definition from EEPROM. The common Turnout data has already been read at this point.
static Turnout *load(struct TurnoutData *turnoutData) {
VpinTurnoutData vpinTurnoutData;
// Read class-specific data from EEPROM
EEPROM.get(EEStore::pointer(), vpinTurnoutData);
EEStore::advance(sizeof(vpinTurnoutData));
// Create new object
VpinTurnout *tt = new VpinTurnout(turnoutData->id, vpinTurnoutData.vpin, turnoutData->closed);
return tt;
}
};
/*************************************************************************************
* LCNTurnout - Turnout controlled by Loconet
*
*************************************************************************************/
class LCNTurnout : public Turnout {
private:
// LCNTurnout has no specific data, and in any case is not written to EEPROM!
// struct LCNTurnoutData {
// } _lcnTurnoutData; // 0 bytes
public:
// Constructor
LCNTurnout(uint16_t id, bool closed=true) :
Turnout(id, TURNOUT_LCN, closed)
{ }
// Create function
static Turnout *create(uint16_t id, bool closed=true) {
Turnout *tt = get(id);
if (!tt) {
// Object already exists, check if it is usable
if (tt->isType(TURNOUT_LCN)) {
// Yes, so return this object
return tt;
} else {
// Incompatible object, delete and recreate
remove(id);
}
}
tt = (Turnout *)new LCNTurnout(id, closed);
return tt;
}
bool activate(bool close) override {
LCN::send('T', _turnoutData.id, close);
// The _turnoutData.closed flag should be updated by a message from the LCN master, later.
return true;
}
// LCN turnouts not saved to EEPROM.
//void save() override { }
//static Turnout *load(struct TurnoutData *turnoutData) {
void print(Print *stream) override {
StringFormatter::send(stream, F("<H %d LCN %d>\n"), _turnoutData.id, _turnoutData.closed);
}
};
#endif

View File

@ -120,7 +120,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
// Send turnout list if changed since last sent (will replace list on client) // Send turnout list if changed since last sent (will replace list on client)
if (turnoutListHash != Turnout::turnoutlistHash) { if (turnoutListHash != Turnout::turnoutlistHash) {
StringFormatter::send(stream,F("PTL")); StringFormatter::send(stream,F("PTL"));
for(Turnout *tt=Turnout::firstTurnout;tt!=NULL;tt=tt->nextTurnout){ for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
int id=tt->getId(); int id=tt->getId();
StringFormatter::send(stream,F("]\\[%d}|{%d}|{%c"), id, id, Turnout::isClosed(id)?'2':'4'); StringFormatter::send(stream,F("]\\[%d}|{%d}|{%c"), id, id, Turnout::isClosed(id)?'2':'4');
} }
@ -161,12 +161,11 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
#endif #endif
else if (cmd[1]=='T' && cmd[2]=='A') { // PTA accessory toggle else if (cmd[1]=='T' && cmd[2]=='A') { // PTA accessory toggle
int id=getInt(cmd+4); int id=getInt(cmd+4);
Turnout * tt=Turnout::get(id); if (!Turnout::exists(id)) {
if (!tt) {
// If turnout does not exist, create it // If turnout does not exist, create it
int addr = ((id - 1) / 4) + 1; int addr = ((id - 1) / 4) + 1;
int subaddr = (id - 1) % 4; int subaddr = (id - 1) % 4;
Turnout::createDCC(id,addr,subaddr); DCCTurnout::create(id,addr,subaddr);
StringFormatter::send(stream, F("HmTurnout %d created\n"),id); StringFormatter::send(stream, F("HmTurnout %d created\n"),id);
} }
switch (cmd[3]) { switch (cmd[3]) {