diff --git a/.github/workflows/build_arduino_ide.yml b/.github/workflows/build_arduino_ide.yml index e430723..bb07e7a 100644 --- a/.github/workflows/build_arduino_ide.yml +++ b/.github/workflows/build_arduino_ide.yml @@ -22,8 +22,10 @@ jobs: - name: esp8266:esp8266 source-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json sketch-paths: | - - examples/simple-read - - examples/simple-write + - examples/simple-read-VS1 + - examples/simple-read-VS2 + - examples/simple-write-VS1 + - examples/simple-write-VS2 libraries: | - name: VitoWiFi source-path: ./ @@ -47,8 +49,10 @@ jobs: - name: esp32:esp32 source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json sketch-paths: | - - examples/simple-read - - examples/simple-write + - examples/simple-read-VS1 + - examples/simple-read-VS2 + - examples/simple-write-VS1 + - examples/simple-write-VS2 libraries: | - name: VitoWiFi source-path: ./ diff --git a/.github/workflows/build_platformio.yml b/.github/workflows/build_platformio.yml index bdee3ae..795ef35 100644 --- a/.github/workflows/build_platformio.yml +++ b/.github/workflows/build_platformio.yml @@ -7,7 +7,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - example: [examples/simple-read/simple-read.ino, examples/simple-write/simple-write.ino] + example: [ + examples/simple-read-VS1/simple-read-VS1.ino, + examples/simple-read-VS2/simple-read-VS2.ino, + examples/simple-write-VS1/simple-write-VS1.ino, + examples/simple-write-VS2/simple-write-VS2.ino + ] steps: - uses: actions/checkout@v3 - uses: actions/cache@v3 @@ -29,26 +34,31 @@ jobs: PLATFORMIO_CI_SRC: ${{ matrix.example }} build-for-esp32: - runs-on: ubuntu-latest - strategy: - matrix: - example: [examples/simple-read/simple-read.ino, examples/simple-write/simple-write.ino] - steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 - with: - path: | - ~/.cache/pip - ~/.platformio/.cache - key: ${{ runner.os }}-pio - - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - name: Install PlatformIO Core - run: pip install --upgrade platformio - - name: Download external libraries - run: pio pkg install --global --library plerup/EspSoftwareSerial - - name: Build PlatformIO examples - run: pio ci --lib="." --board=lolin32 - env: - PLATFORMIO_CI_SRC: ${{ matrix.example }} \ No newline at end of file + runs-on: ubuntu-latest + strategy: + matrix: + example: [ + examples/simple-read-VS1/simple-read-VS1.ino, + examples/simple-read-VS2/simple-read-VS2.ino, + examples/simple-write-VS1/simple-write-VS1.ino, + examples/simple-write-VS2/simple-write-VS2.ino + ] + steps: + - uses: actions/checkout@v3 + - uses: actions/cache@v3 + with: + path: | + ~/.cache/pip + ~/.platformio/.cache + key: ${{ runner.os }}-pio + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + - name: Install PlatformIO Core + run: pip install --upgrade platformio + - name: Download external libraries + run: pio pkg install --global --library plerup/EspSoftwareSerial + - name: Build PlatformIO examples + run: pio ci --lib="." --board=lolin32 + env: + PLATFORMIO_CI_SRC: ${{ matrix.example }} \ No newline at end of file diff --git a/examples/simple-read-VS1/simple-read-VS1.ino b/examples/simple-read-VS1/simple-read-VS1.ino new file mode 100644 index 0000000..afafe1e --- /dev/null +++ b/examples/simple-read-VS1/simple-read-VS1.ino @@ -0,0 +1,109 @@ +/* + +This example defines three datapoints. +The first two are TEMPL type datapoints and have their own callback. +When no specific callback is attached to a datapoint, it uses the global callback. + +Note the difference in return value between the callbacks: +for tempCallback uses value.getFloat() as TEMPL datapoints return a float. +globalCallback uses value.getString(char*,size_t). This method is independent of the returned type. + +*/ + +#include + +#include + +#ifdef ESP8266 +// optolink on full UART, logging output on secondary +#define SERIAL1 Serial +#define SERIAL2 Serial1 +#define SERIALBAUDRATE 74880 +#endif + +#ifdef ESP32 +// optolink on UART2, logging output on UART1 (connected to USB) +#define SERIAL1 Serial1 +#define SERIAL2 Serial +#define SERIALBAUDRATE 115200 +#endif + +#ifndef SERIALBAUDRATE + #error Target platform not supported +#endif + +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 uint8_t* data, uint8_t length, const VitoWiFi::Datapoint& request) { + // raw data can be accessed through the 'response' argument + SERIAL2.print("Raw data received:"); + for (uint8_t i = 0; i < length; ++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(data, length); + SERIAL2.printf("%.1f\n", value); + } else if (request.converter() == VitoWiFi::noconv) { + // in this example, the response is one byte + SERIAL2.printf("%s\n", (data[0] > 0) ? "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(); +} \ No newline at end of file diff --git a/examples/simple-read/simple-read.ino b/examples/simple-read-VS2/simple-read-VS2.ino similarity index 81% rename from examples/simple-read/simple-read.ino rename to examples/simple-read-VS2/simple-read-VS2.ino index 8efe883..6da094b 100644 --- a/examples/simple-read/simple-read.ino +++ b/examples/simple-read-VS2/simple-read-VS2.ino @@ -52,15 +52,14 @@ void onResponse(const VitoWiFi::PacketVS2& response, const VitoWiFi::Datapoint& SERIAL2.print("\n"); // the raw data can be decoded using the datapoint. Be sure to use the correct type - if (strcmp(request.name(), datapoints[0].name()) == 0) { - float outsideTemp = request.decode(response); - SERIAL2.printf("Outside temperature is %.1f\n", outsideTemp); - } else if (strcmp(request.name(), datapoints[1].name()) == 0) { - float outsideTemp = request.decode(response); - SERIAL2.printf("Boiler temperature is %.1f\n", outsideTemp); - } else if (strcmp(request.name(), datapoints[2].name()) == 0) { - bool pumpStatus = request.decode(response); - SERIAL2.printf("Heating pump status is %s\n", pumpStatus ? "ON" : "OFF"); + 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"); } } diff --git a/examples/simple-write-VS1/simple-write-VS1.ino b/examples/simple-write-VS1/simple-write-VS1.ino new file mode 100644 index 0000000..93900fe --- /dev/null +++ b/examples/simple-write-VS1/simple-write-VS1.ino @@ -0,0 +1,121 @@ +/* + +This example defines three datapoints. +The first two are TEMPL type datapoints and have their own callback. +When no specific callback is attached to a datapoint, it uses the global callback. + +Note the difference in return value between the callbacks: +for tempCallback uses value.getFloat() as TEMPL datapoints return a float. +globalCallback uses value.getString(char*,size_t). This method is independent of the returned type. + +*/ + +#include + +#include + +#ifdef ESP8266 +// optolink on full UART, logging output on secondary +#define SERIAL1 Serial +#define SERIAL2 Serial1 +#define SERIALBAUDRATE 74880 +#endif + +#ifdef ESP32 +// optolink on UART2, logging output on UART1 (connected to USB) +#define SERIAL1 Serial1 +#define SERIAL2 Serial +#define SERIALBAUDRATE 115200 +#endif + +#ifndef SERIALBAUDRATE + #error Target platform not supported +#endif + +VitoWiFi::VitoWiFi vitoWiFi(&SERIAL1); +bool readValues = false; +uint8_t datapointIndex = 0; +uint8_t roomTemperature = 20; +bool writeRoomTemp = false; + +VitoWiFi::Datapoint datapoints[] = { + VitoWiFi::Datapoint("roomtemp", 0x2306, 1, VitoWiFi::noconv), + VitoWiFi::Datapoint("boilertemp", 0x0810, 2, VitoWiFi::div10) +}; + +void setRoomTemp(uint8_t value) { + roomTemperature = value; + writeRoomTemp = true; +} + +void onResponse(const uint8_t* data, uint8_t length, const VitoWiFi::Datapoint& request) { + // raw data can be accessed through the 'response' argument + SERIAL2.print("Raw data received:"); + for (uint8_t i = 0; i < length; ++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(data, length); + SERIAL2.printf("%.1f\n", value); + } else if (request.converter() == VitoWiFi::noconv) { + // in this example, the response is one byte + SERIAL2.printf("%s\n", (data[0] > 0) ? "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; + } + } + + if (writeRoomTemp) { + if (vitoWiFi.write(datapoints[0], roomTemperature)) { + writeRoomTemp = false; + } + } + + vitoWiFi.loop(); +} \ No newline at end of file diff --git a/examples/simple-write/simple-write.ino b/examples/simple-write-VS2/simple-write-VS2.ino similarity index 85% rename from examples/simple-write/simple-write.ino rename to examples/simple-write-VS2/simple-write-VS2.ino index ebb36ae..4ffbd8b 100644 --- a/examples/simple-write/simple-write.ino +++ b/examples/simple-write-VS2/simple-write-VS2.ino @@ -58,12 +58,14 @@ void onResponse(const VitoWiFi::PacketVS2& response, const VitoWiFi::Datapoint& SERIAL2.print("\n"); // the raw data can be decoded using the datapoint. Be sure to use the correct type - if (strcmp(request.name(), datapoints[0].name()) == 0) { - uint8_t roomSetTemp = request.decode(response); - SERIAL2.printf("Room set temperature is %u\n", roomSetTemp); - } else if (strcmp(request.name(), datapoints[1].name()) == 0) { - float outsideTemp = request.decode(response); - SERIAL2.printf("Boiler temperature is %.1f\n", outsideTemp); + 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"); } } diff --git a/src/Datapoint/Converter.cpp b/src/Datapoint/Converter.cpp index ccf5e6f..aadd7de 100644 --- a/src/Datapoint/Converter.cpp +++ b/src/Datapoint/Converter.cpp @@ -10,8 +10,6 @@ the LICENSE file. namespace VitoWiFi { -VariantValue emptyValue(nullptr); - VariantValue Div10Convert::decode(const uint8_t* data, uint8_t len) const { assert(len == 1 || len == 2); float retVal = 0; @@ -108,19 +106,19 @@ void NoconvConvert::encode(uint8_t* buf, uint8_t len, const VariantValue& val) c } } -VariantValue NoconvRawConvert::decode(const uint8_t* data, uint8_t len) const { - (void) len; - return VariantValue(data); -} - -void NoconvRawConvert::encode(uint8_t* buf, uint8_t len, const VariantValue& val) const { - const uint8_t* data = val; - std::memcpy(buf, data, len); -} - Div10Convert div10; Div3600Convert div3600; NoconvConvert noconv; -NoconvRawConvert raw; + +void reverse(uint8_t* arr, uint8_t len) { + uint8_t start = 0; + while (0 < len) { + uint8_t temp = arr[start]; + arr[start] = arr[len]; + arr[len] = temp; + start++; + len--; + } +} } // end namespace VitoWiFi diff --git a/src/Datapoint/Converter.h b/src/Datapoint/Converter.h index 89704a8..5104b92 100644 --- a/src/Datapoint/Converter.h +++ b/src/Datapoint/Converter.h @@ -24,14 +24,12 @@ class VariantValue { explicit VariantValue(uint64_t value): _value(value) {} explicit VariantValue(float value): _value(value) {} explicit VariantValue(bool value) : _value(value) {} - explicit VariantValue(const uint8_t* value): _value(value) {} operator uint8_t() const { return _value._uint8Val; } operator uint16_t() const { return _value._uint16Val; } operator uint32_t() const { return _value._uint32Val; } operator uint64_t() const { return _value._uint64Val; } operator float() const { return _value._floatVal; } operator bool() const { return _value._uint8Val; } - operator const uint8_t*() const { return _value._ptrVal; } protected: union _Value { @@ -41,22 +39,21 @@ class VariantValue { _Value(uint64_t v): _uint64Val(v) {} _Value(float v): _floatVal(v) {} _Value(bool v): _uint8Val(v) {} - _Value(const uint8_t* v): _ptrVal(v) {} uint8_t _uint8Val; uint16_t _uint16Val; uint32_t _uint32Val; uint64_t _uint64Val; float _floatVal; - const uint8_t* _ptrVal; } _value; }; -extern VariantValue emptyValue; - class Converter { public: virtual VariantValue decode(const uint8_t* data, uint8_t len) const = 0; virtual void encode(uint8_t* buf, uint8_t len, const VariantValue& val) const = 0; + bool operator==(const Converter& rhs) const { + return (this == &rhs); + } }; class Div10Convert : public Converter { @@ -77,20 +74,10 @@ class NoconvConvert : public Converter { void encode(uint8_t* buf, uint8_t len, const VariantValue& val) const override; }; -class NoconvRawConvert : public Converter { - public: - VariantValue decode(const uint8_t* data, uint8_t len) const override; - void encode(uint8_t* buf, uint8_t len, const VariantValue& val) const override; -}; - extern Div10Convert div10; extern Div3600Convert div3600; extern NoconvConvert noconv; -extern NoconvRawConvert raw; -uint16_t encodeTimeWindow(uint8_t hourStart1, - uint8_t minuteStart1, - uint8_t hourEnd1, - uint8_t minuteEnd1); +void reverse(uint8_t* arr, uint8_t len); } // end namespace VitoWiFi diff --git a/src/Datapoint/Datapoint.cpp b/src/Datapoint/Datapoint.cpp index 3420967..26a1684 100644 --- a/src/Datapoint/Datapoint.cpp +++ b/src/Datapoint/Datapoint.cpp @@ -10,8 +10,6 @@ the LICENSE file. namespace VitoWiFi { -Datapoint emptyDatapoint(nullptr, 0, 0, noconv); - Datapoint::Datapoint(const char* name, uint16_t address, uint8_t length, const Converter& converter) : _name(name) , _address(address) @@ -37,6 +35,10 @@ uint8_t Datapoint::length() const { return _length; } +const Converter& Datapoint::converter() const { + return *_converter; +} + VariantValue Datapoint::decode(const uint8_t* data, uint8_t length) const { return (*_converter).decode(data, length); } diff --git a/src/Datapoint/Datapoint.h b/src/Datapoint/Datapoint.h index d9c7782..609b3c8 100644 --- a/src/Datapoint/Datapoint.h +++ b/src/Datapoint/Datapoint.h @@ -21,6 +21,7 @@ class Datapoint { const char* name() const; uint16_t address() const; uint8_t length() const; + const Converter& converter() const; VariantValue decode(const uint8_t* data, uint8_t length) const; VariantValue decode(const PacketVS2& packet) const; @@ -33,6 +34,4 @@ class Datapoint { const Converter* _converter; }; -extern Datapoint emptyDatapoint; - } // end namespace VitoWiFi diff --git a/src/Helpers.cpp b/src/Helpers.cpp deleted file mode 100644 index 16d662a..0000000 --- a/src/Helpers.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/* -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. -*/ - -#include "Helpers.h" - -namespace VitoWiFi { - -void reverse(uint8_t* arr, uint8_t len) { - uint8_t start = 0; - while (0 < len) { - uint8_t temp = arr[start]; - arr[start] = arr[len]; - arr[len] = temp; - start++; - len--; - } -} - -} // end namespace VitoWiFi diff --git a/src/Helpers.h b/src/Helpers.h index bae5a83..9d13d77 100644 --- a/src/Helpers.h +++ b/src/Helpers.h @@ -12,12 +12,6 @@ the LICENSE file. #include #include -namespace VitoWiFi { - -void reverse(uint8_t* arr, uint8_t len); - -} // end namespace VitoWiFi - #if defined(__linux__) #include // NOLINT [build/c++11] #define millis() std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count() diff --git a/src/VS1/VS1.cpp b/src/VS1/VS1.cpp index 2adbee8..9a8e8d7 100644 --- a/src/VS1/VS1.cpp +++ b/src/VS1/VS1.cpp @@ -18,12 +18,13 @@ VS1::VS1(HardwareSerial* interface) , _lastMillis(_currentMillis) , _requestTime(0) , _bytesTransferred(0) -, _loopResult(OptolinkResult::CONTINUE) , _interface(nullptr) -, _currentDatapoint(emptyDatapoint) +, _currentDatapoint(Datapoint(nullptr, 0x0000, 0, VitoWiFi::noconv)) , _currentRequest() , _responseBuffer(nullptr) -, _allocatedLength(0) { +, _allocatedLength(0) +, _onResponseCallback(nullptr) +, _onErrorCallback(nullptr) { assert(interface != nullptr); _interface = new(std::nothrow) VitoWiFiInternals::HardwareSerialInterface(interface); if (!_interface) { @@ -43,12 +44,13 @@ VS1::VS1(SoftwareSerial* interface) , _lastMillis(_currentMillis) , _requestTime(0) , _bytesTransferred(0) -, _loopResult(OptolinkResult::CONTINUE) , _interface(nullptr) -, _currentDatapoint(emptyDatapoint) +, _currentDatapoint(Datapoint(nullptr, 0x0000, 0, VitoWiFi::noconv)) , _currentRequest() , _responseBuffer(nullptr) -, _allocatedLength(0) { +, _allocatedLength(0) +, _onResponseCallback(nullptr) +, _onErrorCallback(nullptr) { assert(interface != nullptr); _interface = new(std::nothrow) VitoWiFiInternals::SoftwareSerialInterface(interface); if (!_interface) { @@ -71,6 +73,13 @@ VS1::~VS1() { free(_responseBuffer); } +void VS1::onResponse(OnResponseCallback callback) { + _onResponseCallback = callback; +} +void VS1::onError(OnErrorCallback callback) { + _onErrorCallback = callback; +} + bool VS1::read(const Datapoint& datapoint) { if (_state >= State::SEND) return false; if (_currentRequest.createPacket(PacketVS1Type.READ, @@ -101,25 +110,12 @@ bool VS1::write(const Datapoint& datapoint, const VariantValue& value) { return false; } -const uint8_t* VS1::response() const { - return _responseBuffer; -} - -uint8_t VS1::responseLength() const { - return _currentRequest.dataLength(); -} - -const Datapoint& VS1::datapoint() const { - return _currentDatapoint; -} - bool VS1::begin() { _state = State::INIT; return _interface->begin(); } -OptolinkResult VS1::loop() { - _loopResult = OptolinkResult::CONTINUE; +void VS1::loop() { _currentMillis = millis(); switch (_state) { case State::INIT: @@ -146,10 +142,9 @@ OptolinkResult VS1::loop() { } // double timeout to accomodate for connection initialization if (_requestTime != 0 && _currentMillis - _requestTime > 4000UL) { - _loopResult = OptolinkResult::TIMEOUT; _state = State::INIT; + _tryOnError(OptolinkResult::TIMEOUT); } - return _loopResult; } void VS1::_init() { @@ -214,11 +209,19 @@ void VS1::_receive() { } if (_bytesTransferred == _currentRequest.length()) { _bytesTransferred = 0; - _loopResult = OptolinkResult::PACKET; _state = State::IDLE; + _tryOnResponse(); } } +void VS1::_tryOnResponse() { + if (_onResponseCallback) _onResponseCallback(_responseBuffer, _currentRequest.length(), _currentDatapoint); +} + +void VS1::_tryOnError(OptolinkResult result) { + if (_onErrorCallback) _onErrorCallback(result, _currentDatapoint); +} + bool VS1::_expandResponseBuffer(uint8_t newSize) { if (newSize > _allocatedLength) { uint8_t* newBuffer = reinterpret_cast(realloc(_responseBuffer, newSize)); diff --git a/src/VS1/VS1.h b/src/VS1/VS1.h index acaa637..e981f9d 100644 --- a/src/VS1/VS1.h +++ b/src/VS1/VS1.h @@ -10,6 +10,7 @@ the LICENSE file. #include +#include "Logging.h" #include "../Constants.h" #include "../Helpers.h" #include "PacketVS1.h" @@ -38,14 +39,14 @@ class VS1 { ~VS1(); VS1(const VS1&) = delete; + void onResponse(OnResponseCallback callback); + void onError(OnErrorCallback callback); + bool read(const Datapoint& datapoint); bool write(const Datapoint& datapoint, const VariantValue& value); - const uint8_t* response() const; - uint8_t responseLength() const; - const Datapoint& datapoint() const; bool begin(); - OptolinkResult loop(); + void loop(); private: enum class State { @@ -61,12 +62,13 @@ class VS1 { uint32_t _lastMillis; uint32_t _requestTime; uint8_t _bytesTransferred; - OptolinkResult _loopResult; VitoWiFiInternals::SerialInterface* _interface; Datapoint _currentDatapoint; PacketVS1 _currentRequest; uint8_t* _responseBuffer; uint8_t _allocatedLength; + OnResponseCallback _onResponseCallback; + OnErrorCallback _onErrorCallback; void _init(); void _initAck(); @@ -76,6 +78,9 @@ class VS1 { void _send(); void _receive(); + void _tryOnResponse(); + void _tryOnError(OptolinkResult result); + bool _expandResponseBuffer(uint8_t newSize); }; diff --git a/src/VS2/VS2.cpp b/src/VS2/VS2.cpp index 9709b52..eced526 100644 --- a/src/VS2/VS2.cpp +++ b/src/VS2/VS2.cpp @@ -18,11 +18,12 @@ VS2::VS2(HardwareSerial* interface) , _lastMillis(_currentMillis) , _requestTime(0) , _bytesSent(0) -, _loopResult(OptolinkResult::CONTINUE) , _interface(nullptr) , _parser() -, _currentDatapoint(emptyDatapoint) -, _currentPacket() { +, _currentDatapoint(Datapoint(nullptr, 0, 0, noconv)) +, _currentPacket() +, _onResponseCallback(nullptr) +, _onErrorCallback(nullptr) { assert(interface != nullptr); _interface = new(std::nothrow) VitoWiFiInternals::HardwareSerialInterface(interface); if (!_interface) { @@ -37,11 +38,12 @@ VS2::VS2(SoftwareSerial* interface) , _lastMillis(_currentMillis) , _requestTime(0) , _bytesSent(0) -, _loopResult(OptolinkResult::CONTINUE) , _interface(nullptr) , _parser() -, _currentDatapoint(emptyDatapoint) -, _currentPacket() { +, _currentDatapoint(Datapoint(nullptr, 0, 0, noconv)) +, _currentPacket() +, _onResponseCallback(nullptr) +, _onErrorCallback(nullptr) { assert(interface != nullptr); _interface = new(std::nothrow) VitoWiFiInternals::SoftwareSerialInterface(interface); if (!_interface) { @@ -58,8 +60,15 @@ VS2::~VS2() { delete _interface; } +void VS2::onResponse(OnResponseCallback callback) { + _onResponseCallback = callback; +} +void VS2::onError(OnErrorCallback callback) { + _onErrorCallback = callback; +} + bool VS2::read(const Datapoint& datapoint) { - if (_state > State::IDLE) return false; + if (_state > State::IDLE && _state < State::RECEIVE_ACK) return false; if (_currentPacket.createPacket(PacketType::REQUEST, FunctionCode::READ, 0, @@ -73,15 +82,15 @@ bool VS2::read(const Datapoint& datapoint) { } bool VS2::write(const Datapoint& datapoint, const VariantValue& value) { - if (_state > State::IDLE) return false; + if (_state > State::IDLE && _state < State::RECEIVE_ACK) return false; uint8_t* payload = reinterpret_cast(malloc(datapoint.length())); if (!payload) return false; _currentDatapoint.encode(payload, _currentDatapoint.length(), value); if (_currentPacket.createPacket(PacketType::REQUEST, FunctionCode::WRITE, 0, - _currentDatapoint.address(), - _currentDatapoint.length(), + _currentDatapoint.address(), + _currentDatapoint.length(), payload)) { _currentDatapoint = datapoint; _requestTime = (_currentMillis != 0) ? _currentMillis : _currentMillis + 1; @@ -90,21 +99,12 @@ bool VS2::write(const Datapoint& datapoint, const VariantValue& value) { return false; } -const PacketVS2& VS2::response() const { - return _parser.packet(); -} - -const Datapoint& VS2::datapoint() const { - return _currentDatapoint; -} - bool VS2::begin() { _state = State::RESET; return _interface->begin(); } -OptolinkResult VS2::loop() { - _loopResult = OptolinkResult::CONTINUE; +void VS2::loop() { _currentMillis = millis(); switch (_state) { case State::RESET: @@ -142,10 +142,9 @@ OptolinkResult VS2::loop() { break; } if (_requestTime != 0 && _currentMillis - _requestTime > 5000UL) { - _loopResult = OptolinkResult::TIMEOUT; _state = State::RESET; + _tryOnError(OptolinkResult::TIMEOUT); } - return _loopResult; } void VS2::_reset() { @@ -221,8 +220,8 @@ void VS2::_sendAck() { if (buff == 0x06) { // transmit succesful, moving to next state _state = State::RECEIVE; } else if (buff == 0x15) { // transmit negatively acknowledged, return to IDLE - _loopResult = OptolinkResult::NACK; _state = State::IDLE; + _tryOnError(OptolinkResult::NACK); return; } } @@ -233,16 +232,16 @@ void VS2::_receive() { _lastMillis = _currentMillis; VitoWiFiInternals::ParserResult result = _parser.parse(_interface->read()); if (result == VitoWiFiInternals::ParserResult::COMPLETE) { - _loopResult = OptolinkResult::PACKET; _state = State::RECEIVE_ACK; + _tryOnResponse(); return; } else if (result == VitoWiFiInternals::ParserResult::CS_ERROR) { - _loopResult = OptolinkResult::CRC; _state = State::RESET; + _tryOnError(OptolinkResult::CRC); return; } else if (result == VitoWiFiInternals::ParserResult::ERROR) { - _loopResult = OptolinkResult::ERROR; _state = State::RESET; + _tryOnError(OptolinkResult::ERROR); return; } // else: continue @@ -255,4 +254,12 @@ void VS2::_receiveAck() { _state = State::IDLE; } +void VS2::_tryOnResponse() { + if (_onResponseCallback) _onResponseCallback(_parser.packet(), _currentDatapoint); +} + +void VS2::_tryOnError(OptolinkResult result) { + if (_onErrorCallback) _onErrorCallback(result, _currentDatapoint); +} + } // end namespace VitoWiFi diff --git a/src/VS2/VS2.h b/src/VS2/VS2.h index 7649743..ac286af 100644 --- a/src/VS2/VS2.h +++ b/src/VS2/VS2.h @@ -10,6 +10,7 @@ the LICENSE file. #include +#include "Logging.h" #include "../Constants.h" #include "../Helpers.h" #include "ParserVS2.h" @@ -38,13 +39,14 @@ class VS2 { ~VS2(); VS2(const VS2&) = delete; + void onResponse(OnResponseCallback callback); + void onError(OnErrorCallback callback); + bool read(const Datapoint& datapoint); bool write(const Datapoint& datapoint, const VariantValue& value); - const PacketVS2& response() const; - const Datapoint& datapoint() const; bool begin(); - OptolinkResult loop(); + void loop(); private: enum class State { @@ -64,11 +66,12 @@ class VS2 { uint32_t _lastMillis; uint32_t _requestTime; uint8_t _bytesSent; - OptolinkResult _loopResult; VitoWiFiInternals::SerialInterface* _interface; VitoWiFiInternals::ParserVS2 _parser; Datapoint _currentDatapoint; PacketVS2 _currentPacket; + OnResponseCallback _onResponseCallback; + OnErrorCallback _onErrorCallback; void _reset(); void _resetAck(); @@ -80,6 +83,9 @@ class VS2 { void _sendAck(); void _receive(); void _receiveAck(); + + void _tryOnResponse(); + void _tryOnError(OptolinkResult result); }; } // end namespace VitoWiFi diff --git a/src/VitoWiFi.h b/src/VitoWiFi.h index 5fce342..113c4aa 100644 --- a/src/VitoWiFi.h +++ b/src/VitoWiFi.h @@ -15,12 +15,8 @@ the LICENSE file. #include #endif -#include "Constants.h" -#include "Logging.h" -#include "Helpers.h" - -#include "Datapoint/Datapoint.h" #include "VS2/VS2.h" +#include "VS1/VS1.h" namespace VitoWiFi { @@ -30,19 +26,17 @@ class VitoWiFi { #if defined(ARDUINO) template explicit VitoWiFi(IFACE* interface) - : _optolink(interface) - , _onResponse(nullptr) - , _onError(nullptr) { + : _optolink(interface) { // empty } #endif void onResponse(typename PROTOCOLVERSION::OnResponseCallback callback) { - _onResponse = callback; + _optolink.onResponse(callback); } void onError(typename PROTOCOLVERSION::OnErrorCallback callback) { - _onError = callback; + _optolink.onError(callback); } bool begin() { @@ -50,15 +44,7 @@ class VitoWiFi { } void loop() { - OptolinkResult result = _optolink.loop(); - if (result == OptolinkResult::CONTINUE) { - // do nothing - } else if (result == OptolinkResult::PACKET) { - if (_onResponse) _onResponse(_optolink.response(), _optolink.datapoint()); - } else { - // if not CONTINUE or PACKET, it must be an error - if (_onError) _onError(result, _optolink.datapoint()); - } + _optolink.loop(); } bool read(Datapoint datapoint) { @@ -73,8 +59,6 @@ class VitoWiFi { private: PROTOCOLVERSION _optolink; - typename PROTOCOLVERSION::OnResponseCallback _onResponse; - typename PROTOCOLVERSION::OnErrorCallback _onError; }; } // end namespace VitoWiFi diff --git a/test/test_Datapoint/test_Datapoint.cpp b/test/test_Datapoint/test_Datapoint.cpp index 538cb0e..943e647 100644 --- a/test/test_Datapoint/test_Datapoint.cpp +++ b/test/test_Datapoint/test_Datapoint.cpp @@ -20,11 +20,18 @@ using VitoWiFi::VariantValue; void setUp() {} void tearDown() {} +void test_Converter() { + VitoWiFi::Converter* myConv = &VitoWiFi::div10; + + TEST_ASSERT_TRUE(*myConv == VitoWiFi::div10); +} + void test_Bool() { Datapoint dp("temp", 0x0000, 2, VitoWiFi::div10); + Datapoint empty(nullptr, 0x0000, 0, VitoWiFi::noconv); TEST_ASSERT_TRUE(dp); - TEST_ASSERT_FALSE(VitoWiFi::emptyDatapoint); + TEST_ASSERT_FALSE(empty); } void test_TempDecode() { @@ -195,20 +202,9 @@ void test_COPEncode() { TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, buffer, len); } -void test_RawDecode() { - Datapoint dp("count", 0x0000, 4, VitoWiFi::raw); - const uint8_t data[] = {0x01, 0x02, 0x03, 0x04}; - const uint8_t len = 4; - PacketVS2 packet; - packet.createPacket(PacketType::RESPONSE, FunctionCode::READ, 0, 0x0000, len, data); - - const uint8_t* result = dp.decode(packet); - - TEST_ASSERT_EQUAL_HEX8_ARRAY(data, result, len); -} - int main() { UNITY_BEGIN(); + RUN_TEST(test_Converter); RUN_TEST(test_Bool); RUN_TEST(test_TempDecode); RUN_TEST(test_TempEncode); @@ -224,6 +220,5 @@ int main() { RUN_TEST(test_CountShortEncode); RUN_TEST(test_COPDecode); RUN_TEST(test_COPEncode); - RUN_TEST(test_RawDecode); return UNITY_END(); } \ No newline at end of file