From c0269d1511a50e163416c813eec716f179eefe67 Mon Sep 17 00:00:00 2001 From: Bert Melis Date: Tue, 14 Jan 2025 09:40:30 +0100 Subject: [PATCH] generic interface --- .github/workflows/build_arduino_ide.yml | 1 + .github/workflows/build_platformio.yml | 1 + .../generic-interface/generic-interface.ino | 123 ++++++++++++++++++ src/GWG/GWG.h | 29 ++++- src/Interface/GenericInterface.h | 42 ++++++ src/VS1/VS1.h | 26 ++++ src/VS2/VS2.h | 23 +++- 7 files changed, 241 insertions(+), 4 deletions(-) create mode 100644 examples/generic-interface/generic-interface.ino create mode 100644 src/Interface/GenericInterface.h diff --git a/.github/workflows/build_arduino_ide.yml b/.github/workflows/build_arduino_ide.yml index 54102c4..280782d 100644 --- a/.github/workflows/build_arduino_ide.yml +++ b/.github/workflows/build_arduino_ide.yml @@ -46,6 +46,7 @@ jobs: - name: esp32:esp32 source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json sketch-paths: | + - examples/generic-interface - examples/simple-read-VS1 - examples/simple-read-VS2 - examples/simple-write-VS1 diff --git a/.github/workflows/build_platformio.yml b/.github/workflows/build_platformio.yml index 676dc72..3cc4ece 100644 --- a/.github/workflows/build_platformio.yml +++ b/.github/workflows/build_platformio.yml @@ -35,6 +35,7 @@ jobs: strategy: matrix: example: [ + examples/generic-interface/generic-interface.ino examples/simple-read-VS1/simple-read-VS1.ino, examples/simple-read-VS2/simple-read-VS2.ino, examples/simple-write-VS1/simple-write-VS1.ino, diff --git a/examples/generic-interface/generic-interface.ino b/examples/generic-interface/generic-interface.ino new file mode 100644 index 0000000..26344fa --- /dev/null +++ b/examples/generic-interface/generic-interface.ino @@ -0,0 +1,123 @@ +#include + +#include + +/* +This example is to show how you could build your own +interface and serves as a compilation test +*/ + +// Dummy class that has all the methods for VitoWiFi to work +// but doesn't do anything in this case. +// Use as skeleton for your own implementation +class DummyInterface { + public: + bool begin() { + // prepare the interface + // optolink comm at 4800 baud, 8 bits, even parity and 2 stop bits + // called at VitoWiFi::begin() + return true; + } + void end() { + // stop the interface + // called at VitoWiFi::end() + } + std::size_t write(const uint8_t* data, uint8_t length) { + // tries to write `data` with length `length` to the interface + // returns the actually written data + return 0; + } + uint8_t read() { + // read one byte from the interface + // availability of data is checked first + return 0; + } + size_t available() { + // check if data is available + return 0; + } +}; + +// optolink on Dummy Interface, logging output on UART1 (connected to USB) +DummyInterface dummyInterface; +#define SERIAL1 dummyInterface +#define SERIAL2 Serial +#define SERIALBAUDRATE 115200 + +VitoWiFi::VitoWiFi vitoWiFi(&SERIAL1); +bool readValues = false; +uint8_t datapointIndex = 0; + +VitoWiFi::Datapoint datapoints[] = { + VitoWiFi::Datapoint("outsidetemp", 0x5525, 2, VitoWiFi::div10), + VitoWiFi::Datapoint("boilertemp", 0x0810, 2, VitoWiFi::div10), + VitoWiFi::Datapoint("pump", 0x2906, 1, VitoWiFi::noconv) +}; + +void onResponse(const VitoWiFi::PacketVS2& response, const VitoWiFi::Datapoint& request) { + // raw data can be accessed through the 'response' argument + SERIAL2.print("Raw data received:"); + const uint8_t* data = response.data(); + for (uint8_t i = 0; i < response.dataLength(); ++i) { + SERIAL2.printf(" %02x", data[i]); + } + SERIAL2.print("\n"); + + // the raw data can be decoded using the datapoint. Be sure to use the correct type + SERIAL2.printf("%s: ", request.name()); + if (request.converter() == VitoWiFi::div10) { + float value = request.decode(response); + SERIAL2.printf("%.1f\n", value); + } else if (request.converter() == VitoWiFi::noconv) { + bool value = request.decode(response); + // alternatively, we can just cast response.data()[0] to bool + SERIAL2.printf("%s\n", value ? "ON" : "OFF"); + } +} + +void onError(VitoWiFi::OptolinkResult error, const VitoWiFi::Datapoint& request) { + SERIAL2.printf("Datapoint \"%s\" error: ", request.name()); + if (error == VitoWiFi::OptolinkResult::TIMEOUT) { + SERIAL2.print("timeout\n"); + } else if (error == VitoWiFi::OptolinkResult::LENGTH) { + SERIAL2.print("length\n"); + } else if (error == VitoWiFi::OptolinkResult::NACK) { + SERIAL2.print("nack\n"); + } else if (error == VitoWiFi::OptolinkResult::CRC) { + SERIAL2.print("crc\n"); + } else if (error == VitoWiFi::OptolinkResult::ERROR) { + SERIAL2.print("error\n"); + } +} + +void setup() { + delay(1000); + SERIAL2.begin(SERIALBAUDRATE); + SERIAL2.print("Setting up vitoWiFi\n"); + + vitoWiFi.onResponse(onResponse); + vitoWiFi.onError(onError); + vitoWiFi.begin(); + + SERIAL2.print("Setup finished\n"); +} + +void loop() { + static uint32_t lastMillis = 0; + if (millis() - lastMillis > 60000UL) { // read all values every 60 seconds + lastMillis = millis(); + readValues = true; + datapointIndex = 0; + } + + if (readValues) { + if (vitoWiFi.read(datapoints[datapointIndex])) { + ++datapointIndex; + } + if (datapointIndex == 3) { + readValues = false; + } + } + + vitoWiFi.loop(); +} diff --git a/src/GWG/GWG.h b/src/GWG/GWG.h index b327316..91537d5 100644 --- a/src/GWG/GWG.h +++ b/src/GWG/GWG.h @@ -22,9 +22,8 @@ the LICENSE file. #endif #elif defined(__linux__) #include "../Interface/LinuxSerialInterface.h" -#else -#error "platform not supported" #endif +#include "../Interface/GenericInterface.h" namespace VitoWiFi { @@ -41,6 +40,32 @@ class GWG { #else explicit GWG(const char* interface); #endif + template + GWG(C* interface) + : _state(State::UNDEFINED) + , _currentMillis(vw_millis()) + , _lastMillis(_currentMillis) + , _requestTime(0) + , _bytesTransferred(0) + , _interface(nullptr) + , _currentDatapoint(Datapoint(nullptr, 0x0000, 0, VitoWiFi::noconv)) + , _currentRequest() + , _responseBuffer(nullptr) + , _allocatedLength(0) + , _onResponseCallback(nullptr) + , _onErrorCallback(nullptr) { + assert(interface != nullptr); + _interface = new(std::nothrow) VitoWiFiInternals::GenericInterface(interface); + if (!_interface) { + vw_log_e("Could not create serial interface"); + vw_abort(); + } + _responseBuffer = reinterpret_cast(malloc(START_PAYLOAD_LENGTH)); + if (!_responseBuffer) { + vw_log_e("Could not create response buffer"); + vw_abort(); + } + } ~GWG(); GWG(const GWG&) = delete; GWG & operator=(const GWG&) = delete; diff --git a/src/Interface/GenericInterface.h b/src/Interface/GenericInterface.h new file mode 100644 index 0000000..a8ce230 --- /dev/null +++ b/src/Interface/GenericInterface.h @@ -0,0 +1,42 @@ +/* +Copyright (c) 2023 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#include "SerialInterface.h" + +namespace VitoWiFiInternals { + +template +class GenericInterface : public SerialInterface { + public: + explicit GenericInterface(C* interface) + : _interface() { + // empty + } + bool begin() override { + return _interface->begin(); + } + void end() override { + _interface->end(); + } + std::size_t write(const uint8_t* data, uint8_t length) override { + return _interface->write(data, length); + } + uint8_t read() override { + return _interface->read(); + } + size_t available() override { + return _interface->available(); + } + + private: + C* _interface; +}; + +} // end namespace VitoWiFiInternals diff --git a/src/VS1/VS1.h b/src/VS1/VS1.h index c58ed46..680dee3 100644 --- a/src/VS1/VS1.h +++ b/src/VS1/VS1.h @@ -41,6 +41,32 @@ class VS1 { #else explicit VS1(const char* interface); #endif + template + VS1(C* interface) + : _state(State::UNDEFINED) + , _currentMillis(vw_millis()) + , _lastMillis(_currentMillis) + , _requestTime(0) + , _bytesTransferred(0) + , _interface(nullptr) + , _currentDatapoint(Datapoint(nullptr, 0x0000, 0, VitoWiFi::noconv)) + , _currentRequest() + , _responseBuffer(nullptr) + , _allocatedLength(0) + , _onResponseCallback(nullptr) + , _onErrorCallback(nullptr) { + assert(interface != nullptr); + _interface = new(std::nothrow) VitoWiFiInternals::GenericInterface(interface); + if (!_interface) { + vw_log_e("Could not create serial interface"); + vw_abort(); + } + _responseBuffer = reinterpret_cast(malloc(START_PAYLOAD_LENGTH)); + if (!_responseBuffer) { + vw_log_e("Could not create response buffer"); + vw_abort(); + } + } ~VS1(); VS1(const VS1&) = delete; VS1 & operator=(const VS1&) = delete; diff --git a/src/VS2/VS2.h b/src/VS2/VS2.h index 4abff4a..bf08540 100644 --- a/src/VS2/VS2.h +++ b/src/VS2/VS2.h @@ -22,9 +22,8 @@ the LICENSE file. #endif #elif defined(__linux__) #include "../Interface/LinuxSerialInterface.h" -#else -#error "platform not supported" #endif +#include "../Interface/GenericInterface.h" namespace VitoWiFi { @@ -41,6 +40,26 @@ class VS2 { #else explicit VS2(const char* interface); #endif + template + VS2(C* interface) + : _state(State::UNDEFINED) + , _currentMillis(vw_millis()) + , _lastMillis(_currentMillis) + , _requestTime(0) + , _bytesSent(0) + , _interface(nullptr) + , _parser() + , _currentDatapoint(Datapoint(nullptr, 0, 0, noconv)) + , _currentPacket() + , _onResponseCallback(nullptr) + , _onErrorCallback(nullptr) { + assert(interface != nullptr); + _interface = new(std::nothrow) VitoWiFiInternals::GenericInterface(interface); + if (!_interface) { + vw_log_e("Could not create serial interface"); + vw_abort(); + } + } ~VS2(); VS2(const VS2&) = delete; VS2 & operator=(const VS2&) = delete;