mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-23 08:06:13 +01:00
Merge branch 'EX-RAIL-neil2' into EX-RAIL
This commit is contained in:
commit
240b18a0df
|
@ -56,7 +56,10 @@ const int16_t HASH_KEYWORD_LCN = 15137;
|
||||||
const int16_t HASH_KEYWORD_RESET = 26133;
|
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;
|
||||||
|
const int16_t HASH_KEYWORD_C=67;
|
||||||
|
const int16_t HASH_KEYWORD_T=84;
|
||||||
|
|
||||||
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
||||||
bool DCCEXParser::stashBusy;
|
bool DCCEXParser::stashBusy;
|
||||||
|
@ -658,7 +661,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,17 +675,65 @@ 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;
|
{
|
||||||
if (!Turnout::setClosed(p[0],p[1]==0)) return false;
|
bool state = false;
|
||||||
StringFormatter::send(stream, F("<H %d %d>\n"), p[0], p[1]);
|
switch (p[1]) {
|
||||||
return true;
|
// By default turnout command uses 0=throw, 1=close,
|
||||||
|
// but legacy DCC++ behaviour is 1=throw, 0=close.
|
||||||
|
case 0:
|
||||||
|
state = Turnout::useLegacyTurnoutBehaviour;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
state = !Turnout::useLegacyTurnoutBehaviour;
|
||||||
|
break;
|
||||||
|
case HASH_KEYWORD_C:
|
||||||
|
state = true;
|
||||||
|
break;
|
||||||
|
case HASH_KEYWORD_T:
|
||||||
|
state= false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Turnout::setClosed(p[0], state)) return false;
|
||||||
|
|
||||||
default: // Anything else is handled by Turnout class.
|
// Send acknowledgement to caller if the command was not received over Serial
|
||||||
if (!Turnout::create(p[0], params-1, &p[1]))
|
// (acknowledgement messages on Serial are sent by the Turnout class).
|
||||||
return false;
|
if (stream != &Serial) Turnout::printState(p[0], stream);
|
||||||
StringFormatter::send(stream, F("<O>\n"));
|
return true;
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
default: // Anything else is some kind of turnout create function.
|
||||||
|
if (params == 6 && p[1] == HASH_KEYWORD_SERVO) { // <T id SERVO n n n n>
|
||||||
|
if (!ServoTurnout::create(p[0], (VPIN)p[2], (uint16_t)p[3], (uint16_t)p[4], (uint8_t)p[5]))
|
||||||
|
return false;
|
||||||
|
} else
|
||||||
|
if (params == 3 && p[1] == HASH_KEYWORD_VPIN) { // <T id VPIN n>
|
||||||
|
if (!VpinTurnout::create(p[0], p[2])) return false;
|
||||||
|
} else
|
||||||
|
if (params >= 3 && p[1] == HASH_KEYWORD_DCC) {
|
||||||
|
if (params==4 && p[2]>0 && p[2]<=512 && p[3]>=0 && p[3]<4) { // <T id DCC n m>
|
||||||
|
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>, 1<=nn<=2048
|
||||||
|
if (!DCCTurnout::create(p[0], (p[2]-1)/4+1, (p[2]-1)%4)) return false;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
} else
|
||||||
|
if (params==3) { // legacy <T id n n> for DCC accessory
|
||||||
|
if (p[1]>0 && p[1]<=512 && p[2]>=0 && p[2]<4) {
|
||||||
|
if (!DCCTurnout::create(p[0], p[1], p[2])) return false;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (params==4) { // 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;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
|
||||||
|
StringFormatter::send(stream, F("<O>\n"));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -797,7 +848,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;
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ extern ExternalEEPROM EEPROM;
|
||||||
#include <EEPROM.h>
|
#include <EEPROM.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define EESTORE_ID "DCC++0"
|
#define EESTORE_ID "DCC++1"
|
||||||
|
|
||||||
struct EEStoreData{
|
struct EEStoreData{
|
||||||
char id[sizeof(EESTORE_ID)];
|
char id[sizeof(EESTORE_ID)];
|
||||||
|
|
|
@ -274,7 +274,7 @@ private:
|
||||||
uint8_t profile; // Config parameter
|
uint8_t profile; // Config parameter
|
||||||
uint8_t stepNumber; // Index of current step (starting from 0)
|
uint8_t stepNumber; // Index of current step (starting from 0)
|
||||||
uint8_t numSteps; // Number of steps in animation, or 0 if none in progress.
|
uint8_t numSteps; // Number of steps in animation, or 0 if none in progress.
|
||||||
int8_t state;
|
uint8_t currentProfile; // profile being used for current animation.
|
||||||
}; // 12 bytes per element, i.e. per pin in use
|
}; // 12 bytes per element, i.e. per pin in use
|
||||||
|
|
||||||
struct ServoData *_servoData [16];
|
struct ServoData *_servoData [16];
|
||||||
|
|
|
@ -53,19 +53,20 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i
|
||||||
|
|
||||||
int8_t pin = vpin - _firstVpin;
|
int8_t pin = vpin - _firstVpin;
|
||||||
struct ServoData *s = _servoData[pin];
|
struct ServoData *s = _servoData[pin];
|
||||||
if (!s) {
|
if (s == NULL) {
|
||||||
_servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData));
|
_servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData));
|
||||||
s = _servoData[pin];
|
s = _servoData[pin];
|
||||||
if (!s) return false; // Check for failed memory allocation
|
if (!s) return false; // Check for failed memory allocation
|
||||||
}
|
}
|
||||||
|
|
||||||
s->activePosition = params[0];
|
s->activePosition = params[0];
|
||||||
s->currentPosition = s->inactivePosition = params[1];
|
s->inactivePosition = params[1];
|
||||||
s->profile = params[2];
|
s->profile = params[2];
|
||||||
|
int state = params[3];
|
||||||
// Position servo to initial state
|
if (state != -1) {
|
||||||
s->state = -1; // Set unknown state, to force reposition
|
// Position servo to initial state
|
||||||
_write(vpin, params[3]);
|
_writeAnalogue(vpin, state ? s->activePosition : s->inactivePosition, Instant);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -75,6 +76,8 @@ PCA9685::PCA9685(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||||
_firstVpin = firstVpin;
|
_firstVpin = firstVpin;
|
||||||
_nPins = min(nPins, 16);
|
_nPins = min(nPins, 16);
|
||||||
_I2CAddress = I2CAddress;
|
_I2CAddress = I2CAddress;
|
||||||
|
// To save RAM, space for servo configuration is not allocated unless a pin is used.
|
||||||
|
// Initialise the pointers to NULL.
|
||||||
for (int i=0; i<_nPins; i++)
|
for (int i=0; i<_nPins; i++)
|
||||||
_servoData[i] = NULL;
|
_servoData[i] = NULL;
|
||||||
|
|
||||||
|
@ -113,30 +116,12 @@ void PCA9685::_write(VPIN vpin, int value) {
|
||||||
if (value) value = 1;
|
if (value) value = 1;
|
||||||
|
|
||||||
struct ServoData *s = _servoData[pin];
|
struct ServoData *s = _servoData[pin];
|
||||||
if (!s) {
|
if (s == NULL) {
|
||||||
// Pin not configured, just write default positions to servo controller
|
// Pin not configured, just write default positions to servo controller
|
||||||
if (value)
|
writeDevice(pin, value ? _defaultActivePosition : _defaultInactivePosition);
|
||||||
writeDevice(pin, _defaultActivePosition);
|
|
||||||
else
|
|
||||||
writeDevice(pin, _defaultInactivePosition);
|
|
||||||
} else {
|
} else {
|
||||||
// Use configured parameters for advanced transitions
|
// Use configured parameters for advanced transitions
|
||||||
uint8_t profile = s->profile;
|
_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile);
|
||||||
// If current position not known, go straight to selected position.
|
|
||||||
if (s->state == -1) profile = Instant;
|
|
||||||
|
|
||||||
// Animated profile. Initiate the appropriate action.
|
|
||||||
s->numSteps = profile==Fast ? 10 :
|
|
||||||
profile==Medium ? 20 :
|
|
||||||
profile==Slow ? 40 :
|
|
||||||
profile==Bounce ? sizeof(_bounceProfile)-1 :
|
|
||||||
1;
|
|
||||||
s->state = value;
|
|
||||||
s->stepNumber = 0;
|
|
||||||
|
|
||||||
// Update new from/to positions to initiate or change animation.
|
|
||||||
s->fromPosition = s->currentPosition;
|
|
||||||
s->toPosition = s->state ? s->activePosition : s->inactivePosition;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,16 +135,18 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, int profile) {
|
||||||
else if (value < 0) value = 0;
|
else if (value < 0) value = 0;
|
||||||
|
|
||||||
struct ServoData *s = _servoData[pin];
|
struct ServoData *s = _servoData[pin];
|
||||||
|
if (s == NULL) {
|
||||||
if (!s) {
|
// Servo pin not configured, so configure now using defaults
|
||||||
// Servo pin not configured, so configure now.
|
|
||||||
s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1);
|
s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1);
|
||||||
|
if (s == NULL) return; // Check for memory allocation failure
|
||||||
s->activePosition = _defaultActivePosition;
|
s->activePosition = _defaultActivePosition;
|
||||||
s->inactivePosition = _defaultInactivePosition;
|
s->inactivePosition = _defaultInactivePosition;
|
||||||
s->currentPosition = value; // Don't know where we're moving from.
|
s->currentPosition = value;
|
||||||
|
s->profile = Instant;
|
||||||
}
|
}
|
||||||
s->profile = profile;
|
|
||||||
// Animated profile. Initiate the appropriate action.
|
// Animated profile. Initiate the appropriate action.
|
||||||
|
s->currentProfile = profile;
|
||||||
s->numSteps = profile==Fast ? 10 :
|
s->numSteps = profile==Fast ? 10 :
|
||||||
profile==Medium ? 20 :
|
profile==Medium ? 20 :
|
||||||
profile==Slow ? 40 :
|
profile==Slow ? 40 :
|
||||||
|
@ -175,7 +162,7 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, int profile) {
|
||||||
bool PCA9685::_isActive(VPIN vpin) {
|
bool PCA9685::_isActive(VPIN vpin) {
|
||||||
int pin = vpin - _firstVpin;
|
int pin = vpin - _firstVpin;
|
||||||
struct ServoData *s = _servoData[pin];
|
struct ServoData *s = _servoData[pin];
|
||||||
if (!s)
|
if (s == NULL)
|
||||||
return false; // No structure means no animation!
|
return false; // No structure means no animation!
|
||||||
else
|
else
|
||||||
return (s->numSteps != 0);
|
return (s->numSteps != 0);
|
||||||
|
@ -194,16 +181,16 @@ void PCA9685::_loop(unsigned long currentMicros) {
|
||||||
// TODO: Could calculate step number from elapsed time, to allow for erratic loop timing.
|
// TODO: Could calculate step number from elapsed time, to allow for erratic loop timing.
|
||||||
void PCA9685::updatePosition(uint8_t pin) {
|
void PCA9685::updatePosition(uint8_t pin) {
|
||||||
struct ServoData *s = _servoData[pin];
|
struct ServoData *s = _servoData[pin];
|
||||||
if (!s) return;
|
if (s == NULL) return; // No pin configuration/state data
|
||||||
if (s->numSteps == 0) return; // No animation in progress
|
if (s->numSteps == 0) return; // No animation in progress
|
||||||
if (s->stepNumber == 0 && s->fromPosition == s->toPosition) {
|
if (s->stepNumber == 0 && s->fromPosition == s->toPosition) {
|
||||||
// No movement required, so go straight to final step
|
// Go straight to end of sequence, output final position.
|
||||||
s->stepNumber = s->numSteps;
|
s->stepNumber = s->numSteps-1;
|
||||||
}
|
}
|
||||||
if (s->stepNumber < s->numSteps) {
|
if (s->stepNumber < s->numSteps) {
|
||||||
// Animation in progress, reposition servo
|
// Animation in progress, reposition servo
|
||||||
s->stepNumber++;
|
s->stepNumber++;
|
||||||
if (s->profile == Bounce) {
|
if (s->currentProfile == Bounce) {
|
||||||
// Retrieve step positions from array in flash
|
// Retrieve step positions from array in flash
|
||||||
byte profileValue = GETFLASH(&_bounceProfile[s->stepNumber]);
|
byte profileValue = GETFLASH(&_bounceProfile[s->stepNumber]);
|
||||||
s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition);
|
s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition);
|
||||||
|
|
3
LCN.cpp
3
LCN.cpp
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
516
Turnouts.cpp
516
Turnouts.cpp
|
@ -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,390 +20,183 @@
|
||||||
* 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.
|
#ifndef TURNOUTS_CPP
|
||||||
// The public interface has been narrowed to avoid the ambuguity of "activated".
|
#define TURNOUTS_CPP
|
||||||
|
|
||||||
|
// Set the following definition to true for <T id 0> = throw and <T id 1> = close
|
||||||
|
// or to false for <T id 0> = close and <T id 1> = throw (the original way).
|
||||||
|
#ifndef USE_LEGACY_TURNOUT_BEHAVIOUR
|
||||||
|
#define USE_LEGACY_TURNOUT_BEHAVIOUR false
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
//#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
|
||||||
|
|
||||||
// Keywords used for turnout configuration.
|
/*
|
||||||
const int16_t HASH_KEYWORD_SERVO=27709;
|
* Protected static data
|
||||||
const int16_t HASH_KEYWORD_DCC=6436;
|
*/
|
||||||
const int16_t HASH_KEYWORD_VPIN=-415;
|
|
||||||
|
|
||||||
enum unit8_t {
|
Turnout *Turnout::_firstTurnout = 0;
|
||||||
TURNOUT_DCC = 1,
|
|
||||||
TURNOUT_SERVO = 2,
|
|
||||||
TURNOUT_VPIN = 3,
|
|
||||||
TURNOUT_LCN = 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Public static data
|
||||||
|
*/
|
||||||
|
int Turnout::turnoutlistHash = 0;
|
||||||
|
bool Turnout::useLegacyTurnoutBehaviour = USE_LEGACY_TURNOUT_BEHAVIOUR;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Protected static functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
Turnout *Turnout::get(uint16_t id) {
|
||||||
|
// Find turnout object from list.
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
for (Turnout *tt = _firstTurnout; tt != NULL; tt = tt->_nextTurnout)
|
||||||
// Static function to print all Turnout states to stream in form "<H id state>"
|
if (tt->_turnoutData.id == id) return tt;
|
||||||
|
|
||||||
void Turnout::printAll(Print *stream){
|
|
||||||
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Public interface to turnout throw/close
|
|
||||||
bool Turnout::setClosed(int id, bool closed) {
|
|
||||||
// hides the internal activate argument to a single place
|
|
||||||
return activate(id, closed? false: true ); /// Needs cleaning up
|
|
||||||
}
|
|
||||||
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++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Static function to check if the Turnout with ID 'n' is activated or not.
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
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);
|
|
||||||
turnoutlistHash++;
|
|
||||||
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).
|
|
||||||
|
|
||||||
void Turnout::load(){
|
|
||||||
struct TurnoutData data;
|
|
||||||
Turnout *tt=NULL;
|
|
||||||
|
|
||||||
for(uint16_t i=0;i<EEStore::eeStore->data.nTurnouts;i++){
|
|
||||||
// Retrieve data
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Static function to store all Turnout definitions to EEPROM
|
|
||||||
|
|
||||||
void Turnout::store(){
|
|
||||||
Turnout *tt;
|
|
||||||
|
|
||||||
tt=firstTurnout;
|
|
||||||
EEStore::eeStore->data.nTurnouts=0;
|
|
||||||
|
|
||||||
while(tt!=NULL){
|
|
||||||
// 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++;
|
|
||||||
}
|
|
||||||
tt=tt->nextTurnout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Static function for creating a DCC-controlled Turnout.
|
|
||||||
|
|
||||||
Turnout *Turnout::createDCC(int id, uint16_t add, uint8_t subAdd){
|
|
||||||
if (add > 511 || subAdd > 3) return NULL;
|
|
||||||
Turnout *tt=create(id);
|
|
||||||
if (!tt) return(tt);
|
|
||||||
tt->data.type = TURNOUT_DCC;
|
|
||||||
tt->data.size = sizeof(tt->data.header) + sizeof(tt->data.dccAccessoryData);
|
|
||||||
tt->data.active = 0;
|
|
||||||
tt->data.dccAccessoryData.address = ((add-1) << 2) + subAdd + 1;
|
|
||||||
return(tt);
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Static function for creating a LCN-controlled Turnout.
|
|
||||||
|
|
||||||
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 NULL;
|
||||||
}
|
}
|
||||||
return(tt);
|
|
||||||
#else
|
|
||||||
(void)id; (void)vpin; (void)activePosition; (void)inactivePosition; (void)profile; (void)state; // avoid compiler warnings
|
|
||||||
return NULL;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
// Add new turnout to end of chain
|
||||||
// Support for <T id SERVO pin activepos inactive pos profile>
|
void Turnout::add(Turnout *tt) {
|
||||||
// and <T id DCC address subaddress>
|
if (!_firstTurnout)
|
||||||
// and <T id VPIN pin>
|
_firstTurnout = tt;
|
||||||
|
else {
|
||||||
Turnout *Turnout::create(int id, int params, int16_t p[]) {
|
// Find last object on chain
|
||||||
if (p[0] == HASH_KEYWORD_SERVO) { // <T id SERVO n n n n>
|
Turnout *ptr = _firstTurnout;
|
||||||
if (params == 5)
|
for ( ; ptr->_nextTurnout!=0; ptr=ptr->_nextTurnout) {}
|
||||||
return createServo(id, (VPIN)p[1], (uint16_t)p[2], (uint16_t)p[3], (uint8_t)p[4]);
|
// Line new object to last object.
|
||||||
else
|
ptr->_nextTurnout = tt;
|
||||||
return NULL;
|
}
|
||||||
} else
|
turnoutlistHash++;
|
||||||
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;
|
// Remove nominated turnout from turnout linked list and delete the object.
|
||||||
}
|
bool Turnout::remove(uint16_t id) {
|
||||||
|
Turnout *tt,*pp=NULL;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
for(tt=_firstTurnout; tt!=NULL && tt->_turnoutData.id!=id; pp=tt, tt=tt->_nextTurnout) {}
|
||||||
// Create basic Turnout object. The details of what sort of object it is
|
if (tt == NULL) return false;
|
||||||
// controlling are not set here.
|
|
||||||
|
|
||||||
Turnout *Turnout::create(int id){
|
if (tt == _firstTurnout)
|
||||||
Turnout *tt=get(id);
|
_firstTurnout = tt->_nextTurnout;
|
||||||
if (tt==NULL) {
|
else
|
||||||
tt=(Turnout *)calloc(1,sizeof(Turnout));
|
pp->_nextTurnout = tt->_nextTurnout;
|
||||||
if (!tt) return (tt);
|
|
||||||
tt->nextTurnout=firstTurnout;
|
delete (ServoTurnout *)tt;
|
||||||
firstTurnout=tt;
|
|
||||||
tt->data.id=id;
|
turnoutlistHash++;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
turnoutlistHash++;
|
|
||||||
return tt;
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
/*
|
||||||
// Object method to print debug info about the state of a Turnout object
|
* Public static functions
|
||||||
//
|
*/
|
||||||
|
|
||||||
|
bool Turnout::isClosed(uint16_t id) {
|
||||||
|
Turnout *tt = get(id);
|
||||||
|
if (tt)
|
||||||
|
return tt->isClosed();
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static setClosed 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 virtual function setClosedInternal(bool) which is
|
||||||
|
// called from here.
|
||||||
|
bool Turnout::setClosed(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->setClosedInternal(closeFlag);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
// Write byte containing 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 > 0)
|
||||||
|
EEPROM.put(tt->_eepromAddress, *((uint8_t *) &tt->_turnoutData));
|
||||||
|
|
||||||
|
#if defined(RMFT_ACTIVE)
|
||||||
|
RMFT2::turnoutEvent(id, closeFlag);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Send message to JMRI etc. over Serial USB. This is done here
|
||||||
|
// to ensure that the message is sent when the turnout operation
|
||||||
|
// is not initiated by a Serial command.
|
||||||
|
printState(id, &Serial);
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load all turnout objects
|
||||||
|
void Turnout::load() {
|
||||||
|
for (uint16_t i=0; i<EEStore::eeStore->data.nTurnouts; i++) {
|
||||||
|
Turnout::loadTurnout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save all turnout objects
|
||||||
|
void Turnout::store() {
|
||||||
|
EEStore::eeStore->data.nTurnouts=0;
|
||||||
|
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout) {
|
||||||
|
tt->save();
|
||||||
|
EEStore::eeStore->data.nTurnouts++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load one turnout from EEPROM
|
||||||
|
Turnout *Turnout::loadTurnout () {
|
||||||
|
Turnout *tt = 0;
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
switch (turnoutData.turnoutType) {
|
||||||
|
case TURNOUT_SERVO:
|
||||||
|
// Servo turnout
|
||||||
|
tt = ServoTurnout::load(&turnoutData);
|
||||||
|
break;
|
||||||
|
case TURNOUT_DCC:
|
||||||
|
// DCC Accessory turnout
|
||||||
|
tt = DCCTurnout::load(&turnoutData);
|
||||||
|
break;
|
||||||
|
case TURNOUT_VPIN:
|
||||||
|
// VPIN turnout
|
||||||
|
tt = VpinTurnout::load(&turnoutData);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// If we find anything else, then we don't know what it is or how long it is,
|
||||||
|
// so we can't go any further through the EEPROM!
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (tt) {
|
||||||
|
// Save EEPROM address in object. Note that LCN turnouts always have eepromAddress of zero.
|
||||||
|
tt->_eepromAddress = eepromAddress;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef EESTOREDEBUG
|
#ifdef EESTOREDEBUG
|
||||||
void Turnout::print(Turnout *tt) {
|
printAll(&Serial);
|
||||||
tt->print(StringFormatter::diagSerial);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
return tt;
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
// Display, on the specified stream, the current state of the turnout (1 or 0).
|
||||||
Turnout *Turnout::firstTurnout=NULL;
|
void Turnout::printState(uint16_t id, Print *stream) {
|
||||||
int Turnout::turnoutlistHash=0; //bump on every change so clients know when to refresh their lists
|
Turnout *tt = get(id);
|
||||||
|
if (!tt) tt->printState(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
575
Turnouts.h
575
Turnouts.h
|
@ -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,488 @@
|
||||||
* 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
|
||||||
const byte STATUS_ACTIVE=0x80; // Flag as activated in tStatus field
|
enum {
|
||||||
const byte STATUS_TYPE = 0x7f; // Mask for turnout type in tStatus field
|
TURNOUT_DCC = 1,
|
||||||
|
TURNOUT_SERVO = 2,
|
||||||
// The struct 'header' is used to determine the length of the
|
TURNOUT_VPIN = 3,
|
||||||
// overlaid data so must be at least as long as the anonymous fields it
|
TURNOUT_LCN = 4,
|
||||||
// is overlaid with.
|
|
||||||
struct TurnoutData {
|
|
||||||
// Header common to all turnouts
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
int id;
|
|
||||||
uint8_t tStatus;
|
|
||||||
uint8_t size;
|
|
||||||
} header;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
int id;
|
|
||||||
union {
|
|
||||||
uint8_t tStatus;
|
|
||||||
struct {
|
|
||||||
uint8_t active: 1;
|
|
||||||
uint8_t type: 5;
|
|
||||||
uint8_t :2;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
uint8_t size; // set to actual total length of used structure
|
|
||||||
};
|
|
||||||
};
|
|
||||||
// Turnout-type-specific structure elements, different length depending
|
|
||||||
// on turnout type. This allows the data to be packed efficiently
|
|
||||||
// in the EEPROM.
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
// DCC address (Address in bits 15-2, subaddress in bits 1-0
|
|
||||||
uint16_t address; // CS currently supports linear address 1-2048
|
|
||||||
// That's DCC accessory address 1-512 and subaddress 0-3.
|
|
||||||
} dccAccessoryData;
|
|
||||||
|
|
||||||
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:
|
* Turnout - Base class for turnouts.
|
||||||
static Turnout *firstTurnout;
|
*
|
||||||
static int turnoutlistHash;
|
*************************************************************************************/
|
||||||
Turnout *nextTurnout;
|
|
||||||
static Turnout* get(int);
|
|
||||||
static bool remove(int);
|
|
||||||
static bool isClosed(int);
|
|
||||||
static bool setClosed(int n, bool closed); // return false if not found.
|
|
||||||
static void setClosedStateOnly(int n, bool closed);
|
|
||||||
int getId();
|
|
||||||
static void load();
|
|
||||||
static void store();
|
|
||||||
static Turnout *createServo(int id , VPIN vpin , uint16_t activeAngle, uint16_t inactiveAngle, uint8_t profile=1, uint8_t initialState=0);
|
|
||||||
static Turnout *createVpin(int id, VPIN vpin, uint8_t initialState=0);
|
|
||||||
static Turnout *createDCC(int id, uint16_t address, uint8_t subAddress);
|
|
||||||
static Turnout *createLCN(int id, uint8_t initialState=0);
|
|
||||||
static Turnout *create(int id, int params, int16_t p[]);
|
|
||||||
static Turnout *create(int id);
|
|
||||||
static void printAll(Print *);
|
|
||||||
void print(Print *stream);
|
|
||||||
#ifdef EESTOREDEBUG
|
|
||||||
static void print(Turnout *tt);
|
|
||||||
#endif
|
|
||||||
private:
|
|
||||||
int num; // EEPROM address of tStatus in TurnoutData struct, or zero if not stored.
|
|
||||||
TurnoutData data;
|
|
||||||
static bool activate(int n, bool thrown);
|
|
||||||
static bool isActive(int);
|
|
||||||
bool isActive();
|
|
||||||
void activate(bool state);
|
|
||||||
void setActive(bool state);
|
|
||||||
}; // Turnout
|
|
||||||
|
|
||||||
|
class Turnout {
|
||||||
|
protected:
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
bool closed : 1;
|
||||||
|
bool _rfu: 2;
|
||||||
|
uint8_t turnoutType : 5;
|
||||||
|
uint16_t id;
|
||||||
|
} _turnoutData; // 3 bytes
|
||||||
|
|
||||||
|
// Address in eeprom of first byte of the _turnoutData struct (containing the closed flag).
|
||||||
|
// Set to zero if the object has not been saved in EEPROM, e.g. for newly created Turnouts, and
|
||||||
|
// for all LCN turnouts.
|
||||||
|
uint16_t _eepromAddress = 0;
|
||||||
|
|
||||||
|
// Pointer to next turnout on linked list.
|
||||||
|
Turnout *_nextTurnout = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 setClosedInternal(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;
|
||||||
|
static bool useLegacyTurnoutBehaviour;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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; }
|
||||||
|
inline void printState(Print *stream) {
|
||||||
|
StringFormatter::send(stream, F("<H %d %d>\n"),
|
||||||
|
_turnoutData.id, _turnoutData.closed ^ useLegacyTurnoutBehaviour);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Virtual functions
|
||||||
|
*/
|
||||||
|
virtual void print(Print *stream) {
|
||||||
|
(void)stream; // avoid compiler warnings.
|
||||||
|
}
|
||||||
|
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 setClosed(uint16_t id, bool closeFlag);
|
||||||
|
|
||||||
|
inline static bool setClosed(uint16_t id) {
|
||||||
|
return setClosed(id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static bool setThrown(uint16_t id) {
|
||||||
|
return setClosed(id, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
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->printState(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printState(uint16_t id, 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
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// 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 directly to specified position - we don't know where it is moving from.
|
||||||
|
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
|
||||||
|
(void)id; (void)vpin; (void)thrownPosition; (void)closedPosition;
|
||||||
|
(void)profile; (void)closed; // avoid compiler warnings.
|
||||||
|
return NULL;
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
Turnout *tt = ServoTurnout::create(turnoutData->id, servoTurnoutData.vpin, servoTurnoutData.thrownPosition,
|
||||||
|
servoTurnoutData.closedPosition, servoTurnoutData.profile, turnoutData->closed);
|
||||||
|
return tt;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ^ useLegacyTurnoutBehaviour);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// ServoTurnout-specific code for throwing or closing a servo turnout.
|
||||||
|
bool setClosedInternal(bool close) override {
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
IODevice::writeAnalogue(_servoTurnoutData.vpin,
|
||||||
|
close ? _servoTurnoutData.closedPosition : _servoTurnoutData.thrownPosition, _servoTurnoutData.profile);
|
||||||
|
_turnoutData.closed = close;
|
||||||
|
#else
|
||||||
|
(void)close; // avoid compiler warnings
|
||||||
|
#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));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*************************************************************************************
|
||||||
|
* DCCTurnout - Turnout controlled by DCC Accessory Controller.
|
||||||
|
*
|
||||||
|
*************************************************************************************/
|
||||||
|
class DCCTurnout : public Turnout {
|
||||||
|
private:
|
||||||
|
// 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
|
||||||
|
uint16_t address; // CS currently supports linear address 1-2048
|
||||||
|
// That's DCC accessory address 1-512 and subaddress 0-3.
|
||||||
|
} _dccTurnoutData; // 2 bytes
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
DCCTurnout(uint16_t id, uint16_t address, uint8_t subAdd) :
|
||||||
|
Turnout(id, TURNOUT_DCC, false)
|
||||||
|
{
|
||||||
|
_dccTurnoutData.address = ((address-1) << 2) + subAdd + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Create function
|
||||||
|
static Turnout *create(uint16_t id, uint16_t add, uint8_t subAdd) {
|
||||||
|
Turnout *tt = get(id);
|
||||||
|
if (tt) {
|
||||||
|
// Object already exists, check if it is usable
|
||||||
|
if (tt->isType(TURNOUT_DCC)) {
|
||||||
|
// Yes, so set parameters<T>
|
||||||
|
DCCTurnout *dt = (DCCTurnout *)tt;
|
||||||
|
dt->_dccTurnoutData.address = ((add-1) << 2) + subAdd + 1;
|
||||||
|
// Don't touch the _closed parameter, retain the original value.
|
||||||
|
return tt;
|
||||||
|
} else {
|
||||||
|
// Incompatible object, delete and recreate
|
||||||
|
remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tt = (Turnout *)new DCCTurnout(id, add, subAdd);
|
||||||
|
return tt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ^ useLegacyTurnoutBehaviour);
|
||||||
|
// Also report using classic DCC++ syntax for DCC accessory turnouts
|
||||||
|
StringFormatter::send(stream, F("<H %d %d %d %d>\n"), _turnoutData.id,
|
||||||
|
(((_dccTurnoutData.address-1) >> 2)+1), ((_dccTurnoutData.address-1) & 3),
|
||||||
|
_turnoutData.closed ^ useLegacyTurnoutBehaviour);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool setClosedInternal(bool close) override {
|
||||||
|
// DCC++ Classic behaviour is that Throw writes a 1 in the packet,
|
||||||
|
// and Close writes a 0.
|
||||||
|
// RCN-214 specifies that Throw is 0 and Close is 1.
|
||||||
|
DCC::setAccessory((((_dccTurnoutData.address-1) >> 2) + 1),
|
||||||
|
((_dccTurnoutData.address-1) & 3), close ^ useLegacyTurnoutBehaviour);
|
||||||
|
_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));
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************************
|
||||||
|
* 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
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
VpinTurnout(uint16_t id, VPIN vpin, bool closed=true) :
|
||||||
|
Turnout(id, TURNOUT_VPIN, closed)
|
||||||
|
{
|
||||||
|
_vpinTurnoutData.vpin = vpin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(Print *stream) override {
|
||||||
|
StringFormatter::send(stream, F("<H %d VPIN %d %d>\n"), _turnoutData.id, _vpinTurnoutData.vpin,
|
||||||
|
_turnoutData.closed ^ useLegacyTurnoutBehaviour);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool setClosedInternal(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));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************************
|
||||||
|
* 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
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
LCNTurnout(uint16_t id, bool closed=true) :
|
||||||
|
Turnout(id, TURNOUT_LCN, closed)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public:
|
||||||
|
// 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 setClosedInternal(bool close) override {
|
||||||
|
// Assume that the LCN command still uses 1 for throw and 0 for close...
|
||||||
|
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 ^ useLegacyTurnoutBehaviour);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -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]) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user