diff --git a/examples/DCCEXProtocol_Serial/DCCEXProtocol_Serial.ino b/examples/DCCEXProtocol_Serial/DCCEXProtocol_Serial.ino new file mode 100644 index 0000000..7cfa130 --- /dev/null +++ b/examples/DCCEXProtocol_Serial/DCCEXProtocol_Serial.ino @@ -0,0 +1,181 @@ +// DCCEXProtocol_Serial +// +// Shows how to use DCCEXProtocol over serial using a Mega2560 +// This uses a Mega2560 (or other AVR device) that has a second hardware serial port (Serial1) +// Tested with Arduino Mega2560 +// +// Peter Cole (PeteGSX) 2024 + +#include + +// If we haven't got a custom config.h, use the example +#if __has_include("config.h") +#include "config.h" +#else +#warning config.h not found. Using defaults from config.example.h +#include "config.example.h" +#endif + +// Setup serial macros if not already defined elsewhere +#ifndef CONSOLE +#define CONSOLE Serial // All output should go to this +#endif +#ifndef CLIENT +#define CLIENT Serial1 // All DCCEXProtocol commands/responses/broadcasts use this +#endif + +// Declare functions to call from our delegate +void printRoster(); +void printTurnouts(); +void printRoutes(); +void printTurntables(); + +// Setup the delegate class to process broadcasts/responses +class MyDelegate : public DCCEXProtocolDelegate { +public: + void receivedServerVersion(int major, int minor, int patch) { + CONSOLE.print("\n\nReceived version: "); + CONSOLE.print(major); + CONSOLE.print("."); + CONSOLE.print(minor); + CONSOLE.print("."); + CONSOLE.println(patch); + } + + void receivedTrackPower(TrackPower state) { + CONSOLE.print("\n\nReceived Track Power: "); + CONSOLE.println(state); + CONSOLE.println("\n\n"); + } + + void receivedRosterList() { + CONSOLE.println("\n\nReceived Roster"); + printRoster(); + } + void receivedTurnoutList() { + CONSOLE.print("\n\nReceived Turnouts/Points list"); + printTurnouts(); + CONSOLE.println("\n\n"); + } + void receivedRouteList() { + CONSOLE.print("\n\nReceived Routes List"); + printRoutes(); + CONSOLE.println("\n\n"); + } + void receivedTurntableList() { + CONSOLE.print("\n\nReceived Turntables list"); + printTurntables(); + CONSOLE.println("\n\n"); + } + + void receivedScreenUpdate(int screen, int row, char *message) { + CONSOLE.println("\n\nReceived screen|row|message"); + CONSOLE.print(screen); + CONSOLE.print("|"); + CONSOLE.print(row); + CONSOLE.print("|"); + CONSOLE.println(message); + } + +}; + +// Global objects +DCCEXProtocol dccexProtocol; +MyDelegate myDelegate; + +void printRoster() { + for (Loco *loco = dccexProtocol.roster->getFirst(); loco; loco = loco->getNext()) { + int id = loco->getAddress(); + char *name = loco->getName(); + CONSOLE.print(id); + CONSOLE.print(" ~"); + CONSOLE.print(name); + CONSOLE.println("~"); + for (int i = 0; i < 32; i++) { + char *fName = loco->getFunctionName(i); + if (fName != nullptr) { + CONSOLE.print("loadFunctionLabels() "); + CONSOLE.print(fName); + if (loco->isFunctionMomentary(i)) { + CONSOLE.print(" - Momentary"); + } + CONSOLE.println(); + } + } + } + CONSOLE.println("\n"); +} + +void printTurnouts() { + for (Turnout *turnout = dccexProtocol.turnouts->getFirst(); turnout; turnout = turnout->getNext()) { + int id = turnout->getId(); + char *name = turnout->getName(); + CONSOLE.print(id); + CONSOLE.print(" ~"); + CONSOLE.print(name); + CONSOLE.println("~"); + } + CONSOLE.println("\n"); +} + +void printRoutes() { + for (Route *route = dccexProtocol.routes->getFirst(); route; route = route->getNext()) { + int id = route->getId(); + char *name = route->getName(); + CONSOLE.print(id); + CONSOLE.print(" ~"); + CONSOLE.print(name); + CONSOLE.println("~"); + } + CONSOLE.println("\n"); +} + +void printTurntables() { + for (Turntable *turntable = dccexProtocol.turntables->getFirst(); turntable; turntable = turntable->getNext()) { + int id = turntable->getId(); + char *name = turntable->getName(); + CONSOLE.print(id); + CONSOLE.print(" ~"); + CONSOLE.print(name); + CONSOLE.println("~"); + + int j = 0; + for (TurntableIndex *turntableIndex = turntable->getFirstIndex(); turntableIndex; + turntableIndex = turntableIndex->getNextIndex()) { + char *indexName = turntableIndex->getName(); + CONSOLE.print(" index"); + CONSOLE.print(j); + CONSOLE.print(" ~"); + CONSOLE.print(indexName); + CONSOLE.println("~"); + j++; + } + } + CONSOLE.println("\n"); +} + +void setup() { + CONSOLE.begin(115200); + CLIENT.begin(115200); + CONSOLE.println(F("DCCEXProtocol Serial Connection Demo")); + CONSOLE.println(F("")); + + // Direct logs to CONSOLE + dccexProtocol.setLogStream(&CONSOLE); + + // Set the delegate for broadcasts/responses + dccexProtocol.setDelegate(&myDelegate); + + // Connect to the CS via CLIENT + dccexProtocol.connect(&CLIENT); + CONSOLE.println(F("DCC-EX connected")); + + dccexProtocol.requestServerVersion(); +} + +void loop() { + // Parse incoming messages + dccexProtocol.check(); + + dccexProtocol.getLists(true, true, true, true); +} diff --git a/examples/DCCEXProtocol_Serial/config.example.h b/examples/DCCEXProtocol_Serial/config.example.h new file mode 100644 index 0000000..66224ee --- /dev/null +++ b/examples/DCCEXProtocol_Serial/config.example.h @@ -0,0 +1,5 @@ +// Example config file for the DCCEXProtocol_Serial example. +// Copy to config.h if you need to customise these for your setup. + +#define CONSOLE Serial +#define CLIENT Serial1 diff --git a/library.properties b/library.properties index 58f601e..ca86f11 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=DCCEXProtocol -version=0.0.12 +version=0.0.13 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 3d970a0..fef1715 100644 --- a/src/DCCEXProtocol.cpp +++ b/src/DCCEXProtocol.cpp @@ -96,6 +96,10 @@ void DCCEXProtocol::check() { _cmdBuffer[_bufflen] = r; _bufflen++; _cmdBuffer[_bufflen] = 0; + } else { + // Clear buffer if full + _cmdBuffer[0] = 0; + _bufflen = 0; } if (r == '>') { @@ -523,6 +527,12 @@ void DCCEXProtocol::_sendCommand() { void DCCEXProtocol::_processCommand() { if (_delegate) { switch (DCCEXInbound::getOpcode()) { + case '@': // Screen update + if (DCCEXInbound::isTextParameter(2) && DCCEXInbound::getParameterCount() == 3) { + _processScreenUpdate(); + } + break; + case 'i': // iDCC-EX server info if (DCCEXInbound::isTextParameter(0)) { _processServerDescription(); @@ -627,7 +637,7 @@ void DCCEXProtocol::_processCommand() { void DCCEXProtocol::_processServerDescription() { // - // console->println(F("processServerDescription()")); + // _console->println(F("processServerDescription()")); if (_delegate) { char *description{DCCEXInbound::getTextParameter(0) + 7}; int *version = _version; @@ -665,13 +675,17 @@ void DCCEXProtocol::_processServerDescription() { //receivedServerVersion(_version[0], _version[1], _version[2]); } - // console->println(F("processServerDescription(): end")); + // _console->println(F("processServerDescription(): end")); } void DCCEXProtocol::_processMessage() { // _delegate->receivedMessage(DCCEXInbound::getTextParameter(0)); } +void DCCEXProtocol::_processScreenUpdate() { //<@ screen row "message"> + _delegate->receivedScreenUpdate(DCCEXInbound::getNumber(0), DCCEXInbound::getNumber(1), DCCEXInbound::getTextParameter(2)); +} + // Consist/loco methods void DCCEXProtocol::_processLocoBroadcast() { // diff --git a/src/DCCEXProtocol.h b/src/DCCEXProtocol.h index f5dfdb7..e64beca 100644 --- a/src/DCCEXProtocol.h +++ b/src/DCCEXProtocol.h @@ -34,6 +34,10 @@ /* Version information: +0.0.13 - Fix bug to allow compilation on AVR platforms, change ssize_t to int + - Add serial connectivity example + - Add support for SCREEN updates to delegate + - Enhance buffer management to clear command buffer if full 0.0.12 - Improved memory management 0.0.11 - support for individual track power receivedIndividualTrackPower(TrackPower state, int track) - improved logic for overall track power @@ -176,6 +180,12 @@ class DCCEXProtocolDelegate { /// @brief Notify when a loco address is read from the programming track /// @param address DCC address read from the programming track, or -1 for a failure to read virtual void receivedReadLoco(int address) {} + + /// @brief Notify when a screen update is received + /// @param screen Screen number + /// @param row Row number + /// @param message Message to display on the screen/row + virtual void receivedScreenUpdate(int screen, int row, char *message) {} }; /// @brief Main class for the DCCEXProtocol library @@ -445,6 +455,7 @@ class DCCEXProtocol { void _processCommand(); void _processServerDescription(); void _processMessage(); + void _processScreenUpdate(); // Consist/loco methods void _processLocoBroadcast(); @@ -506,7 +517,7 @@ class DCCEXProtocol { DCCEXProtocolDelegate *_delegate = nullptr; // Pointer to the delegate for notifications unsigned long _lastServerResponseTime; // Records the timestamp of the last server response char _inputBuffer[512]; // Char array for input buffer - ssize_t _nextChar; // where the next character to be read goes in the buffer + int _nextChar; // where the next character to be read goes in the buffer bool _receivedVersion = false; // Flag that server version has been received bool _receivedLists = false; // Flag if all requested lists have been received bool _rosterRequested = false; // Flag that roster has been requested diff --git a/tests/DCCEXProtocol.cpp b/tests/DCCEXProtocol.cpp new file mode 100644 index 0000000..9f095d9 --- /dev/null +++ b/tests/DCCEXProtocol.cpp @@ -0,0 +1,12 @@ +#include "DCCEXProtocolTest.hpp" + +TEST_F(DCCEXProtocolTest, clearBufferWhenFull) { + // Fill buffer with garbage + for (auto i{0uz}; i < 500uz; ++i) + _stream.write(static_cast('A' + (random() % 26))); + + // Proceed with normal message + _stream << R"()"; + EXPECT_CALL(_delegate, receivedMessage(StrEq("Hello World"))).Times(Exactly(1)); + _dccexProtocol.check(); +}