diff --git a/docs/usage.rst b/docs/usage.rst index 942d0f4..3d24a79 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -19,6 +19,8 @@ Once the `DCCEXProtocol` object is instantiated, a connection must be made to th It is also recommended to enable logging to an Arduino Stream using the `setLogStream(&stream)` method. +For WiFi clients, long periods of no interactive commands being sent may cause the WiFi client to be disconnected, so it is recommended to enable heartbeats for these, which defaults to sending a heartbeat every 60 seconds. If commands are sent regularly, no heartbeats are sent. + An example using an ESP32 with WiFi to connect to EX-CommandStation, with logging to the serial console: .. code-block:: cpp @@ -34,6 +36,7 @@ An example using an ESP32 with WiFi to connect to EX-CommandStation, with loggin while(1) delay(1000); } dccexProtocol.setLogStream(&Serial); + dccexProtocol.enableHeartbeat(); dccexProtocol.connect(&client); } @@ -100,7 +103,7 @@ All objects are contained within linked lists and can be access via for loops: // route methods are available here } - for (Turntable* turntable=dccexProtocol.roster->getFirst(); turntable; turntable=turntable->getNext()) { + for (Turntable* turntable=dccexProtocol.turntables->getFirst(); turntable; turntable=turntable->getNext()) { // turntable methods are available here for (TurntableIndex* ttIndex=turntable->getFirstIndex(); ttIndex; ttIndex=ttIndex->getNextIndex()) { // turntable index methods are available here diff --git a/examples/DCCEXProtocol_Basic/DCCEXProtocol_Basic.ino b/examples/DCCEXProtocol_Basic/DCCEXProtocol_Basic.ino index 1edec00..a1c3f6d 100644 --- a/examples/DCCEXProtocol_Basic/DCCEXProtocol_Basic.ino +++ b/examples/DCCEXProtocol_Basic/DCCEXProtocol_Basic.ino @@ -48,6 +48,8 @@ void setup() { dccexProtocol.setLogStream(&Serial); + dccexProtocol.enableHeartbeat(); + // Pass the communication to wiThrottleProtocol dccexProtocol.connect(&client); Serial.println("DCC-EX connected"); diff --git a/examples/DCCEXProtocol_Consist_Control/DCCEXProtocol_Consist_Control.ino b/examples/DCCEXProtocol_Consist_Control/DCCEXProtocol_Consist_Control.ino index 97dfaa3..0ba6ee6 100644 --- a/examples/DCCEXProtocol_Consist_Control/DCCEXProtocol_Consist_Control.ino +++ b/examples/DCCEXProtocol_Consist_Control/DCCEXProtocol_Consist_Control.ino @@ -85,6 +85,8 @@ void setup() { // Pass the delegate instance to wiThrottleProtocol dccexProtocol.setDelegate(&myDelegate); + dccexProtocol.enableHeartbeat(); + // Pass the communication to wiThrottleProtocol dccexProtocol.connect(&client); Serial.println("DCC-EX connected"); diff --git a/library.properties b/library.properties index 4a118c0..d514aa0 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=DCCEXProtocol -version=0.0.16 +version=0.0.17 author=Peter Cole, Peter Akers maintainer=Peter Cole, Peter Akers sentence=DCC-EX Native Protocol implementation diff --git a/src/DCCEXProtocol.cpp b/src/DCCEXProtocol.cpp index 48cec3b..92bb798 100644 --- a/src/DCCEXProtocol.cpp +++ b/src/DCCEXProtocol.cpp @@ -60,11 +60,16 @@ DCCEXProtocol::DCCEXProtocol(int maxCmdBuffer) { DCCEXInbound::setup(MAX_COMMAND_PARAMS); _cmdBuffer[0] = 0; _bufflen = 0; + + // Set heartbeat defaults + _enableHeartbeat = 0; + _heartbeatDelay = 0; + _lastHeartbeat = 0; } DCCEXProtocol::~DCCEXProtocol() { // Free memory for command buffer - delete[] (_cmdBuffer); + delete[](_cmdBuffer); // Cleanup command parser DCCEXInbound::cleanup(); @@ -76,6 +81,11 @@ void DCCEXProtocol::setDelegate(DCCEXProtocolDelegate *delegate) { this->_delega // Set the Stream used for logging void DCCEXProtocol::setLogStream(Stream *console) { this->_console = console; } +void DCCEXProtocol::enableHeartbeat(unsigned long heartbeatDelay) { + _enableHeartbeat = true; + _heartbeatDelay = heartbeatDelay; +} + void DCCEXProtocol::connect(Stream *stream) { _init(); this->_stream = stream; @@ -114,6 +124,9 @@ void DCCEXProtocol::check() { _bufflen = 0; } } + if (_enableHeartbeat) { + _sendHeartbeat(); + } } } @@ -532,7 +545,8 @@ void DCCEXProtocol::_sendCommand() { _stream->println(_outboundCommand); _console->print("==> "); _console->println(_outboundCommand); - *_outboundCommand = 0; // clear it once it has been sent + *_outboundCommand = 0; // clear it once it has been sent + _lastHeartbeat = millis(); // If we sent a command, a heartbeat isn't necessary } } @@ -547,7 +561,7 @@ void DCCEXProtocol::_processCommand() { _processScreenUpdate(); } break; - + case 'i': // iDCC-EX server info if (DCCEXInbound::isTextParameter(0)) { _processServerDescription(); @@ -698,7 +712,16 @@ void DCCEXProtocol::_processMessage() { // } void DCCEXProtocol::_processScreenUpdate() { //<@ screen row "message"> - _delegate->receivedScreenUpdate(DCCEXInbound::getNumber(0), DCCEXInbound::getNumber(1), DCCEXInbound::getTextParameter(2)); + _delegate->receivedScreenUpdate(DCCEXInbound::getNumber(0), DCCEXInbound::getNumber(1), + DCCEXInbound::getTextParameter(2)); +} + +void DCCEXProtocol::_sendHeartbeat() { + if (millis() - _lastHeartbeat > _heartbeatDelay) { + _lastHeartbeat = millis(); + sprintf(_outboundCommand, "<#>"); + _sendCommand(); + } } // Consist/loco methods @@ -1106,17 +1129,17 @@ void DCCEXProtocol::_processTurntableIndexEntry() { // - // console->println(F("processTurntableAction(): ")); + // _console->println(F("_processTurntableBroadcast(): ")); int id = DCCEXInbound::getNumber(0); int newIndex = DCCEXInbound::getNumber(1); bool moving = DCCEXInbound::getNumber(2); Turntable *tt = getTurntableById(id); - if (tt && tt->getIndex() != newIndex) { + if (tt) { tt->setIndex(newIndex); tt->setMoving(moving); } _delegate->receivedTurntableAction(id, newIndex, moving); - // console->println(F("processTurntableAction(): end")); + // _console->println(F("processTurntableAction(): end")); } // Track management methods diff --git a/src/DCCEXProtocol.h b/src/DCCEXProtocol.h index a364c3a..10d8707 100644 --- a/src/DCCEXProtocol.h +++ b/src/DCCEXProtocol.h @@ -34,6 +34,9 @@ /* Version information: +0.0.17 - Fix typo in turntable example + - Fix bug where the turntable isMoving() method always returned true + - Add enableHeartbeat(heartbeatDelay) to send a heartbeat every x ms if a command is not sent 0.0.16 - add public sendCommand method 0.0.15 - any acquired loco is now retained in the roster 0.0.14 - add getNumberSupportedLocos() used for the fake heartbeat @@ -211,6 +214,10 @@ class DCCEXProtocol { /// @param console void setLogStream(Stream *console); + /// @brief Enable heartbeat if required - can help WiFi connections that drop out + /// @param heartbeatDelay Time in milliseconds between heartbeats - defaults to one minute (60000ms) + void enableHeartbeat(unsigned long heartbeatDelay = 60000); + /// @brief Connect the stream object to interact with DCC-EX /// @param stream void connect(Stream *stream); @@ -466,6 +473,7 @@ class DCCEXProtocol { void _processServerDescription(); void _processMessage(); void _processScreenUpdate(); + void _sendHeartbeat(); // Consist/loco methods void _processLocoBroadcast(); @@ -538,6 +546,9 @@ class DCCEXProtocol { bool _receivedRouteList = false; // Flag that route list received bool _turntableListRequested = false; // Flag that turntable list requested bool _receivedTurntableList = false; // Flag that turntable list received + bool _enableHeartbeat; // Flag if heartbeat is enabled + unsigned long _heartbeatDelay; // Delay between heartbeats if enabled + unsigned long _lastHeartbeat; // Time in ms of the last heartbeat, also set by sending a command }; #endif // DCCEXPROTOCOL_H diff --git a/src/DCCEXTurntables.cpp b/src/DCCEXTurntables.cpp index 0cf8335..5f8502b 100644 --- a/src/DCCEXTurntables.cpp +++ b/src/DCCEXTurntables.cpp @@ -29,7 +29,6 @@ #include "DCCEXTurntables.h" #include - // class TurntableIndex TurntableIndex *TurntableIndex::_first = nullptr; @@ -62,6 +61,7 @@ Turntable::Turntable(int id) { _index = 0; _numberOfIndexes = 0; _name = nullptr; + _isMoving = false; _firstIndex = nullptr; _indexCount = 0; _next = nullptr;