diff --git a/.gitignore b/.gitignore index 5acf4242..fb617e20 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,35 @@ *.exe *.out -.DS_Store .idea build +# +# MacOS +# +.DS_Store +.localized +__MACOSX/ +.AppleDouble +._* + +# +# Visual Studio Code +# +.vscode/ +!.vscode/settings.json +!.vscode/extensions.json +!.vscode/*.code-snippets +!*.code-workspace +# Built Visual Studio Code Extensions +*.vsix + +# +# PlatformIO +# +.pio +.pioenvs +.piolibdeps + +# Local working notes +notes/ diff --git a/Arduino/Arduino.h b/Arduino/Arduino.h index 3f90b4e7..18325d64 100644 --- a/Arduino/Arduino.h +++ b/Arduino/Arduino.h @@ -4,6 +4,7 @@ #include #include #include +#include #define F(s) s @@ -28,15 +29,4 @@ void pinMode(int, PinMode); void digitalWrite(int, PinState); byte digitalRead(int); -struct Serial_T -{ - void begin(int baudrate); - - bool available(); - char read(); - void flush(); - unsigned char readBytesUntil(int termChar, char *string, int length); - void print(const char *); - void println(const char *); -}; -extern struct Serial_T Serial; \ No newline at end of file +extern Stream Serial; \ No newline at end of file diff --git a/Arduino/Stream.h b/Arduino/Stream.h new file mode 100644 index 00000000..40a3ed8a --- /dev/null +++ b/Arduino/Stream.h @@ -0,0 +1,17 @@ +#pragma once + +struct ENDL_T; + +class Stream +{ +public: + Stream() = default; + virtual ~Stream() = default; + + virtual void begin(int); + virtual bool available(); + virtual char read(); + virtual void print(const char *); + virtual void println(const char *); + virtual void flush(); +}; diff --git a/Arduino/Streaming.h b/Arduino/Streaming.h index 2a477db7..5e657635 100644 --- a/Arduino/Streaming.h +++ b/Arduino/Streaming.h @@ -1,15 +1,17 @@ #pragma once #include +#include struct ENDL_T {}; -Serial_T & operator<<(Serial_T &, int); -Serial_T & operator<<(Serial_T &, unsigned int); -Serial_T & operator<<(Serial_T &, long); -Serial_T & operator<<(Serial_T &, unsigned long); -Serial_T & operator<<(Serial_T &, const char *); -Serial_T & operator<<(Serial_T & s, const ENDL_T & e); +Stream & operator<<(Stream &, int); +Stream & operator<<(Stream &, unsigned int); +Stream & operator<<(Stream &, long); +Stream & operator<<(Stream &, unsigned long); +Stream & operator<<(Stream &, const char *); +Stream & operator<<(Stream & s, const ENDL_T & e); + extern ENDL_T endl; template T _HEX(T v) { return v;} template T _WIDTH(T v, int width) { return v;} diff --git a/CMakeLists.txt b/CMakeLists.txt index 43ea24fd..8788edd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,6 +112,7 @@ add_executable(VLCB_Arduino examples/VLCB_SerialGC_4in4out/VLCB_SerialGC_4in4out.ino examples/VLCB_SerialGC_4in4out_slot/VLCB_SerialGC_4in4out_slot.ino examples/VLCB_SerialGC_empty/VLCB_SerialGC_empty.ino + examples/VLCB_SerialGC_SerialUI_empty/VLCB_SerialGC_SerialUI_empty.ino ) add_executable(testAll diff --git a/README.md b/README.md index abe3a7e8..26a42c54 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ by email to vlcb@rosvall.ie or create an [issue in GitHub](https://github.com/Sv * John Fletcher - Contributor * Chris Andrews - Contributor * David Ellis - Contributor -* brocci - Contributor to the CBUS library +* Bruno Rocci - Contributor to the CBUS library ## License diff --git a/docs/Examples.md b/docs/Examples.md index 17f1022b..c9f1a50f 100644 --- a/docs/Examples.md +++ b/docs/Examples.md @@ -107,3 +107,11 @@ use for programming the Arduino. You can then start FCU (or any other configuration utility) and connect through this serial port. The FCU will then communicate with your Arduino module as a CBUS module. + +## [VLCB_SerialGC_SerialUI_empty](../examples/VLCB_SerialGC_SerialUI_empty/VLCB_SerialGC_SerialUI_empty.ino) +This example demonstrates split serial usage on a single node. +The hardware serial port is dedicated to [SerialGC](../docs/CanTransport.md) for VLCB/GridConnect traffic, +while a separate SoftwareSerial port is used for [SerialUserInterface](SerialUserInterface.md) commands and status output. + +This layout is useful when you want to keep the transport channel and the user console separate. +It also shows the intended use of the Stream-based serial refactor for either HardwareSerial or SoftwareSerial. diff --git a/examples/VLCB_SerialGC_SerialUI_empty/VLCB_SerialGC_SerialUI_empty.ino b/examples/VLCB_SerialGC_SerialUI_empty/VLCB_SerialGC_SerialUI_empty.ino new file mode 100644 index 00000000..5265c59c --- /dev/null +++ b/examples/VLCB_SerialGC_SerialUI_empty/VLCB_SerialGC_SerialUI_empty.ino @@ -0,0 +1,143 @@ +// Copyright (C) Bruno Rocci (bruno_rocci@hotmail.com) +// This file is part of VLCB-Arduino project on https://github.com/SvenRosvall/VLCB-Arduino +// Licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. +// The full licence can be found at: http://creativecommons.org/licenses/by-nc-sa/4.0 + +/* + 3rd party libraries needed for compilation: (not for binary-only distributions) + + Streaming -- C++ stream style output, v5, (http://arduiniana.org/libraries/streaming/) + SoftwareSerial -- Arduino software serial library (not needed when USE_MEGA_SERIAL1 is set) +*/ + +// This example demonstrates split serial usage: +// +// - SerialGC uses the default hardware Serial port for GridConnect transport +// - SerialUserInterface uses SoftwareSerial for user commands and status output +// - Optionally, hardware Serial1 can be used for the SerialUserInterface on Mega2560 by setting the USE_MEGA_SERIAL1 build flag in platformio.ini +// +// This is useful on boards where the USB serial port is already committed to +// the VLCB/GridConnect transport, but a separate command console is still wanted. +// +// Based on VLCB_SerialGC_empty example from Sven Rosvall (MERG 3777) + +// 3rd party libraries +#include +#ifndef USE_MEGA_SERIAL1 +#include +#endif + +// VLCB library header files +#include +#include +#include + +// forward function declarations +void printConfig(); + +// constants +const byte VER_MAJ = 1; // code major version +const char VER_MIN = 'a'; // code minor version +const byte VER_BUILD = 0; // code build number +const byte MANUFACTURER = MANU_DEV; // for boards in development. +const byte MODULE_ID = 98; // VLCB module type + +// module name, must be at most 7 characters +char mname[] = "SPLIT"; + +const byte LED_GRN = 4; // VLCB green Unitialised LED pin +const byte LED_YLW = 7; // VLCB yellow Normal LED pin +const byte SWITCH0 = 8; // VLCB push button switch pin + +#ifdef USE_MEGA_SERIAL1 +// Use hardware Serial1 on pins 18 (TX1) and 19 (RX1) +#define serialUserPort Serial1 +#else +// Use SoftwareSerial on pins 2 (RX) and 3 (TX) +const byte UI_RX_PIN = 2; +const byte UI_TX_PIN = 3; +SoftwareSerial serialUserPort(UI_RX_PIN, UI_TX_PIN); +#endif + +// module objects +VLCB::SerialGC serialGC(Serial); // CAN transport object using hardware serial +VLCB::SerialUserInterface serialUserInterface(serialUserPort); + +// Service objects +VLCB::LEDUserInterface ledUserInterface(LED_GRN, LED_YLW, SWITCH0); +VLCB::MinimumNodeServiceWithDiagnostics mnService; +VLCB::CanService serialCanService(&serialGC); + +// +/// setup VLCB - runs once at power on from setup() +// +void setupVLCB() +{ + VLCB::checkStartupAction(LED_GRN, LED_YLW, SWITCH0); + + VLCB::setServices({ + &mnService, &ledUserInterface, &serialUserInterface, &serialCanService}); + + // set module parameters + VLCB::setVersion(VER_MAJ, VER_MIN, VER_BUILD); + VLCB::setModuleId(MANUFACTURER, MODULE_ID); + + // set module name + VLCB::setName(mname); + + // initialise and load configuration + VLCB::begin(); + + // show code version and copyright notice on the UI console + serialUserPort << F("> mode = ") << VLCB::Configuration::modeString(VLCB::getCurrentMode()); + serialUserPort << F(", CANID = ") << VLCB::getCANID(); + serialUserPort << F(", NN = ") << VLCB::getNodeNum() << endl; + + printConfig(); +} + +// +/// setup - runs once at power on +// +void setup() +{ + Serial.begin(115200); + serialUserPort.begin(9600); + delay(2000); // Give some time to PIO to open the serial monitor before printing anything + +#ifdef USE_MEGA_SERIAL1 + serialUserPort << F("> ** VLCB split: SerialGC + Serial1 UI example ** ") << __FILE__ << endl; +#else + serialUserPort << F("> ** VLCB split: SerialGC + SoftwareSerial UI example ** ") << __FILE__ << endl; +#endif + + setupVLCB(); + + serialUserPort << F("> ready") << endl << endl; +} + +// +/// loop - runs forever +// +void loop() +{ + // + /// do VLCB message, switch and LED processing + // + VLCB::process(); + + // bottom of loop() +} + +// +/// print code version config details and copyright notice +// +void printConfig() +{ + // code version + serialUserPort << F("> code version = ") << VER_MAJ << VER_MIN << F(" build ") << VER_BUILD << endl; + serialUserPort << F("> compiled on ") << __DATE__ << F(" at ") << __TIME__ << F(", compiler ver = ") << __cplusplus << endl; + + // copyright + serialUserPort << F("> Copyright © 2026 Bruno Rocci (MERG 9690)") << endl; +} diff --git a/src/SerialGC.cpp b/src/SerialGC.cpp index 4484e58c..8e875719 100644 --- a/src/SerialGC.cpp +++ b/src/SerialGC.cpp @@ -19,7 +19,6 @@ namespace VLCB bool SerialGC::begin() { - Serial << F("> ** GridConnect over serial ** ") << endl; receivedCount = 0; transmitCount = 0; return true; diff --git a/src/SerialGC.h b/src/SerialGC.h index 67ebd1b0..5ebf3329 100644 --- a/src/SerialGC.h +++ b/src/SerialGC.h @@ -7,6 +7,7 @@ // header files #include +#include #include #include #include @@ -24,7 +25,7 @@ namespace VLCB class SerialGC : public CanTransport { public: - SerialGC(typeof(Serial)& _serial = Serial) : serial(_serial) {} + SerialGC(Stream& _serial = Serial) : serial(_serial) {} /// @cond LIBRARY bool begin(); @@ -48,7 +49,7 @@ namespace VLCB /// @endcond private: - typeof(Serial)& serial; + Stream& serial; char rxBuffer[RXBUFFERSIZE]; // Define a byte array to store the incoming data char txBuffer[RXBUFFERSIZE]; // Define a byte array to store the outgoing data diff --git a/src/SerialUserInterface.cpp b/src/SerialUserInterface.cpp index 65798f57..80bff3a0 100644 --- a/src/SerialUserInterface.cpp +++ b/src/SerialUserInterface.cpp @@ -24,10 +24,10 @@ void SerialUserInterface::processAction(const Action &action) void SerialUserInterface::processSerialInput() { - if (Serial.available()) + if (serial.available()) { Configuration *modconfig = controller->getModuleConfig(); - char c = Serial.read(); + char c = serial.read(); switch (c) { @@ -36,78 +36,78 @@ void SerialUserInterface::processSerialInput() printConfig(); // node identity - Serial << F("> VLCB node configuration") << endl; - Serial << F("> mode = ") << Configuration::modeString(modconfig->currentMode) + serial << F("> VLCB node configuration") << endl; + serial << F("> mode = ") << Configuration::modeString(modconfig->currentMode) << F(", CANID = ") << modconfig->CANID << F(", node number = ") << modconfig->nodeNum << endl; - Serial << endl; + serial << endl; break; case 'e': // EEPROM learned event data table - Serial << F("> stored events ") << endl; - Serial << F(" max events = ") << modconfig->getNumEvents() + serial << F("> stored events ") << endl; + serial << F(" max events = ") << modconfig->getNumEvents() << F(" EVs per event = ") << modconfig->getNumEVs() << F(" bytes per event = ") << modconfig->EE_BYTES_PER_EVENT << endl; { byte uev = modconfig->numEvents(); - Serial << F(" stored events = ") << uev << F(", free = ") << (modconfig->getNumEvents() - uev) << endl; - Serial << F(" using ") << (uev * modconfig->EE_BYTES_PER_EVENT) << F(" of ") + serial << F(" stored events = ") << uev << F(", free = ") << (modconfig->getNumEvents() - uev) << endl; + serial << F(" using ") << (uev * modconfig->EE_BYTES_PER_EVENT) << F(" of ") << (modconfig->getNumEvents() * modconfig->EE_BYTES_PER_EVENT) << F(" bytes") << endl << endl; } - Serial << F(" Ev# | NNhi | NNlo | ENhi | ENlo | "); + serial << F(" Ev# | NNhi | NNlo | ENhi | ENlo | "); for (byte j = 0; j < (modconfig->getNumEVs()); j++) { - Serial << _FMT(F("EV% | "), _WIDTHZ(j + 1, 3)); + serial << _FMT(F("EV% | "), _WIDTHZ(j + 1, 3)); } - Serial << F("Hash |") << endl; - Serial << F(" --------------------------------------------------------------") << endl; + serial << F("Hash |") << endl; + serial << F(" --------------------------------------------------------------") << endl; // for each event data line for (byte j = 0; j < modconfig->getNumEvents(); j++) { if (modconfig->getEvTableEntry(j) != 0) { - Serial << _FMT(F(" % | "), _WIDTHZ(j, 3)); + serial << _FMT(F(" % | "), _WIDTHZ(j, 3)); // for each data byte of this event byte evarray[EE_HASH_BYTES]; modconfig->readEvent(j, evarray); for (byte e = 0; e < 4; e++) { - Serial << _FMT(F(" 0x% | "), _WIDTHZ(_HEX(evarray[e]), 2)); + serial << _FMT(F(" 0x% | "), _WIDTHZ(_HEX(evarray[e]), 2)); } for (byte ev = 1; ev <= modconfig->getNumEVs(); ev++) { - Serial << _FMT(F(" 0x% | "), _WIDTHZ(_HEX(modconfig->getEventEVval(j, ev)), 2)); + serial << _FMT(F(" 0x% | "), _WIDTHZ(_HEX(modconfig->getEventEVval(j, ev)), 2)); } - Serial << _FMT("%", _WIDTH(modconfig->getEvTableEntry(j), 4)) << endl; + serial << _FMT("%", _WIDTH(modconfig->getEvTableEntry(j), 4)) << endl; } } - Serial << endl; + serial << endl; break; // NVs case 'v': // note NVs number from 1, not 0 - Serial << F("> Node variables") << endl; - Serial << F(" NV Val") << endl; - Serial << F(" --------------------") << endl; + serial << F("> Node variables") << endl; + serial << F(" NV Val") << endl; + serial << F(" --------------------") << endl; for (byte j = 1; j <= modconfig->getNumNodeVariables(); j++) { byte v = modconfig->readNV(j); - Serial << _FMT(F(" - % : % | 0x%"), _WIDTHZ(j, 2), _WIDTH(v, 3), _HEX(v)) << endl; + serial << _FMT(F(" - % : % | 0x%"), _WIDTHZ(j, 2), _WIDTH(v, 3), _HEX(v)) << endl; } - Serial << endl << endl; + serial << endl << endl; break; @@ -123,12 +123,12 @@ void SerialUserInterface::processSerialInput() case 'm': // free memory - Serial << F("Free SRAM = ") << modconfig->freeSRAM() << F(" bytes") << endl; + serial << F("Free SRAM = ") << modconfig->freeSRAM() << F(" bytes") << endl; break; case 'a': // Action queue info - Serial << F("Action Queue Size=") << controller->getActionQueue().bufUse() + serial << F("Action Queue Size=") << controller->getActionQueue().bufUse() << F(" High Water Mark=") << controller->getActionQueue().getHighWaterMark() << F(" Overflows=") << controller->getActionQueue().getOverflows() << endl; break; @@ -140,11 +140,11 @@ void SerialUserInterface::processSerialInput() case '\r': case '\n': - Serial << endl; + serial << endl; break; default: - Serial << F("> unknown command ") << c << endl; + serial << F("> unknown command ") << c << endl; break; } } @@ -176,15 +176,15 @@ void SerialUserInterface::indicateMode(VlcbModeParams mode) switch (mode) { case MODE_NORMAL: - Serial << F("Module in NORMAL mode") << endl; + serial << F("Module in NORMAL mode") << endl; break; case MODE_UNINITIALISED: - Serial << F("Module in UNINITIALISED mode") << endl; + serial << F("Module in UNINITIALISED mode") << endl; break; case MODE_SETUP: - Serial << F("Module in SETUP mode") << endl; + serial << F("Module in SETUP mode") << endl; break; default: diff --git a/src/SerialUserInterface.h b/src/SerialUserInterface.h index 134b4305..397e7259 100644 --- a/src/SerialUserInterface.h +++ b/src/SerialUserInterface.h @@ -5,6 +5,9 @@ #pragma once +#include +#include + #include "Service.h" #include "Configuration.h" #include "Transport.h" @@ -19,6 +22,8 @@ namespace VLCB class SerialUserInterface : public Service { public: + SerialUserInterface(Stream& _serial = Serial) : serial(_serial) {} + /// @cond LIBRARY virtual VlcbServiceTypes getServiceID() const override { return SERVICE_ID_NONE; }; virtual byte getServiceVersionID() const override { return 1; }; @@ -28,6 +33,8 @@ class SerialUserInterface : public Service /// @endcond private: + Stream& serial; + void handleAction(const Action &action); void processSerialInput(); void indicateMode(VlcbModeParams i); diff --git a/test/ArduinoMock.cpp b/test/ArduinoMock.cpp index 6706771c..c8b9f284 100644 --- a/test/ArduinoMock.cpp +++ b/test/ArduinoMock.cpp @@ -99,64 +99,70 @@ byte lowByte(unsigned int i) return i & 0xFF; } -void Serial_T::begin(int baudRate) +void Stream::begin(int baudRate) { } -bool Serial_T::available() +bool Stream::available() { - return true; + return true; } -char Serial_T::read() +char Stream::read() { - return ' '; + return ' '; } -void Serial_T::print(const char *) +void Stream::print(const char *) { } -void Serial_T::println(const char *) +void Stream::println(const char *) { } -void Serial_T::flush() +void Stream::flush() { } -Serial_T & operator<<(Serial_T & s, int i) +Stream & operator<<(Stream & s, int i) { - std::cout << i; - return s; + std::cout << i; + return s; } -Serial_T & operator<<(Serial_T & s, unsigned int i) + +Stream & operator<<(Stream & s, unsigned int i) { - std::cout << i; - return s; + std::cout << i; + return s; } -Serial_T & operator<<(Serial_T & s, long i) + +Stream & operator<<(Stream & s, long i) { - std::cout << i; - return s; + std::cout << i; + return s; } -Serial_T & operator<<(Serial_T & s, unsigned long i) + +Stream & operator<<(Stream & s, unsigned long i) { - std::cout << i; - return s; + std::cout << i; + return s; } -Serial_T & operator<<(Serial_T & s, const char * i) + +Stream & operator<<(Stream & s, const char * i) { - std::cout << i; - return s; + std::cout << i; + return s; } -Serial_T & operator<<(Serial_T & s, const ENDL_T & e) + +Stream & operator<<(Stream & s, const ENDL_T & e) { - std::cout << std::endl; - return s; + std::cout << std::endl; + return s; } + ENDL_T endl; -Serial_T Serial; +Stream Serial; //template T _HEX(T); // Hardware values used by ADC free running mode.