diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 816cf85..2c50ac1 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -56,7 +56,10 @@ const int16_t HASH_KEYWORD_LCN = 15137; const int16_t HASH_KEYWORD_RESET = 26133; const int16_t HASH_KEYWORD_SPEED28 = -17064; 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]; bool DCCEXParser::stashBusy; @@ -658,7 +661,7 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[]) case 0: // list turnout definitions { 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; tt->print(stream); @@ -672,17 +675,65 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[]) StringFormatter::send(stream, F("\n")); return true; - case 2: // turnout 0=CLOSE,1=THROW - if (p[1]>1 || p[1]<0 ) return false; - if (!Turnout::setClosed(p[0],p[1]==0)) return false; - StringFormatter::send(stream, F("\n"), p[0], p[1]); - return true; + case 2: // + { + bool state = false; + switch (p[1]) { + // 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. - if (!Turnout::create(p[0], params-1, &p[1])) - return false; - StringFormatter::send(stream, F("\n")); - return true; + // Send acknowledgement to caller if the command was not received over Serial + // (acknowledgement messages on Serial are sent by the Turnout class). + if (stream != &Serial) Turnout::printState(p[0], stream); + return true; + } + + default: // Anything else is some kind of turnout create function. + if (params == 6 && p[1] == HASH_KEYWORD_SERVO) { // + 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) { // + 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) { // + if (!DCCTurnout::create(p[0], p[2], p[3])) return false; + } else if (params==3 && p[2]>0 && p[2]<=512*4) { // , 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 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 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("\n")); + return true; } } @@ -797,7 +848,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) StringFormatter::send(stream, F("128 Speedsteps")); return true; - case HASH_KEYWORD_SERVO: + case HASH_KEYWORD_SERVO: // IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0); break; diff --git a/EEStore.h b/EEStore.h index 247e30a..8fc98bd 100644 --- a/EEStore.h +++ b/EEStore.h @@ -29,7 +29,7 @@ extern ExternalEEPROM EEPROM; #include #endif -#define EESTORE_ID "DCC++0" +#define EESTORE_ID "DCC++1" struct EEStoreData{ char id[sizeof(EESTORE_ID)]; diff --git a/IODevice.h b/IODevice.h index a542f56..eaffec8 100644 --- a/IODevice.h +++ b/IODevice.h @@ -274,7 +274,7 @@ private: uint8_t profile; // Config parameter uint8_t stepNumber; // Index of current step (starting from 0) 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 struct ServoData *_servoData [16]; diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index 1c24335..9e97234 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -53,19 +53,20 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i int8_t pin = vpin - _firstVpin; struct ServoData *s = _servoData[pin]; - if (!s) { + if (s == NULL) { _servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData)); s = _servoData[pin]; if (!s) return false; // Check for failed memory allocation } s->activePosition = params[0]; - s->currentPosition = s->inactivePosition = params[1]; + s->inactivePosition = params[1]; s->profile = params[2]; - - // Position servo to initial state - s->state = -1; // Set unknown state, to force reposition - _write(vpin, params[3]); + int state = params[3]; + if (state != -1) { + // Position servo to initial state + _writeAnalogue(vpin, state ? s->activePosition : s->inactivePosition, Instant); + } return true; } @@ -75,6 +76,8 @@ PCA9685::PCA9685(VPIN firstVpin, int nPins, uint8_t I2CAddress) { _firstVpin = firstVpin; _nPins = min(nPins, 16); _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++) _servoData[i] = NULL; @@ -113,30 +116,12 @@ void PCA9685::_write(VPIN vpin, int value) { if (value) value = 1; struct ServoData *s = _servoData[pin]; - if (!s) { + if (s == NULL) { // Pin not configured, just write default positions to servo controller - if (value) - writeDevice(pin, _defaultActivePosition); - else - writeDevice(pin, _defaultInactivePosition); + writeDevice(pin, value ? _defaultActivePosition : _defaultInactivePosition); } else { // Use configured parameters for advanced transitions - uint8_t profile = 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; + _writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile); } } @@ -150,16 +135,18 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, int profile) { else if (value < 0) value = 0; struct ServoData *s = _servoData[pin]; - - if (!s) { - // Servo pin not configured, so configure now. + if (s == NULL) { + // Servo pin not configured, so configure now using defaults s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1); + if (s == NULL) return; // Check for memory allocation failure s->activePosition = _defaultActivePosition; 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. + s->currentProfile = profile; s->numSteps = profile==Fast ? 10 : profile==Medium ? 20 : profile==Slow ? 40 : @@ -175,7 +162,7 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, int profile) { bool PCA9685::_isActive(VPIN vpin) { int pin = vpin - _firstVpin; struct ServoData *s = _servoData[pin]; - if (!s) + if (s == NULL) return false; // No structure means no animation! else 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. void PCA9685::updatePosition(uint8_t 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->stepNumber == 0 && s->fromPosition == s->toPosition) { - // No movement required, so go straight to final step - s->stepNumber = s->numSteps; + // Go straight to end of sequence, output final position. + s->stepNumber = s->numSteps-1; } if (s->stepNumber < s->numSteps) { // Animation in progress, reposition servo s->stepNumber++; - if (s->profile == Bounce) { + if (s->currentProfile == Bounce) { // Retrieve step positions from array in flash byte profileValue = GETFLASH(&_bounceProfile[s->stepNumber]); s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition); diff --git a/LCN.cpp b/LCN.cpp index e9d0886..16b3f3f 100644 --- a/LCN.cpp +++ b/LCN.cpp @@ -48,8 +48,7 @@ void LCN::loop() { } else if (ch == 't' || ch == 'T') { // Turnout opcodes if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch); - Turnout * tt = Turnout::get(id); - if (!tt) tt=Turnout::createLCN(id); + if (!Turnout::exists(id)) LCNTurnout::create(id); Turnout::setClosedStateOnly(id,ch=='t'); Turnout::turnoutlistHash++; // signals ED update of turnout data id = 0; diff --git a/RMFT2.cpp b/RMFT2.cpp index d3ffd75..1ccedf9 100644 --- a/RMFT2.cpp +++ b/RMFT2.cpp @@ -77,7 +77,7 @@ byte RMFT2::flags[MAX_FLAGS]; VPIN id=GET_OPERAND(0); int addr=GET_OPERAND(1); byte subAddr=GET_OPERAND(2); - Turnout::createDCC(id,addr,subAddr); + DCCTurnout::create(id,addr,subAddr); continue; } @@ -87,14 +87,14 @@ byte RMFT2::flags[MAX_FLAGS]; int activeAngle=GET_OPERAND(2); int inactiveAngle=GET_OPERAND(3); int profile=GET_OPERAND(4); - Turnout::createServo(id,pin,activeAngle,inactiveAngle,profile); + ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile); continue; } if (opcode==OPCODE_PINTURNOUT) { int16_t id=GET_OPERAND(0); VPIN pin=GET_OPERAND(1); - Turnout::createVpin(id,pin); + VpinTurnout::create(id,pin); continue; } // other opcodes are not needed on this pass diff --git a/Turnouts.cpp b/Turnouts.cpp index c18337b..22bc175 100644 --- a/Turnouts.cpp +++ b/Turnouts.cpp @@ -1,4 +1,5 @@ /* + * © 2021 Restructured Neil McKechnie * © 2013-2016 Gregg E. Berman * © 2020, Chris Harlow. All rights reserved. * © 2020, Harald Barth. @@ -19,390 +20,183 @@ * along with CommandStation. If not, see . */ -// >>>>>> ATTENTION: This class requires major cleaning. -// The public interface has been narrowed to avoid the ambuguity of "activated". +#ifndef TURNOUTS_CPP +#define TURNOUTS_CPP +// Set the following definition to true for = throw and = close +// or to false for = close and = throw (the original way). +#ifndef USE_LEGACY_TURNOUT_BEHAVIOUR +#define USE_LEGACY_TURNOUT_BEHAVIOUR false +#endif - -//#define EESTOREDEBUG #include "defines.h" -#include "Turnouts.h" #include "EEStore.h" #include "StringFormatter.h" #include "RMFT2.h" +#include "Turnouts.h" #ifdef EESTOREDEBUG #include "DIAG.h" #endif -// Keywords used for turnout configuration. -const int16_t HASH_KEYWORD_SERVO=27709; -const int16_t HASH_KEYWORD_DCC=6436; -const int16_t HASH_KEYWORD_VPIN=-415; + /* + * Protected static data + */ -enum unit8_t { - TURNOUT_DCC = 1, - TURNOUT_SERVO = 2, - TURNOUT_VPIN = 3, - TURNOUT_LCN = 4, -}; - - - - - -/////////////////////////////////////////////////////////////////////////////// -// Static function to print all Turnout states to stream in form "" - -void Turnout::printAll(Print *stream){ - for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout) - StringFormatter::send(stream, F("\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: -// -// -// -// - -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("\n"), data.id, state); - break; - case TURNOUT_DCC: - // DCC Turnout - StringFormatter::send(stream, F("\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("\n"), data.id, data.vpinData.vpin, state); - break; - case TURNOUT_SERVO: - // Servo Turnout - StringFormatter::send(stream, F("\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. + Turnout *Turnout::_firstTurnout = 0; + /* + * Public static data + */ + int Turnout::turnoutlistHash = 0; + bool Turnout::useLegacyTurnoutBehaviour = USE_LEGACY_TURNOUT_BEHAVIOUR; -bool Turnout::activate(int n, bool state){ - Turnout * tt=get(n); - if (!tt) return false; - tt->activate(state); - turnoutlistHash++; - return true; -} + /* + * Protected static functions + */ -/////////////////////////////////////////////////////////////////////////////// -// 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;idata.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); + Turnout *Turnout::get(uint16_t id) { + // Find turnout object from list. + for (Turnout *tt = _firstTurnout; tt != NULL; tt = tt->_nextTurnout) + if (tt->_turnoutData.id == id) return tt; return NULL; } - return(tt); -#else - (void)id; (void)vpin; (void)activePosition; (void)inactivePosition; (void)profile; (void)state; // avoid compiler warnings - return NULL; -#endif -} -/////////////////////////////////////////////////////////////////////////////// -// Support for -// and -// and + // Add new turnout to end of chain + void Turnout::add(Turnout *tt) { + 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++; + } + + // Remove nominated turnout from turnout linked list and delete the object. + bool Turnout::remove(uint16_t id) { + Turnout *tt,*pp=NULL; -Turnout *Turnout::create(int id, int params, int16_t p[]) { - if (p[0] == HASH_KEYWORD_SERVO) { // - if (params == 5) - return createServo(id, (VPIN)p[1], (uint16_t)p[2], (uint16_t)p[3], (uint8_t)p[4]); - else - return NULL; - } else - if (p[0] == HASH_KEYWORD_VPIN) { // - if (params==2) - return createVpin(id, p[1]); + 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 - return NULL; - } else - if (p[0]==HASH_KEYWORD_DCC) { - if (params==3 && p[1]>0 && p[1]<=512 && p[2]>=0 && p[2]<4) // - return createDCC(id, p[1], p[2]); - else if (params==2 && p[1]>0 && p[1]<=512*4) // - return createDCC(id, (p[1]-1)/4+1, (p[1]-1)%4); - else - return NULL; - } else if (params==2) { // for DCC or LCN - return createDCC(id, p[0], p[1]); + pp->_nextTurnout = tt->_nextTurnout; + + delete (ServoTurnout *)tt; + + turnoutlistHash++; + return true; } - else if (params==3) { // legacy for Servo - return createServo(id, (VPIN)p[0], (uint16_t)p[1], (uint16_t)p[2]); + + + /* + * Public static functions + */ + + bool Turnout::isClosed(uint16_t id) { + Turnout *tt = get(id); + if (tt) + return tt->isClosed(); + else + return false; } - return NULL; -} + // 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); -/////////////////////////////////////////////////////////////////////////////// -// Create basic Turnout object. The details of what sort of object it is -// controlling are not set here. + 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)); -Turnout *Turnout::create(int id){ - Turnout *tt=get(id); - if (tt==NULL) { - tt=(Turnout *)calloc(1,sizeof(Turnout)); - if (!tt) return (tt); - tt->nextTurnout=firstTurnout; - firstTurnout=tt; - tt->data.id=id; + #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; } - turnoutlistHash++; - return tt; -} -/////////////////////////////////////////////////////////////////////////////// -// -// Object method to print debug info about the state of a Turnout object -// + // Load all turnout objects + void Turnout::load() { + for (uint16_t i=0; idata.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 -void Turnout::print(Turnout *tt) { - tt->print(StringFormatter::diagSerial); -} + printAll(&Serial); #endif + return tt; + } -/////////////////////////////////////////////////////////////////////////////// -Turnout *Turnout::firstTurnout=NULL; -int Turnout::turnoutlistHash=0; //bump on every change so clients know when to refresh their lists + // Display, on the specified stream, the current state of the turnout (1 or 0). + void Turnout::printState(uint16_t id, Print *stream) { + Turnout *tt = get(id); + if (!tt) tt->printState(stream); + } + +#endif \ No newline at end of file diff --git a/Turnouts.h b/Turnouts.h index db73214..bb2c57e 100644 --- a/Turnouts.h +++ b/Turnouts.h @@ -1,4 +1,6 @@ /* + * © 2021 Restructured Neil McKechnie + * © 2013-2016 Gregg E. Berman * © 2020, Chris Harlow. All rights reserved. * * This file is part of Asbelos DCC API @@ -17,109 +19,488 @@ * along with CommandStation. If not, see . */ -/* - * 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 Turnouts_h +//#define EESTOREDEBUG +#include "defines.h" +#include "EEStore.h" +#include "StringFormatter.h" +#include "RMFT2.h" +#ifdef EESTOREDEBUG +#include "DIAG.h" +#endif -#include #include "DCC.h" #include "LCN.h" -#include "IODevice.h" - -const byte STATUS_ACTIVE=0x80; // Flag as activated in tStatus field -const byte STATUS_TYPE = 0x7f; // Mask for turnout type in tStatus field - -// The struct 'header' is used to determine the length of the -// overlaid data so must be at least as long as the anonymous fields it -// 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; - }; +// Turnout type definitions +enum { + TURNOUT_DCC = 1, + TURNOUT_SERVO = 2, + TURNOUT_VPIN = 3, + TURNOUT_LCN = 4, }; +/************************************************************************************* + * Turnout - Base class for turnouts. + * + *************************************************************************************/ + class Turnout { -public: - 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 +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("\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 + } + + // 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("\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 + 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("\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("\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("\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("\n"), _turnoutData.id, + _turnoutData.closed ^ useLegacyTurnoutBehaviour); + } + +}; + diff --git a/WiThrottle.cpp b/WiThrottle.cpp index e9cfdfd..07017ba 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -120,7 +120,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { // Send turnout list if changed since last sent (will replace list on client) if (turnoutListHash != Turnout::turnoutlistHash) { 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(); 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 else if (cmd[1]=='T' && cmd[2]=='A') { // PTA accessory toggle int id=getInt(cmd+4); - Turnout * tt=Turnout::get(id); - if (!tt) { + if (!Turnout::exists(id)) { // If turnout does not exist, create it int addr = ((id - 1) / 4) + 1; int subaddr = (id - 1) % 4; - Turnout::createDCC(id,addr,subaddr); + DCCTurnout::create(id,addr,subaddr); StringFormatter::send(stream, F("HmTurnout %d created\n"),id); } switch (cmd[3]) {