2020-07-03 18:35:02 +02:00
|
|
|
/*
|
2021-08-19 22:22:59 +02:00
|
|
|
* © 2021 Restructured Neil McKechnie
|
2020-10-03 14:07:25 +02:00
|
|
|
* © 2013-2016 Gregg E. Berman
|
2020-07-03 18:35:02 +02:00
|
|
|
* © 2020, Chris Harlow. All rights reserved.
|
2020-10-03 14:07:25 +02:00
|
|
|
* © 2020, Harald Barth.
|
2020-07-03 18:35:02 +02:00
|
|
|
*
|
|
|
|
* This file is part of Asbelos DCC API
|
|
|
|
*
|
|
|
|
* This is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* It is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2021-08-18 19:55:22 +02:00
|
|
|
|
|
|
|
|
2021-08-23 13:43:14 +02:00
|
|
|
#include "defines.h" // includes config.h
|
2020-06-03 15:26:49 +02:00
|
|
|
#include "EEStore.h"
|
2021-01-04 16:57:03 +01:00
|
|
|
#include "StringFormatter.h"
|
2021-12-05 13:08:59 +01:00
|
|
|
#include "CommandDistributor.h"
|
2021-08-03 23:12:25 +02:00
|
|
|
#include "RMFT2.h"
|
2021-08-19 22:22:59 +02:00
|
|
|
#include "Turnouts.h"
|
2021-08-22 23:25:23 +02:00
|
|
|
#include "DCC.h"
|
|
|
|
#include "LCN.h"
|
2020-10-03 14:07:25 +02:00
|
|
|
#ifdef EESTOREDEBUG
|
|
|
|
#include "DIAG.h"
|
|
|
|
#endif
|
|
|
|
|
2021-08-19 22:22:59 +02:00
|
|
|
/*
|
|
|
|
* Protected static data
|
|
|
|
*/
|
2021-08-18 19:55:22 +02:00
|
|
|
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ Turnout *Turnout::_firstTurnout = 0;
|
2021-08-18 19:55:22 +02:00
|
|
|
|
2021-08-19 22:22:59 +02:00
|
|
|
/*
|
|
|
|
* Public static data
|
|
|
|
*/
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ int Turnout::turnoutlistHash = 0;
|
2021-08-19 22:22:59 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Protected static functions
|
|
|
|
*/
|
|
|
|
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ Turnout *Turnout::get(uint16_t id) {
|
2021-08-19 22:22:59 +02:00
|
|
|
// Find turnout object from list.
|
|
|
|
for (Turnout *tt = _firstTurnout; tt != NULL; tt = tt->_nextTurnout)
|
|
|
|
if (tt->_turnoutData.id == id) return tt;
|
|
|
|
return NULL;
|
|
|
|
}
|
2021-08-03 23:12:25 +02:00
|
|
|
|
2021-08-19 22:22:59 +02:00
|
|
|
// Add new turnout to end of chain
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ void Turnout::add(Turnout *tt) {
|
2021-08-19 22:22:59 +02:00
|
|
|
if (!_firstTurnout)
|
|
|
|
_firstTurnout = tt;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
turnoutlistHash++;
|
2021-08-03 23:12:25 +02:00
|
|
|
}
|
2021-08-19 22:22:59 +02:00
|
|
|
|
2021-12-05 13:08:59 +01:00
|
|
|
|
2021-08-22 23:30:09 +02:00
|
|
|
|
2021-08-19 22:22:59 +02:00
|
|
|
// Remove nominated turnout from turnout linked list and delete the object.
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ bool Turnout::remove(uint16_t id) {
|
2021-08-19 22:22:59 +02:00
|
|
|
Turnout *tt,*pp=NULL;
|
2021-08-03 23:12:25 +02:00
|
|
|
|
2021-08-19 22:22:59 +02:00
|
|
|
for(tt=_firstTurnout; tt!=NULL && tt->_turnoutData.id!=id; pp=tt, tt=tt->_nextTurnout) {}
|
|
|
|
if (tt == NULL) return false;
|
|
|
|
|
|
|
|
if (tt == _firstTurnout)
|
|
|
|
_firstTurnout = tt->_nextTurnout;
|
|
|
|
else
|
|
|
|
pp->_nextTurnout = tt->_nextTurnout;
|
2021-08-18 19:55:22 +02:00
|
|
|
|
2021-08-19 22:22:59 +02:00
|
|
|
delete (ServoTurnout *)tt;
|
2021-08-18 19:55:22 +02:00
|
|
|
|
2021-08-19 22:22:59 +02:00
|
|
|
turnoutlistHash++;
|
|
|
|
return true;
|
|
|
|
}
|
2021-08-03 23:12:25 +02:00
|
|
|
|
2020-06-03 15:26:49 +02:00
|
|
|
|
2021-08-19 22:22:59 +02:00
|
|
|
/*
|
|
|
|
* Public static functions
|
|
|
|
*/
|
2021-08-03 23:12:25 +02:00
|
|
|
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ bool Turnout::isClosed(uint16_t id) {
|
2021-08-19 22:22:59 +02:00
|
|
|
Turnout *tt = get(id);
|
|
|
|
if (tt)
|
|
|
|
return tt->isClosed();
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
2021-08-03 23:12:25 +02:00
|
|
|
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ bool Turnout::setClosedStateOnly(uint16_t id, bool closeFlag) {
|
2021-08-22 23:25:23 +02:00
|
|
|
Turnout *tt = get(id);
|
2021-08-27 00:04:13 +02:00
|
|
|
if (!tt) return false;
|
2021-08-27 16:45:22 +02:00
|
|
|
tt->_turnoutData.closed = closeFlag;
|
|
|
|
|
|
|
|
// I know it says setClosedStateOnly, but we need to tell others
|
|
|
|
// that the state has changed too.
|
|
|
|
#if defined(RMFT_ACTIVE)
|
|
|
|
RMFT2::turnoutEvent(id, closeFlag);
|
|
|
|
#endif
|
|
|
|
|
2021-12-05 13:08:59 +01:00
|
|
|
CommandDistributor::broadcastTurnout(id, closeFlag);
|
2021-08-22 23:25:23 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-08-22 15:07:16 +02:00
|
|
|
// Static setClosed function is invoked from close(), throw() etc. to perform the
|
2021-08-19 22:22:59 +02:00
|
|
|
// common parts of the turnout operation. Code which is specific to a turnout
|
2021-08-22 15:07:16 +02:00
|
|
|
// type should be placed in the virtual function setClosedInternal(bool) which is
|
2021-08-19 22:22:59 +02:00
|
|
|
// called from here.
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ bool Turnout::setClosed(uint16_t id, bool closeFlag) {
|
2021-08-27 22:47:48 +02:00
|
|
|
#if defined(DIAG_IO)
|
2021-08-19 22:22:59 +02:00
|
|
|
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;
|
2021-08-22 15:07:16 +02:00
|
|
|
bool ok = tt->setClosedInternal(closeFlag);
|
2021-08-19 22:22:59 +02:00
|
|
|
|
2021-08-22 00:16:52 +02:00
|
|
|
if (ok) {
|
2021-09-06 13:30:25 +02:00
|
|
|
turnoutlistHash++; // let withrottle know something changed
|
|
|
|
|
2021-08-22 00:16:52 +02:00
|
|
|
// 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)
|
2021-08-23 18:36:50 +02:00
|
|
|
EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags);
|
2021-08-22 00:16:52 +02:00
|
|
|
|
|
|
|
#if defined(RMFT_ACTIVE)
|
|
|
|
RMFT2::turnoutEvent(id, closeFlag);
|
|
|
|
#endif
|
|
|
|
|
2021-12-05 13:08:59 +01:00
|
|
|
// Send message to JMRI etc.
|
|
|
|
CommandDistributor::broadcastTurnout(id, closeFlag);
|
2021-08-22 00:16:52 +02:00
|
|
|
}
|
2021-08-19 22:22:59 +02:00
|
|
|
return ok;
|
|
|
|
}
|
2021-08-03 23:12:25 +02:00
|
|
|
|
2021-08-19 22:22:59 +02:00
|
|
|
// Load all turnout objects
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ void Turnout::load() {
|
2021-08-19 22:22:59 +02:00
|
|
|
for (uint16_t i=0; i<EEStore::eeStore->data.nTurnouts; i++) {
|
|
|
|
Turnout::loadTurnout();
|
|
|
|
}
|
|
|
|
}
|
2021-08-03 23:12:25 +02:00
|
|
|
|
2021-08-19 22:22:59 +02:00
|
|
|
// Save all turnout objects
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ void Turnout::store() {
|
2021-08-19 22:22:59 +02:00
|
|
|
EEStore::eeStore->data.nTurnouts=0;
|
|
|
|
for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout) {
|
|
|
|
tt->save();
|
|
|
|
EEStore::eeStore->data.nTurnouts++;
|
|
|
|
}
|
|
|
|
}
|
2020-07-23 18:34:35 +02:00
|
|
|
|
2021-08-19 22:22:59 +02:00
|
|
|
// Load one turnout from EEPROM
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ Turnout *Turnout::loadTurnout () {
|
2021-08-19 22:43:55 +02:00
|
|
|
Turnout *tt = 0;
|
2021-08-19 22:22:59 +02:00
|
|
|
// Read turnout type from EEPROM
|
|
|
|
struct TurnoutData turnoutData;
|
2021-08-23 18:36:50 +02:00
|
|
|
int eepromAddress = EEStore::pointer() + offsetof(struct TurnoutData, flags); // Address of byte containing the closed flag.
|
2021-08-19 22:22:59 +02:00
|
|
|
EEPROM.get(EEStore::pointer(), turnoutData);
|
|
|
|
EEStore::advance(sizeof(turnoutData));
|
2021-08-03 23:12:25 +02:00
|
|
|
|
2021-08-19 22:22:59 +02:00
|
|
|
switch (turnoutData.turnoutType) {
|
2021-08-03 23:12:25 +02:00
|
|
|
case TURNOUT_SERVO:
|
2021-08-19 22:22:59 +02:00
|
|
|
// Servo turnout
|
|
|
|
tt = ServoTurnout::load(&turnoutData);
|
2021-08-18 00:41:34 +02:00
|
|
|
break;
|
2021-08-03 23:12:25 +02:00
|
|
|
case TURNOUT_DCC:
|
2021-08-19 22:22:59 +02:00
|
|
|
// DCC Accessory turnout
|
|
|
|
tt = DCCTurnout::load(&turnoutData);
|
2021-08-03 23:12:25 +02:00
|
|
|
break;
|
|
|
|
case TURNOUT_VPIN:
|
2021-08-19 22:22:59 +02:00
|
|
|
// VPIN turnout
|
|
|
|
tt = VpinTurnout::load(&turnoutData);
|
2021-08-03 23:12:25 +02:00
|
|
|
break;
|
2021-08-19 22:43:55 +02:00
|
|
|
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;
|
2021-08-03 23:12:25 +02:00
|
|
|
}
|
2021-08-22 00:16:52 +02:00
|
|
|
if (tt) {
|
2021-08-19 22:22:59 +02:00
|
|
|
// Save EEPROM address in object. Note that LCN turnouts always have eepromAddress of zero.
|
2021-08-23 18:36:50 +02:00
|
|
|
tt->_eepromAddress = eepromAddress + offsetof(struct TurnoutData, flags);
|
2021-08-03 23:12:25 +02:00
|
|
|
}
|
|
|
|
|
2020-10-03 14:07:25 +02:00
|
|
|
#ifdef EESTOREDEBUG
|
2021-08-19 22:22:59 +02:00
|
|
|
printAll(&Serial);
|
2020-10-03 14:07:25 +02:00
|
|
|
#endif
|
2021-08-19 22:22:59 +02:00
|
|
|
return tt;
|
|
|
|
}
|
2020-06-03 15:26:49 +02:00
|
|
|
|
2021-08-22 23:25:23 +02:00
|
|
|
|
|
|
|
/*************************************************************************************
|
|
|
|
* ServoTurnout - Turnout controlled by servo device.
|
|
|
|
*
|
|
|
|
*************************************************************************************/
|
|
|
|
|
|
|
|
// Private Constructor
|
|
|
|
ServoTurnout::ServoTurnout(uint16_t id, VPIN vpin, uint16_t thrownPosition, uint16_t closedPosition, uint8_t profile, bool closed) :
|
|
|
|
Turnout(id, TURNOUT_SERVO, closed)
|
|
|
|
{
|
|
|
|
_servoTurnoutData.vpin = vpin;
|
|
|
|
_servoTurnoutData.thrownPosition = thrownPosition;
|
|
|
|
_servoTurnoutData.closedPosition = closedPosition;
|
|
|
|
_servoTurnoutData.profile = profile;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create function
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ Turnout *ServoTurnout::create(uint16_t id, VPIN vpin, uint16_t thrownPosition, uint16_t closedPosition, uint8_t profile, bool closed) {
|
2021-08-22 23:25:23 +02:00
|
|
|
#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
|
Make defaults for PWM (servo) positions 0 (PWM off) if not configured.
When writing to a PWM device (servo or LED for example), it is possible to request the target position in the call, or to ask for a SET or RESET position. In the latter case, the positions corresponding to SET and RESET must be known, i.e. preconfigured. Defaults were assigned for this, but because the correct values will depend on the hardware device being driven, the defaults have been removed.
In addition, the <T> command, when defining a servo turnout, now configures the PWM positions (not required by <T> commands, but desirable for consistency with other commands).
2021-08-29 13:04:13 +02:00
|
|
|
// will provide all the data that is required! However, if someone has configured
|
|
|
|
// a Turnout, we should ensure that the SET() RESET() and other commands that use write()
|
|
|
|
// behave consistently with the turnout commands.
|
|
|
|
IODevice::configureServo(vpin, thrownPosition, closedPosition, profile, 0, closed);
|
2021-08-22 23:25:23 +02:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load a Servo turnout definition from EEPROM. The common Turnout data has already been read at this point.
|
|
|
|
Turnout *ServoTurnout::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;
|
|
|
|
}
|
|
|
|
|
2021-08-24 23:18:51 +02:00
|
|
|
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed
|
2021-08-22 23:25:23 +02:00
|
|
|
void ServoTurnout::print(Print *stream) {
|
|
|
|
StringFormatter::send(stream, F("<H %d SERVO %d %d %d %d %d>\n"), _turnoutData.id, _servoTurnoutData.vpin,
|
|
|
|
_servoTurnoutData.thrownPosition, _servoTurnoutData.closedPosition, _servoTurnoutData.profile,
|
2021-08-24 23:18:51 +02:00
|
|
|
!_turnoutData.closed);
|
2021-08-22 23:25:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// ServoTurnout-specific code for throwing or closing a servo turnout.
|
|
|
|
bool ServoTurnout::setClosedInternal(bool close) {
|
|
|
|
#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 ServoTurnout::save() {
|
|
|
|
// 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.
|
|
|
|
*
|
|
|
|
*************************************************************************************/
|
|
|
|
|
2021-08-23 13:43:14 +02:00
|
|
|
#if defined(DCC_TURNOUTS_RCN_213)
|
|
|
|
const bool DCCTurnout::rcn213Compliant = true;
|
|
|
|
#else
|
|
|
|
const bool DCCTurnout::rcn213Compliant = false;
|
|
|
|
#endif
|
|
|
|
|
2021-08-22 23:25:23 +02:00
|
|
|
// 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::DCCTurnout(uint16_t id, uint16_t address, uint8_t subAdd) :
|
|
|
|
Turnout(id, TURNOUT_DCC, false)
|
|
|
|
{
|
2021-09-22 11:38:11 +02:00
|
|
|
_dccTurnoutData.address = address;
|
|
|
|
_dccTurnoutData.subAddress = subAdd;
|
2021-08-22 23:25:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create function
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ Turnout *DCCTurnout::create(uint16_t id, uint16_t add, uint8_t subAdd) {
|
2021-08-22 23:25:23 +02:00
|
|
|
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;
|
2021-09-22 11:38:11 +02:00
|
|
|
dt->_dccTurnoutData.address = add;
|
|
|
|
dt->_dccTurnoutData.subAddress = subAdd;
|
2021-08-22 23:25:23 +02:00
|
|
|
// 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.
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ Turnout *DCCTurnout::load(struct TurnoutData *turnoutData) {
|
2021-08-22 23:25:23 +02:00
|
|
|
DCCTurnoutData dccTurnoutData;
|
|
|
|
// Read class-specific data from EEPROM
|
|
|
|
EEPROM.get(EEStore::pointer(), dccTurnoutData);
|
|
|
|
EEStore::advance(sizeof(dccTurnoutData));
|
|
|
|
|
|
|
|
// Create new object
|
2021-09-22 11:38:11 +02:00
|
|
|
DCCTurnout *tt = new DCCTurnout(turnoutData->id, dccTurnoutData.address, dccTurnoutData.subAddress);
|
2021-08-22 23:25:23 +02:00
|
|
|
|
|
|
|
return tt;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DCCTurnout::print(Print *stream) {
|
|
|
|
StringFormatter::send(stream, F("<H %d DCC %d %d %d>\n"), _turnoutData.id,
|
2021-09-22 11:38:11 +02:00
|
|
|
_dccTurnoutData.address, _dccTurnoutData.subAddress, !_turnoutData.closed);
|
2021-08-23 13:43:14 +02:00
|
|
|
// Also report using classic DCC++ syntax for DCC accessory turnouts, since JMRI expects this.
|
2021-08-22 23:25:23 +02:00
|
|
|
StringFormatter::send(stream, F("<H %d %d %d %d>\n"), _turnoutData.id,
|
2021-09-22 11:38:11 +02:00
|
|
|
_dccTurnoutData.address, _dccTurnoutData.subAddress, !_turnoutData.closed);
|
2021-08-22 23:25:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool DCCTurnout::setClosedInternal(bool close) {
|
|
|
|
// DCC++ Classic behaviour is that Throw writes a 1 in the packet,
|
|
|
|
// and Close writes a 0.
|
2021-08-23 13:43:14 +02:00
|
|
|
// RCN-213 specifies that Throw is 0 and Close is 1.
|
2021-09-22 11:38:11 +02:00
|
|
|
DCC::setAccessory(_dccTurnoutData.address, _dccTurnoutData.subAddress, close ^ !rcn213Compliant);
|
2021-08-22 23:25:23 +02:00
|
|
|
_turnoutData.closed = close;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DCCTurnout::save() {
|
|
|
|
// 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.
|
|
|
|
*
|
|
|
|
*************************************************************************************/
|
|
|
|
|
|
|
|
// Constructor
|
|
|
|
VpinTurnout::VpinTurnout(uint16_t id, VPIN vpin, bool closed) :
|
|
|
|
Turnout(id, TURNOUT_VPIN, closed)
|
|
|
|
{
|
|
|
|
_vpinTurnoutData.vpin = vpin;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create function
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ Turnout *VpinTurnout::create(uint16_t id, VPIN vpin, bool closed) {
|
2021-08-22 23:25:23 +02:00
|
|
|
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.
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ Turnout *VpinTurnout::load(struct TurnoutData *turnoutData) {
|
2021-08-22 23:25:23 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-08-24 23:18:51 +02:00
|
|
|
// Report 1 for thrown, 0 for closed.
|
2021-08-22 23:25:23 +02:00
|
|
|
void VpinTurnout::print(Print *stream) {
|
|
|
|
StringFormatter::send(stream, F("<H %d VPIN %d %d>\n"), _turnoutData.id, _vpinTurnoutData.vpin,
|
2021-08-24 23:18:51 +02:00
|
|
|
!_turnoutData.closed);
|
2021-08-22 23:25:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool VpinTurnout::setClosedInternal(bool close) {
|
|
|
|
IODevice::write(_vpinTurnoutData.vpin, close);
|
|
|
|
_turnoutData.closed = close;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VpinTurnout::save() {
|
|
|
|
// 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
|
|
|
|
*
|
|
|
|
*************************************************************************************/
|
|
|
|
|
|
|
|
// LCNTurnout has no specific data, and in any case is not written to EEPROM!
|
|
|
|
// struct LCNTurnoutData {
|
|
|
|
// } _lcnTurnoutData; // 0 bytes
|
|
|
|
|
|
|
|
// Constructor
|
|
|
|
LCNTurnout::LCNTurnout(uint16_t id, bool closed) :
|
|
|
|
Turnout(id, TURNOUT_LCN, closed)
|
|
|
|
{ }
|
|
|
|
|
|
|
|
// Create function
|
2021-08-27 16:45:22 +02:00
|
|
|
/* static */ Turnout *LCNTurnout::create(uint16_t id, bool closed) {
|
2021-08-22 23:25:23 +02:00
|
|
|
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 LCNTurnout::setClosedInternal(bool close) {
|
|
|
|
// 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) {
|
|
|
|
|
2021-08-24 23:18:51 +02:00
|
|
|
// Report 1 for thrown, 0 for closed.
|
2021-08-22 23:25:23 +02:00
|
|
|
void LCNTurnout::print(Print *stream) {
|
|
|
|
StringFormatter::send(stream, F("<H %d LCN %d>\n"), _turnoutData.id,
|
2021-08-24 23:18:51 +02:00
|
|
|
!_turnoutData.closed);
|
2021-08-22 23:25:23 +02:00
|
|
|
}
|
|
|
|
|