1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-27 10:06:13 +01:00

Delay Turnout <H> response following movement.

This commit is contained in:
Neil McKechnie 2021-11-11 13:30:37 +00:00
parent 0e78cf6e55
commit 8e6c232d05
3 changed files with 92 additions and 19 deletions

View File

@ -129,6 +129,7 @@ void DCCEXParser::loop(Stream &stream)
} }
} }
Sensor::checkAll(&stream); // Update and print changes Sensor::checkAll(&stream); // Update and print changes
Turnout::loop(); // Check for outstanding turnout responses
} }
int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte *cmd) int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte *cmd)

View File

@ -43,6 +43,8 @@
*/ */
/* static */ int Turnout::turnoutlistHash = 0; /* static */ int Turnout::turnoutlistHash = 0;
/* static */ unsigned long Turnout::_lastLoopEntry = 0;
/* /*
* Protected static functions * Protected static functions
*/ */
@ -68,6 +70,48 @@
turnoutlistHash++; turnoutlistHash++;
} }
/* static */ void Turnout::loop() {
// Here, we check whether the turnout state needs updating.
// For Servo and VPIN turnouts, we need to check the isBusy() status of the device
// and when it goes to 0 we can send out a turnout notification.
// We allow a small delay before we start checking isBusy(), to allow the
// operation time to start.
unsigned long currentMillis = millis();
// Check for updates once every 100ms.
if (currentMillis - _lastLoopEntry > 100) {
_lastLoopEntry = currentMillis;
Turnout *tt = _firstTurnout;
for ( ; tt != 0; tt=tt->_nextTurnout) {
if (tt->_delayResponse > 1) {
tt->_delayResponse--;
} else if (tt->_delayResponse == 1) {
// Pointer has been triggered and grace time has elapsed, so check if completed
if (!tt->isPending()) {
tt->sendResponse();
}
}
}
}
}
// Function called when the turnout has changed.
void Turnout::sendResponse() {
turnoutlistHash++; // let withrottle know something changed
#if defined(RMFT_ACTIVE)
RMFT2::turnoutEvent(_turnoutData.id, _turnoutData.closed);
#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(&Serial);
// Clear flag pending operation
_delayResponse = 0;
}
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed; // For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed;
void Turnout::printState(Print *stream) { void Turnout::printState(Print *stream) {
StringFormatter::send(stream, F("<H %d %d>\n"), StringFormatter::send(stream, F("<H %d %d>\n"),
@ -139,24 +183,22 @@
if (!tt) return false; if (!tt) return false;
bool ok = tt->setClosedInternal(closeFlag); bool ok = tt->setClosedInternal(closeFlag);
// Check return value to see if this type of turnout is immediate
// or if we need to check for completion.
if (ok) { if (ok) {
turnoutlistHash++; // let withrottle know something changed tt->sendResponse();
} else {
// Set flag indicating that the operation is pending and needs to be checked for completion
// starting in 4 loop counts (starts checking at 1).
tt->_delayResponse = 5;
}
// Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress // Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress
// is always zero for LCN turnouts. // is always zero for LCN turnouts.
if (EEStore::eeStore->data.nTurnouts > 0 && tt->_eepromAddress > 0) if (EEStore::eeStore->data.nTurnouts > 0 && tt->_eepromAddress > 0)
EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags); EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags);
#if defined(RMFT_ACTIVE) return true;
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.
tt->printState(&Serial);
}
return ok;
} }
// Load all turnout objects // Load all turnout objects
@ -301,10 +343,16 @@
IODevice::writeAnalogue(_servoTurnoutData.vpin, IODevice::writeAnalogue(_servoTurnoutData.vpin,
close ? _servoTurnoutData.closedPosition : _servoTurnoutData.thrownPosition, _servoTurnoutData.profile); close ? _servoTurnoutData.closedPosition : _servoTurnoutData.thrownPosition, _servoTurnoutData.profile);
_turnoutData.closed = close; _turnoutData.closed = close;
return false; // Don't send update messages yet.
#else #else
(void)close; // avoid compiler warnings (void)close; // avoid compiler warnings
#endif
return true; return true;
#endif
}
// Determine if the turnout is moving into its new state.
bool ServoTurnout::isPending() {
return IODevice::isBusy(_servoTurnoutData.vpin);
} }
void ServoTurnout::save() { void ServoTurnout::save() {
@ -458,12 +506,22 @@
!_turnoutData.closed); !_turnoutData.closed);
} }
// setClosedInternal is called from the base class's setClosed() method. It returns true if the
// operation is considered to be completed, and false if it is started but not completed.
// For VPINs which are attached to servos, we ought to return false so that the confirmation
// message to JMRI is deferred until the servo movement is completed. However, if we do this
// it will also affect VPINs that are GPIO pins; when it polls the pin it will put the pin into
// input mode, which is not desirable. Hence, we return true here so that no polling takes place.
bool VpinTurnout::setClosedInternal(bool close) { bool VpinTurnout::setClosedInternal(bool close) {
IODevice::write(_vpinTurnoutData.vpin, close); IODevice::write(_vpinTurnoutData.vpin, close);
_turnoutData.closed = close; _turnoutData.closed = close;
return true; return true;
} }
bool VpinTurnout::isPending() {
return IODevice::isBusy(_vpinTurnoutData.vpin);
}
void VpinTurnout::save() { void VpinTurnout::save() {
// Write turnout definition and current position to EEPROM // Write turnout definition and current position to EEPROM
// First write common servo data, then // First write common servo data, then

View File

@ -72,6 +72,9 @@ protected:
// Pointer to next turnout on linked list. // Pointer to next turnout on linked list.
Turnout *_nextTurnout = 0; Turnout *_nextTurnout = 0;
// Delay counter for responses. If non-zero, there is a pending response to be sent.
uint8_t _delayResponse = 0;
/* /*
* Constructor * Constructor
*/ */
@ -88,6 +91,7 @@ protected:
static Turnout *_firstTurnout; static Turnout *_firstTurnout;
static int _turnoutlistHash; static int _turnoutlistHash;
static unsigned long _lastLoopEntry;
/* /*
* Virtual functions * Virtual functions
@ -95,6 +99,7 @@ protected:
virtual bool setClosedInternal(bool close) = 0; // Mandatory in subclass virtual bool setClosedInternal(bool close) = 0; // Mandatory in subclass
virtual void save() {} virtual void save() {}
virtual bool isPending() { return false; };
/* /*
* Static functions * Static functions
@ -104,6 +109,11 @@ protected:
static void add(Turnout *tt); static void add(Turnout *tt);
/*
* Other functions
*/
void sendResponse();
public: public:
/* /*
* Static data * Static data
@ -155,6 +165,8 @@ public:
inline static Turnout *first() { return _firstTurnout; } inline static Turnout *first() { return _firstTurnout; }
static void loop();
// Load all turnout definitions. // Load all turnout definitions.
static void load(); static void load();
// Load one turnout definition // Load one turnout definition
@ -201,6 +213,7 @@ protected:
// ServoTurnout-specific code for throwing or closing a servo turnout. // ServoTurnout-specific code for throwing or closing a servo turnout.
bool setClosedInternal(bool close) override; bool setClosedInternal(bool close) override;
void save() override; void save() override;
bool isPending() override;
}; };
@ -265,6 +278,7 @@ public:
protected: protected:
bool setClosedInternal(bool close) override; bool setClosedInternal(bool close) override;
void save() override; void save() override;
bool isPending() override;
}; };