diff --git a/.gitignore b/.gitignore index aea01f1..c1b94f5 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,8 @@ _build # Coverage info file lcov.info test_coverage/ + +# cmake cache. +.cache/ +build/ +build-*/ \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..3b284e8 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,49 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "CMake Configure Tests (Debug)", + "type": "shell", + "command": "cmake", + "args": [ + "-S", + ".", + "-B", + "build-cmake-tests-debug", + "-DCMAKE_BUILD_TYPE=Debug", + "-DDCCEX_BUILD_TESTS=ON" + ], + "group": "build" + }, + { + "label": "CMake Build Tests (Debug)", + "type": "shell", + "command": "cmake", + "args": [ + "--build", + "build-cmake-tests-debug", + "-j" + ], + "dependsOn": "CMake Configure Tests (Debug)", + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "CMake Run Tests (Debug)", + "type": "shell", + "command": "ctest", + "args": [ + "--test-dir", + "build-cmake-tests-debug", + "--output-on-failure" + ], + "dependsOn": "CMake Build Tests (Debug)", + "group": { + "kind": "test", + "isDefault": true + } + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5901da4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,68 @@ +cmake_minimum_required(VERSION 3.11) + +# Detect ESP-IDF build +if(DEFINED ENV{IDF_PATH}) + file(GLOB_RECURSE SRC src/*.cpp) + set(COMPONENT_SRCS ${SRC}) # Add all your source files here + set(COMPONENT_ADD_INCLUDEDIRS src) + set(COMPONENT_PRIV_INCLUDEDIRS "") + set(COMPONENT_REQUIRES "") + set(COMPONENT_PRIV_REQUIRES "") + register_component() +else() + include(FetchContent) + list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) + + project(DCCEXProtocol LANGUAGES CXX) + + option(DCCEX_BUILD_TESTS "Build native GoogleTest test target" ON) + + file(GLOB_RECURSE SRC src/*.cpp) + add_library(DCCEXProtocol STATIC ${SRC}) + add_library(DCCEX::Protocol ALIAS DCCEXProtocol) + + # Stuck at C++11 + target_compile_options(DCCEXProtocol PRIVATE -std=c++11) + + # Don't bother users with warnings by setting 'SYSTEM' + if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + target_include_directories(DCCEXProtocol PUBLIC src) + else() + target_include_directories(DCCEXProtocol SYSTEM PUBLIC src) + endif() + + foreach(FILE ${SRC}) + message(${FILE}) + endforeach() + + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SRC}) + + if(DCCEX_BUILD_TESTS) + include(CTest) + enable_testing() + + FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.zip + ) + FetchContent_MakeAvailable(googletest) + + file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS test/*.cpp) + add_executable(DCCEXProtocolTests ${TEST_SOURCES}) + + target_compile_definitions(DCCEXProtocolTests PRIVATE NATIVE_TESTING) + target_include_directories(DCCEXProtocolTests PRIVATE test/mocks test/setup) + target_link_libraries(DCCEXProtocolTests PRIVATE DCCEXProtocol GTest::gtest GTest::gmock) + + include(GoogleTest) + gtest_discover_tests(DCCEXProtocolTests) + + add_custom_target(check + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure + DEPENDS DCCEXProtocolTests + USES_TERMINAL + ) + + add_custom_target(run-tests DEPENDS check) + endif() +endif() diff --git a/examples/DCCEXProtocol_Basic/DCCEXProtocol_Basic.ino b/examples/DCCEXProtocol_Basic/DCCEXProtocol_Basic.ino index a1c3f6d..f3ffdff 100644 --- a/examples/DCCEXProtocol_Basic/DCCEXProtocol_Basic.ino +++ b/examples/DCCEXProtocol_Basic/DCCEXProtocol_Basic.ino @@ -7,8 +7,11 @@ // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 // Luca Dentella, 2020 +#include #include #include +#include +#include // If we haven't got a custom config.h, use the example @@ -19,9 +22,59 @@ #include "config.example.h" #endif +using namespace DCCExController; + +class ArduinoDCCMillis : public DCCMillis { +public: + unsigned long millis() const override { return ::millis(); } +}; + +class ArduinoDCCStream : public DCCStream { +public: + explicit ArduinoDCCStream(Stream &stream) : _stream(stream) {} + + int available() const override { + return const_cast(_stream).available(); + } + + int read() override { return _stream.read(); } + + size_t write(const uint8_t *buffer, size_t size) override { + return _stream.write(buffer, size); + } + + void flush() override { _stream.flush(); } + + void println(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.println(message); + } + + void print(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.print(message); + } + +private: + Stream &_stream; +}; + + + // Global objects WiFiClient client; -DCCEXProtocol dccexProtocol; +ArduinoDCCMillis dccMillis; +ArduinoDCCStream dccTransport(client); +ArduinoDCCStream dccLog(Serial); +DCCEXProtocol dccexProtocol(&dccMillis); void setup() { @@ -46,12 +99,12 @@ void setup() { } Serial.println("Connected to the server"); - dccexProtocol.setLogStream(&Serial); + dccexProtocol.setLogStream(&dccLog); dccexProtocol.enableHeartbeat(); // Pass the communication to wiThrottleProtocol - dccexProtocol.connect(&client); + dccexProtocol.connect(&dccTransport); Serial.println("DCC-EX connected"); } diff --git a/examples/DCCEXProtocol_CSConsist_Control/DCCEXProtocol_CSConsist_Control.ino b/examples/DCCEXProtocol_CSConsist_Control/DCCEXProtocol_CSConsist_Control.ino index cc7e1fb..c36511e 100644 --- a/examples/DCCEXProtocol_CSConsist_Control/DCCEXProtocol_CSConsist_Control.ino +++ b/examples/DCCEXProtocol_CSConsist_Control/DCCEXProtocol_CSConsist_Control.ino @@ -5,6 +5,7 @@ // // Peter Cole (PeteGSX), 2026 +#include #include #include @@ -16,6 +17,52 @@ #include "config.example.h" #endif +using namespace DCCExController; + +class ArduinoDCCMillis : public DCCMillis { +public: + unsigned long millis() const override { return ::millis(); } +}; + +class ArduinoDCCStream : public DCCStream { +public: + explicit ArduinoDCCStream(Stream &stream) : _stream(stream) {} + + int available() const override { + return const_cast(_stream).available(); + } + + int read() override { return _stream.read(); } + + size_t write(const uint8_t *buffer, size_t size) override { + return _stream.write(buffer, size); + } + + void flush() override { _stream.flush(); } + + void println(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.println(message); + } + + void print(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.print(message); + } + +private: + Stream &_stream; +}; + + // Delegate class class MyDelegate : public DCCEXProtocolDelegate { @@ -47,7 +94,10 @@ CSConsist *csConsist = nullptr; // Global objects WiFiClient client; -DCCEXProtocol dccexProtocol; +ArduinoDCCMillis dccMillis; +ArduinoDCCStream dccTransport(client); +ArduinoDCCStream dccLog(Serial); +DCCEXProtocol dccexProtocol(&dccMillis); MyDelegate myDelegate; void setup() { @@ -74,7 +124,7 @@ void setup() { Serial.println("Connected to the server"); // Logging on Serial - dccexProtocol.setLogStream(&Serial); + dccexProtocol.setLogStream(&dccLog); // Pass the delegate instance to wiThrottleProtocol dccexProtocol.setDelegate(&myDelegate); @@ -82,7 +132,7 @@ void setup() { dccexProtocol.enableHeartbeat(); // Pass the communication to wiThrottleProtocol - dccexProtocol.connect(&client); + dccexProtocol.connect(&dccTransport); Serial.println("DCC-EX connected"); // Turn track power on for locos to move @@ -95,7 +145,7 @@ void loop() { // parse incoming messages dccexProtocol.check(); - if (!consist) { + if (!csConsist) { // Create a new CSConsist for loco address 11 in the normal direction of travel, and replicate functions across the // consist. // By default, functions will only affect the lead loco diff --git a/examples/DCCEXProtocol_Consist_Control/DCCEXProtocol_Consist_Control.ino b/examples/DCCEXProtocol_Consist_Control/DCCEXProtocol_Consist_Control.ino index ab49861..491c648 100644 --- a/examples/DCCEXProtocol_Consist_Control/DCCEXProtocol_Consist_Control.ino +++ b/examples/DCCEXProtocol_Consist_Control/DCCEXProtocol_Consist_Control.ino @@ -17,6 +17,7 @@ manual operation only. // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 // Luca Dentella, 2020 +#include #include #include @@ -28,6 +29,52 @@ manual operation only. #include "config.example.h" #endif +using namespace DCCExController; + +class ArduinoDCCMillis : public DCCMillis { +public: + unsigned long millis() const override { return ::millis(); } +}; + +class ArduinoDCCStream : public DCCStream { +public: + explicit ArduinoDCCStream(Stream &stream) : _stream(stream) {} + + int available() const override { + return const_cast(_stream).available(); + } + + int read() override { return _stream.read(); } + + size_t write(const uint8_t *buffer, size_t size) override { + return _stream.write(buffer, size); + } + + void flush() override { _stream.flush(); } + + void println(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.println(message); + } + + void print(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.print(message); + } + +private: + Stream &_stream; +}; + + // Delegate class class MyDelegate : public DCCEXProtocolDelegate { @@ -80,7 +127,11 @@ Consist *consist = nullptr; // Global objects WiFiClient client; -DCCEXProtocol dccexProtocol; +ArduinoDCCMillis dccMillis; +ArduinoDCCStream dccTransport(client); +ArduinoDCCStream dccLog(Serial); +DCCEXProtocol dccexProtocol(&dccMillis); + MyDelegate myDelegate; void setup() { @@ -109,7 +160,7 @@ void setup() { Serial.println("Connected to the server"); // Logging on Serial - dccexProtocol.setLogStream(&Serial); + dccexProtocol.setLogStream(&dccLog); // Pass the delegate instance to wiThrottleProtocol dccexProtocol.setDelegate(&myDelegate); @@ -117,7 +168,7 @@ void setup() { dccexProtocol.enableHeartbeat(); // Pass the communication to wiThrottleProtocol - dccexProtocol.connect(&client); + dccexProtocol.connect(&dccTransport); Serial.println("DCC-EX connected"); dccexProtocol.requestServerVersion(); diff --git a/examples/DCCEXProtocol_Delegate/DCCEXProtocol_Delegate.ino b/examples/DCCEXProtocol_Delegate/DCCEXProtocol_Delegate.ino index ac12307..93551e8 100644 --- a/examples/DCCEXProtocol_Delegate/DCCEXProtocol_Delegate.ino +++ b/examples/DCCEXProtocol_Delegate/DCCEXProtocol_Delegate.ino @@ -6,6 +6,7 @@ // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 // Luca Dentella, 2020 +#include #include #include @@ -18,6 +19,51 @@ #include "config.example.h" #endif +using namespace DCCExController; + +class ArduinoDCCMillis : public DCCMillis { +public: + unsigned long millis() const override { return ::millis(); } +}; + +class ArduinoDCCStream : public DCCStream { +public: + explicit ArduinoDCCStream(Stream &stream) : _stream(stream) {} + + int available() const override { + return const_cast(_stream).available(); + } + + int read() override { return _stream.read(); } + + size_t write(const uint8_t *buffer, size_t size) override { + return _stream.write(buffer, size); + } + + void flush() override { _stream.flush(); } + + void println(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.println(message); + } + + void print(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.print(message); + } + +private: + Stream &_stream; +}; + // Delegate class class MyDelegate : public DCCEXProtocolDelegate { @@ -34,7 +80,10 @@ public: // Global objects WiFiClient client; -DCCEXProtocol dccexProtocol; +ArduinoDCCMillis dccMillis; +ArduinoDCCStream dccTransport(client); +ArduinoDCCStream dccLog(Serial); +DCCEXProtocol dccexProtocol(&dccMillis); MyDelegate myDelegate; void setup() { @@ -61,10 +110,10 @@ void setup() { Serial.println("Connected to the server"); // Setup logging to serial console - dccexProtocol.setLogStream(&Serial); + dccexProtocol.setLogStream(&dccLog); // Pass the delegate instance to wiThrottleProtocol - dccexProtocol.setDelegate(&myDelegate); + dccexProtocol.setDelegate(&dccTransport); // Pass the communication to wiThrottleProtocol dccexProtocol.connect(&client); diff --git a/examples/DCCEXProtocol_Loco_Control/DCCEXProtocol_Loco_Control.ino b/examples/DCCEXProtocol_Loco_Control/DCCEXProtocol_Loco_Control.ino index 64f1d05..d956d94 100644 --- a/examples/DCCEXProtocol_Loco_Control/DCCEXProtocol_Loco_Control.ino +++ b/examples/DCCEXProtocol_Loco_Control/DCCEXProtocol_Loco_Control.ino @@ -6,6 +6,7 @@ // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 // Luca Dentella, 2020 +#include #include #include @@ -17,6 +18,51 @@ #include "config.example.h" #endif +using namespace DCCExController; + +class ArduinoDCCMillis : public DCCMillis { +public: + unsigned long millis() const override { return ::millis(); } +}; + +class ArduinoDCCStream : public DCCStream { +public: + explicit ArduinoDCCStream(Stream &stream) : _stream(stream) {} + + int available() const override { + return const_cast(_stream).available(); + } + + int read() override { return _stream.read(); } + + size_t write(const uint8_t *buffer, size_t size) override { + return _stream.write(buffer, size); + } + + void flush() override { _stream.flush(); } + + void println(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.println(message); + } + + void print(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.print(message); + } + +private: + Stream &_stream; +}; + void printRoster(); // Delegate class @@ -70,7 +116,10 @@ Loco *loco = nullptr; // Global objects WiFiClient client; -DCCEXProtocol dccexProtocol; +ArduinoDCCMillis dccMillis; +ArduinoDCCStream dccTransport(client); +ArduinoDCCStream dccLog(Serial); +DCCEXProtocol dccexProtocol(&dccMillis); MyDelegate myDelegate; void setup() { @@ -97,13 +146,13 @@ void setup() { Serial.println("Connected to the server"); // Logging on Serial - dccexProtocol.setLogStream(&Serial); + dccexProtocol.setLogStream(&dccLog); // Pass the delegate instance to wiThrottleProtocol dccexProtocol.setDelegate(&myDelegate); // Pass the communication to wiThrottleProtocol - dccexProtocol.connect(&client); + dccexProtocol.connect(&dccTransport); Serial.println("DCC-EX connected"); dccexProtocol.requestServerVersion(); diff --git a/examples/DCCEXProtocol_Multi_Throttle_Control/DCCEXProtocol_Multi_Throttle_Control.ino b/examples/DCCEXProtocol_Multi_Throttle_Control/DCCEXProtocol_Multi_Throttle_Control.ino index acec9ff..91a2cd0 100644 --- a/examples/DCCEXProtocol_Multi_Throttle_Control/DCCEXProtocol_Multi_Throttle_Control.ino +++ b/examples/DCCEXProtocol_Multi_Throttle_Control/DCCEXProtocol_Multi_Throttle_Control.ino @@ -6,6 +6,7 @@ // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 // Luca Dentella, 2020 +#include #include #include @@ -18,6 +19,51 @@ #include "config.example.h" #endif +using namespace DCCExController; + +class ArduinoDCCMillis : public DCCMillis { +public: + unsigned long millis() const override { return ::millis(); } +}; + +class ArduinoDCCStream : public DCCStream { +public: + explicit ArduinoDCCStream(Stream &stream) : _stream(stream) {} + + int available() const override { + return const_cast(_stream).available(); + } + + int read() override { return _stream.read(); } + + size_t write(const uint8_t *buffer, size_t size) override { + return _stream.write(buffer, size); + } + + void flush() override { _stream.flush(); } + + void println(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.println(message); + } + + void print(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.print(message); + } + +private: + Stream &_stream; +}; + // Delegate class class MyDelegate : public DCCEXProtocolDelegate { @@ -99,7 +145,11 @@ unsigned long lastTime = 0; // Global objects WiFiClient client; -DCCEXProtocol dccexProtocol; +ArduinoDCCMillis dccMillis; +ArduinoDCCStream dccTransport(client); +ArduinoDCCStream dccLog(Serial); +DCCEXProtocol dccexProtocol(&dccMillis); + MyDelegate myDelegate; // Define an array for two throttles @@ -130,13 +180,13 @@ void setup() { Serial.println("Connected to the server"); // Logging on Serial - dccexProtocol.setLogStream(&Serial); + dccexProtocol.setLogStream(&dccLog); // Pass the delegate instance to wiThrottleProtocol dccexProtocol.setDelegate(&myDelegate); // Pass the communication to wiThrottleProtocol - dccexProtocol.connect(&client); + dccexProtocol.connect(&dccTransport); Serial.println("DCC-EX connected"); dccexProtocol.requestServerVersion(); diff --git a/examples/DCCEXProtocol_Roster_etc/DCCEXProtocol_Roster_etc.ino b/examples/DCCEXProtocol_Roster_etc/DCCEXProtocol_Roster_etc.ino index 9a5936c..072c31b 100644 --- a/examples/DCCEXProtocol_Roster_etc/DCCEXProtocol_Roster_etc.ino +++ b/examples/DCCEXProtocol_Roster_etc/DCCEXProtocol_Roster_etc.ino @@ -6,6 +6,7 @@ // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 // Luca Dentella, 2020 +#include #include #include @@ -18,6 +19,51 @@ #include "config.example.h" #endif +using namespace DCCExController; + +class ArduinoDCCMillis : public DCCMillis { +public: + unsigned long millis() const override { return ::millis(); } +}; + +class ArduinoDCCStream : public DCCStream { +public: + explicit ArduinoDCCStream(Stream &stream) : _stream(stream) {} + + int available() const override { + return const_cast(_stream).available(); + } + + int read() override { return _stream.read(); } + + size_t write(const uint8_t *buffer, size_t size) override { + return _stream.write(buffer, size); + } + + void flush() override { _stream.flush(); } + + void println(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.println(message); + } + + void print(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.print(message); + } + +private: + Stream &_stream; +}; + void printRoster(); void printTurnouts(); void printRoutes(); @@ -69,7 +115,10 @@ bool done = false; // Global objects WiFiClient client; -DCCEXProtocol dccexProtocol; +ArduinoDCCMillis dccMillis; +ArduinoDCCStream dccTransport(client); +ArduinoDCCStream dccLog(Serial); +DCCEXProtocol dccexProtocol(&dccMillis); MyDelegate myDelegate; void printRoster() { @@ -167,13 +216,13 @@ void setup() { Serial.println("Connected to the server"); // Enable logging on Serial - dccexProtocol.setLogStream(&Serial); + dccexProtocol.setLogStream(&dccLog); // Pass the delegate instance to wiThrottleProtocol dccexProtocol.setDelegate(&myDelegate); // Pass the communication to wiThrottleProtocol - dccexProtocol.connect(&client); + dccexProtocol.connect(&dccTransport); Serial.println("DCC-EX connected"); dccexProtocol.requestServerVersion(); diff --git a/examples/DCCEXProtocol_Serial/DCCEXProtocol_Serial.ino b/examples/DCCEXProtocol_Serial/DCCEXProtocol_Serial.ino index 74b9595..c23728e 100644 --- a/examples/DCCEXProtocol_Serial/DCCEXProtocol_Serial.ino +++ b/examples/DCCEXProtocol_Serial/DCCEXProtocol_Serial.ino @@ -6,6 +6,7 @@ // // Peter Cole (PeteGSX) 2024 +#include #include // If we haven't got a custom config.h, use the example @@ -24,6 +25,51 @@ #define CLIENT Serial1 // All DCCEXProtocol commands/responses/broadcasts use this #endif +using namespace DCCExController; + +class ArduinoDCCMillis : public DCCMillis { +public: + unsigned long millis() const override { return ::millis(); } +}; + +class ArduinoDCCStream : public DCCStream { +public: + explicit ArduinoDCCStream(Stream &stream) : _stream(stream) {} + + int available() const override { + return const_cast(_stream).available(); + } + + int read() override { return _stream.read(); } + + size_t write(const uint8_t *buffer, size_t size) override { + return _stream.write(buffer, size); + } + + void flush() override { _stream.flush(); } + + void println(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.println(message); + } + + void print(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.print(message); + } + +private: + Stream &_stream; +}; + // Declare functions to call from our delegate void printRoster(); void printTurnouts(); @@ -68,7 +114,7 @@ public: CONSOLE.println("\n\n"); } - void receivedScreenUpdate(int screen, int row, char *message) override { + void receivedScreenUpdate(int screen, int row, const char *message) override { CONSOLE.println("\n\nReceived screen|row|message"); CONSOLE.print(screen); CONSOLE.print("|"); @@ -80,7 +126,10 @@ public: }; // Global objects -DCCEXProtocol dccexProtocol; +ArduinoDCCMillis dccMillis; +ArduinoDCCStream dccTransport(CLIENT); +ArduinoDCCStream dccLog(CONSOLE); +DCCEXProtocol dccexProtocol(&dccMillis); MyDelegate myDelegate; void printRoster() { @@ -161,13 +210,13 @@ void setup() { CONSOLE.println(F("")); // Direct logs to CONSOLE - dccexProtocol.setLogStream(&CONSOLE); + dccexProtocol.setLogStream(&dccLog); // Set the delegate for broadcasts/responses dccexProtocol.setDelegate(&myDelegate); // Connect to the CS via CLIENT - dccexProtocol.connect(&CLIENT); + dccexProtocol.connect(&dccTransport); CONSOLE.println(F("DCC-EX connected")); dccexProtocol.requestServerVersion(); diff --git a/examples/DCCEXProtocol_Track_type/DCCEXProtocol_Track_type.ino b/examples/DCCEXProtocol_Track_type/DCCEXProtocol_Track_type.ino index c06a114..7b986f4 100644 --- a/examples/DCCEXProtocol_Track_type/DCCEXProtocol_Track_type.ino +++ b/examples/DCCEXProtocol_Track_type/DCCEXProtocol_Track_type.ino @@ -6,6 +6,7 @@ // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 // Luca Dentella, 2020 +#include #include #include @@ -18,6 +19,51 @@ #include "config.example.h" #endif +using namespace DCCExController; + +class ArduinoDCCMillis : public DCCMillis { +public: + unsigned long millis() const override { return ::millis(); } +}; + +class ArduinoDCCStream : public DCCStream { +public: + explicit ArduinoDCCStream(Stream &stream) : _stream(stream) {} + + int available() const override { + return const_cast(_stream).available(); + } + + int read() override { return _stream.read(); } + + size_t write(const uint8_t *buffer, size_t size) override { + return _stream.write(buffer, size); + } + + void flush() override { _stream.flush(); } + + void println(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.println(message); + } + + void print(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.print(message); + } + +private: + Stream &_stream; +}; + // Delegate class class MyDelegate : public DCCEXProtocolDelegate { @@ -34,7 +80,10 @@ public: // Global objects WiFiClient client; -DCCEXProtocol dccexProtocol; +ArduinoDCCMillis dccMillis; +ArduinoDCCStream dccTransport(client); +ArduinoDCCStream dccLog(Serial); +DCCEXProtocol dccexProtocol(&dccMillis); MyDelegate myDelegate; // for changes @@ -64,13 +113,13 @@ void setup() { } Serial.println("Connected to the server"); - dccexProtocol.setLogStream(&Serial); + dccexProtocol.setLogStream(&dccLog); // Pass the delegate instance to wiThrottleProtocol dccexProtocol.setDelegate(&myDelegate); // Pass the communication to wiThrottleProtocol - dccexProtocol.connect(&client); + dccexProtocol.connect(&dccTransport); Serial.println("DCC-EX connected"); } diff --git a/examples/DCCEXProtocol_Turnout_Control/DCCEXProtocol_Turnout_Control.ino b/examples/DCCEXProtocol_Turnout_Control/DCCEXProtocol_Turnout_Control.ino index 67e2d2f..c3a3713 100644 --- a/examples/DCCEXProtocol_Turnout_Control/DCCEXProtocol_Turnout_Control.ino +++ b/examples/DCCEXProtocol_Turnout_Control/DCCEXProtocol_Turnout_Control.ino @@ -8,6 +8,7 @@ // Peter Akers (Flash62au), Peter Cole (PeteGSX) and Chris Harlow (UKBloke), 2023 // Luca Dentella, 2020 +#include #include #include @@ -20,6 +21,51 @@ #include "config.example.h" #endif +using namespace DCCExController; + +class ArduinoDCCMillis : public DCCMillis { +public: + unsigned long millis() const override { return ::millis(); } +}; + +class ArduinoDCCStream : public DCCStream { +public: + explicit ArduinoDCCStream(Stream &stream) : _stream(stream) {} + + int available() const override { + return const_cast(_stream).available(); + } + + int read() override { return _stream.read(); } + + size_t write(const uint8_t *buffer, size_t size) override { + return _stream.write(buffer, size); + } + + void flush() override { _stream.flush(); } + + void println(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.println(message); + } + + void print(const char *format, ...) override { + char message[160]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + _stream.print(message); + } + +private: + Stream &_stream; +}; + void printTurnouts(); // Delegate class @@ -57,7 +103,10 @@ Turnout *turnout2 = nullptr; // Global objects WiFiClient client; -DCCEXProtocol dccexProtocol; +ArduinoDCCMillis dccMillis; +ArduinoDCCStream dccTransport(client); +ArduinoDCCStream dccLog(Serial); +DCCEXProtocol dccexProtocol(&dccMillis); MyDelegate myDelegate; void printTurnouts() { @@ -96,13 +145,13 @@ void setup() { Serial.println("Connected to the server"); // Logging on Serial - dccexProtocol.setLogStream(&Serial); + dccexProtocol.setLogStream(&dccLog); // Pass the delegate instance to wiThrottleProtocol dccexProtocol.setDelegate(&myDelegate); // Pass the communication to wiThrottleProtocol - dccexProtocol.connect(&client); + dccexProtocol.connect(&dccTransport); Serial.println("DCC-EX connected"); dccexProtocol.requestServerVersion(); diff --git a/idf_component.yml b/idf_component.yml new file mode 100644 index 0000000..701a01a --- /dev/null +++ b/idf_component.yml @@ -0,0 +1,15 @@ +version: 0.1.0 +name: DCCEXProtocol +description: "DCCEXProtocol library for ESP-IDF" +# dependencies: + # Add ESP-IDF components your library depends on, for example: + # idf: "driver" + # idf: "esp_timer" + # idf: "freertos" + # idf: "cxx" + # Uncomment and add as needed +targets: + - esp32 + - esp32s2 + - esp32s3 + - esp32c3 \ No newline at end of file diff --git a/src/DCCEXCSConsist.cpp b/src/DCCEXCSConsist.cpp index 7a70981..7a99e81 100644 --- a/src/DCCEXCSConsist.cpp +++ b/src/DCCEXCSConsist.cpp @@ -23,6 +23,7 @@ #include "DCCEXCSConsist.h" // CSConsist public methods +namespace DCCExController { CSConsist *CSConsist::_first = nullptr; bool CSConsist::_alwaysReplicateFunctions = false; @@ -215,3 +216,5 @@ CSConsist::~CSConsist() { } } } + +} // namespace DCCExController \ No newline at end of file diff --git a/src/DCCEXCSConsist.h b/src/DCCEXCSConsist.h index b8c439f..2d7b982 100644 --- a/src/DCCEXCSConsist.h +++ b/src/DCCEXCSConsist.h @@ -24,8 +24,9 @@ #define DCCEXCSCONSIST_H #include "DCCEXLoco.h" -#include +#include +namespace DCCExController { /** * @brief Structure for a CSConsistMember */ @@ -202,5 +203,6 @@ class CSConsist { static CSConsist *_first; static bool _alwaysReplicateFunctions; }; +} // namespace DCCExController #endif // DCCEXCSCONSIST_H diff --git a/src/DCCEXInbound.cpp b/src/DCCEXInbound.cpp index d5264a1..b70db76 100644 --- a/src/DCCEXInbound.cpp +++ b/src/DCCEXInbound.cpp @@ -33,12 +33,15 @@ // The dump() function is used to list the parameters obtained. // so this is the best place to look for how to access the results. #include "DCCEXInbound.h" -#include +#include +#include +namespace DCCExController { + // Internal stuff for the parser and getters. const int32_t QUOTE_FLAG = 0x77777000; const int32_t QUOTE_FLAG_AREA = 0xFFFFF000; -enum splitState : byte { +enum splitState : uint8_t { FIND_START, SET_OPCODE, SKIP_SPACES, @@ -50,7 +53,7 @@ enum splitState : byte { int16_t DCCEXInbound::_maxParams = 0; int16_t DCCEXInbound::_parameterCount = 0; -byte DCCEXInbound::_opcode = 0; +uint8_t DCCEXInbound::_opcode = 0; int32_t *DCCEXInbound::_parameterValues = nullptr; char *DCCEXInbound::_cmdBuffer = nullptr; @@ -70,7 +73,7 @@ void DCCEXInbound::cleanup() { } } -byte DCCEXInbound::getOpcode() { return _opcode; } +uint8_t DCCEXInbound::getOpcode() { return _opcode; } int16_t DCCEXInbound::getParameterCount() { return _parameterCount; } @@ -116,7 +119,7 @@ bool DCCEXInbound::parse(char *command) { splitState state = FIND_START; while (_parameterCount < _maxParams) { - byte hot = *remainingCmd; + uint8_t hot = *remainingCmd; if (hot == 0) return false; // no > on end of command. @@ -199,30 +202,31 @@ bool DCCEXInbound::parse(char *command) { return false; // we ran out of max parameters } -void DCCEXInbound::dump(Print *out) { - out->print(F("\nDCCEXInbound Opcode='")); - if (_opcode) - out->write(_opcode); - else - out->print(F("\\0")); - out->println('\''); - - for (int i = 0; i < getParameterCount(); i++) { - if (isTextParameter(i)) { - out->print(F("getTextParameter(")); - out->print(i); - out->print(F(")=\"")); - out->print(getTextParameter(i)); - out->println('"'); - } else { - out->print(F("getNumber(")); - out->print(i); - out->print(F(")=")); - out->println(getNumber(i)); - } - } -} +// void DCCEXInbound::dump(Print *out) { +// out->print(F("\nDCCEXInbound Opcode='")); +// if (_opcode) +// out->write(_opcode); +// else +// out->print(F("\\0")); +// out->println('\''); + +// for (int i = 0; i < getParameterCount(); i++) { +// if (isTextParameter(i)) { +// out->print(F("getTextParameter(")); +// out->print(i); +// out->print(F(")=\"")); +// out->print(getTextParameter(i)); +// out->println('"'); +// } else { +// out->print(F("getNumber(")); +// out->print(i); +// out->print(F(")=")); +// out->println(getNumber(i)); +// } +// } +// } // Private methods bool DCCEXInbound::_isTextInternal(int16_t n) { return ((_parameterValues[n] & QUOTE_FLAG_AREA) == QUOTE_FLAG); } +} // namespace DCCExController \ No newline at end of file diff --git a/src/DCCEXInbound.h b/src/DCCEXInbound.h index 9d60a00..d9d2cab 100644 --- a/src/DCCEXInbound.h +++ b/src/DCCEXInbound.h @@ -30,7 +30,7 @@ #ifndef DCCEXINBOUND_H #define DCCEXINBOUND_H -#include +#include /* How to use this: 1) setup is done once with your expected max parameter count. @@ -40,7 +40,8 @@ 3) Use the get... functions to access the parameters. These parameters are ONLY VALID until you next call parse. */ - +namespace DCCExController { + /// @brief Inbound DCC-EX command parser class to parse commands and provide interpreted parameters class DCCEXInbound { public: @@ -58,7 +59,7 @@ class DCCEXInbound { static bool parse(char *command); /// @brief Gets the DCC-EX OPCODE of the parsed command (the first char after the <) - static byte getOpcode(); + static uint8_t getOpcode(); /// @brief Gets number of parameters detected after OPCODE is 4 parameters! /// @return Number of parameters @@ -86,15 +87,15 @@ class DCCEXInbound { /// @brief dump list of parameters obtained /// @param out Address of output e.g. &Serial - static void dump(Print *); + // static void dump(Print *); private: static int16_t _maxParams; static int16_t _parameterCount; - static byte _opcode; + static uint8_t _opcode; static int32_t *_parameterValues; static char *_cmdBuffer; static bool _isTextInternal(int16_t n); }; - +} // namespace DCCExController #endif diff --git a/src/DCCEXLoco.cpp b/src/DCCEXLoco.cpp index 69c1547..794b710 100644 --- a/src/DCCEXLoco.cpp +++ b/src/DCCEXLoco.cpp @@ -28,8 +28,12 @@ */ #include "DCCEXLoco.h" -#include +#include +#include +#include +#include +namespace DCCExController { // class Loco // Public methods @@ -333,9 +337,9 @@ void Consist::addLoco(int address, Facing facing) { if (_locoCount == 0) { facing = FacingForward; if (_name == nullptr) { - int addressLength = (address == 0) ? 1 : log10(address) + 1; + int addressLength = snprintf(nullptr, 0, "%d", address); char *newName = new char[addressLength + 1]; - itoa(address, newName, 10); + snprintf(newName, addressLength + 1, "%d", address); setName(newName); delete[] newName; } @@ -482,3 +486,4 @@ void Consist::_addLocoToConsist(ConsistLoco *conLoco) { } _locoCount++; } +} // namespace DCCExController \ No newline at end of file diff --git a/src/DCCEXLoco.h b/src/DCCEXLoco.h index e580ebb..dde2aea 100644 --- a/src/DCCEXLoco.h +++ b/src/DCCEXLoco.h @@ -30,8 +30,10 @@ #ifndef DCCEXLOCO_H #define DCCEXLOCO_H -#include +#include +namespace DCCExController { + static const int MAX_FUNCTIONS = 32; const int MAX_OBJECT_NAME_LENGTH = 30; // including Loco name, Turnout/Point names, Route names, etc. names #define MAX_SINGLE_COMMAND_PARAM_LENGTH 500 // Unfortunately includes the function list for an individual loco @@ -359,4 +361,5 @@ class Consist { void _addLocoToConsist(ConsistLoco *consistLoco); }; +} // namespace DCCExController #endif \ No newline at end of file diff --git a/src/DCCEXProtocol.cpp b/src/DCCEXProtocol.cpp index bb58db5..0e49fdc 100644 --- a/src/DCCEXProtocol.cpp +++ b/src/DCCEXProtocol.cpp @@ -41,6 +41,14 @@ Function/method prefixes #include "DCCEXProtocol.h" +#include +#include +#include +#include +#include + +namespace DCCExController { + static const int MIN_SPEED = 0; static const int MAX_SPEED = 126; @@ -48,8 +56,13 @@ static const int MAX_SPEED = 126; // Public methods // Protocol and server methods -DCCEXProtocol::DCCEXProtocol(int maxCmdBuffer, int maxCommandParams, unsigned long userChangeDelay) { +DCCEXProtocol::DCCEXProtocol(DCCMillis *millisProvider, int maxCmdBuffer, int maxCommandParams, unsigned long userChangeDelay) { // Init streams + if(millisProvider == nullptr){ + // Cannot proceed without a millis provider, so do not proceed + while(true); + } + _millisProvider = millisProvider; _stream = &_nullStream; _console = &_nullStream; @@ -87,16 +100,21 @@ DCCEXProtocol::~DCCEXProtocol() { void DCCEXProtocol::setDelegate(DCCEXProtocolDelegate *delegate) { this->_delegate = delegate; } // Set the Stream used for logging -void DCCEXProtocol::setLogStream(Stream *console) { this->_console = console; } +void DCCEXProtocol::setLogStream(DCCStream *console) { this->_console = console; } void DCCEXProtocol::enableHeartbeat(unsigned long heartbeatDelay) { _enableHeartbeat = true; _heartbeatDelay = heartbeatDelay; } -void DCCEXProtocol::connect(Stream *stream) { - _init(); +void DCCEXProtocol::connect(DCCStream *stream) { + if(!stream){ + // Cannot connect without a stream, so do not proceed + return; + } + this->_stream = stream; + _init(); } void DCCEXProtocol::disconnect() { return; } @@ -762,7 +780,7 @@ void DCCEXProtocol::_init() { memset(_inputBuffer, 0, sizeof(_inputBuffer)); _nextChar = 0; // last Response time - _lastServerResponseTime = millis(); + _lastServerResponseTime = _millisProvider->millis(); } void DCCEXProtocol::_sendCommand() { @@ -773,13 +791,13 @@ void DCCEXProtocol::_sendCommand() { _console->println(_outboundCommand); } *_outboundCommand = 0; // clear it once it has been sent - _lastHeartbeat = millis(); // If we sent a command, a heartbeat isn't necessary + _lastHeartbeat = _millisProvider->millis(); // If we sent a command, a heartbeat isn't necessary } } void DCCEXProtocol::_processCommand() { // last Response time - _lastServerResponseTime = millis(); + _lastServerResponseTime = _millisProvider->millis(); switch (DCCEXInbound::getOpcode()) { case '@': // Screen update @@ -980,8 +998,8 @@ void DCCEXProtocol::_processScreenUpdate() { //<@ screen row "message"> } void DCCEXProtocol::_sendHeartbeat() { - if (millis() - _lastHeartbeat > _heartbeatDelay) { - _lastHeartbeat = millis(); + if (_millisProvider->millis() - _lastHeartbeat > _heartbeatDelay) { + _lastHeartbeat = _millisProvider->millis(); _sendOpcode('#'); } } @@ -1069,8 +1087,8 @@ void DCCEXProtocol::_processReadResponse() { // - -1 = error } void DCCEXProtocol::_processPendingUserChanges() { - if (millis() - _lastUserChange > _userChangeDelay) { - _lastUserChange = millis(); + if (_millisProvider->millis() - _lastUserChange > _userChangeDelay) { + _lastUserChange = _millisProvider->millis(); _setLocos(Loco::getFirst()); _setLocos(Loco::getFirstLocalLoco()); } @@ -1577,7 +1595,7 @@ void DCCEXProtocol::_cmdAppend(const char *s) { void DCCEXProtocol::_cmdAppend(int n) { char buf[12]; // Enough for -2147483648 - itoa(n, buf, 10); + snprintf(buf, sizeof(buf), "%d", n); _cmdAppend(buf); } @@ -1722,3 +1740,5 @@ void DCCEXProtocol::_sendFourParams(char opcode, int param1, int param2, int par _cmdAppend(param4); _cmdSend(); } + +}; // namespace DCCEX \ No newline at end of file diff --git a/src/DCCEXProtocol.h b/src/DCCEXProtocol.h index 2efd281..0a9cf7b 100644 --- a/src/DCCEXProtocol.h +++ b/src/DCCEXProtocol.h @@ -44,9 +44,14 @@ Version information: MOVED TO DCCEXProtocolVersion.h #include "DCCEXProtocolVersion.h" #include "DCCEXRoutes.h" #include "DCCEXTurnouts.h" +#include "DCCMillis.h" #include "DCCEXTurntables.h" -#include +#include "DCCStream.h" +#include + +namespace DCCExController { + const int MAX_OUTBOUND_COMMAND_LENGTH = 100; // Max number of bytes for outbound commands // Valid track power state values @@ -72,14 +77,14 @@ enum MomentumAlgorithm { }; /// @brief Nullstream class for initial DCCEXProtocol instantiation to direct streams to nothing -class NullStream : public Stream { +class NullStream : public DCCStream { public: /// @brief Constructor for the NullStream object NullStream() {} /// @brief Dummy availability check /// @return Returns false (0) always - int available() { return 0; } + int available() const { return 0; } /// @brief Dummy flush method void flush() {} @@ -102,6 +107,9 @@ class NullStream : public Stream { /// @param size Size of buffer /// @return Returns size of buffer always size_t write(const uint8_t *buffer, size_t size) { return size; } + + void println(const char *format, ...) {} + void print(const char *format, ...) {} }; /// @brief Delegate responses and broadcast events to the client software to enable custom event handlers @@ -243,7 +251,7 @@ class DCCEXProtocol { /// @param maxCmdBuffer Optional - maximum number of bytes for the command buffer (default 500) /// @param maxCommandParams Optional - maximum number of parameters to parse via the DCCEXInbound parser (default 50) /// @param userChangeDelay Optional - time in ms between sending throttle changes (default 100) - DCCEXProtocol(int maxCmdBuffer = 500, int maxCommandParams = 50, unsigned long userChangeDelay = 100); + DCCEXProtocol(DCCMillis *millisProvider, int maxCmdBuffer = 500, int maxCommandParams = 50, unsigned long userChangeDelay = 100); /// @brief Destructor for the DCCEXProtocol object ~DCCEXProtocol(); @@ -254,7 +262,7 @@ class DCCEXProtocol { /// @brief Set the stream object for console output /// @param console - void setLogStream(Stream *console); + void setLogStream(DCCStream *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) @@ -262,7 +270,7 @@ class DCCEXProtocol { /// @brief Connect the stream object to interact with DCC-EX /// @param stream - void connect(Stream *stream); + void connect(DCCStream *stream); /// @brief DEPRECATED - Does nothing, retained for backwards compatibility only /// @details Will be removed in 2.0.0. @@ -893,8 +901,8 @@ class DCCEXProtocol { int _routeCount = 0; // Count of route objects received int _turntableCount = 0; // Count of turntable objects received int _version[3] = {}; // EX-CommandStation version x.y.z - Stream *_stream; // Stream object where commands are sent/received - Stream *_console; // Stream object for console output + DCCStream *_stream; // Stream object where commands are sent/received + DCCStream *_console; // Stream object for console output NullStream _nullStream; // Send streams to null if no object provided int _bufflen; // Used to ensure command buffer size not exceeded int _maxCmdBuffer; // Max size for the command buffer @@ -921,6 +929,7 @@ class DCCEXProtocol { unsigned long _userChangeDelay; // Delay in ms between sending throttle commands unsigned long _lastUserChange; // Time in ms of the last throttle command bool _debug = false; // Enable output of send/receive commands to console + DCCMillis *_millisProvider; // Pointer to a DCCMillis provider for time functions // Helper methods to build the outbound command /** @@ -1066,4 +1075,7 @@ class DCCEXProtocol { void _sendFourParams(char opcode, int param1, int param2, int param3, int param4); }; +} // namespace DCCExController + #endif // DCCEXPROTOCOL_H + diff --git a/src/DCCEXRoutes.cpp b/src/DCCEXRoutes.cpp index 4a006f3..3e1ae9e 100644 --- a/src/DCCEXRoutes.cpp +++ b/src/DCCEXRoutes.cpp @@ -27,10 +27,12 @@ */ #include "DCCEXRoutes.h" -#include -// Public methods +#include +// Public methods +namespace DCCExController { + Route *Route::_first = nullptr; Route::Route(int id) { @@ -140,3 +142,4 @@ void Route::_removeFromList(Route *route) { } } } +} // namespace DCCExController \ No newline at end of file diff --git a/src/DCCEXRoutes.h b/src/DCCEXRoutes.h index e0b29b8..291de24 100644 --- a/src/DCCEXRoutes.h +++ b/src/DCCEXRoutes.h @@ -29,8 +29,8 @@ #ifndef DCCEXROUTES_H #define DCCEXROUTES_H -#include - +namespace DCCExController { + enum RouteType { RouteTypeRoute = 'R', RouteTypeAutomation = 'A', @@ -96,5 +96,5 @@ class Route { /// @param route Pointer to the route to remove void _removeFromList(Route *route); }; - +} // namespace DCCExController #endif \ No newline at end of file diff --git a/src/DCCEXTurnouts.cpp b/src/DCCEXTurnouts.cpp index 53d2bf3..1645887 100644 --- a/src/DCCEXTurnouts.cpp +++ b/src/DCCEXTurnouts.cpp @@ -27,8 +27,11 @@ */ #include "DCCEXTurnouts.h" -#include +#include + +namespace DCCExController { + Turnout *Turnout::_first = nullptr; Turnout::Turnout(int id, bool thrown) { @@ -139,3 +142,4 @@ void Turnout::_removeFromList(Turnout *turnout) { } } } +} // namespace DCCExController \ No newline at end of file diff --git a/src/DCCEXTurnouts.h b/src/DCCEXTurnouts.h index 7ef5b05..e7593a5 100644 --- a/src/DCCEXTurnouts.h +++ b/src/DCCEXTurnouts.h @@ -29,8 +29,8 @@ #ifndef DCCEXTURNOUTS_H #define DCCEXTURNOUTS_H -#include - +namespace DCCExController { + /// @brief Class to contain and maintain the various Turnout/Point attributes and methods class Turnout { public: @@ -94,4 +94,6 @@ class Turnout { void _removeFromList(Turnout *turnout); }; +} // namespace DCCExController + #endif \ No newline at end of file diff --git a/src/DCCEXTurntables.cpp b/src/DCCEXTurntables.cpp index 67bb092..8b1dbbd 100644 --- a/src/DCCEXTurntables.cpp +++ b/src/DCCEXTurntables.cpp @@ -27,10 +27,11 @@ */ #include "DCCEXTurntables.h" -#include +#include // class TurntableIndex - +namespace DCCExController { + TurntableIndex::TurntableIndex(int ttId, int id, int angle, const char *name) { _ttId = ttId; _id = id; @@ -231,3 +232,4 @@ void Turntable::_removeFromList(Turntable *turntable) { } } } +} // namespace DCCExController \ No newline at end of file diff --git a/src/DCCEXTurntables.h b/src/DCCEXTurntables.h index d5377b0..cea026d 100644 --- a/src/DCCEXTurntables.h +++ b/src/DCCEXTurntables.h @@ -29,8 +29,8 @@ #ifndef DCCEXTURNTABLES_H #define DCCEXTURNTABLES_H -#include - +namespace DCCExController { + enum TurntableType { TurntableTypeDCC = 0, TurntableTypeEXTT = 1, @@ -187,5 +187,6 @@ class Turntable { /// @param turntable Pointer to the turntable to remove void _removeFromList(Turntable *turntable); }; +} // namespace DCCExController #endif \ No newline at end of file diff --git a/src/DCCMillis.h b/src/DCCMillis.h new file mode 100644 index 0000000..4e7492b --- /dev/null +++ b/src/DCCMillis.h @@ -0,0 +1,52 @@ +/* -*- c++ -*- + * + * DCCEXProtocol + * + * This package implements a DCCEX native protocol connection, + * allow a device to communicate with a DCC-EX EX-CommandStation. + * + * Copyright © 2023 Chris Harlow + * Copyright © 2023 Peter Akers + * Copyright © 2023 Peter Cole + * + * This work is licensed under the Creative Commons Attribution-ShareAlike + * 4.0 International License. To view a copy of this license, visit + * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * Attribution — You must give appropriate credit, provide a link to the + * license, and indicate if changes were made. You may do so in any + * reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * ShareAlike — If you remix, transform, or build upon the material, you + * must distribute your contributions under the same license as the + * original. + * + * All other rights reserved. + * + */ + +#ifndef DCCMILLIS_H +#define DCCMILLIS_H + +/* to remove the dependance on Arduino's millis() function + * this file provides an abstract interface for a millis() function + */ + +namespace DCCExController { + +class DCCMillis +{ +public: + virtual ~DCCMillis() = default; + /** + * @brief Get the current time in milliseconds + * @return Time in milliseconds + */ + virtual unsigned long millis() const = 0; +}; + +}; // namespace DCCExController + +#endif // DCCMILLIS_H \ No newline at end of file diff --git a/src/DCCStream.h b/src/DCCStream.h new file mode 100644 index 0000000..a23472d --- /dev/null +++ b/src/DCCStream.h @@ -0,0 +1,23 @@ +#ifndef _STREAM_H +#define _STREAM_H + +#include +#include + +namespace DCCExController { + + class DCCStream { +public: + virtual ~DCCStream() {} + + virtual int available() const = 0; + virtual int read() = 0; + virtual size_t write(const uint8_t *buffer, size_t size) = 0; + virtual void flush() = 0; + virtual void println(const char* format, ...) = 0; + virtual void print(const char* format, ...) = 0; +}; + +}; + +#endif \ No newline at end of file diff --git a/targets/ESPIDF/ESP_Millis.h b/targets/ESPIDF/ESP_Millis.h new file mode 100644 index 0000000..71b7c56 --- /dev/null +++ b/targets/ESPIDF/ESP_Millis.h @@ -0,0 +1,14 @@ +#ifndef _ESP_MILLIS_H +#define _ESP_MILLIS_H + +#include +#include + +inline uint64_t millis() { return esp_timer_get_time() / 1000ULL; } + +class ESPDCCMillis : public DCCExController::DCCMillis { +public: + unsigned long millis() const override { return ::millis(); } +}; + +#endif \ No newline at end of file diff --git a/targets/ESPIDF/wifi_connection.cpp b/targets/ESPIDF/wifi_connection.cpp new file mode 100644 index 0000000..65511bc --- /dev/null +++ b/targets/ESPIDF/wifi_connection.cpp @@ -0,0 +1,5 @@ +#include "wifi_connection.h" + +namespace utilities { +QueueHandle_t tcp_fail_queue = xQueueCreate(10, sizeof(err_t)); +} // namespace utilities \ No newline at end of file diff --git a/targets/ESPIDF/wifi_connection.h b/targets/ESPIDF/wifi_connection.h new file mode 100644 index 0000000..a3de919 --- /dev/null +++ b/targets/ESPIDF/wifi_connection.h @@ -0,0 +1,255 @@ +#ifndef _WIFI_CONNECTION_H +#define _WIFI_CONNECTION_H + +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include +#include +#include // For get_absolute_time(), to_ms_since_boot() +#include +#include +#include +#include + +// Declare a queue handle +namespace utilities { +extern QueueHandle_t tcp_fail_queue; + +class TCPSocketStream : public DCCExController::DCCStream { +private: + struct tcp_pcb *pcb; + struct pbuf *recv_buffer; + bool failed = false; + err_t err; + + // Heartbeat tracking + uint64_t heartbeat_sent_time = 0; + bool awaiting_heartbeat = false; + + static err_t recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { + TCPSocketStream *stream = static_cast(arg); + if (p == nullptr) { + // Connection closed + printf("Connection closed by remote host\n"); + tcp_close(tpcb); + stream->pcb = nullptr; + pbuf_free(p); + stream->failed = true; + stream->err = err; + // Notify main core of TCP failure + xQueueSendToBack(tcp_fail_queue, &stream->err, portMAX_DELAY); + return ERR_ABRT; + } + if (err == ERR_OK) { + if (stream->recv_buffer == nullptr) { + stream->recv_buffer = p; + } else { + pbuf_chain(stream->recv_buffer, p); // Safely chain the new buffer + } + uint8_t *data = (uint8_t *)p->payload; + if (stream->awaiting_heartbeat && data[0] == '<') { + stream->awaiting_heartbeat = false; + } + } else { + pbuf_free(p); + } + return ERR_OK; + } + +public: + explicit TCPSocketStream(struct tcp_pcb *pcb) : pcb(pcb), recv_buffer(nullptr) { + // Create a queue (example) + + tcp_recv(pcb, recv_callback); + tcp_arg(pcb, this); + + // Enable keepalive + pcb->keep_idle = 5000; // ms + pcb->keep_intvl = 1000; // ms + pcb->keep_cnt = 5; + pcb->so_options |= SOF_KEEPALIVE; + tcp_keepalive(pcb); // idle, interval, count (values in ms) + } + + // Check if data is available to read + int available() const { return recv_buffer ? recv_buffer->tot_len : 0; } + + // Read a single byte from the socket + int read() { + LOCK_TCPIP_CORE(); + if (recv_buffer == nullptr) { + UNLOCK_TCPIP_CORE(); + return -1; // No data + } + uint8_t byte = *static_cast(recv_buffer->payload); + pbuf_remove_header(recv_buffer, 1); + if (recv_buffer->len == 0) { + struct pbuf *next = recv_buffer->next; + pbuf_free(recv_buffer); + recv_buffer = next; + } + UNLOCK_TCPIP_CORE(); + return byte; + } + + // Write a buffer to the socket + size_t write(const uint8_t *buffer, size_t size) { + if (failed) { + return -1; // Already failed + } + LOCK_TCPIP_CORE(); + err_t err = tcp_write(pcb, buffer, size, TCP_WRITE_FLAG_COPY); + if (err == ERR_OK) { + tcp_output(pcb); + UNLOCK_TCPIP_CORE(); + return size; + } + UNLOCK_TCPIP_CORE(); + printf("Error writing to TCP socket: %d\n", err); + failed = true; + // Notify main core of TCP failure + xQueueSendToBack(tcp_fail_queue, &err, portMAX_DELAY); + return err; // Error + } + + // No-op for sockets (no explicit flushing needed) + void flush() {} + + // Send a string with a newline + void println(const char *format, ...) { + char buffer[256]; + + va_list args; // Declare the variable argument list + + // Initialize the variable argument list + va_start(args, format); + + // Use vsnprintf to format the string into the buffer + vsnprintf(buffer, sizeof(buffer), format, args); + + // End the variable argument list + va_end(args); + + write(reinterpret_cast(buffer), strlen(buffer)); + write(reinterpret_cast("\n"), 1); + + if (strcmp(format, "<#>") == 0) { + notifyHeartbeatSent(); + } + } + + // Send a string + void print(const char *format, ...) { + char buffer[256]; + + va_list args; // Declare the variable argument list + + // Initialize the variable argument list + va_start(args, format); + + // Use vsnprintf to format the string into the buffer + vsnprintf(buffer, sizeof(buffer), format, args); + + // End the variable argument list + va_end(args); + + write(reinterpret_cast(buffer), strlen(buffer)); + } + + // Call this externally when <#> is sent + void notifyHeartbeatSent() { + heartbeat_sent_time = esp_timer_get_time(); + awaiting_heartbeat = true; + } + + // Call this periodically (e.g. in your main loop) + void checkHeartbeatTimeout() { + if (awaiting_heartbeat) { + int64_t elapsed = esp_timer_get_time() - heartbeat_sent_time; + if (elapsed > 10000000) { // 10 seconds + printf("Heartbeat timeout\n"); + awaiting_heartbeat = false; + err_t timeout_err = ERR_TIMEOUT; + xQueueSendToBack(tcp_fail_queue, &timeout_err, portMAX_DELAY); + } + } + } + + bool isFailed() { return failed; } + + // Destructor to close the socket + ~TCPSocketStream() { + LOCK_TCPIP_CORE(); + if (pcb != nullptr) { + tcp_close(pcb); + pcb = nullptr; + } + if (recv_buffer != nullptr) { + pbuf_free(recv_buffer); + recv_buffer = nullptr; + } + UNLOCK_TCPIP_CORE(); + } +}; + +class LoggingStream : public DCCExController::DCCStream { + +public: + explicit LoggingStream(struct tcp_pcb *pcb) {} + + // Check if data is available to read + int available() const { return 0; } + + // Read a single byte from the socket + int read() { return 0; } + + // Write a buffer to the socket + size_t write(const uint8_t *buffer, size_t size) { + return 0; // Error + } + + // No-op for sockets (no explicit flushing needed) + void flush() {} + + // Send a string with a newline + void println(const char *format, ...) { + char buffer[256]; + + va_list args; // Declare the variable argument list + + // Initialize the variable argument list + va_start(args, format); + + // Use vsnprintf to format the string into the buffer + vsnprintf(buffer, sizeof(buffer), format, args); + + // End the variable argument list + va_end(args); + + printf(buffer); + printf("\n"); + } + + // Send a string + void print(const char *format, ...) { + char buffer[256]; + + va_list args; // Declare the variable argument list + + // Initialize the variable argument list + va_start(args, format); + + // Use vsnprintf to format the string into the buffer + vsnprintf(buffer, sizeof(buffer), format, args); + + // End the variable argument list + va_end(args); + + printf(buffer); + } + + // Destructor to close the socket + ~LoggingStream() {} +}; +} // namespace utilities +#endif \ No newline at end of file diff --git a/targets/arduino/ArduinoStream.h b/targets/arduino/ArduinoStream.h new file mode 100644 index 0000000..418e12c --- /dev/null +++ b/targets/arduino/ArduinoStream.h @@ -0,0 +1,31 @@ +#ifndef ARDUINOSTREAM_H +#define ARDUINOSTREAM_H +#include "../../src/DCCStream.h" +#include +#include + +class ArduinoStream : public DCCExController::DCCStream { + private: + WiFiClient &client; + public: + + ArduinoStream(WifiClient &client): client(client){ + + } + ~ArduinoStream() {} + + int available() { return client.available(); } + int read() {return client.read();} + size_t write(const uint8_t *buffer, size_t size){ + return client.write(buffer,size); + }; + void flush() {return client.flush(); }; + void println(const char *format, ...){ + Serial.println(format, ...); + }; + void print(const char *format, ...){ + Serial.print(format, ...); + }; +}; + +#endif // ARDUINOSTREAM_H \ No newline at end of file diff --git a/test/mocks/Arduino.h b/test/mocks/Arduino.h deleted file mode 100644 index b2af7ab..0000000 --- a/test/mocks/Arduino.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * © 2025 Peter Cole - * - * 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 this code. If not, see . - */ - -/** - * @brief Mock Arduino.h to ensure source files requiring this still function - */ - -#ifndef ARDUINO_H -#define ARDUINO_H - -#include "Print.h" -#include "Stream.h" -#include -#include -#include - -// Define states -#define HIGH 0x1 -#define LOW 0x0 - -// Define pin modes -#define INPUT 0x0 -#define OUTPUT 0x1 -#define INPUT_PULLUP 0x2 - -// Define F() macro for FlashStringHelper -#define F(str) (str) - -// Define common Arduino types -typedef uint8_t byte; - -// Declare micros so it can be mocked/returned -inline unsigned long _currentMicros = 0; - -// Declare millis so it can be mocked/returned -inline unsigned long _currentMillis = 0; - -// Define a mock class to enable EXPECT_CALL tests against these -class MockArduino { -public: - MOCK_METHOD(void, mockPinMode, (int pin, int mode), ()); - MOCK_METHOD(void, mockDigitalWrite, (int pin, int value), ()); - - // Set up a singleton instance for this class to work with gmock - static MockArduino &getInstance() { - static MockArduino instance; - return instance; - } -}; - -// Mock Arduino functions -inline void pinMode(int pin, int mode) { MockArduino::getInstance().mockPinMode(pin, mode); } -inline void digitalWrite(int pin, int value) { MockArduino::getInstance().mockDigitalWrite(pin, value); } -inline int digitalRead(int pin) { return 0; } -inline void delay(unsigned long ms) {} -inline unsigned long micros() { return _currentMicros; } -inline unsigned long millis() { return _currentMillis; } -inline void analogWrite(int pin, int value) {} -inline int analogRead(int pin) { return 0; } - -inline void advanceMicros(unsigned long us) { _currentMicros += us; } -inline void advanceMillis(unsigned long ms) { _currentMillis += ms; } - -inline void resetMicros() { _currentMicros = 0; } -inline void resetMillis() { _currentMillis = 0; } - -// Mock itoa: converts integer to string -inline char *itoa(int value, char *str, int base) { - if (base == 10) { - sprintf(str, "%d", value); - } else if (base == 16) { - sprintf(str, "%x", value); - } - return str; -} - -// Mock ltoa: converts long integer to string -inline char *ltoa(long value, char *str, int base) { - if (base == 10) { - sprintf(str, "%ld", value); - } else if (base == 16) { - sprintf(str, "%lx", value); - } - return str; -} - -// Mock utoa: converts unsigned int to string -inline char *utoa(unsigned int value, char *str, int base) { - if (base == 10) { - sprintf(str, "%u", value); - } - return str; -} - -#endif // ARDUINO_H diff --git a/test/mocks/MockDCCEXProtocolDelegate.h b/test/mocks/MockDCCEXProtocolDelegate.h index d846df0..795aaf1 100644 --- a/test/mocks/MockDCCEXProtocolDelegate.h +++ b/test/mocks/MockDCCEXProtocolDelegate.h @@ -1,6 +1,8 @@ #include #include +using namespace DCCExController; + class MockDCCEXProtocolDelegate : public DCCEXProtocolDelegate { public: // Notify when the server version has been received @@ -36,6 +38,9 @@ class MockDCCEXProtocolDelegate : public DCCEXProtocolDelegate { // Notify when a track current is received MOCK_METHOD(void, receivedTrackCurrent, (char track, int current), (override)); + // Notify when an individual track power state change is received + MOCK_METHOD(void, receivedIndividualTrackPower, (TrackPower, int), (override)); + // Notify when a track type change is received MOCK_METHOD(void, receivedTrackType, (char, TrackManagerMode, int), (override)); @@ -71,4 +76,5 @@ class MockDCCEXProtocolDelegate : public DCCEXProtocolDelegate { // Notify when a fast clock time has been received MOCK_METHOD(void, receivedFastClockTime, (int minutes), (override)); + }; \ No newline at end of file diff --git a/test/mocks/MockDCCMillis.h b/test/mocks/MockDCCMillis.h new file mode 100644 index 0000000..aa2f515 --- /dev/null +++ b/test/mocks/MockDCCMillis.h @@ -0,0 +1,12 @@ +#ifndef MOCK_DCCMILLIS_H +#define MOCK_DCCMILLIS_H + +#include +#include "millis.h" + +class MockDCCMillis : public DCCExController::DCCMillis { +public: + unsigned long millis() const override { return getMillis(); } +}; + +#endif // MOCK_DCCMILLIS_H diff --git a/test/mocks/Print.h b/test/mocks/Print.h deleted file mode 100644 index dd995f0..0000000 --- a/test/mocks/Print.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * © 2025 Peter Cole - * - * 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 this code. If not, see . - */ - -#ifndef PRINT_H -#define PRINT_H - -#include -#include -#include - -// To handle F("") macros in mocks, we need a dummy type -typedef const char *__FlashStringHelper; - -class Print { -public: - virtual ~Print() {} - - // The single "Bottleneck" method. - // Every print call must eventually call this. - virtual size_t write(uint8_t c) = 0; - - // Multi-byte write helper - virtual size_t write(const uint8_t *buffer, size_t size) { - size_t n = 0; - while (size--) { - if (write(*buffer++)) - n++; - else - break; - } - return n; - } - - // --- Print Overloads --- - - void print(const char *s) { - if (s) - while (*s) - write(*s++); - } - - void print(const std::string &s) { print(s.c_str()); } - - void print(__FlashStringHelper *s) { print((const char *)s); } - - void print(char c) { write(c); } - - void print(int n) { print(std::to_string(n).c_str()); } - - void print(long n) { print(std::to_string(n).c_str()); } - - // --- Println Overloads --- - - void println() { - write('\r'); - write('\n'); - } - - void println(const char *s) { - print(s); - println(); - } - - void println(const std::string &s) { - print(s); - println(); - } - - void println(__FlashStringHelper *s) { - print(s); - println(); - } - - void println(int n) { - print(n); - println(); - } -}; - -#endif // PRINT_H diff --git a/test/mocks/Stream.h b/test/mocks/Stream.h index 3f760bb..3d97c13 100644 --- a/test/mocks/Stream.h +++ b/test/mocks/Stream.h @@ -18,25 +18,30 @@ #ifndef STREAM_H #define STREAM_H -#include "Print.h" +#include +#include +#include +#include + +using namespace DCCExController; /** * @brief Mock Stream class to simulate Arduino Stream objects eg. Serial. * @details Utilises a separate input and output buffer to cater for bi-directional comms. */ -class Stream : public Print { +class Stream : public DCCStream { public: /** * @brief Determines if there are more characters in the buffer * @return int Length of the buffer */ - int available() const { return _inputBuffer.length(); } + int available() const override { return _inputBuffer.length(); } /** * @brief Read a char from the buffer * @return int Char */ - int read() { + int read() override { if (_inputBuffer.empty()) return -1; char c = _inputBuffer[0]; @@ -49,11 +54,17 @@ class Stream : public Print { * @param c Char to write * @return size_t */ - virtual size_t write(uint8_t c) override { + virtual size_t write(uint8_t c) { _outputBuffer += (char)c; return 1; } + + virtual size_t write(const uint8_t *buffer, size_t size) override { + _outputBuffer += std::string(reinterpret_cast(buffer), size); + return size; + }; + /** * @brief Helper to write data to the buffer using << * @tparam T @@ -61,7 +72,7 @@ class Stream : public Print { * @return Stream& */ template Stream &operator<<(const T &data) { - // We bypass write() and put this straight into input + // We bypass write() and put this straight into output _inputBuffer += data; return *this; } @@ -75,14 +86,43 @@ class Stream : public Print { /** * @brief Clear the output buffer */ - void clearOutput() { _outputBuffer.clear(); } + void clearOutput() { _outputBuffer = ""; } - /** - * @brief Clear the input buffer - */ - void clearInput() { _inputBuffer.clear(); } + + virtual void flush() override { + _outputBuffer = ""; + } + + + virtual void println(const char* format, ...) override { + va_list args; + va_start(args, format); + _appendFormat(format, args); + va_end(args); + _outputBuffer += "\r\n"; + } + + virtual void print(const char* format, ...) override { + va_list args; + va_start(args, format); + _appendFormat(format, args); + va_end(args); + } private: + void _appendFormat(const char *format, va_list args) { + va_list argsCopy; + va_copy(argsCopy, args); + int needed = vsnprintf(nullptr, 0, format, argsCopy); + va_end(argsCopy); + if (needed <= 0) { + return; + } + std::string formatted(needed + 1, '\0'); + vsnprintf(&formatted[0], formatted.size(), format, args); + _outputBuffer += formatted.c_str(); + } + std::string _inputBuffer; // Data for read() std::string _outputBuffer; // Data from write()/print() }; diff --git a/test/mocks/millis.h b/test/mocks/millis.h new file mode 100644 index 0000000..f5bc9dc --- /dev/null +++ b/test/mocks/millis.h @@ -0,0 +1,12 @@ +#ifndef MILLIS_H +#define MILLIS_H + +#include + +inline unsigned long _currentMillis = 0; + +inline void advanceMillis(unsigned long ms) { _currentMillis += ms; } +inline void resetMillis() { _currentMillis = 0; } +inline unsigned long getMillis() { return _currentMillis; } + +#endif \ No newline at end of file diff --git a/test/setup/TestHarnessBase.hpp b/test/setup/TestHarnessBase.hpp index ae24d6e..107f6fd 100644 --- a/test/setup/TestHarnessBase.hpp +++ b/test/setup/TestHarnessBase.hpp @@ -30,11 +30,15 @@ #ifndef TESTHARNESSBASE_HPP #define TESTHARNESSBASE_HPP -#include "../mocks/Arduino.h" +#include #include "../mocks/MockDCCEXProtocolDelegate.h" +#include "../mocks/MockDCCMillis.h" +#include "../mocks/Stream.h" +#include "millis.h" #include using namespace testing; +using namespace DCCExController; /// @brief Test fixture to setup and tear down tests class TestHarnessBase : public Test { @@ -43,27 +47,27 @@ class TestHarnessBase : public Test { virtual ~TestHarnessBase() {} protected: + MockDCCMillis _millisProvider; + DCCEXProtocol _dccexProtocol{&_millisProvider}; + MockDCCEXProtocolDelegate _delegate; + Stream _console; + Stream _stream; + + void SetUp() override { - millis(); _dccexProtocol.setDelegate(&_delegate); _dccexProtocol.setLogStream(&_console); _dccexProtocol.connect(&_stream); _dccexProtocol.clearRoster(); + resetMillis(); } void TearDown() override { - resetMillis(); - _stream.clearInput(); - _stream.clearOutput(); _dccexProtocol.clearAllLists(); CSConsist::clearCSConsists(); CSConsist::setAlwaysReplicateFunctions(false); } - DCCEXProtocol _dccexProtocol; - MockDCCEXProtocolDelegate _delegate; - Stream _console; - Stream _stream; }; #endif // TESTHARNESSBASE_HPP diff --git a/test/setup/TestHarnessNoDelegate.h b/test/setup/TestHarnessNoDelegate.h index 496c6c5..f791c44 100644 --- a/test/setup/TestHarnessNoDelegate.h +++ b/test/setup/TestHarnessNoDelegate.h @@ -23,10 +23,13 @@ #ifndef TESTHARNESSNODELEGATE_H #define TESTHARNESSNODELEGATE_H -#include "../mocks/Arduino.h" +#include #include +#include "../mocks/MockDCCMillis.h" +#include "../mocks/Stream.h" using namespace testing; +using namespace DCCExController; /// @brief Test fixture to setup and tear down tests class TestHarnessNoDelegate : public Test { @@ -36,19 +39,18 @@ class TestHarnessNoDelegate : public Test { protected: void SetUp() override { - millis(); _dccexProtocol.setLogStream(&_console); _dccexProtocol.connect(&_stream); } void TearDown() override { resetMillis(); - _stream.clearInput(); _stream.clearOutput(); _dccexProtocol.clearAllLists(); } - DCCEXProtocol _dccexProtocol; + MockDCCMillis _millisProvider; + DCCEXProtocol _dccexProtocol{&_millisProvider}; Stream _console; Stream _stream; }; diff --git a/test/setup/TurnoutTests.h b/test/setup/TurnoutTests.h index a9fec47..5c19dba 100644 --- a/test/setup/TurnoutTests.h +++ b/test/setup/TurnoutTests.h @@ -27,7 +27,7 @@ */ #ifndef TURNOUTTESTS_H -#define TUNROUTTESTS_H +#define TURNOUTTESTS_H #include "TestHarnessBase.hpp"