2020-07-03 18:35:02 +02:00
|
|
|
/*
|
2022-01-07 02:28:35 +01:00
|
|
|
* © 2021 Neil McKechnie
|
|
|
|
* © 2021 M Steve Todd
|
|
|
|
* © 2021 Fred Decker
|
|
|
|
* © 2020-2021 Harald Barth
|
|
|
|
* © 2020-2021 Chris Harlow
|
2020-10-03 14:07:25 +02:00
|
|
|
* © 2013-2016 Gregg E. Berman
|
2022-01-07 02:28:35 +01:00
|
|
|
* All rights reserved.
|
2020-07-03 18:35:02 +02:00
|
|
|
*
|
2022-01-07 02:28:35 +01:00
|
|
|
* This file is part of CommandStation-EX
|
2020-07-03 18:35:02 +02:00
|
|
|
*
|
|
|
|
* 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
|
2021-11-08 02:07:21 +01:00
|
|
|
#ifndef DISABLE_EEPROM
|
2020-06-03 15:26:49 +02:00
|
|
|
#include "EEStore.h"
|
2021-11-08 02:07:21 +01:00
|
|
|
#endif
|
2021-01-04 16:57:03 +01:00
|
|
|
#include "StringFormatter.h"
|
2021-12-05 13:08:59 +01:00
|
|
|
#include "CommandDistributor.h"
|
2022-01-30 18:31:26 +01:00
|
|
|
#include "EXRAIL2.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
|
|
|
// I know it says setClosedStateOnly, but we need to tell others
|
2023-03-25 12:14:58 +01:00
|
|
|
// that the state has changed too. But we only broadcast if there
|
|
|
|
// really has been a change.
|
|
|
|
if (tt->_turnoutData.closed != closeFlag) {
|
|
|
|
tt->_turnoutData.closed = closeFlag;
|
|
|
|
CommandDistributor::broadcastTurnout(id, closeFlag);
|
|
|
|
}
|
|
|
|
#if defined(EXRAIL_ACTIVE)
|
|
|
|
RMFT2::turnoutEvent(id, closeFlag);
|
|
|
|
#endif
|
2021-08-22 23:25:23 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-03-25 19:28:37 +01:00
|
|
|
#define DIAG_IO
|
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) {
|
2023-03-25 12:14:58 +01:00
|
|
|
#if defined(DIAG_IO)
|
|
|
|
DIAG(F("Turnout(%d,%c)"), id, closeFlag ? 'c':'t');
|
|
|
|
#endif
|
2021-08-19 22:22:59 +02:00
|
|
|
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) {
|
2023-03-25 12:14:58 +01:00
|
|
|
tt->setClosedStateOnly(id, closeFlag);
|
2021-11-08 02:07:21 +01:00
|
|
|
#ifndef DISABLE_EEPROM
|
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-11-08 02:07:21 +01:00
|
|
|
EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags);
|
|
|
|
#endif
|
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-11-08 02:07:21 +01:00
|
|
|
#ifndef DISABLE_EEPROM
|
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;
|
2023-11-07 03:54:25 +01:00
|
|
|
case TURNOUT_HBRIDGE:
|
|
|
|
// HBRIDGE turnout
|
|
|
|
tt = HBridgeTurnout::load(&turnoutData);
|
|
|
|
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
|
2023-02-15 01:51:21 +01:00
|
|
|
printAll(&USB_SERIAL);
|
2020-10-03 14:07:25 +02:00
|
|
|
#endif
|
2021-08-19 22:22:59 +02:00
|
|
|
return tt;
|
|
|
|
}
|
2021-11-08 02:07:21 +01:00
|
|
|
#endif
|
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);
|
2023-05-07 23:58:47 +02:00
|
|
|
DIAG(F("Turnout 0x%x size %d size %d"), tt, sizeof(Turnout),sizeof(struct TurnoutData));
|
2021-08-22 23:25:23 +02:00
|
|
|
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) {
|
2021-11-08 02:07:21 +01:00
|
|
|
#ifndef DISABLE_EEPROM
|
2021-08-22 23:25:23 +02:00
|
|
|
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-11-08 02:07:21 +01:00
|
|
|
#else
|
|
|
|
(void)turnoutData;
|
|
|
|
return NULL;
|
|
|
|
#endif
|
2021-08-22 23:25:23 +02:00
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
#else
|
|
|
|
(void)close; // avoid compiler warnings
|
|
|
|
#endif
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ServoTurnout::save() {
|
2021-11-08 02:07:21 +01:00
|
|
|
#ifndef DISABLE_EEPROM
|
2021-08-22 23:25:23 +02:00
|
|
|
// 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));
|
2021-11-08 02:07:21 +01:00
|
|
|
#endif
|
2021-08-22 23:25:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************************
|
|
|
|
* 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-11-08 02:07:21 +01:00
|
|
|
#ifndef DISABLE_EEPROM
|
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;
|
2021-11-08 02:07:21 +01:00
|
|
|
#else
|
|
|
|
(void)turnoutData;
|
|
|
|
return NULL;
|
|
|
|
#endif
|
2021-08-22 23:25:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DCCTurnout::save() {
|
2021-11-08 02:07:21 +01:00
|
|
|
#ifndef DISABLE_EEPROM
|
2021-08-22 23:25:23 +02:00
|
|
|
// 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));
|
2021-11-08 02:07:21 +01:00
|
|
|
#endif
|
2021-08-22 23:25:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*************************************************************************************
|
|
|
|
* 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-11-08 02:07:21 +01:00
|
|
|
#ifndef DISABLE_EEPROM
|
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-11-08 02:07:21 +01:00
|
|
|
#else
|
|
|
|
(void)turnoutData;
|
|
|
|
return NULL;
|
|
|
|
#endif
|
2021-08-22 23:25:23 +02:00
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VpinTurnout::save() {
|
2021-11-08 02:07:21 +01:00
|
|
|
#ifndef DISABLE_EEPROM
|
2021-08-22 23:25:23 +02:00
|
|
|
// 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));
|
2021-11-08 02:07:21 +01:00
|
|
|
#endif
|
2021-08-22 23:25:23 +02:00
|
|
|
}
|
|
|
|
|
2023-11-07 03:54:25 +01:00
|
|
|
/*************************************************************************************
|
|
|
|
* HBridgeTurnout - Turnout controlled through a pair of HAL pins.
|
|
|
|
* Typically connected to Motor H-Bridge. Delay is used to quickly turn on/off power.
|
|
|
|
*************************************************************************************/
|
|
|
|
|
|
|
|
// Constructor
|
|
|
|
HBridgeTurnout::HBridgeTurnout(uint16_t id, VPIN pin1, VPIN pin2, uint16_t millisDelay, bool closed) :
|
|
|
|
Turnout(id, TURNOUT_HBRIDGE, closed)
|
|
|
|
{
|
|
|
|
_hbridgeTurnoutData.pin1 = pin1;
|
|
|
|
_hbridgeTurnoutData.pin2 = pin2;
|
|
|
|
_hbridgeTurnoutData.millisDelay = millisDelay;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create function
|
|
|
|
/* static */ Turnout *HBridgeTurnout::create(uint16_t id, VPIN pin1, VPIN pin2, uint16_t millisDelay, bool closed) {
|
|
|
|
Turnout *tt = get(id);
|
|
|
|
if (tt) {
|
|
|
|
// Object already exists, check if it is usable
|
|
|
|
if (tt->isType(TURNOUT_HBRIDGE)) {
|
|
|
|
// Yes, so set parameters
|
|
|
|
HBridgeTurnout *hbt = (HBridgeTurnout *)tt;
|
|
|
|
hbt->_hbridgeTurnoutData.pin1 = pin1;
|
|
|
|
hbt->_hbridgeTurnoutData.pin2 = pin2;
|
|
|
|
hbt->_hbridgeTurnoutData.millisDelay = millisDelay;
|
|
|
|
// Don't touch the _closed parameter, retain the original value.
|
|
|
|
return tt;
|
|
|
|
} else {
|
|
|
|
// Incompatible object, delete and recreate
|
|
|
|
remove(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tt = (Turnout *)new HBridgeTurnout(id, pin1, pin2, millisDelay, closed);
|
|
|
|
return tt;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load a VPIN turnout definition from EEPROM. The common Turnout data has already been read at this point.
|
|
|
|
/* static */ Turnout *HBridgeTurnout::load(struct TurnoutData *turnoutData) {
|
|
|
|
#ifndef DISABLE_EEPROM
|
|
|
|
HBridgeTurnoutData hbridgeTurnoutData;
|
|
|
|
// Read class-specific data from EEPROM
|
|
|
|
EEPROM.get(EEStore::pointer(), hbridgeTurnoutData);
|
|
|
|
EEStore::advance(sizeof(hbridgeTurnoutData));
|
|
|
|
|
|
|
|
// Create new object
|
|
|
|
HBridgeTurnout *tt = new HBridgeTurnout(turnoutData->id, hbridgeTurnoutData.pin1,
|
|
|
|
hbridgeTurnoutData.pin2, hbridgeTurnoutData.millisDelay, turnoutData->closed);
|
|
|
|
|
|
|
|
return tt;
|
|
|
|
#else
|
|
|
|
(void)turnoutData;
|
|
|
|
return NULL;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// Report 1 for thrown, 0 for closed.
|
|
|
|
void HBridgeTurnout::print(Print *stream) {
|
|
|
|
StringFormatter::send(stream, F("<H %d HBRIDGE %d %d %d>\n"), _turnoutData.id, _hbridgeTurnoutData.pin1, _hbridgeTurnoutData.pin2,
|
|
|
|
!_turnoutData.closed);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HBridgeTurnout::turnUpDown(VPIN pin) {
|
|
|
|
// HBridge turnouts require very small, prescribed time to keep pin1 or pin2 in HIGH state.
|
|
|
|
// Otherwise internal coil of the turnout will burn.
|
|
|
|
IODevice::write(pin, HIGH);
|
|
|
|
// HARD LIMIT to maximum 0.5 second to avoid burning the coil
|
|
|
|
delay(min(_hbridgeTurnoutData.millisDelay, 500));
|
|
|
|
IODevice::write(pin, LOW);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HBridgeTurnout::setClosedInternal(bool close) {
|
|
|
|
turnUpDown(close ? _hbridgeTurnoutData.pin2 : _hbridgeTurnoutData.pin1);
|
|
|
|
_turnoutData.closed = close;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HBridgeTurnout::save() {
|
2023-11-07 04:29:41 +01:00
|
|
|
#ifndef DISABLE_EEPROM
|
2023-11-07 03:54:25 +01:00
|
|
|
// 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(), _hbridgeTurnoutData);
|
|
|
|
EEStore::advance(sizeof(_hbridgeTurnoutData));
|
|
|
|
#endif
|
|
|
|
}
|
2021-08-22 23:25:23 +02:00
|
|
|
|
|
|
|
/*************************************************************************************
|
|
|
|
* 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);
|
2023-03-25 19:28:37 +01:00
|
|
|
// The _turnoutData.closed flag should be updated by a message from the LCN master.
|
|
|
|
// but in this implementation it is updated in setClosedStateOnly() instead.
|
|
|
|
// If the LCN master updates this, setClosedStateOnly() and all setClosedInternal()
|
|
|
|
// have to be updated accordingly so that the closed flag is only set once.
|
2021-08-22 23:25:23 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|