From bf1a8280e4015c62fc05f7b40ad7371a6cae3080 Mon Sep 17 00:00:00 2001 From: Moritz Wirger Date: Wed, 26 May 2021 19:49:50 +0200 Subject: [PATCH 01/15] Add library code, adapt example & add clang-format --- .clang-format | 58 +++++++++++ ArduinoPollDemo/ArduinoPollDemo.ino | 156 ---------------------------- MicroInverterArduino.cpp | 95 +++++++++++++++++ MicroInverterArduino.h | 78 ++++++++++++++ examples/PollDemo/PollDemo.ino | 48 +++++++++ 5 files changed, 279 insertions(+), 156 deletions(-) create mode 100644 .clang-format delete mode 100644 ArduinoPollDemo/ArduinoPollDemo.ino create mode 100644 MicroInverterArduino.cpp create mode 100644 MicroInverterArduino.h create mode 100644 examples/PollDemo/PollDemo.ino diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..792a92e --- /dev/null +++ b/.clang-format @@ -0,0 +1,58 @@ +--- +# Based on Webkit style +BasedOnStyle: Webkit +IndentWidth: 4 +ColumnLimit: 120 +--- +Language: Cpp +Standard: Cpp11 +# Pointers aligned to the left +DerivePointerAlignment: false +PointerAlignment: Left +AccessModifierOffset: -4 +AllowShortFunctionsOnASingleLine: Inline +AlwaysBreakTemplateDeclarations: true +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakConstructorInitializers: BeforeColon +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +Cpp11BracedListStyle: true +FixNamespaceComments: true +IncludeBlocks: Regroup +IncludeCategories: + # C++ standard headers (no .h) + - Regex: '<[[:alnum:]_-]+>' + Priority: 1 + # Extenal libraries (with .h) + - Regex: '<[[:alnum:]_./-]+>' + Priority: 2 + # Headers from same folder + - Regex: '"[[:alnum:]_.-]+"' + Priority: 3 + # Headers from other folders + - Regex: '"[[:alnum:]_/.-]+"' + Priority: 4 +IndentCaseLabels: false +NamespaceIndentation: All +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterTemplateKeyword: true +SpacesInAngles: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +UseTab: Never \ No newline at end of file diff --git a/ArduinoPollDemo/ArduinoPollDemo.ino b/ArduinoPollDemo/ArduinoPollDemo.ino deleted file mode 100644 index 2628e4d..0000000 --- a/ArduinoPollDemo/ArduinoPollDemo.ino +++ /dev/null @@ -1,156 +0,0 @@ -#define prog_pin 4 -#define RXD2 16 -#define TXD2 17 - - -/* - The function send_cmd will send to the supplied inverter ID and wait for up to 100ms for an answer, - - The CMD is as followed: - - 0xC0 (cmd1 = 0) = Poll Inverter status, like voltage power etc. - - 0xC1 (cmd1 = 1) = Turn inverter ON - 0xC1 (cmd1 = 2) = Turn inverter OFF - 0xC1 (cmd1 = 3) = Reboot inverter - - 0xC3 (cmd1 1-100) = Set the PowerGrade of the Inverter -*/ - -uint8_t rx_buffer[50] = {0}; - -void setup() { - Serial.begin(115200); - Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2); - pinMode(LED_BUILTIN, OUTPUT); - pinMode(prog_pin, OUTPUT); - digitalWrite(prog_pin, HIGH); - delay(1000); - Serial.println("Welcome to Micro Inverter Interface By ATCnetz.de"); - - read_rf_setting(); -} - -long last_send = 0; -void loop() { - - if (millis() - last_send > 2000) { - last_send = millis(); - digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); - Serial.println(""); - Serial.println("Sending request now"); - - if (wait_for_answer(send_cmd(0x11002793, 0xC0, 0x00))) { - if (rx_buffer[1] == 0xc0) { // Got Status back - - uint32_t device_id = rx_buffer[6] << 24 | rx_buffer[7] << 16 | rx_buffer[8] << 8 | rx_buffer[9] & 0xff; - - int state = rx_buffer[25];// not reversed - - float dc_voltage = (rx_buffer[15] << 8 | rx_buffer[16]) / 100; - float dc_current = (rx_buffer[17] << 8 | rx_buffer[18]) / 100; - float dc_power = dc_voltage * dc_current; - - float ac_voltage = (rx_buffer[19] << 8 | rx_buffer[20]) / 100; - float ac_current = (rx_buffer[21] << 8 | rx_buffer[22]) / 100; - float ac_power = ac_voltage * ac_current; - - const uint32_t tempTotal = rx_buffer[10] << 24 | rx_buffer[11] << 16 | rx_buffer[12] << 8 | (rx_buffer[13] & 0xFF); - float power_gen_total = *((float*)&tempTotal); - - int temperature = rx_buffer[26];// not reversed - - Serial.println("*********************************************"); - Serial.println("Received Inverter Status"); - Serial.print("Device: "); - Serial.println(device_id, HEX); - Serial.println("Status: " + String(state)); - Serial.println("DC_Voltage: " + String(dc_voltage) + "V"); - Serial.println("DC_Current: " + String(dc_current) + "A"); - Serial.println("DC_Power: " + String(dc_power) + "W"); - Serial.println("AC_Voltage: " + String(ac_voltage) + "V"); - Serial.println("AC_Current: " + String(ac_current) + "A"); - Serial.println("AC_Power: " + String(ac_power) + "W"); - Serial.println("Power gen total: " + String(power_gen_total)); - Serial.println("Temperature: " + String(temperature)); - } - } - } -} - -uint8_t send_cmd(uint32_t device_id, uint8_t cmd, uint8_t mode) { - uint8_t crc = 0x43; - Serial2.write(0x43); - Serial2.write(cmd); - crc += cmd; - - Serial2.write(0x00); - Serial2.write(0x00); - Serial2.write(0x00); - Serial2.write(0x00); - - Serial2.write((device_id >> 24) & 0xff); - Serial2.write((device_id >> 16) & 0xff); - Serial2.write((device_id >> 8) & 0xff); - Serial2.write(device_id & 0xff); - crc += (device_id >> 24) & 0xff; - crc += (device_id >> 16) & 0xff; - crc += (device_id >> 8) & 0xff; - crc += device_id & 0xff; - - Serial2.write(0x00); - Serial2.write(0x00); - Serial2.write(0x00); - - Serial2.write(mode); - crc += mode; - Serial2.write(crc); - return cmd; -} - -bool wait_for_answer(uint8_t cmd) { - uint8_t expected_len = (cmd == 0xC0) ? 26 : 14; - long start_time = millis(); - int rx_pos = 0; - while (millis() - start_time < 100 && rx_pos <= expected_len) { - if (Serial2.available()) { - rx_buffer[rx_pos++] = Serial2.read(); - /* Serial.print("Received one pos:"); - Serial.print(rx_pos - 1); - Serial.print(" buff 0x"); - Serial.println(rx_buffer[rx_pos - 1], HEX);*/ - if (rx_pos > expected_len) { - while (Serial2.available())Serial2.read(); - return true; - } - } - } - while (Serial2.available())Serial2.read(); - return false; -} - -void read_rf_setting() { - uint8_t req_cmd[] = {0xAA, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x18}; - - digitalWrite(prog_pin, LOW); - delay(400); - Serial.println("Writing_settings_req:"); - for (int posi = 0; posi < sizeof(req_cmd); posi++) { - Serial2.write(req_cmd[posi]); - Serial.print(req_cmd[posi], HEX); - } - Serial.println(""); - long start_time = millis(); - Serial.println("Reading_settings:"); - while (millis() - start_time < 1000) { - while (Serial2.available()) { - - Serial.print(" 0x"); - Serial.print(Serial2.read(), HEX); - } - } - Serial.println(""); - - digitalWrite(prog_pin, HIGH); - delay(10); -} diff --git a/MicroInverterArduino.cpp b/MicroInverterArduino.cpp new file mode 100644 index 0000000..9ac99b9 --- /dev/null +++ b/MicroInverterArduino.cpp @@ -0,0 +1,95 @@ +#include + +#include "MicroInverterArduino.h" + +MicroInverterArduino::MicroInverterArduino(Stream& stream) : mStream(stream) { } + +MicroInverterArduino::~MicroInverterArduino() { } + +MicroInverterArduino::Status MicroInverterArduino::getStatus(const uint32_t deviceID) +{ + sendCommand(Command::STATUS, 0x00, deviceID); + Status status; + if (waitForAnswer(26)) // command == Command::STATUS ? 26 : 14 + { + status.valid = true; + + status.deviceID = mBuffer[6] << 24 | mBuffer[7] << 16 | mBuffer[8] << 8 | (mBuffer[9] & 0xFF); + + status.state = 0; // not reversed + status.powerGrade = 0; // not reversed + + status.dcVoltage = (mBuffer[15] << 8 | mBuffer[16]) / 100; + status.dcCurrent = (mBuffer[17] << 8 | mBuffer[18]) / 100; + status.dcPower = status.dcVoltage * status.dcCurrent; + + status.acVoltage = (mBuffer[19] << 8 | mBuffer[20]) / 100; + status.acCurrent = (mBuffer[21] << 8 | mBuffer[22]) / 100; + status.acPower = status.acVoltage * status.acCurrent; + + status.totalGeneratedPower = 0; // not reversed + status.temperature = mBuffer[26]; // not reversed + } + else + { + status.valid = false; + } + return status; +} + +void MicroInverterArduino::sendCommand(const Command command, const uint8_t value, const uint32_t deviceID) +{ + uint8_t* bufferPointer = &mBuffer[0]; + + *bufferPointer++ = 0x43; + *bufferPointer++ = command; + *bufferPointer++ = 0x00; + *bufferPointer++ = 0x00; + *bufferPointer++ = 0x00; + *bufferPointer++ = 0x00; + *bufferPointer++ = (deviceID >> 24) & 0xFF; + *bufferPointer++ = (deviceID >> 16) & 0xFF; + *bufferPointer++ = (deviceID >> 8) & 0xFF; + *bufferPointer++ = deviceID & 0xFF; + *bufferPointer++ = 0x00; + *bufferPointer++ = 0x00; + *bufferPointer++ = 0x00; + *bufferPointer++ = value; + *bufferPointer++ = calcCRC(); + + mStream.write(&mBuffer[0], 15); +} + +bool MicroInverterArduino::waitForAnswer(const size_t expectedSize) +{ + const uint32_t startTime = millis(); + while (millis() - startTime < 100 && !mStream.available()) + { + delay(1); + } + + const int available = mStream.available(); + if (available) + { + const size_t read = mStream.readBytes(&mBuffer[0], expectedSize); + + while (mStream.available()) + { + mStream.read(); + } + + return read == expectedSize; + } + + return false; +} + +uint8_t MicroInverterArduino::calcCRC() const +{ + uint8_t crc = 0; + for (int i = 0; i < 14; ++i) + { + crc += mBuffer[i]; + } + return crc; +} \ No newline at end of file diff --git a/MicroInverterArduino.h b/MicroInverterArduino.h new file mode 100644 index 0000000..fc0c6ba --- /dev/null +++ b/MicroInverterArduino.h @@ -0,0 +1,78 @@ +#pragma once + +#include + +/// @brief Class for micro inverter communication +class MicroInverterArduino +{ +public: + /// @brief Contains status information of a specific inverter + struct Status + { + uint32_t deviceID; /// Unique inverter identifier + + int state; /// Inverter state (not reversed) + int powerGrade; /// Power grade (not reversed) + + int totalGeneratedPower; /// Total generated power (kWh? not reversed) + int temperature; /// Inverter temperature (not reversed) + + bool valid; /// Validity of the contained data + + float dcVoltage; /// DC voltage in Volts (panel voltage) + float dcCurrent; /// DC current in Amperes (panel current) + float dcPower; /// DC power in Watts (panel power) + + float acVoltage; /// AC voltage in Volts + float acCurrent; /// AC current in Amperes + float acPower; /// AC power in Watts + }; + +public: + /// @brief Construct a new Micro Inverter Arduino object + /// + /// @param stream Stream to communicate with the wireless module + MicroInverterArduino(Stream& stream); + + /// @brief Destroy the Micro Inverter Arduino object + ~MicroInverterArduino(); + + /// @brief Get the status of the given device + /// + /// @param deviceID Unique device identifier + /// @return Status of the device (Status.valid == true) or empty status (Status.valid == false) + Status getStatus(const uint32_t deviceID); + +private: + /// @brief All known commands + enum Command + { + STATUS = 0xC0, /// Get status command + CONTROL = 0xC1, /// Control command + POWER_GRADE = 0xC3, /// Set power grade command + }; + +private: + /// @brief Send a specific command to a specific inverter with a specific value + /// + /// @param command Command to send + /// @param value Value to send + /// @param deviceID Recipient inverter identifier + void sendCommand(const Command command, const uint8_t value, const uint32_t deviceID); + + /// @brief Wait for an answer with the given expected size + /// + /// @param expectedSize Expected answer size in bytes + /// @return true If an answer with correct lenght was received + /// @return false If not + bool waitForAnswer(const size_t expectedSize); + + /// @brief Calculate the checksum for a message inside the buffer + /// + /// @return uint8_t CRC + uint8_t calcCRC() const; + +private: + Stream& mStream; /// Stream for communication + uint8_t mBuffer[32] = {0}; /// Inernal buffer +}; diff --git a/examples/PollDemo/PollDemo.ino b/examples/PollDemo/PollDemo.ino new file mode 100644 index 0000000..c7fbcd0 --- /dev/null +++ b/examples/PollDemo/PollDemo.ino @@ -0,0 +1,48 @@ +#include "MicroInverterArduino.h" + +constexpr const uint8_t RX_PIN = 16; +constexpr const uint8_t TX_PIN = 17; + +MicroInverterArduino inverter(Serial2); + +void setup() +{ + Serial.begin(115200); + Serial2.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN); + pinMode(LED_BUILTIN, OUTPUT); + delay(1000); + Serial.println("Welcome to Micro Inverter Interface by ATCnetz.de and enwi"); +} + +long lastSendMillis = 0; +void loop() +{ + const uint32_t currentMillis = millis(); + if (currentMillis - lastSendMillis > 2000) + { + lastSendMillis = currentMillis; + + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); + Serial.println(""); + Serial.println("Sending request now"); + + const MicroInverterArduino::Status status = inverter.getStatus(0x11002793); + if (status.valid) + { + Serial.println("*********************************************"); + Serial.println("Received Inverter Status"); + Serial.print("Device: "); + Serial.println(status.deviceID, HEX); + Serial.println("Status: " + String(status.state)); + Serial.println("PowerGrade: " + String(status.powerGrade)); + Serial.println("DC_Voltage: " + String(status.dcVoltage) + "V"); + Serial.println("DC_Current: " + String(status.dcCurrent) + "A"); + Serial.println("DC_Power: " + String(status.dcPower) + "W"); + Serial.println("AC_Voltage: " + String(status.acVoltage) + "V"); + Serial.println("AC_Current: " + String(status.acCurrent) + "A"); + Serial.println("AC_Power: " + String(status.acPower) + "W"); + Serial.println("Power gen total: " + String(status.totalGeneratedPower)); + Serial.println("Temperature: " + String(status.temperature)); + } + } +} From 80a24b0ecfa463d35dc28382996efa23efaa136c Mon Sep 17 00:00:00 2001 From: Moritz Wirger Date: Wed, 26 May 2021 20:03:32 +0200 Subject: [PATCH 02/15] Fix reading 1 byte less than needed for getStatus --- MicroInverterArduino.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MicroInverterArduino.cpp b/MicroInverterArduino.cpp index 9ac99b9..61daa9f 100644 --- a/MicroInverterArduino.cpp +++ b/MicroInverterArduino.cpp @@ -10,7 +10,7 @@ MicroInverterArduino::Status MicroInverterArduino::getStatus(const uint32_t devi { sendCommand(Command::STATUS, 0x00, deviceID); Status status; - if (waitForAnswer(26)) // command == Command::STATUS ? 26 : 14 + if (waitForAnswer(27)) // command == Command::STATUS ? 27 : 15 { status.valid = true; From 5958aefbc179be0a9dfab1470818ded9b12236cd Mon Sep 17 00:00:00 2001 From: Moritz Wirger Date: Wed, 26 May 2021 20:12:11 +0200 Subject: [PATCH 03/15] Add programming pin to class and disable it --- MicroInverterArduino.cpp | 16 +++++++++++++++- MicroInverterArduino.h | 10 +++++++++- examples/PollDemo/PollDemo.ino | 3 ++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/MicroInverterArduino.cpp b/MicroInverterArduino.cpp index 61daa9f..2bc6727 100644 --- a/MicroInverterArduino.cpp +++ b/MicroInverterArduino.cpp @@ -2,7 +2,11 @@ #include "MicroInverterArduino.h" -MicroInverterArduino::MicroInverterArduino(Stream& stream) : mStream(stream) { } +MicroInverterArduino::MicroInverterArduino(Stream& stream, const uint8_t progPin) : mStream(stream), mProgPin(progPin) +{ + pinMode(mProgPin, OUTPUT); + disableProgramming(); +} MicroInverterArduino::~MicroInverterArduino() { } @@ -92,4 +96,14 @@ uint8_t MicroInverterArduino::calcCRC() const crc += mBuffer[i]; } return crc; +} + +void MicroInverterArduino::enableProgramming() +{ + digitalWrite(mProgPin, LOW); +} + +void MicroInverterArduino::disableProgramming() +{ + digitalWrite(mProgPin, HIGH); } \ No newline at end of file diff --git a/MicroInverterArduino.h b/MicroInverterArduino.h index fc0c6ba..97b3813 100644 --- a/MicroInverterArduino.h +++ b/MicroInverterArduino.h @@ -32,7 +32,8 @@ class MicroInverterArduino /// @brief Construct a new Micro Inverter Arduino object /// /// @param stream Stream to communicate with the wireless module - MicroInverterArduino(Stream& stream); + /// @param progPin Programming enable pin of wireless module (active low) + MicroInverterArduino(Stream& stream, const uint8_t progPin); /// @brief Destroy the Micro Inverter Arduino object ~MicroInverterArduino(); @@ -72,7 +73,14 @@ class MicroInverterArduino /// @return uint8_t CRC uint8_t calcCRC() const; + /// @brief Enable programming mode of the wireless module + void enableProgramming(); + + /// @brief Disable programming mode of the wireless module + void disableProgramming(); + private: Stream& mStream; /// Stream for communication + uint8_t mProgPin; /// Programming enable pin of wireless module (active low) uint8_t mBuffer[32] = {0}; /// Inernal buffer }; diff --git a/examples/PollDemo/PollDemo.ino b/examples/PollDemo/PollDemo.ino index c7fbcd0..32874d5 100644 --- a/examples/PollDemo/PollDemo.ino +++ b/examples/PollDemo/PollDemo.ino @@ -1,9 +1,10 @@ #include "MicroInverterArduino.h" +constexpr const uint8_t PROG_PIN = 4; constexpr const uint8_t RX_PIN = 16; constexpr const uint8_t TX_PIN = 17; -MicroInverterArduino inverter(Serial2); +MicroInverterArduino inverter(Serial2, PROG_PIN); void setup() { From 150020b761f4705d9b365c0fee349aa178ca8cfa Mon Sep 17 00:00:00 2001 From: Moritz Wirger Date: Thu, 27 May 2021 19:45:46 +0200 Subject: [PATCH 04/15] Add flush function for RX and flush before sending --- MicroInverterArduino.cpp | 24 ++++++++++-------------- MicroInverterArduino.h | 3 +++ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/MicroInverterArduino.cpp b/MicroInverterArduino.cpp index 2bc6727..538ec66 100644 --- a/MicroInverterArduino.cpp +++ b/MicroInverterArduino.cpp @@ -12,6 +12,7 @@ MicroInverterArduino::~MicroInverterArduino() { } MicroInverterArduino::Status MicroInverterArduino::getStatus(const uint32_t deviceID) { + flushRX(); // Need to flush RX now to make sure it is empty for waitForAnswer() sendCommand(Command::STATUS, 0x00, deviceID); Status status; if (waitForAnswer(27)) // command == Command::STATUS ? 27 : 15 @@ -67,24 +68,14 @@ void MicroInverterArduino::sendCommand(const Command command, const uint8_t valu bool MicroInverterArduino::waitForAnswer(const size_t expectedSize) { const uint32_t startTime = millis(); - while (millis() - startTime < 100 && !mStream.available()) + while (millis() - startTime < 100) { - delay(1); - } - - const int available = mStream.available(); - if (available) - { - const size_t read = mStream.readBytes(&mBuffer[0], expectedSize); - - while (mStream.available()) + if (mStream.available()) { - mStream.read(); + return mStream.readBytes(&mBuffer[0], expectedSize) == expectedSize; } - - return read == expectedSize; + delay(1); } - return false; } @@ -106,4 +97,9 @@ void MicroInverterArduino::enableProgramming() void MicroInverterArduino::disableProgramming() { digitalWrite(mProgPin, HIGH); +} + +void MicroInverterArduino::flushRX() +{ + while (mStream.read() != -1) { } } \ No newline at end of file diff --git a/MicroInverterArduino.h b/MicroInverterArduino.h index 97b3813..12f74df 100644 --- a/MicroInverterArduino.h +++ b/MicroInverterArduino.h @@ -79,6 +79,9 @@ class MicroInverterArduino /// @brief Disable programming mode of the wireless module void disableProgramming(); + /// @brief Flush the receive buffer of the stream + void flushRX(); + private: Stream& mStream; /// Stream for communication uint8_t mProgPin; /// Programming enable pin of wireless module (active low) From 0100b7388c05b4446b657d04b0c083cb99baf6de Mon Sep 17 00:00:00 2001 From: Moritz Wirger Date: Thu, 27 May 2021 19:51:27 +0200 Subject: [PATCH 05/15] Rename library to NETSGPClient - Rename Status to InverterStatus --- MicroInverterArduino.cpp => NETSGPClient.cpp | 22 ++++++++++---------- MicroInverterArduino.h => NETSGPClient.h | 17 ++++++++------- examples/PollDemo/PollDemo.ino | 6 +++--- 3 files changed, 23 insertions(+), 22 deletions(-) rename MicroInverterArduino.cpp => NETSGPClient.cpp (76%) rename MicroInverterArduino.h => NETSGPClient.h (86%) diff --git a/MicroInverterArduino.cpp b/NETSGPClient.cpp similarity index 76% rename from MicroInverterArduino.cpp rename to NETSGPClient.cpp index 538ec66..8335159 100644 --- a/MicroInverterArduino.cpp +++ b/NETSGPClient.cpp @@ -1,20 +1,20 @@ #include -#include "MicroInverterArduino.h" +#include "NETSGPClient.h" -MicroInverterArduino::MicroInverterArduino(Stream& stream, const uint8_t progPin) : mStream(stream), mProgPin(progPin) +NETSGPClient::NETSGPClient(Stream& stream, const uint8_t progPin) : mStream(stream), mProgPin(progPin) { pinMode(mProgPin, OUTPUT); disableProgramming(); } -MicroInverterArduino::~MicroInverterArduino() { } +NETSGPClient::~NETSGPClient() { } -MicroInverterArduino::Status MicroInverterArduino::getStatus(const uint32_t deviceID) +NETSGPClient::InverterStatus NETSGPClient::getStatus(const uint32_t deviceID) { flushRX(); // Need to flush RX now to make sure it is empty for waitForAnswer() sendCommand(Command::STATUS, 0x00, deviceID); - Status status; + InverterStatus status; if (waitForAnswer(27)) // command == Command::STATUS ? 27 : 15 { status.valid = true; @@ -42,7 +42,7 @@ MicroInverterArduino::Status MicroInverterArduino::getStatus(const uint32_t devi return status; } -void MicroInverterArduino::sendCommand(const Command command, const uint8_t value, const uint32_t deviceID) +void NETSGPClient::sendCommand(const Command command, const uint8_t value, const uint32_t deviceID) { uint8_t* bufferPointer = &mBuffer[0]; @@ -65,7 +65,7 @@ void MicroInverterArduino::sendCommand(const Command command, const uint8_t valu mStream.write(&mBuffer[0], 15); } -bool MicroInverterArduino::waitForAnswer(const size_t expectedSize) +bool NETSGPClient::waitForAnswer(const size_t expectedSize) { const uint32_t startTime = millis(); while (millis() - startTime < 100) @@ -79,7 +79,7 @@ bool MicroInverterArduino::waitForAnswer(const size_t expectedSize) return false; } -uint8_t MicroInverterArduino::calcCRC() const +uint8_t NETSGPClient::calcCRC() const { uint8_t crc = 0; for (int i = 0; i < 14; ++i) @@ -89,17 +89,17 @@ uint8_t MicroInverterArduino::calcCRC() const return crc; } -void MicroInverterArduino::enableProgramming() +void NETSGPClient::enableProgramming() { digitalWrite(mProgPin, LOW); } -void MicroInverterArduino::disableProgramming() +void NETSGPClient::disableProgramming() { digitalWrite(mProgPin, HIGH); } -void MicroInverterArduino::flushRX() +void NETSGPClient::flushRX() { while (mStream.read() != -1) { } } \ No newline at end of file diff --git a/MicroInverterArduino.h b/NETSGPClient.h similarity index 86% rename from MicroInverterArduino.h rename to NETSGPClient.h index 12f74df..d40cb98 100644 --- a/MicroInverterArduino.h +++ b/NETSGPClient.h @@ -3,11 +3,11 @@ #include /// @brief Class for micro inverter communication -class MicroInverterArduino +class NETSGPClient { public: /// @brief Contains status information of a specific inverter - struct Status + struct InverterStatus { uint32_t deviceID; /// Unique inverter identifier @@ -29,20 +29,21 @@ class MicroInverterArduino }; public: - /// @brief Construct a new Micro Inverter Arduino object + /// @brief Construct a new NETSGPClient object /// /// @param stream Stream to communicate with the wireless module /// @param progPin Programming enable pin of wireless module (active low) - MicroInverterArduino(Stream& stream, const uint8_t progPin); + NETSGPClient(Stream& stream, const uint8_t progPin); - /// @brief Destroy the Micro Inverter Arduino object - ~MicroInverterArduino(); + /// @brief Destroy the NETSGPClient object + ~NETSGPClient(); /// @brief Get the status of the given device /// /// @param deviceID Unique device identifier - /// @return Status of the device (Status.valid == true) or empty status (Status.valid == false) - Status getStatus(const uint32_t deviceID); + /// @return InverterStatus Status of the inverter (InverterStatus.valid == true) or empty status + /// (InverterStatus.valid == false) + InverterStatus getStatus(const uint32_t deviceID); private: /// @brief All known commands diff --git a/examples/PollDemo/PollDemo.ino b/examples/PollDemo/PollDemo.ino index 32874d5..7917a01 100644 --- a/examples/PollDemo/PollDemo.ino +++ b/examples/PollDemo/PollDemo.ino @@ -1,10 +1,10 @@ -#include "MicroInverterArduino.h" +#include "NETSGPClient.h" constexpr const uint8_t PROG_PIN = 4; constexpr const uint8_t RX_PIN = 16; constexpr const uint8_t TX_PIN = 17; -MicroInverterArduino inverter(Serial2, PROG_PIN); +NETSGPClient client(Serial2, PROG_PIN); void setup() { @@ -27,7 +27,7 @@ void loop() Serial.println(""); Serial.println("Sending request now"); - const MicroInverterArduino::Status status = inverter.getStatus(0x11002793); + const NETSGPClient::InverterStatus status = client.getStatus(0x11002793); if (status.valid) { Serial.println("*********************************************"); From 1bfe3eaea915f449970d80add7b190f21866d811 Mon Sep 17 00:00:00 2001 From: Moritz Wirger Date: Thu, 27 May 2021 21:50:41 +0200 Subject: [PATCH 06/15] Add function for setting up RF module - Add function for reading RF module settings - Add function for writing RF module settings - Make calcCRC function more modular and use it for readRFModuleSettings() and writeRFModuleSettings() - Use setDefaultRFSettings() in example --- NETSGPClient.cpp | 100 +++++++++++++++++++++++++++-- NETSGPClient.h | 111 +++++++++++++++++++++++++++++---- examples/PollDemo/PollDemo.ino | 8 ++- 3 files changed, 202 insertions(+), 17 deletions(-) diff --git a/NETSGPClient.cpp b/NETSGPClient.cpp index 8335159..7c86e02 100644 --- a/NETSGPClient.cpp +++ b/NETSGPClient.cpp @@ -42,6 +42,96 @@ NETSGPClient::InverterStatus NETSGPClient::getStatus(const uint32_t deviceID) return status; } +LC12S::Settings NETSGPClient::readRFModuleSettings() +{ + uint8_t* bufferPointer = &mBuffer[0]; + + *bufferPointer++ = 0xAA; // command byte + *bufferPointer++ = 0x5C; // command byte + *bufferPointer++ = 0x00; // module identifier + *bufferPointer++ = 0x00; // module identifier + *bufferPointer++ = 0x00; // networking identifier + *bufferPointer++ = 0x00; // networking identifier + *bufferPointer++ = 0x00; // NC must be 0 + *bufferPointer++ = 0x00; // RF power + *bufferPointer++ = 0x00; // NC must be 0 + *bufferPointer++ = 0x00; // Baudrate + *bufferPointer++ = 0x00; // NC must be 0 + *bufferPointer++ = 0x00; // RF channel (0 - 127) + *bufferPointer++ = 0x00; // NC must be 0 + *bufferPointer++ = 0x00; // NC must be 0 + *bufferPointer++ = 0x00; // NC must be 0 + *bufferPointer++ = 0x12; // Length + *bufferPointer++ = 0x00; // NC must be 0 + *bufferPointer++ = 0x18; // Checksum + + enableProgramming(); + + mStream.write(&mBuffer[0], 18); + const size_t read = mStream.readBytes(&mBuffer[0], 18); + + disableProgramming(); + + LC12S::Settings settings; + if (read == 18 && mBuffer[0] == 0xAA && mBuffer[1] == 0x5D && mBuffer[17] == calcCRC(17)) + { + settings.valid = true; + + settings.moduleID = mBuffer[2] << 8 | (mBuffer[3] & 0xFF); + settings.networkID = mBuffer[4] << 8 | (mBuffer[5] & 0xFF); + settings.rfPower = static_cast(mBuffer[7]); + settings.baudrate = static_cast(mBuffer[9]); + settings.rfChannel = mBuffer[11]; + } + else + { + settings.valid = false; + } + return settings; +} + +bool NETSGPClient::writeRFModuleSettings(const LC12S::Settings& settings) +{ + uint8_t* bufferPointer = &mBuffer[0]; + + *bufferPointer++ = 0xAA; // command byte + *bufferPointer++ = 0x5A; // command byte + *bufferPointer++ = (settings.moduleID >> 8) & 0xFF; // module identifier + *bufferPointer++ = settings.moduleID & 0xFF; // module identifier + *bufferPointer++ = (settings.networkID >> 8) & 0xFF; // networking identifier + *bufferPointer++ = settings.networkID & 0xFF; // networking identifier + *bufferPointer++ = 0x00; // NC must be 0 + *bufferPointer++ = settings.rfPower; // RF power + *bufferPointer++ = 0x00; // NC must be 0 + *bufferPointer++ = settings.baudrate; // Baudrate + *bufferPointer++ = 0x00; // NC must be 0 + *bufferPointer++ = settings.rfChannel; // RF channel (0 - 127) + *bufferPointer++ = 0x00; // NC must be 0 + *bufferPointer++ = 0x00; // NC must be 0 + *bufferPointer++ = 0x00; // NC must be 0 + *bufferPointer++ = 0x12; // Length + *bufferPointer++ = 0x00; // NC must be 0 + *bufferPointer++ = calcCRC(16); // Checksum, we can calc for 16 bytes since 17th one is 0 + + enableProgramming(); + + mStream.write(&mBuffer[0], 18); + const size_t read = mStream.readBytes(&mBuffer[0], 18); + + disableProgramming(); + + return read == 18 && mBuffer[0] == 0xAA && mBuffer[1] == 0x5B && mBuffer[17] == calcCRC(17); +} + +bool NETSGPClient::setDefaultRFSettings() +{ + if (readRFModuleSettings() != LC12S::DEFAULT_SETTINGS) + { + return writeRFModuleSettings(LC12S::DEFAULT_SETTINGS); + } + return true; +} + void NETSGPClient::sendCommand(const Command command, const uint8_t value, const uint32_t deviceID) { uint8_t* bufferPointer = &mBuffer[0]; @@ -60,7 +150,7 @@ void NETSGPClient::sendCommand(const Command command, const uint8_t value, const *bufferPointer++ = 0x00; *bufferPointer++ = 0x00; *bufferPointer++ = value; - *bufferPointer++ = calcCRC(); + *bufferPointer++ = calcCRC(14); mStream.write(&mBuffer[0], 15); } @@ -79,10 +169,10 @@ bool NETSGPClient::waitForAnswer(const size_t expectedSize) return false; } -uint8_t NETSGPClient::calcCRC() const +uint8_t NETSGPClient::calcCRC(const size_t bytes) const { uint8_t crc = 0; - for (int i = 0; i < 14; ++i) + for (size_t i = 0; i < bytes; ++i) { crc += mBuffer[i]; } @@ -92,14 +182,16 @@ uint8_t NETSGPClient::calcCRC() const void NETSGPClient::enableProgramming() { digitalWrite(mProgPin, LOW); + delay(400); } void NETSGPClient::disableProgramming() { digitalWrite(mProgPin, HIGH); + delay(10); } void NETSGPClient::flushRX() { while (mStream.read() != -1) { } -} \ No newline at end of file +} diff --git a/NETSGPClient.h b/NETSGPClient.h index d40cb98..aaebf0d 100644 --- a/NETSGPClient.h +++ b/NETSGPClient.h @@ -2,6 +2,70 @@ #include +/// @brief LC12S 2.4GHz RF module specific stuff +namespace LC12S +{ + /// @brief RF power setting in dBm + enum RFPower + { + DBM_12 = 0, // 12 dBm + DBM_10 = 1, // 10 dBm + DBM_09 = 2, // 9 dBm + DBM_08 = 3, // 8 dBm + DBM_06 = 4, // 6 dBm + DBM_03 = 5, // 3 dBm + DBM_00 = 6, // 0 dBm + DBM_N_02 = 7, // -2 dBm + DBM_N_05 = 8, // -5 dBm + DBM_N_10 = 9, // -10 dBm + DBM_N_15 = 10, // -15 dBm + DBM_N_20 = 11, // -20 dBm + DBM_N_25 = 12, // -25 dBm + DBM_N_30 = 13, // -30 dBm + DBM_N_35 = 14, // -35 dBm + }; + + /// @brief Baudrate setting in baud + enum Baudrate + { + BPS_600 = 0, + BPS_1200 = 1, + BPS_2400 = 2, + BPS_4800 = 3, + BPS_9600 = 4, + BPS_19200 = 5, + BPS_38400 = 6, + }; + + /// @brief LC12S module settings + struct Settings + { + uint16_t moduleID; /// Module identifier + uint16_t networkID; /// Network identifier + LC12S::RFPower rfPower; /// RF power + LC12S::Baudrate baudrate; /// Baudrate + uint8_t rfChannel; /// RF channel + bool valid; /// Is this settings object valid (true) or not (false) + + bool operator==(const Settings& rhs) const + { + return moduleID == rhs.moduleID && networkID == rhs.networkID && rfPower == rhs.rfPower + && baudrate == rhs.baudrate && rfChannel == rhs.rfChannel && valid == rhs.valid; + } + bool operator!=(const Settings& rhs) const { return !operator==(rhs); } + }; + + constexpr const Settings DEFAULT_SETTINGS = { + .moduleID = 0x58AF, + .networkID = 0x0000, + .rfPower = RFPower::DBM_12, + .baudrate = Baudrate::BPS_9600, + .rfChannel = 0x64, + .valid = true, + }; + +} // namespace LC12S + /// @brief Class for micro inverter communication class NETSGPClient { @@ -29,22 +93,40 @@ class NETSGPClient }; public: - /// @brief Construct a new NETSGPClient object + /// @brief Construct a new NETSGPClient object. /// - /// @param stream Stream to communicate with the wireless module - /// @param progPin Programming enable pin of wireless module (active low) + /// @param stream Stream to communicate with the RF module + /// @param progPin Programming enable pin of RF module (active low) NETSGPClient(Stream& stream, const uint8_t progPin); /// @brief Destroy the NETSGPClient object ~NETSGPClient(); - /// @brief Get the status of the given device + /// @brief Get the status of the given device. /// /// @param deviceID Unique device identifier /// @return InverterStatus Status of the inverter (InverterStatus.valid == true) or empty status /// (InverterStatus.valid == false) InverterStatus getStatus(const uint32_t deviceID); + /// @brief Read the settings of the RF module + LC12S::Settings readRFModuleSettings(); + + /// @brief Change the settings of the RF module to the provided ones. + /// + /// @param settings Settings to write to the RF module + /// @return true If settings were written successfully + /// @return false If not + bool writeRFModuleSettings(const LC12S::Settings& settings); + + /// @brief Set the RF module to its default settings if needed. + /// + /// This function will read the RF module settings and then compare these with the default ones and if they + /// mismatch will write the default config + /// @return true If settings are correct or written successfully + /// @return false If settings could not be written + bool setDefaultRFSettings(); + private: /// @brief All known commands enum Command @@ -55,36 +137,41 @@ class NETSGPClient }; private: - /// @brief Send a specific command to a specific inverter with a specific value + /// @brief Send a specific command to a specific inverter with a specific value. /// /// @param command Command to send /// @param value Value to send /// @param deviceID Recipient inverter identifier void sendCommand(const Command command, const uint8_t value, const uint32_t deviceID); - /// @brief Wait for an answer with the given expected size + /// @brief Wait for an answer with the given expected size. /// /// @param expectedSize Expected answer size in bytes /// @return true If an answer with correct lenght was received /// @return false If not bool waitForAnswer(const size_t expectedSize); - /// @brief Calculate the checksum for a message inside the buffer + /// @brief Calculate the checksum for a message inside the buffer. /// + /// @param bytes The amount of bytes to calculate the checksum for /// @return uint8_t CRC - uint8_t calcCRC() const; + uint8_t calcCRC(const size_t bytes) const; - /// @brief Enable programming mode of the wireless module + /// @brief Enable programming mode of the RF module. + /// + /// This function will delay code execution for 400ms void enableProgramming(); - /// @brief Disable programming mode of the wireless module + /// @brief Disable programming mode of the RF module. + /// + /// This function will delay code execution for 10ms void disableProgramming(); - /// @brief Flush the receive buffer of the stream + /// @brief Flush the receive buffer of the stream. void flushRX(); private: Stream& mStream; /// Stream for communication - uint8_t mProgPin; /// Programming enable pin of wireless module (active low) + uint8_t mProgPin; /// Programming enable pin of RF module (active low) uint8_t mBuffer[32] = {0}; /// Inernal buffer }; diff --git a/examples/PollDemo/PollDemo.ino b/examples/PollDemo/PollDemo.ino index 7917a01..80fc088 100644 --- a/examples/PollDemo/PollDemo.ino +++ b/examples/PollDemo/PollDemo.ino @@ -13,9 +13,15 @@ void setup() pinMode(LED_BUILTIN, OUTPUT); delay(1000); Serial.println("Welcome to Micro Inverter Interface by ATCnetz.de and enwi"); + + // Make sure the RF module is set to the correct settings + if (!client.setDefaultRFSettings()) + { + Serial.println("Could not set RF module to default settings"); + } } -long lastSendMillis = 0; +uint32_t lastSendMillis = 0; void loop() { const uint32_t currentMillis = millis(); From dbdf6398ad0384bb12f1076ae58c201d4a46e279 Mon Sep 17 00:00:00 2001 From: Moritz Wirger Date: Thu, 27 May 2021 22:22:45 +0200 Subject: [PATCH 07/15] Set state and totalGeneratedPower in getStatus - Remove powerGrade from InverterStatus --- NETSGPClient.cpp | 10 ++++++---- NETSGPClient.h | 9 ++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/NETSGPClient.cpp b/NETSGPClient.cpp index 7c86e02..de6ea70 100644 --- a/NETSGPClient.cpp +++ b/NETSGPClient.cpp @@ -21,8 +21,10 @@ NETSGPClient::InverterStatus NETSGPClient::getStatus(const uint32_t deviceID) status.deviceID = mBuffer[6] << 24 | mBuffer[7] << 16 | mBuffer[8] << 8 | (mBuffer[9] & 0xFF); - status.state = 0; // not reversed - status.powerGrade = 0; // not reversed + status.totalGeneratedPower + = static_cast(mBuffer[10] << 24 | mBuffer[11] << 16 | mBuffer[12] << 8 | (mBuffer[13] & 0xFF)); + + // CRC = mBuffer[14] status.dcVoltage = (mBuffer[15] << 8 | mBuffer[16]) / 100; status.dcCurrent = (mBuffer[17] << 8 | mBuffer[18]) / 100; @@ -32,8 +34,8 @@ NETSGPClient::InverterStatus NETSGPClient::getStatus(const uint32_t deviceID) status.acCurrent = (mBuffer[21] << 8 | mBuffer[22]) / 100; status.acPower = status.acVoltage * status.acCurrent; - status.totalGeneratedPower = 0; // not reversed - status.temperature = mBuffer[26]; // not reversed + status.state = mBuffer[25]; // not fully reversed + status.temperature = mBuffer[26]; // not fully reversed } else { diff --git a/NETSGPClient.h b/NETSGPClient.h index aaebf0d..a7be4e2 100644 --- a/NETSGPClient.h +++ b/NETSGPClient.h @@ -75,14 +75,13 @@ class NETSGPClient { uint32_t deviceID; /// Unique inverter identifier - int state; /// Inverter state (not reversed) - int powerGrade; /// Power grade (not reversed) - - int totalGeneratedPower; /// Total generated power (kWh? not reversed) - int temperature; /// Inverter temperature (not reversed) + uint8_t state; /// Inverter state (not reversed) + uint8_t temperature; /// Inverter temperature (not reversed) bool valid; /// Validity of the contained data + float totalGeneratedPower; /// Total generated power (kWh? not reversed) + float dcVoltage; /// DC voltage in Volts (panel voltage) float dcCurrent; /// DC current in Amperes (panel current) float dcPower; /// DC power in Watts (panel power) From a5fd63badc8638d4e2ae6176ae4f7f2c55daf08c Mon Sep 17 00:00:00 2001 From: Moritz Wirger Date: Sat, 29 May 2021 10:03:42 +0200 Subject: [PATCH 08/15] Fix conversion of totalGeneratedPower - Fix setDefaultRFSettings overwriting unique moduleID - Fix PollDemo accessing deleted member powerGrade --- NETSGPClient.cpp | 14 ++++++++++---- NETSGPClient.h | 6 +++--- examples/PollDemo/PollDemo.ino | 1 - 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/NETSGPClient.cpp b/NETSGPClient.cpp index de6ea70..f479e6e 100644 --- a/NETSGPClient.cpp +++ b/NETSGPClient.cpp @@ -21,8 +21,8 @@ NETSGPClient::InverterStatus NETSGPClient::getStatus(const uint32_t deviceID) status.deviceID = mBuffer[6] << 24 | mBuffer[7] << 16 | mBuffer[8] << 8 | (mBuffer[9] & 0xFF); - status.totalGeneratedPower - = static_cast(mBuffer[10] << 24 | mBuffer[11] << 16 | mBuffer[12] << 8 | (mBuffer[13] & 0xFF)); + const uint32_t tempTotal = mBuffer[10] << 24 | mBuffer[11] << 16 | mBuffer[12] << 8 | (mBuffer[13] & 0xFF); + status.totalGeneratedPower = *((float*)&tempTotal); // CRC = mBuffer[14] @@ -127,9 +127,15 @@ bool NETSGPClient::writeRFModuleSettings(const LC12S::Settings& settings) bool NETSGPClient::setDefaultRFSettings() { - if (readRFModuleSettings() != LC12S::DEFAULT_SETTINGS) + LC12S::Settings settings = readRFModuleSettings(); + if (settings != LC12S::DEFAULT_SETTINGS) { - return writeRFModuleSettings(LC12S::DEFAULT_SETTINGS); + // Copy over default settings without moduleID since that is uinque for each module + settings.networkID = LC12S::DEFAULT_SETTINGS.networkID; + settings.rfPower = LC12S::DEFAULT_SETTINGS.rfPower; + settings.baudrate = LC12S::DEFAULT_SETTINGS.baudrate; + settings.rfChannel = LC12S::DEFAULT_SETTINGS.rfChannel; + return writeRFModuleSettings(settings); } return true; } diff --git a/NETSGPClient.h b/NETSGPClient.h index a7be4e2..67a8f2e 100644 --- a/NETSGPClient.h +++ b/NETSGPClient.h @@ -40,7 +40,7 @@ namespace LC12S /// @brief LC12S module settings struct Settings { - uint16_t moduleID; /// Module identifier + uint16_t moduleID; /// Unique module identifier uint16_t networkID; /// Network identifier LC12S::RFPower rfPower; /// RF power LC12S::Baudrate baudrate; /// Baudrate @@ -49,8 +49,8 @@ namespace LC12S bool operator==(const Settings& rhs) const { - return moduleID == rhs.moduleID && networkID == rhs.networkID && rfPower == rhs.rfPower - && baudrate == rhs.baudrate && rfChannel == rhs.rfChannel && valid == rhs.valid; + return networkID == rhs.networkID && rfPower == rhs.rfPower && baudrate == rhs.baudrate + && rfChannel == rhs.rfChannel && valid == rhs.valid; } bool operator!=(const Settings& rhs) const { return !operator==(rhs); } }; diff --git a/examples/PollDemo/PollDemo.ino b/examples/PollDemo/PollDemo.ino index 80fc088..fcd7e15 100644 --- a/examples/PollDemo/PollDemo.ino +++ b/examples/PollDemo/PollDemo.ino @@ -41,7 +41,6 @@ void loop() Serial.print("Device: "); Serial.println(status.deviceID, HEX); Serial.println("Status: " + String(status.state)); - Serial.println("PowerGrade: " + String(status.powerGrade)); Serial.println("DC_Voltage: " + String(status.dcVoltage) + "V"); Serial.println("DC_Current: " + String(status.dcCurrent) + "A"); Serial.println("DC_Power: " + String(status.dcPower) + "W"); From 2138739c60de569b445842813dd2f73f0fb9afd9 Mon Sep 17 00:00:00 2001 From: Moritz Wirger Date: Sat, 29 May 2021 10:20:43 +0200 Subject: [PATCH 09/15] Increase timeout of waitForAnswer --- NETSGPClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NETSGPClient.cpp b/NETSGPClient.cpp index f479e6e..277ed0d 100644 --- a/NETSGPClient.cpp +++ b/NETSGPClient.cpp @@ -166,7 +166,7 @@ void NETSGPClient::sendCommand(const Command command, const uint8_t value, const bool NETSGPClient::waitForAnswer(const size_t expectedSize) { const uint32_t startTime = millis(); - while (millis() - startTime < 100) + while (millis() - startTime < 1000) { if (mStream.available()) { From 6c313fb4421b83219053ea4a3f74d0c70a9ba776 Mon Sep 17 00:00:00 2001 From: Moritz Wirger Date: Sat, 29 May 2021 11:27:55 +0200 Subject: [PATCH 10/15] Add new AsyncNETSGPClient for async communication - Remove duplicate code by adding fillInverterStatusFromBuffer() function - Add AsyncDemo as an example --- AsyncNETSGPClient.cpp | 49 ++++++++++++++++++++++++++++++++ AsyncNETSGPClient.h | 45 +++++++++++++++++++++++++++++ NETSGPClient.cpp | 40 ++++++++++++++------------ NETSGPClient.h | 12 ++++++-- examples/AsyncDemo/AsyncDemo.ino | 49 ++++++++++++++++++++++++++++++++ 5 files changed, 174 insertions(+), 21 deletions(-) create mode 100644 AsyncNETSGPClient.cpp create mode 100644 AsyncNETSGPClient.h create mode 100644 examples/AsyncDemo/AsyncDemo.ino diff --git a/AsyncNETSGPClient.cpp b/AsyncNETSGPClient.cpp new file mode 100644 index 0000000..8240cfd --- /dev/null +++ b/AsyncNETSGPClient.cpp @@ -0,0 +1,49 @@ +#include + +#include "AsyncNETSGPClient.h" + +AsyncNETSGPClient::AsyncNETSGPClient(Stream& stream, const uint8_t progPin, const uint8_t interval) + : NETSGPClient(stream, progPin), mIntervalMS(1000 * interval) +{ } + +void AsyncNETSGPClient::update() +{ + const uint32_t currentMillis = millis(); + + // Send comands at mIntervalMS + if (currentMillis - mLastUpdateMS >= mIntervalMS) + { + mLastUpdateMS = currentMillis; + + for (const uint32_t deviceID : mDevices) + { + sendCommand(Command::STATUS, 0x00, deviceID); + } + } + + // Check for answers + if (mStream.available() >= 27) + { + const uint8_t header[2] = {43, Command::STATUS}; + if (!mStream.findUntil(&header[0], 2, nullptr, 0)) + { + return; + } + // We found a magic byte and the command + + // Read rest of message + if (mStream.readBytes(&mBuffer[2], 25) != 25) + { + return; + } + + if (mCallback) + { + InverterStatus status; + status.valid = true; // TODO maybe use checksum for this + fillInverterStatusFromBuffer(&mBuffer[0], status); + + mCallback(status); + } + } +} \ No newline at end of file diff --git a/AsyncNETSGPClient.h b/AsyncNETSGPClient.h new file mode 100644 index 0000000..eabccf4 --- /dev/null +++ b/AsyncNETSGPClient.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include "NETSGPClient.h" + +/// @brief Async version of NETSGPClient +class AsyncNETSGPClient : public NETSGPClient +{ + typedef void (*InverterStatusCallback)(const NETSGPClient::InverterStatus); + +public: + /// @brief Construct a new AsyncNETSGPClient object. + /// + /// @param stream Stream to communicate with the RF module + /// @param progPin Programming enable pin of RF module (active low) + /// @param interval The update interval in seconds, default is 2 seconds + AsyncNETSGPClient(Stream& stream, const uint8_t progPin, const uint8_t interval = 2); + + /// @brief Set the callback for inverter status updates + /// + /// @param callback Callback that gets called on updates, may be nullptr + void setStatusCallback(InverterStatusCallback callback) { mCallback = callback; } + + /// @brief Register a new inverter to receive status updates + /// + /// @param deviceID The device identifier of the inverter + void registerInverter(const uint32_t deviceID) { mDevices.insert(deviceID); } + + /// @brief Deregister an inverter to not receive status updates + /// + /// @param deviceID The device identifier of the inverter + void deregisterInverter(const uint32_t deviceID) { mDevices.erase(deviceID); } + + /// @brief Update the internal state + /// + /// @note Needs to be called inside loop() + void update(); + +private: + uint16_t mIntervalMS; /// Update interval in milliseconds + uint32_t mLastUpdateMS; /// Last update time in milliseconds + std::set mDevices; /// All devices to poll + InverterStatusCallback mCallback = nullptr; /// Callback for status updates +}; diff --git a/NETSGPClient.cpp b/NETSGPClient.cpp index 277ed0d..7ac8ac8 100644 --- a/NETSGPClient.cpp +++ b/NETSGPClient.cpp @@ -18,24 +18,7 @@ NETSGPClient::InverterStatus NETSGPClient::getStatus(const uint32_t deviceID) if (waitForAnswer(27)) // command == Command::STATUS ? 27 : 15 { status.valid = true; - - status.deviceID = mBuffer[6] << 24 | mBuffer[7] << 16 | mBuffer[8] << 8 | (mBuffer[9] & 0xFF); - - const uint32_t tempTotal = mBuffer[10] << 24 | mBuffer[11] << 16 | mBuffer[12] << 8 | (mBuffer[13] & 0xFF); - status.totalGeneratedPower = *((float*)&tempTotal); - - // CRC = mBuffer[14] - - status.dcVoltage = (mBuffer[15] << 8 | mBuffer[16]) / 100; - status.dcCurrent = (mBuffer[17] << 8 | mBuffer[18]) / 100; - status.dcPower = status.dcVoltage * status.dcCurrent; - - status.acVoltage = (mBuffer[19] << 8 | mBuffer[20]) / 100; - status.acCurrent = (mBuffer[21] << 8 | mBuffer[22]) / 100; - status.acPower = status.acVoltage * status.acCurrent; - - status.state = mBuffer[25]; // not fully reversed - status.temperature = mBuffer[26]; // not fully reversed + fillInverterStatusFromBuffer(&mBuffer[0], status); } else { @@ -203,3 +186,24 @@ void NETSGPClient::flushRX() { while (mStream.read() != -1) { } } + +void NETSGPClient::fillInverterStatusFromBuffer(const uint8_t* buffer, InverterStatus& status) +{ + status.deviceID = buffer[6] << 24 | buffer[7] << 16 | buffer[8] << 8 | (buffer[9] & 0xFF); + + const uint32_t tempTotal = buffer[10] << 24 | buffer[11] << 16 | buffer[12] << 8 | (buffer[13] & 0xFF); + status.totalGeneratedPower = *((float*)&tempTotal); + + // CRC = buffer[14] + + status.dcVoltage = (buffer[15] << 8 | buffer[16]) / 100; + status.dcCurrent = (buffer[17] << 8 | buffer[18]) / 100; + status.dcPower = status.dcVoltage * status.dcCurrent; + + status.acVoltage = (buffer[19] << 8 | buffer[20]) / 100; + status.acCurrent = (buffer[21] << 8 | buffer[22]) / 100; + status.acPower = status.acVoltage * status.acCurrent; + + status.state = buffer[25]; // not fully reversed + status.temperature = buffer[26]; // not fully reversed +} \ No newline at end of file diff --git a/NETSGPClient.h b/NETSGPClient.h index 67a8f2e..abf21bc 100644 --- a/NETSGPClient.h +++ b/NETSGPClient.h @@ -126,7 +126,7 @@ class NETSGPClient /// @return false If settings could not be written bool setDefaultRFSettings(); -private: +protected: /// @brief All known commands enum Command { @@ -135,7 +135,7 @@ class NETSGPClient POWER_GRADE = 0xC3, /// Set power grade command }; -private: +protected: /// @brief Send a specific command to a specific inverter with a specific value. /// /// @param command Command to send @@ -169,7 +169,13 @@ class NETSGPClient /// @brief Flush the receive buffer of the stream. void flushRX(); -private: + /// @brief Fill the given inverter status from the given buffer + /// + /// @param buffer Bufffer containing raw inverter status data, must be at least 27 bytes in size + /// @param status Inverter status to fill + void fillInverterStatusFromBuffer(const uint8_t* buffer, InverterStatus& status); + +protected: Stream& mStream; /// Stream for communication uint8_t mProgPin; /// Programming enable pin of RF module (active low) uint8_t mBuffer[32] = {0}; /// Inernal buffer diff --git a/examples/AsyncDemo/AsyncDemo.ino b/examples/AsyncDemo/AsyncDemo.ino new file mode 100644 index 0000000..58b37cb --- /dev/null +++ b/examples/AsyncDemo/AsyncDemo.ino @@ -0,0 +1,49 @@ +#include "AsyncNETSGPClient.h" + +constexpr const uint8_t PROG_PIN = 4; +constexpr const uint8_t RX_PIN = 16; +constexpr const uint8_t TX_PIN = 17; + +AsyncNETSGPClient client(Serial2, PROG_PIN); // Defaults to update every 2 seconds +// AsyncNETSGPClient client(Serial2, PROG_PIN, 10); // Update every 10 seconds + +void onInverterStatus(const AsyncNETSGPClient::InverterStatus status) +{ + // We do not need to check status.valid, because only valid ones are anounced + Serial.println("*********************************************"); + Serial.println("Received Inverter Status"); + Serial.print("Device: "); + Serial.println(status.deviceID, HEX); + Serial.println("Status: " + String(status.state)); + Serial.println("DC_Voltage: " + String(status.dcVoltage) + "V"); + Serial.println("DC_Current: " + String(status.dcCurrent) + "A"); + Serial.println("DC_Power: " + String(status.dcPower) + "W"); + Serial.println("AC_Voltage: " + String(status.acVoltage) + "V"); + Serial.println("AC_Current: " + String(status.acCurrent) + "A"); + Serial.println("AC_Power: " + String(status.acPower) + "W"); + Serial.println("Power gen total: " + String(status.totalGeneratedPower)); + Serial.println("Temperature: " + String(status.temperature)); +} + +void setup() +{ + Serial.begin(115200); + Serial2.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN); + pinMode(LED_BUILTIN, OUTPUT); + delay(1000); + Serial.println("Welcome to Micro Inverter Interface by ATCnetz.de and enwi"); + + // Make sure the RF module is set to the correct settings + if (!client.setDefaultRFSettings()) + { + Serial.println("Could not set RF module to default settings"); + } + + client.setStatusCallback(onInverterStatus); + client.registerInverter(0x11002793); +} + +void loop() +{ + client.update(); +} From 3801b757db2fbdfc91de9f4bcc26f1aa686e9080 Mon Sep 17 00:00:00 2001 From: Moritz Wirger Date: Sat, 29 May 2021 12:17:20 +0200 Subject: [PATCH 11/15] Cleanup AsyncNETSGPClient and README --- AsyncNETSGPClient.cpp | 11 +++++------ AsyncNETSGPClient.h | 2 +- README.md | 26 ++++++++++++++++---------- examples/AsyncDemo/AsyncDemo.ino | 2 +- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/AsyncNETSGPClient.cpp b/AsyncNETSGPClient.cpp index 8240cfd..9791e5d 100644 --- a/AsyncNETSGPClient.cpp +++ b/AsyncNETSGPClient.cpp @@ -22,19 +22,19 @@ void AsyncNETSGPClient::update() } // Check for answers - if (mStream.available() >= 27) + while (mStream.available() >= 27) { + // Search for a magic byte and command const uint8_t header[2] = {43, Command::STATUS}; - if (!mStream.findUntil(&header[0], 2, nullptr, 0)) + if (!mStream.find(&header[0], 2)) { - return; + continue; } - // We found a magic byte and the command // Read rest of message if (mStream.readBytes(&mBuffer[2], 25) != 25) { - return; + continue; } if (mCallback) @@ -42,7 +42,6 @@ void AsyncNETSGPClient::update() InverterStatus status; status.valid = true; // TODO maybe use checksum for this fillInverterStatusFromBuffer(&mBuffer[0], status); - mCallback(status); } } diff --git a/AsyncNETSGPClient.h b/AsyncNETSGPClient.h index eabccf4..5185d39 100644 --- a/AsyncNETSGPClient.h +++ b/AsyncNETSGPClient.h @@ -7,7 +7,7 @@ /// @brief Async version of NETSGPClient class AsyncNETSGPClient : public NETSGPClient { - typedef void (*InverterStatusCallback)(const NETSGPClient::InverterStatus); + typedef void (*InverterStatusCallback)(const NETSGPClient::InverterStatus&); public: /// @brief Construct a new AsyncNETSGPClient object. diff --git a/README.md b/README.md index f9725af..b7ff5d3 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,24 @@ -# MicroInverterArduino -Arduino Interface for cheap 2.4ghz RF enabled Solar Micro Inverters +# NETSGPClient +Arduino Interface for cheap 2.4ghz RF enabled Solar Micro Inverters using the so-called NETSGP protocol for communication. Here is a YouTube video that shows the general function https://youtu.be/uA2eMhF7RCY [![YoutubeVideo](https://img.youtube.com/vi/uA2eMhF7RCY/0.jpg)](https://www.youtube.com/watch?v=uA2eMhF7RCY) -The Arduino Poll demo shows how to request a Status from the Micro inverter, input your inverter id to get an answer. +## Examples +The `PollDemo` shows how to request a status from the Micro Inverter synchronously. +Input your inverter id to get status updates. +The `AsyncDemo` shoows how to get a status from multiple Micro Inverters asynchronously. +Input one or more inverter identifiers to get status updates inside the callback -Works on Inverters named Like: -SG200MS -SG300MS -SG600MD -SG700MD -SG1000MQ -SG1200MQ +## Supported Devices +| Model | Tested | Compatible | Notes | ID starting with | +|:---------------|--------------------|--------------------|-------------|------------------| +| `SG200MS` | :white_check_mark: | :white_check_mark: | 200W model | 11000001 | +| `SG300MS` | :x: | :grey_question: | 300W model | 19000001 | +| `SG600MD` | :x: | :grey_question: | 600W model | 38000001 | +| `SG700MD` | :x: | :grey_question: | 700W model | 41000001 | +| `SG1000MQ` | :x: | :grey_question: | 1.0kW model | 48000001 | +| `SG1200MQ` | :x: | :grey_question: | 1.2kW model | 52000001 | diff --git a/examples/AsyncDemo/AsyncDemo.ino b/examples/AsyncDemo/AsyncDemo.ino index 58b37cb..5b3d9f9 100644 --- a/examples/AsyncDemo/AsyncDemo.ino +++ b/examples/AsyncDemo/AsyncDemo.ino @@ -7,7 +7,7 @@ constexpr const uint8_t TX_PIN = 17; AsyncNETSGPClient client(Serial2, PROG_PIN); // Defaults to update every 2 seconds // AsyncNETSGPClient client(Serial2, PROG_PIN, 10); // Update every 10 seconds -void onInverterStatus(const AsyncNETSGPClient::InverterStatus status) +void onInverterStatus(const AsyncNETSGPClient::InverterStatus& status) { // We do not need to check status.valid, because only valid ones are anounced Serial.println("*********************************************"); From ec90849e45119f4398688d83e4e0ef39bf7c22ee Mon Sep 17 00:00:00 2001 From: Moritz Wirger Date: Sat, 29 May 2021 13:31:07 +0200 Subject: [PATCH 12/15] Make library more Arduino like - Fix misspell in readme --- README.md | 4 ++-- keywords.txt | 38 ++++++++++++++++++++++++++++++++++++++ library.properties | 11 +++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 keywords.txt create mode 100644 library.properties diff --git a/README.md b/README.md index b7ff5d3..cf6a60b 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ https://youtu.be/uA2eMhF7RCY The `PollDemo` shows how to request a status from the Micro Inverter synchronously. Input your inverter id to get status updates. -The `AsyncDemo` shoows how to get a status from multiple Micro Inverters asynchronously. -Input one or more inverter identifiers to get status updates inside the callback +The `AsyncDemo` shows how to get a status from multiple Micro Inverters asynchronously. +Input one or more inverter identifiers to get status updates inside the callback. ## Supported Devices diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..394ef2f --- /dev/null +++ b/keywords.txt @@ -0,0 +1,38 @@ +####################################### +# Syntax Coloring Map For Test +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +NETSGPClient KEYWORD1 +AsyncNETSGPClient KEYWORD1 +LC12S KEYWORD1 +RFPower KEYWORD1 +Baudrate KEYWORD1 +Settings KEYWORD1 +InverterStatus KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +getStatus KEYWORD2 +readRFModuleSettings KEYWORD2 +writeRFModuleSettings KEYWORD2 +setDefaultRFSettings KEYWORD2 +setStatusCallback KEYWORD2 +registerInverter KEYWORD2 +deregisterInverter KEYWORD2 +update KEYWORD2 + +####################################### +# Instances (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### + +DEFAULT_SETTINGS LITERAL1 diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..8321c2d --- /dev/null +++ b/library.properties @@ -0,0 +1,11 @@ +name=NETSGPClient +version=1.0 +author=Aaron Christophel, Moritz Wirger +maintainer=Aaron Christophel, Moritz Wirger +sentence=Interface for MicroInverters speaking the so-called NETSGP protocol. +paragraph=An LC12S 2.4GHz RF module is needed for this library +category=Communication +url=https://github.com/atc1441/NETSGPClient +architectures=* +depends=StandardCplusplus +dot_a_linkage=true From f6328f48bd56e370cdaf383142a8f739216c1c8e Mon Sep 17 00:00:00 2001 From: Moritz Wirger Date: Sat, 29 May 2021 14:15:11 +0200 Subject: [PATCH 13/15] Fix searching for wrong magic byte in update --- AsyncNETSGPClient.cpp | 2 +- NETSGPClient.cpp | 2 +- NETSGPClient.h | 1 + keywords.txt | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/AsyncNETSGPClient.cpp b/AsyncNETSGPClient.cpp index 9791e5d..4fe1793 100644 --- a/AsyncNETSGPClient.cpp +++ b/AsyncNETSGPClient.cpp @@ -25,7 +25,7 @@ void AsyncNETSGPClient::update() while (mStream.available() >= 27) { // Search for a magic byte and command - const uint8_t header[2] = {43, Command::STATUS}; + const uint8_t header[2] = {MAGIC_BYTE, Command::STATUS}; if (!mStream.find(&header[0], 2)) { continue; diff --git a/NETSGPClient.cpp b/NETSGPClient.cpp index 7ac8ac8..6911e90 100644 --- a/NETSGPClient.cpp +++ b/NETSGPClient.cpp @@ -127,7 +127,7 @@ void NETSGPClient::sendCommand(const Command command, const uint8_t value, const { uint8_t* bufferPointer = &mBuffer[0]; - *bufferPointer++ = 0x43; + *bufferPointer++ = MAGIC_BYTE; *bufferPointer++ = command; *bufferPointer++ = 0x00; *bufferPointer++ = 0x00; diff --git a/NETSGPClient.h b/NETSGPClient.h index abf21bc..111630c 100644 --- a/NETSGPClient.h +++ b/NETSGPClient.h @@ -179,4 +179,5 @@ class NETSGPClient Stream& mStream; /// Stream for communication uint8_t mProgPin; /// Programming enable pin of RF module (active low) uint8_t mBuffer[32] = {0}; /// Inernal buffer + static const uint8_t MAGIC_BYTE = 0x43; /// Magic byte indicating start of messages }; diff --git a/keywords.txt b/keywords.txt index 394ef2f..a939259 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,5 +1,5 @@ ####################################### -# Syntax Coloring Map For Test +# Syntax Coloring Map For NETSGPClient ####################################### ####################################### From 650134524351fc7010583ee6bbb7478f7ac824d5 Mon Sep 17 00:00:00 2001 From: Moritz Wirger Date: Sat, 29 May 2021 14:48:59 +0200 Subject: [PATCH 14/15] Cleanup code and remove duplications - Remove flushRX and search the stream for messages which is less error prone - Rename waitForMessage to waitForAnswer and don't read data inside that function --- AsyncNETSGPClient.cpp | 18 +++--------------- NETSGPClient.cpp | 29 +++++++++++++++++++---------- NETSGPClient.h | 17 ++++++++++------- examples/AsyncDemo/AsyncDemo.ino | 15 +++++++++++---- examples/PollDemo/PollDemo.ino | 11 ++++++----- 5 files changed, 49 insertions(+), 41 deletions(-) diff --git a/AsyncNETSGPClient.cpp b/AsyncNETSGPClient.cpp index 4fe1793..e9c4fc0 100644 --- a/AsyncNETSGPClient.cpp +++ b/AsyncNETSGPClient.cpp @@ -24,24 +24,12 @@ void AsyncNETSGPClient::update() // Check for answers while (mStream.available() >= 27) { - // Search for a magic byte and command - const uint8_t header[2] = {MAGIC_BYTE, Command::STATUS}; - if (!mStream.find(&header[0], 2)) - { - continue; - } - - // Read rest of message - if (mStream.readBytes(&mBuffer[2], 25) != 25) - { - continue; - } - - if (mCallback) + // Search for a read status message + if (findAndReadStatusMessage() && mCallback) { InverterStatus status; - status.valid = true; // TODO maybe use checksum for this fillInverterStatusFromBuffer(&mBuffer[0], status); + status.valid = true; // TODO maybe use checksum for this mCallback(status); } } diff --git a/NETSGPClient.cpp b/NETSGPClient.cpp index 6911e90..7551c5d 100644 --- a/NETSGPClient.cpp +++ b/NETSGPClient.cpp @@ -12,12 +12,12 @@ NETSGPClient::~NETSGPClient() { } NETSGPClient::InverterStatus NETSGPClient::getStatus(const uint32_t deviceID) { - flushRX(); // Need to flush RX now to make sure it is empty for waitForAnswer() sendCommand(Command::STATUS, 0x00, deviceID); InverterStatus status; - if (waitForAnswer(27)) // command == Command::STATUS ? 27 : 15 + if (waitForMessage()) // command == Command::STATUS ? 27 : 15 { status.valid = true; + findAndReadStatusMessage(); fillInverterStatusFromBuffer(&mBuffer[0], status); } else @@ -146,20 +146,32 @@ void NETSGPClient::sendCommand(const Command command, const uint8_t value, const mStream.write(&mBuffer[0], 15); } -bool NETSGPClient::waitForAnswer(const size_t expectedSize) +bool NETSGPClient::waitForMessage() { const uint32_t startTime = millis(); while (millis() - startTime < 1000) { if (mStream.available()) { - return mStream.readBytes(&mBuffer[0], expectedSize) == expectedSize; + return true; } delay(1); } return false; } +bool NETSGPClient::findAndReadStatusMessage() +{ + // Search for a status header consisting of magic byte and status command byte + if (!mStream.find(&STATUS_HEADER[0], 2)) + { + return false; + } + + // Read rest of message + return mStream.readBytes(&mBuffer[2], 25) == 25; +} + uint8_t NETSGPClient::calcCRC(const size_t bytes) const { uint8_t crc = 0; @@ -182,11 +194,6 @@ void NETSGPClient::disableProgramming() delay(10); } -void NETSGPClient::flushRX() -{ - while (mStream.read() != -1) { } -} - void NETSGPClient::fillInverterStatusFromBuffer(const uint8_t* buffer, InverterStatus& status) { status.deviceID = buffer[6] << 24 | buffer[7] << 16 | buffer[8] << 8 | (buffer[9] & 0xFF); @@ -206,4 +213,6 @@ void NETSGPClient::fillInverterStatusFromBuffer(const uint8_t* buffer, InverterS status.state = buffer[25]; // not fully reversed status.temperature = buffer[26]; // not fully reversed -} \ No newline at end of file +} + +const uint8_t NETSGPClient::STATUS_HEADER[2] = {MAGIC_BYTE, Command::STATUS}; \ No newline at end of file diff --git a/NETSGPClient.h b/NETSGPClient.h index 111630c..3acd5dd 100644 --- a/NETSGPClient.h +++ b/NETSGPClient.h @@ -143,12 +143,17 @@ class NETSGPClient /// @param deviceID Recipient inverter identifier void sendCommand(const Command command, const uint8_t value, const uint32_t deviceID); - /// @brief Wait for an answer with the given expected size. + /// @brief Wait for a message with a timeout of 1 second /// - /// @param expectedSize Expected answer size in bytes - /// @return true If an answer with correct lenght was received + /// @return true If stream contains a message within timeout /// @return false If not - bool waitForAnswer(const size_t expectedSize); + bool waitForMessage(); + + /// @brief Try to find a status message in the stream and if present read it into mBuffer + /// + /// @return true If message was found and read into mBuffer + /// @return false If not + bool findAndReadStatusMessage(); /// @brief Calculate the checksum for a message inside the buffer. /// @@ -166,9 +171,6 @@ class NETSGPClient /// This function will delay code execution for 10ms void disableProgramming(); - /// @brief Flush the receive buffer of the stream. - void flushRX(); - /// @brief Fill the given inverter status from the given buffer /// /// @param buffer Bufffer containing raw inverter status data, must be at least 27 bytes in size @@ -180,4 +182,5 @@ class NETSGPClient uint8_t mProgPin; /// Programming enable pin of RF module (active low) uint8_t mBuffer[32] = {0}; /// Inernal buffer static const uint8_t MAGIC_BYTE = 0x43; /// Magic byte indicating start of messages + static const uint8_t STATUS_HEADER[2]; /// Start of status message containing magic byte and status command byte }; diff --git a/examples/AsyncDemo/AsyncDemo.ino b/examples/AsyncDemo/AsyncDemo.ino index 5b3d9f9..93b0029 100644 --- a/examples/AsyncDemo/AsyncDemo.ino +++ b/examples/AsyncDemo/AsyncDemo.ino @@ -1,8 +1,9 @@ #include "AsyncNETSGPClient.h" -constexpr const uint8_t PROG_PIN = 4; -constexpr const uint8_t RX_PIN = 16; -constexpr const uint8_t TX_PIN = 17; +constexpr const uint8_t PROG_PIN = 4; /// Programming enable pin of RF module +constexpr const uint8_t RX_PIN = 16; /// RX pin of RF module +constexpr const uint8_t TX_PIN = 17; /// TX pin of RF module +constexpr const uint32_t inverterID = 0x11002793; /// Identifier of your inverter (see label on inverter) AsyncNETSGPClient client(Serial2, PROG_PIN); // Defaults to update every 2 seconds // AsyncNETSGPClient client(Serial2, PROG_PIN, 10); // Update every 10 seconds @@ -39,11 +40,17 @@ void setup() Serial.println("Could not set RF module to default settings"); } + // Make sure you set your callback to receive status updates client.setStatusCallback(onInverterStatus); - client.registerInverter(0x11002793); + // Register the inverter whose status should be read + // This function can be called throughout your program to add inverters as you like + // To remove an inverter whose status should not be updated anymore you can call + // client.deregisterInverter(inverterID); + client.registerInverter(inverterID); } void loop() { + // The AsyncNETSGPClient needs to be actively updated client.update(); } diff --git a/examples/PollDemo/PollDemo.ino b/examples/PollDemo/PollDemo.ino index fcd7e15..0fa43b6 100644 --- a/examples/PollDemo/PollDemo.ino +++ b/examples/PollDemo/PollDemo.ino @@ -1,10 +1,11 @@ #include "NETSGPClient.h" -constexpr const uint8_t PROG_PIN = 4; -constexpr const uint8_t RX_PIN = 16; -constexpr const uint8_t TX_PIN = 17; +constexpr const uint8_t PROG_PIN = 4; /// Programming enable pin of RF module +constexpr const uint8_t RX_PIN = 16; /// RX pin of RF module +constexpr const uint8_t TX_PIN = 17; /// TX pin of RF module +constexpr const uint32_t inverterID = 0x11002793; /// Identifier of your inverter (see label on inverter) -NETSGPClient client(Serial2, PROG_PIN); +NETSGPClient client(Serial2, PROG_PIN); /// NETSGPClient instance void setup() { @@ -33,7 +34,7 @@ void loop() Serial.println(""); Serial.println("Sending request now"); - const NETSGPClient::InverterStatus status = client.getStatus(0x11002793); + const NETSGPClient::InverterStatus status = client.getStatus(inverterID); if (status.valid) { Serial.println("*********************************************"); From 5321ec34cd4df07e05e13f026c93abc60a60334b Mon Sep 17 00:00:00 2001 From: Moritz Wirger Date: Sat, 29 May 2021 14:53:03 +0200 Subject: [PATCH 15/15] Document calllback typedef --- AsyncNETSGPClient.h | 1 + 1 file changed, 1 insertion(+) diff --git a/AsyncNETSGPClient.h b/AsyncNETSGPClient.h index 5185d39..646b83e 100644 --- a/AsyncNETSGPClient.h +++ b/AsyncNETSGPClient.h @@ -7,6 +7,7 @@ /// @brief Async version of NETSGPClient class AsyncNETSGPClient : public NETSGPClient { + /// @brief Callback function type definition for inverter status updates typedef void (*InverterStatusCallback)(const NETSGPClient::InverterStatus&); public: