From fc1267fe5532813c8d20265f8848d5a9324dec87 Mon Sep 17 00:00:00 2001 From: Marc-Philip Date: Sun, 30 Jun 2024 18:48:55 +0200 Subject: [PATCH 001/110] massage file handling --- pio-scripts/patch_apply.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pio-scripts/patch_apply.py b/pio-scripts/patch_apply.py index 5734c4aa..0091f3c2 100644 --- a/pio-scripts/patch_apply.py +++ b/pio-scripts/patch_apply.py @@ -28,15 +28,17 @@ def replaceInFile(in_file, out_file, text, subs, flags=0): Taken from https://www.studytonight.com/python-howtos/search-and-replace-a-text-in-a-file-in-python """ if os.path.exists(in_file): - with open(in_file, "rb") as infile: - with open(out_file, "wb") as outfile: - #read the file contents - file_contents = infile.read() - text_pattern = re.compile(re.escape(text), flags) - file_contents = text_pattern.sub(subs, file_contents.decode('utf-8')) - outfile.seek(0) - outfile.truncate() - outfile.write(file_contents.encode()) + #read the file contents + with open(in_file, "r", encoding="utf-8") as infile: + file_contents = infile.read() + + # do replacement + text_pattern = re.compile(re.escape(text), flags) + file_contents = text_pattern.sub(subs, file_contents) + + # write the result + with open(out_file, "w", encoding="utf-8") as outfile: + outfile.write(file_contents) def main(): if (env.GetProjectOption('custom_patches', '') == ''): From 1d92d9ed08ab9c275d5d72caa69fd9684d17b34a Mon Sep 17 00:00:00 2001 From: Marc-Philip Date: Sun, 30 Jun 2024 20:46:12 +0200 Subject: [PATCH 002/110] fix comment --- pio-scripts/patch_apply.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pio-scripts/patch_apply.py b/pio-scripts/patch_apply.py index 0091f3c2..a15ae343 100644 --- a/pio-scripts/patch_apply.py +++ b/pio-scripts/patch_apply.py @@ -23,10 +23,8 @@ def is_tool(name): return which(name) is not None def replaceInFile(in_file, out_file, text, subs, flags=0): - """ - Function for replacing content for the given file - Taken from https://www.studytonight.com/python-howtos/search-and-replace-a-text-in-a-file-in-python - """ + """Function for replacing content for the given file.""" + if os.path.exists(in_file): #read the file contents with open(in_file, "r", encoding="utf-8") as infile: From 5ee411fcc6c64e6b39cacf4356645de6b883d699 Mon Sep 17 00:00:00 2001 From: Marc-Philip Date: Mon, 1 Jul 2024 06:53:10 +0200 Subject: [PATCH 003/110] Update patch_apply.py --- pio-scripts/patch_apply.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pio-scripts/patch_apply.py b/pio-scripts/patch_apply.py index a15ae343..5c77f683 100644 --- a/pio-scripts/patch_apply.py +++ b/pio-scripts/patch_apply.py @@ -26,7 +26,7 @@ def replaceInFile(in_file, out_file, text, subs, flags=0): """Function for replacing content for the given file.""" if os.path.exists(in_file): - #read the file contents + # read the file contents with open(in_file, "r", encoding="utf-8") as infile: file_contents = infile.read() From 6f9ded5f20d9b5f87f091f2f18451f301ad35db6 Mon Sep 17 00:00:00 2001 From: Bernhard Kirchen Date: Thu, 26 Sep 2024 21:45:43 +0200 Subject: [PATCH 004/110] issue template: fix typo --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index d41f9824..8f357930 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -48,7 +48,7 @@ body: description: How did you install OpenDTU? options: - Pre-Compiled binary from GitHub releases - - Pre-Compiles binary from GitHub actions/pull-request + - Pre-Compiled binary from GitHub actions/pull-request - Self-Compiled validations: required: true From 251bb7bd8933bf7359494fad652824a9cb7dc8f5 Mon Sep 17 00:00:00 2001 From: LennartF22 <18723691+LennartF22@users.noreply.github.com> Date: Fri, 27 Sep 2024 00:31:15 +0200 Subject: [PATCH 005/110] Add connection check for W5500 before full initialization --- include/W5500.h | 11 +++- src/NetworkSettings.cpp | 6 +- src/W5500.cpp | 127 ++++++++++++++++++++++++++-------------- 3 files changed, 99 insertions(+), 45 deletions(-) diff --git a/include/W5500.h b/include/W5500.h index f62c3312..d85cb016 100644 --- a/include/W5500.h +++ b/include/W5500.h @@ -2,19 +2,28 @@ #pragma once #include +#include #include // required for esp_eth_handle_t #include +#include + class W5500 { +private: + explicit W5500(spi_device_handle_t spi, gpio_num_t pin_int); + public: - explicit W5500(int8_t pin_mosi, int8_t pin_miso, int8_t pin_sclk, int8_t pin_cs, int8_t pin_int, int8_t pin_rst); W5500(const W5500&) = delete; W5500& operator=(const W5500&) = delete; ~W5500(); + static std::unique_ptr setup(int8_t pin_mosi, int8_t pin_miso, int8_t pin_sclk, int8_t pin_cs, int8_t pin_int, int8_t pin_rst); String macAddress(); private: + static bool connection_check_spi(spi_device_handle_t spi); + static bool connection_check_interrupt(gpio_num_t pin_int); + esp_eth_handle_t eth_handle; esp_netif_t* eth_netif; }; diff --git a/src/NetworkSettings.cpp b/src/NetworkSettings.cpp index 33de9255..75c4bb12 100644 --- a/src/NetworkSettings.cpp +++ b/src/NetworkSettings.cpp @@ -34,7 +34,11 @@ void NetworkSettingsClass::init(Scheduler& scheduler) if (PinMapping.isValidW5500Config()) { PinMapping_t& pin = PinMapping.get(); - _w5500 = std::make_unique(pin.w5500_mosi, pin.w5500_miso, pin.w5500_sclk, pin.w5500_cs, pin.w5500_int, pin.w5500_rst); + _w5500 = W5500::setup(pin.w5500_mosi, pin.w5500_miso, pin.w5500_sclk, pin.w5500_cs, pin.w5500_int, pin.w5500_rst); + if (_w5500) + MessageOutput.println("W5500: Connection successful"); + else + MessageOutput.println("W5500: Connection error!!"); } else if (PinMapping.isValidEthConfig()) { PinMapping_t& pin = PinMapping.get(); #if ESP_ARDUINO_VERSION_MAJOR < 3 diff --git a/src/W5500.cpp b/src/W5500.cpp index 0f5b5739..fc770c44 100644 --- a/src/W5500.cpp +++ b/src/W5500.cpp @@ -12,52 +12,10 @@ void tcpipInit(); void add_esp_interface_netif(esp_interface_t interface, esp_netif_t* esp_netif); -W5500::W5500(int8_t pin_mosi, int8_t pin_miso, int8_t pin_sclk, int8_t pin_cs, int8_t pin_int, int8_t pin_rst) +W5500::W5500(spi_device_handle_t spi, gpio_num_t pin_int) : eth_handle(nullptr) , eth_netif(nullptr) { - gpio_reset_pin(static_cast(pin_rst)); - gpio_set_level(static_cast(pin_rst), 0); - gpio_set_direction(static_cast(pin_rst), GPIO_MODE_OUTPUT); - - gpio_reset_pin(static_cast(pin_cs)); - gpio_reset_pin(static_cast(pin_int)); - - esp_err_t err = gpio_install_isr_service(ARDUINO_ISR_FLAG); - if (err != ESP_ERR_INVALID_STATE) // don't raise an error when ISR service is already installed - ESP_ERROR_CHECK(err); - - auto bus_config = std::make_shared( - static_cast(pin_mosi), - static_cast(pin_miso), - static_cast(pin_sclk)); - - spi_device_interface_config_t device_config { - .command_bits = 16, // actually address phase - .address_bits = 8, // actually command phase - .dummy_bits = 0, - .mode = 0, - .duty_cycle_pos = 0, - .cs_ena_pretrans = 0, // only 0 supported - .cs_ena_posttrans = 0, // only 0 supported - .clock_speed_hz = 20000000, // stable with OpenDTU Fusion shield - .input_delay_ns = 0, - .spics_io_num = pin_cs, - .flags = 0, - .queue_size = 20, - .pre_cb = nullptr, - .post_cb = nullptr, - }; - - spi_device_handle_t spi = SpiManagerInst.alloc_device("", bus_config, device_config); - if (!spi) - ESP_ERROR_CHECK(ESP_FAIL); - - // Reset sequence - delayMicroseconds(500); - gpio_set_level(static_cast(pin_rst), 1); - delayMicroseconds(1000); - // Arduino function to start networking stack if not already started tcpipInit(); @@ -98,6 +56,61 @@ W5500::~W5500() // TODO(LennartF22): support cleanup at some point? } +std::unique_ptr W5500::setup(int8_t pin_mosi, int8_t pin_miso, int8_t pin_sclk, int8_t pin_cs, int8_t pin_int, int8_t pin_rst) +{ + gpio_reset_pin(static_cast(pin_rst)); + gpio_set_level(static_cast(pin_rst), 0); + gpio_set_direction(static_cast(pin_rst), GPIO_MODE_OUTPUT); + + gpio_reset_pin(static_cast(pin_cs)); + gpio_reset_pin(static_cast(pin_int)); + + esp_err_t err = gpio_install_isr_service(ARDUINO_ISR_FLAG); + if (err != ESP_ERR_INVALID_STATE) // don't raise an error when ISR service is already installed + ESP_ERROR_CHECK(err); + + auto bus_config = std::make_shared( + static_cast(pin_mosi), + static_cast(pin_miso), + static_cast(pin_sclk)); + + spi_device_interface_config_t device_config { + .command_bits = 16, // actually address phase + .address_bits = 8, // actually command phase + .dummy_bits = 0, + .mode = 0, + .duty_cycle_pos = 0, + .cs_ena_pretrans = 0, // only 0 supported + .cs_ena_posttrans = 0, // only 0 supported + .clock_speed_hz = 20000000, // stable with OpenDTU Fusion shield + .input_delay_ns = 0, + .spics_io_num = pin_cs, + .flags = 0, + .queue_size = 20, + .pre_cb = nullptr, + .post_cb = nullptr, + }; + + spi_device_handle_t spi = SpiManagerInst.alloc_device("", bus_config, device_config); + if (!spi) + return nullptr; + + // Reset sequence + delayMicroseconds(500); + gpio_set_level(static_cast(pin_rst), 1); + delayMicroseconds(1000); + + if (!connection_check_spi(spi)) + return nullptr; + if (!connection_check_interrupt(static_cast(pin_int))) + return nullptr; + + // Return to default state once again after connection check + gpio_reset_pin(static_cast(pin_int)); + + return std::unique_ptr(new W5500(spi, static_cast(pin_int))); +} + String W5500::macAddress() { uint8_t mac_addr[6] = {}; @@ -109,3 +122,31 @@ String W5500::macAddress() mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); return String(mac_addr_str); } + +bool W5500::connection_check_spi(spi_device_handle_t spi) +{ + spi_transaction_t trans = { + .flags = SPI_TRANS_USE_RXDATA, + .cmd = 0x0039, // actually address (VERSIONR) + .addr = (0b00000 << 3) | (0 << 2) | (0b00 < 0), // actually command (common register, read, VDM) + .length = 8, + .rxlength = 8, + .user = nullptr, + .tx_buffer = nullptr, + .rx_data = {}, + }; + ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &trans)); + + // Version number (VERSIONR) is always 0x04 + return *reinterpret_cast(&trans.rx_data) == 0x04; +} + +bool W5500::connection_check_interrupt(gpio_num_t pin_int) +{ + gpio_set_direction(pin_int, GPIO_MODE_INPUT); + gpio_set_pull_mode(pin_int, GPIO_PULLDOWN_ONLY); + int level = gpio_get_level(pin_int); + + // Interrupt line must be high + return level == 1; +} From b05975b97c30fbe4f85270c6b4c47114d6c01305 Mon Sep 17 00:00:00 2001 From: LennartF22 <18723691+LennartF22@users.noreply.github.com> Date: Fri, 27 Sep 2024 01:27:42 +0200 Subject: [PATCH 006/110] Prevent warning on GPIO ISR service registration --- src/W5500.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/W5500.cpp b/src/W5500.cpp index fc770c44..bf539434 100644 --- a/src/W5500.cpp +++ b/src/W5500.cpp @@ -65,10 +65,6 @@ std::unique_ptr W5500::setup(int8_t pin_mosi, int8_t pin_miso, int8_t pin gpio_reset_pin(static_cast(pin_cs)); gpio_reset_pin(static_cast(pin_int)); - esp_err_t err = gpio_install_isr_service(ARDUINO_ISR_FLAG); - if (err != ESP_ERR_INVALID_STATE) // don't raise an error when ISR service is already installed - ESP_ERROR_CHECK(err); - auto bus_config = std::make_shared( static_cast(pin_mosi), static_cast(pin_miso), @@ -105,7 +101,12 @@ std::unique_ptr W5500::setup(int8_t pin_mosi, int8_t pin_miso, int8_t pin if (!connection_check_interrupt(static_cast(pin_int))) return nullptr; - // Return to default state once again after connection check + // Use Arduino functions to temporarily attach interrupt to enable the GPIO ISR service + // (if we used ESP-IDF functions, a warning would be printed the first time anyone uses attachInterrupt) + attachInterrupt(pin_int, nullptr, FALLING); + detachInterrupt(pin_int); + + // Return to default state once again after connection check and temporary interrupt registration gpio_reset_pin(static_cast(pin_int)); return std::unique_ptr(new W5500(spi, static_cast(pin_int))); From cafdb305a3729da4409779701043b37d9d7cf1de Mon Sep 17 00:00:00 2001 From: LennartF22 <18723691+LennartF22@users.noreply.github.com> Date: Sat, 28 Sep 2024 02:37:09 +0200 Subject: [PATCH 007/110] Adjust name of OpenDTU Fusion v2 PoE build environment --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index bc81b1a7..82111268 100644 --- a/platformio.ini +++ b/platformio.ini @@ -254,7 +254,7 @@ build_flags = ${env.build_flags} -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -[env:opendtufusionv2_shield] +[env:opendtufusionv2_poe] board = esp32-s3-devkitc-1 upload_protocol = esp-builtin debug_tool = esp-builtin From 69d2727106f183ecad6b6786a45d51e734cf60e6 Mon Sep 17 00:00:00 2001 From: LennartF22 <18723691+LennartF22@users.noreply.github.com> Date: Sat, 28 Sep 2024 02:42:05 +0200 Subject: [PATCH 008/110] Add device profiles for OpenDTU Fusion v2 PoE with displays --- docs/DeviceProfiles/opendtu_fusion.json | 78 +++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/docs/DeviceProfiles/opendtu_fusion.json b/docs/DeviceProfiles/opendtu_fusion.json index bb1e4108..990f4c46 100644 --- a/docs/DeviceProfiles/opendtu_fusion.json +++ b/docs/DeviceProfiles/opendtu_fusion.json @@ -243,5 +243,83 @@ "data": 2, "clk": 1 } + }, + { + "name": "OpenDTU Fusion v2 PoE with SH1106 Display", + "links": [ + {"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"} + ], + "nrf24": { + "miso": 48, + "mosi": 35, + "clk": 36, + "irq": 47, + "en": 38, + "cs": 37 + }, + "cmt": { + "clk": 6, + "cs": 4, + "fcs": 21, + "sdio": 5, + "gpio2": 3, + "gpio3": 8 + }, + "w5500": { + "mosi": 40, + "miso": 41, + "sclk": 39, + "cs": 42, + "int": 44, + "rst": 43 + }, + "led": { + "led0": 17, + "led1": 18 + }, + "display": { + "type": 3, + "data": 2, + "clk": 1 + } + }, + { + "name": "OpenDTU Fusion v2 PoE with SSD1306 Display", + "links": [ + {"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"} + ], + "nrf24": { + "miso": 48, + "mosi": 35, + "clk": 36, + "irq": 47, + "en": 38, + "cs": 37 + }, + "cmt": { + "clk": 6, + "cs": 4, + "fcs": 21, + "sdio": 5, + "gpio2": 3, + "gpio3": 8 + }, + "w5500": { + "mosi": 40, + "miso": 41, + "sclk": 39, + "cs": 42, + "int": 44, + "rst": 43 + }, + "led": { + "led0": 17, + "led1": 18 + }, + "display": { + "type": 2, + "data": 2, + "clk": 1 + } } ] From ebb225f6c063e1946739cab7674c319a98d66784 Mon Sep 17 00:00:00 2001 From: Bernhard Kirchen Date: Mon, 30 Sep 2024 14:18:33 +0200 Subject: [PATCH 009/110] Fix: avoid deprecated setAuthentication() to fix memory exhaustion with ESPAsyncWebServer 3.3.0, the setAuthentication() method became deprecated and a replacement method was provided which acts as a shim and uses the new middleware-based approach to setup authentication. in order to eventually apply a changed "read-only access allowed" setting, the setAuthentication() method was called periodically. the shim implementation each time allocates a new AuthenticationMiddleware and adds it to the chain of middlewares, eventually exhausting the memory. we now use the new middleware-based approach ourselves and only add the respective AuthenticatonMiddleware instance once to the respective websocket server instance. a regression where enabling unauthenticated read-only access is not applied until reboot is also fixed. all the AuthenticationMiddleware instances were never removed from the chain of middlewares when calling setAuthentication("", ""). --- include/WebApi.h | 1 + include/WebApi_ws_console.h | 2 ++ include/WebApi_ws_live.h | 2 ++ src/WebApi.cpp | 6 ++++++ src/WebApi_security.cpp | 2 ++ src/WebApi_ws_console.cpp | 23 +++++++++++++++++------ src/WebApi_ws_live.cpp | 22 ++++++++++++++++------ 7 files changed, 46 insertions(+), 12 deletions(-) diff --git a/include/WebApi.h b/include/WebApi.h index b6fdbd08..2932f015 100644 --- a/include/WebApi.h +++ b/include/WebApi.h @@ -30,6 +30,7 @@ class WebApiClass { public: WebApiClass(); void init(Scheduler& scheduler); + void reload(); static bool checkCredentials(AsyncWebServerRequest* request); static bool checkCredentialsReadonly(AsyncWebServerRequest* request); diff --git a/include/WebApi_ws_console.h b/include/WebApi_ws_console.h index cf7beecc..b3194319 100644 --- a/include/WebApi_ws_console.h +++ b/include/WebApi_ws_console.h @@ -8,9 +8,11 @@ class WebApiWsConsoleClass { public: WebApiWsConsoleClass(); void init(AsyncWebServer& server, Scheduler& scheduler); + void reload(); private: AsyncWebSocket _ws; + AuthenticationMiddleware _simpleDigestAuth; Task _wsCleanupTask; void wsCleanupTaskCb(); diff --git a/include/WebApi_ws_live.h b/include/WebApi_ws_live.h index 05f8ab8f..e16372e9 100644 --- a/include/WebApi_ws_live.h +++ b/include/WebApi_ws_live.h @@ -11,6 +11,7 @@ class WebApiWsLiveClass { public: WebApiWsLiveClass(); void init(AsyncWebServer& server, Scheduler& scheduler); + void reload(); private: static void generateInverterCommonJsonResponse(JsonObject& root, std::shared_ptr inv); @@ -24,6 +25,7 @@ private: void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); AsyncWebSocket _ws; + AuthenticationMiddleware _simpleDigestAuth; uint32_t _lastPublishStats[INV_MAX_COUNT] = { 0 }; diff --git a/src/WebApi.cpp b/src/WebApi.cpp index 1a5b2870..117c305a 100644 --- a/src/WebApi.cpp +++ b/src/WebApi.cpp @@ -39,6 +39,12 @@ void WebApiClass::init(Scheduler& scheduler) _server.begin(); } +void WebApiClass::reload() +{ + _webApiWsConsole.reload(); + _webApiWsLive.reload(); +} + bool WebApiClass::checkCredentials(AsyncWebServerRequest* request) { CONFIG_T& config = Configuration.get(); diff --git a/src/WebApi_security.cpp b/src/WebApi_security.cpp index ddd8bb50..6be21ca6 100644 --- a/src/WebApi_security.cpp +++ b/src/WebApi_security.cpp @@ -71,6 +71,8 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); + + WebApi.reload(); } void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request) diff --git a/src/WebApi_ws_console.cpp b/src/WebApi_ws_console.cpp index 1f1efcb2..4dd2d693 100644 --- a/src/WebApi_ws_console.cpp +++ b/src/WebApi_ws_console.cpp @@ -21,16 +21,27 @@ void WebApiWsConsoleClass::init(AsyncWebServer& server, Scheduler& scheduler) scheduler.addTask(_wsCleanupTask); _wsCleanupTask.enable(); + + _simpleDigestAuth.setUsername(AUTH_USERNAME); + _simpleDigestAuth.setRealm("console websocket"); + + reload(); +} + +void WebApiWsConsoleClass::reload() +{ + _ws.removeMiddleware(&_simpleDigestAuth); + + auto const& config = Configuration.get(); + + if (config.Security.AllowReadonly) { return; } + + _simpleDigestAuth.setPassword(config.Security.Password); + _ws.addMiddleware(&_simpleDigestAuth); } void WebApiWsConsoleClass::wsCleanupTaskCb() { // see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients _ws.cleanupClients(); - - if (Configuration.get().Security.AllowReadonly) { - _ws.setAuthentication("", ""); - } else { - _ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password); - } } diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index 4fa79837..c4b4a1f1 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -36,18 +36,28 @@ void WebApiWsLiveClass::init(AsyncWebServer& server, Scheduler& scheduler) scheduler.addTask(_sendDataTask); _sendDataTask.enable(); + _simpleDigestAuth.setUsername(AUTH_USERNAME); + _simpleDigestAuth.setRealm("live websocket"); + + reload(); +} + +void WebApiWsLiveClass::reload() +{ + _ws.removeMiddleware(&_simpleDigestAuth); + + auto const& config = Configuration.get(); + + if (config.Security.AllowReadonly) { return; } + + _simpleDigestAuth.setPassword(config.Security.Password); + _ws.addMiddleware(&_simpleDigestAuth); } void WebApiWsLiveClass::wsCleanupTaskCb() { // see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients _ws.cleanupClients(); - - if (Configuration.get().Security.AllowReadonly) { - _ws.setAuthentication("", ""); - } else { - _ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password); - } } void WebApiWsLiveClass::sendDataTaskCb() From d5d1a9982fa076e5ec69bd7e1dcc4a768bd5f192 Mon Sep 17 00:00:00 2001 From: Bernhard Kirchen Date: Mon, 30 Sep 2024 15:53:30 +0200 Subject: [PATCH 010/110] Fix: force websocket clients to authenticate when changing the security settings (disabling read-only access or changing the password), existing websocket connections are now closed, forcing the respective clients to authenticate (with the new password). otherwise, existing websocket clients keep connected even though the security settings now expect authentication with a (changed) password. --- src/WebApi_ws_console.cpp | 3 +++ src/WebApi_ws_live.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/WebApi_ws_console.cpp b/src/WebApi_ws_console.cpp index 4dd2d693..51035f6f 100644 --- a/src/WebApi_ws_console.cpp +++ b/src/WebApi_ws_console.cpp @@ -36,8 +36,11 @@ void WebApiWsConsoleClass::reload() if (config.Security.AllowReadonly) { return; } + _ws.enable(false); _simpleDigestAuth.setPassword(config.Security.Password); _ws.addMiddleware(&_simpleDigestAuth); + _ws.closeAll(); + _ws.enable(true); } void WebApiWsConsoleClass::wsCleanupTaskCb() diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index c4b4a1f1..29c204a3 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -50,8 +50,11 @@ void WebApiWsLiveClass::reload() if (config.Security.AllowReadonly) { return; } + _ws.enable(false); _simpleDigestAuth.setPassword(config.Security.Password); _ws.addMiddleware(&_simpleDigestAuth); + _ws.closeAll(); + _ws.enable(true); } void WebApiWsLiveClass::wsCleanupTaskCb() From 99a37fe01c5ef94c4638421a3af010908717d72a Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 30 Sep 2024 18:47:41 +0200 Subject: [PATCH 011/110] webapp: Update dependencies --- webapp/package.json | 2 +- webapp/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index 908058cc..5f954fce 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -40,7 +40,7 @@ "prettier": "^3.3.3", "pulltorefreshjs": "^0.1.22", "sass": "^1.77.6", - "terser": "^5.34.0", + "terser": "^5.34.1", "typescript": "^5.6.2", "vite": "^5.4.8", "vite-plugin-compression": "^0.5.1", diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 9d68694e..b6569670 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -2506,10 +2506,10 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -terser@^5.34.0: - version "5.34.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.34.0.tgz#62f2496542290bc6d8bf886edaee7fac158e37e4" - integrity sha512-y5NUX+U9HhVsK/zihZwoq4r9dICLyV2jXGOriDAVOeKhq3LKVjgJbGO90FisozXLlJfvjHqgckGmJFBb9KYoWQ== +terser@^5.34.1: + version "5.34.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.34.1.tgz#af40386bdbe54af0d063e0670afd55c3105abeb6" + integrity sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" From 2234ac97039a21eb6c471b0c584c43379e974c0c Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Wed, 2 Oct 2024 10:32:58 +0200 Subject: [PATCH 012/110] Upgrade ESPAsyncWebServer from 3.3.1 to 3.3.7 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 82111268..2b3a4c95 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,7 +41,7 @@ build_unflags = -std=gnu++11 lib_deps = - mathieucarbou/ESPAsyncWebServer @ 3.3.1 + mathieucarbou/ESPAsyncWebServer @ 3.3.7 bblanchon/ArduinoJson @ 7.2.0 https://github.com/bertmelis/espMqttClient.git#v1.7.0 nrf24/RF24 @ 1.4.9 From 38b5807ef772a3568704cb2a3546d25fde430c1b Mon Sep 17 00:00:00 2001 From: mbo18 Date: Wed, 2 Oct 2024 10:44:43 +0200 Subject: [PATCH 013/110] Remove icon because device_class is set --- src/MqttHandleHass.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MqttHandleHass.cpp b/src/MqttHandleHass.cpp index ab4a2ad1..0363cc8e 100644 --- a/src/MqttHandleHass.cpp +++ b/src/MqttHandleHass.cpp @@ -61,7 +61,7 @@ void MqttHandleHassClass::publishConfig() publishDtuSensor("IP", "dtu/ip", "", "mdi:network-outline", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); publishDtuSensor("WiFi Signal", "dtu/rssi", "dBm", "", DEVICE_CLS_SIGNAL_STRENGTH, CATEGORY_DIAGNOSTIC); publishDtuSensor("Uptime", "dtu/uptime", "s", "", DEVICE_CLS_DURATION, CATEGORY_DIAGNOSTIC); - publishDtuSensor("Temperature", "dtu/temperature", "°C", "mdi:thermometer", DEVICE_CLS_TEMPERATURE, CATEGORY_DIAGNOSTIC); + publishDtuSensor("Temperature", "dtu/temperature", "°C", "", DEVICE_CLS_TEMPERATURE, CATEGORY_DIAGNOSTIC); publishDtuSensor("Heap Size", "dtu/heap/size", "Bytes", "mdi:memory", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); publishDtuSensor("Heap Free", "dtu/heap/free", "Bytes", "mdi:memory", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); publishDtuSensor("Largest Free Heap Block", "dtu/heap/maxalloc", "Bytes", "mdi:memory", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); From 9df3e30bb2852d0e70f24675d10feef2aa371b96 Mon Sep 17 00:00:00 2001 From: mbo18 Date: Wed, 2 Oct 2024 11:02:52 +0200 Subject: [PATCH 014/110] Remove unused DEVICE_CLASS_TEMP --- include/MqttHandleHass.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/MqttHandleHass.h b/include/MqttHandleHass.h index 1a3acf59..7dcf9410 100644 --- a/include/MqttHandleHass.h +++ b/include/MqttHandleHass.h @@ -13,7 +13,6 @@ enum DeviceClassType { DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, - DEVICE_CLS_TEMP, DEVICE_CLS_POWER_FACTOR, DEVICE_CLS_REACTIVE_POWER, DEVICE_CLS_CONNECTIVITY, @@ -22,7 +21,7 @@ enum DeviceClassType { DEVICE_CLS_TEMPERATURE, DEVICE_CLS_RESTART }; -const char* const deviceClass_name[] = { 0, "current", "energy", "power", "voltage", "frequency", "temperature", "power_factor", "reactive_power", "connectivity", "duration", "signal_strength", "temperature", "restart" }; +const char* const deviceClass_name[] = { 0, "current", "energy", "power", "voltage", "frequency", "power_factor", "reactive_power", "connectivity", "duration", "signal_strength", "temperature", "restart" }; enum StateClassType { STATE_CLS_NONE = 0, @@ -55,7 +54,7 @@ const byteAssign_fieldDeviceClass_t deviceFieldAssignment[] = { { FLD_IAC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT }, { FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT }, { FLD_F, DEVICE_CLS_FREQ, STATE_CLS_MEASUREMENT }, - { FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT }, + { FLD_T, DEVICE_CLS_TEMPERATURE, STATE_CLS_MEASUREMENT }, { FLD_PF, DEVICE_CLS_POWER_FACTOR, STATE_CLS_MEASUREMENT }, { FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE }, { FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE }, From 0c2b6f1a61a25233c1945cd109cb09110e8c5072 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Wed, 2 Oct 2024 18:13:12 +0200 Subject: [PATCH 015/110] Fix: Add state_class to several Home Assistant sensors state_class was added to yieldtotal, yieldday ac power and temperature for the whole dtu closes: #2324 --- include/MqttHandleHass.h | 18 +++--- src/MqttHandleHass.cpp | 123 +++++++++++++++++++++++---------------- 2 files changed, 82 insertions(+), 59 deletions(-) diff --git a/include/MqttHandleHass.h b/include/MqttHandleHass.h index 7dcf9410..c0a22c70 100644 --- a/include/MqttHandleHass.h +++ b/include/MqttHandleHass.h @@ -74,21 +74,21 @@ private: static void publish(const String& subtopic, const String& payload); static void publish(const String& subtopic, const JsonDocument& doc); - static void addCommonMetadata(JsonDocument& doc, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category); + static void addCommonMetadata(JsonDocument& doc, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); // Binary Sensor - static void publishBinarySensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const CategoryType category); - static void publishDtuBinarySensor(const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const CategoryType category); - static void publishInverterBinarySensor(std::shared_ptr inv, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const CategoryType category); + static void publishBinarySensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); + static void publishDtuBinarySensor(const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); + static void publishInverterBinarySensor(std::shared_ptr inv, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); // Sensor - static void publishSensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category); - static void publishDtuSensor(const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category); - static void publishInverterSensor(std::shared_ptr inv, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category); + static void publishSensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); + static void publishDtuSensor(const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); + static void publishInverterSensor(std::shared_ptr inv, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); static void publishInverterField(std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear = false); - static void publishInverterButton(std::shared_ptr inv, const String& name, const String& state_topic, const String& payload, const String& icon, const DeviceClassType device_class, const CategoryType category); - static void publishInverterNumber(std::shared_ptr inv, const String& name, const String& state_topic, const String& command_topic, const int16_t min, const int16_t max, float step, const String& unit_of_measure, const String& icon, const CategoryType category); + static void publishInverterButton(std::shared_ptr inv, const String& name, const String& state_topic, const String& payload, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category); + static void publishInverterNumber(std::shared_ptr inv, const String& name, const String& state_topic, const String& command_topic, const int16_t min, const int16_t max, float step, const String& unit_of_measure, const String& icon, const StateClassType state_class, const CategoryType category); static void createInverterInfo(JsonDocument& doc, std::shared_ptr inv); static void createDtuInfo(JsonDocument& doc); diff --git a/src/MqttHandleHass.cpp b/src/MqttHandleHass.cpp index 0363cc8e..7afa87da 100644 --- a/src/MqttHandleHass.cpp +++ b/src/MqttHandleHass.cpp @@ -58,45 +58,45 @@ void MqttHandleHassClass::publishConfig() const CONFIG_T& config = Configuration.get(); // publish DTU sensors - publishDtuSensor("IP", "dtu/ip", "", "mdi:network-outline", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishDtuSensor("WiFi Signal", "dtu/rssi", "dBm", "", DEVICE_CLS_SIGNAL_STRENGTH, CATEGORY_DIAGNOSTIC); - publishDtuSensor("Uptime", "dtu/uptime", "s", "", DEVICE_CLS_DURATION, CATEGORY_DIAGNOSTIC); - publishDtuSensor("Temperature", "dtu/temperature", "°C", "", DEVICE_CLS_TEMPERATURE, CATEGORY_DIAGNOSTIC); - publishDtuSensor("Heap Size", "dtu/heap/size", "Bytes", "mdi:memory", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishDtuSensor("Heap Free", "dtu/heap/free", "Bytes", "mdi:memory", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishDtuSensor("Largest Free Heap Block", "dtu/heap/maxalloc", "Bytes", "mdi:memory", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishDtuSensor("Lifetime Minimum Free Heap", "dtu/heap/minfree", "Bytes", "mdi:memory", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishDtuSensor("IP", "dtu/ip", "", "mdi:network-outline", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishDtuSensor("WiFi Signal", "dtu/rssi", "dBm", "", DEVICE_CLS_SIGNAL_STRENGTH, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishDtuSensor("Uptime", "dtu/uptime", "s", "", DEVICE_CLS_DURATION, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishDtuSensor("Temperature", "dtu/temperature", "°C", "", DEVICE_CLS_TEMPERATURE, STATE_CLS_MEASUREMENT, CATEGORY_DIAGNOSTIC); + publishDtuSensor("Heap Size", "dtu/heap/size", "Bytes", "mdi:memory", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishDtuSensor("Heap Free", "dtu/heap/free", "Bytes", "mdi:memory", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishDtuSensor("Largest Free Heap Block", "dtu/heap/maxalloc", "Bytes", "mdi:memory", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishDtuSensor("Lifetime Minimum Free Heap", "dtu/heap/minfree", "Bytes", "mdi:memory", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishDtuSensor("Yield Total", "ac/yieldtotal", "kWh", "", DEVICE_CLS_ENERGY, CATEGORY_NONE); - publishDtuSensor("Yield Day", "ac/yieldday", "Wh", "", DEVICE_CLS_ENERGY, CATEGORY_NONE); - publishDtuSensor("AC Power", "ac/power", "W", "", DEVICE_CLS_PWR, CATEGORY_NONE); + publishDtuSensor("Yield Total", "ac/yieldtotal", "kWh", "", DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING, CATEGORY_NONE); + publishDtuSensor("Yield Day", "ac/yieldday", "Wh", "", DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING, CATEGORY_NONE); + publishDtuSensor("AC Power", "ac/power", "W", "", DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT, CATEGORY_NONE); - publishDtuBinarySensor("Status", config.Mqtt.Lwt.Topic, config.Mqtt.Lwt.Value_Online, config.Mqtt.Lwt.Value_Offline, DEVICE_CLS_CONNECTIVITY, CATEGORY_DIAGNOSTIC); + publishDtuBinarySensor("Status", config.Mqtt.Lwt.Topic, config.Mqtt.Lwt.Value_Online, config.Mqtt.Lwt.Value_Offline, DEVICE_CLS_CONNECTIVITY, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); // Loop all inverters for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); - publishInverterButton(inv, "Turn Inverter Off", "cmd/power", "0", "mdi:power-plug-off", DEVICE_CLS_NONE, CATEGORY_CONFIG); - publishInverterButton(inv, "Turn Inverter On", "cmd/power", "1", "mdi:power-plug", DEVICE_CLS_NONE, CATEGORY_CONFIG); - publishInverterButton(inv, "Restart Inverter", "cmd/restart", "1", "", DEVICE_CLS_RESTART, CATEGORY_CONFIG); - publishInverterButton(inv, "Reset Radio Statistics", "cmd/reset_rf_stats", "1", "", DEVICE_CLS_NONE, CATEGORY_CONFIG); + publishInverterButton(inv, "Turn Inverter Off", "cmd/power", "0", "mdi:power-plug-off", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_CONFIG); + publishInverterButton(inv, "Turn Inverter On", "cmd/power", "1", "mdi:power-plug", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_CONFIG); + publishInverterButton(inv, "Restart Inverter", "cmd/restart", "1", "", DEVICE_CLS_RESTART, STATE_CLS_NONE, CATEGORY_CONFIG); + publishInverterButton(inv, "Reset Radio Statistics", "cmd/reset_rf_stats", "1", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_CONFIG); - publishInverterNumber(inv, "Limit NonPersistent Relative", "status/limit_relative", "cmd/limit_nonpersistent_relative", 0, 100, 0.1, "%", "mdi:speedometer", CATEGORY_CONFIG); - publishInverterNumber(inv, "Limit Persistent Relative", "status/limit_relative", "cmd/limit_persistent_relative", 0, 100, 0.1, "%", "mdi:speedometer", CATEGORY_CONFIG); + publishInverterNumber(inv, "Limit NonPersistent Relative", "status/limit_relative", "cmd/limit_nonpersistent_relative", 0, 100, 0.1, "%", "mdi:speedometer", STATE_CLS_NONE, CATEGORY_CONFIG); + publishInverterNumber(inv, "Limit Persistent Relative", "status/limit_relative", "cmd/limit_persistent_relative", 0, 100, 0.1, "%", "mdi:speedometer", STATE_CLS_NONE, CATEGORY_CONFIG); - publishInverterNumber(inv, "Limit NonPersistent Absolute", "status/limit_absolute", "cmd/limit_nonpersistent_absolute", 0, MAX_INVERTER_LIMIT, 1, "W", "mdi:speedometer", CATEGORY_CONFIG); - publishInverterNumber(inv, "Limit Persistent Absolute", "status/limit_absolute", "cmd/limit_persistent_absolute", 0, MAX_INVERTER_LIMIT, 1, "W", "mdi:speedometer", CATEGORY_CONFIG); + publishInverterNumber(inv, "Limit NonPersistent Absolute", "status/limit_absolute", "cmd/limit_nonpersistent_absolute", 0, MAX_INVERTER_LIMIT, 1, "W", "mdi:speedometer", STATE_CLS_NONE, CATEGORY_CONFIG); + publishInverterNumber(inv, "Limit Persistent Absolute", "status/limit_absolute", "cmd/limit_persistent_absolute", 0, MAX_INVERTER_LIMIT, 1, "W", "mdi:speedometer", STATE_CLS_NONE, CATEGORY_CONFIG); - publishInverterBinarySensor(inv, "Reachable", "status/reachable", "1", "0", DEVICE_CLS_CONNECTIVITY, CATEGORY_DIAGNOSTIC); - publishInverterBinarySensor(inv, "Producing", "status/producing", "1", "0", DEVICE_CLS_NONE, CATEGORY_NONE); + publishInverterBinarySensor(inv, "Reachable", "status/reachable", "1", "0", DEVICE_CLS_CONNECTIVITY, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterBinarySensor(inv, "Producing", "status/producing", "1", "0", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_NONE); - publishInverterSensor(inv, "TX Requests", "radio/tx_request", "", "", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishInverterSensor(inv, "RX Success", "radio/rx_success", "", "", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishInverterSensor(inv, "RX Fail Receive Nothing", "radio/rx_fail_nothing", "", "", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishInverterSensor(inv, "RX Fail Receive Partial", "radio/rx_fail_partial", "", "", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishInverterSensor(inv, "RX Fail Receive Corrupt", "radio/rx_fail_corrupt", "", "", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); - publishInverterSensor(inv, "TX Re-Request Fragment", "radio/tx_re_request", "", "", DEVICE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterSensor(inv, "TX Requests", "radio/tx_request", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterSensor(inv, "RX Success", "radio/rx_success", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterSensor(inv, "RX Fail Receive Nothing", "radio/rx_fail_nothing", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterSensor(inv, "RX Fail Receive Partial", "radio/rx_fail_partial", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterSensor(inv, "RX Fail Receive Corrupt", "radio/rx_fail_corrupt", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterSensor(inv, "TX Re-Request Fragment", "radio/tx_re_request", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); // Loop all channels for (auto& t : inv->Statistics()->getChannelTypes()) { @@ -142,7 +142,6 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr if (!clear) { const String stateTopic = MqttSettings.getPrefix() + MqttHandleInverter.getTopic(inv, type, channel, fieldType.fieldId); - const char* stateCls = stateClass_name[fieldType.stateClsId]; String name; if (type != TYPE_DC) { @@ -155,7 +154,7 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr JsonDocument root; createInverterInfo(root, inv); - addCommonMetadata(root, unit_of_measure, "", fieldType.deviceClsId, CATEGORY_NONE); + addCommonMetadata(root, unit_of_measure, "", fieldType.deviceClsId, fieldType.stateClsId, CATEGORY_NONE); root["name"] = name; root["stat_t"] = stateTopic; @@ -164,9 +163,6 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr if (Configuration.get().Mqtt.Hass.Expire) { root["exp_aft"] = Hoymiles.getNumInverters() * max(Hoymiles.PollInterval(), Configuration.get().Mqtt.PublishInterval) * inv->getReachableThreshold(); } - if (stateCls != 0) { - root["stat_cla"] = stateCls; - } publish(configTopic, root); } else { @@ -174,7 +170,10 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr } } -void MqttHandleHassClass::publishInverterButton(std::shared_ptr inv, const String& name, const String& state_topic, const String& payload, const String& icon, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::publishInverterButton( + std::shared_ptr inv, const String& name, const String& state_topic, const String& payload, + const String& icon, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { const String serial = inv->serialString(); @@ -190,7 +189,7 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptr inv, const String& name, const String& stateTopic, const String& command_topic, const int16_t min, const int16_t max, float step, - const String& unit_of_measure, const String& icon, const CategoryType category) + const String& unit_of_measure, const String& icon, + const StateClassType state_class, const CategoryType category) { const String serial = inv->serialString(); @@ -221,7 +221,7 @@ void MqttHandleHassClass::publishInverterNumber( JsonDocument root; createInverterInfo(root, inv); - addCommonMetadata(root, unit_of_measure, icon, DEVICE_CLS_NONE, category); + addCommonMetadata(root, unit_of_measure, icon, DEVICE_CLS_NONE, state_class, category); root["name"] = name; root["uniq_id"] = serial + "_" + buttonId; @@ -307,7 +307,10 @@ void MqttHandleHassClass::publish(const String& subtopic, const JsonDocument& do publish(subtopic, buffer); } -void MqttHandleHassClass::addCommonMetadata(JsonDocument& doc, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::addCommonMetadata( + JsonDocument& doc, + const String& unit_of_measure, const String& icon, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { if (unit_of_measure != "") { doc["unit_of_meas"] = unit_of_measure; @@ -318,12 +321,18 @@ void MqttHandleHassClass::addCommonMetadata(JsonDocument& doc, const String& uni if (device_class != DEVICE_CLS_NONE) { doc["dev_cla"] = deviceClass_name[device_class]; } + if (state_class != STATE_CLS_NONE) { + doc["stat_cla"] = stateClass_name[state_class];; + } if (category != CATEGORY_NONE) { doc["ent_cat"] = category_name[category]; } } -void MqttHandleHassClass::publishBinarySensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::publishBinarySensor( + JsonDocument& doc, + const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { String sensor_id = name; sensor_id.toLowerCase(); @@ -335,31 +344,39 @@ void MqttHandleHassClass::publishBinarySensor(JsonDocument& doc, const String& r doc["pl_on"] = payload_on; doc["pl_off"] = payload_off; - addCommonMetadata(doc, "", "", device_class, category); + addCommonMetadata(doc, "", "", device_class, state_class, category); const String configTopic = "binary_sensor/" + root_device + "/" + sensor_id + "/config"; publish(configTopic, doc); } -void MqttHandleHassClass::publishDtuBinarySensor(const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::publishDtuBinarySensor( + const String& name, const String& state_topic, const String& payload_on, const String& payload_off, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { const String dtuId = getDtuUniqueId(); JsonDocument root; createDtuInfo(root); - publishBinarySensor(root, dtuId, dtuId, name, state_topic, payload_on, payload_off, device_class, category); + publishBinarySensor(root, dtuId, dtuId, name, state_topic, payload_on, payload_off, device_class, state_class, category); } -void MqttHandleHassClass::publishInverterBinarySensor(std::shared_ptr inv, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::publishInverterBinarySensor( + std::shared_ptr inv, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { const String serial = inv->serialString(); JsonDocument root; createInverterInfo(root, inv); - publishBinarySensor(root, "dtu_" + serial, serial, name, serial + "/" + state_topic, payload_on, payload_off, device_class, category); + publishBinarySensor(root, "dtu_" + serial, serial, name, serial + "/" + state_topic, payload_on, payload_off, device_class, state_class, category); } -void MqttHandleHassClass::publishSensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::publishSensor( + JsonDocument& doc, + const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, + const String& unit_of_measure, const String& icon, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { String sensor_id = name; sensor_id.toLowerCase(); @@ -369,7 +386,7 @@ void MqttHandleHassClass::publishSensor(JsonDocument& doc, const String& root_de doc["uniq_id"] = unique_id_prefix + "_" + sensor_id; doc["stat_t"] = MqttSettings.getPrefix() + state_topic; - addCommonMetadata(doc, unit_of_measure, icon, device_class, category); + addCommonMetadata(doc, unit_of_measure, icon, device_class, state_class, category); const CONFIG_T& config = Configuration.get(); doc["avty_t"] = MqttSettings.getPrefix() + config.Mqtt.Lwt.Topic; @@ -380,20 +397,26 @@ void MqttHandleHassClass::publishSensor(JsonDocument& doc, const String& root_de publish(configTopic, doc); } -void MqttHandleHassClass::publishDtuSensor(const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::publishDtuSensor( + const String& name, const String& state_topic, + const String& unit_of_measure, const String& icon, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { const String dtuId = getDtuUniqueId(); JsonDocument root; createDtuInfo(root); - publishSensor(root, dtuId, dtuId, name, state_topic, unit_of_measure, icon, device_class, category); + publishSensor(root, dtuId, dtuId, name, state_topic, unit_of_measure, icon, device_class, state_class, category); } -void MqttHandleHassClass::publishInverterSensor(std::shared_ptr inv, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const CategoryType category) +void MqttHandleHassClass::publishInverterSensor( + std::shared_ptr inv, const String& name, const String& state_topic, + const String& unit_of_measure, const String& icon, + const DeviceClassType device_class, const StateClassType state_class, const CategoryType category) { const String serial = inv->serialString(); JsonDocument root; createInverterInfo(root, inv); - publishSensor(root, "dtu_" + serial, serial, name, serial + "/" + state_topic, unit_of_measure, icon, device_class, category); + publishSensor(root, "dtu_" + serial, serial, name, serial + "/" + state_topic, unit_of_measure, icon, device_class, state_class, category); } From d0b2b972e20d91ccdb48f98c9dfabab2818fe9cb Mon Sep 17 00:00:00 2001 From: janrombold <92722795+janrombold@users.noreply.github.com> Date: Fri, 4 Oct 2024 00:19:26 +0200 Subject: [PATCH 016/110] Update UpgradePartition.md Fixed typo --- docs/UpgradePartition.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/UpgradePartition.md b/docs/UpgradePartition.md index 782463f5..f919844c 100644 --- a/docs/UpgradePartition.md +++ b/docs/UpgradePartition.md @@ -1,3 +1,3 @@ # Upgrade Partition -This documentation will has been moved and can be found here: +This documentation has been moved and can be found here: From edfe06e31eae286fbb88da69e9b65420cfa8628a Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Fri, 4 Oct 2024 17:36:17 +0200 Subject: [PATCH 017/110] Feature: Show RSSI of last received packet in radio stats The value is also published via MQTT --- lib/Hoymiles/src/HoymilesRadio_CMT.cpp | 2 +- lib/Hoymiles/src/HoymilesRadio_NRF.cpp | 2 +- lib/Hoymiles/src/inverters/InverterAbstract.cpp | 9 ++++++++- lib/Hoymiles/src/inverters/InverterAbstract.h | 6 +++++- src/MqttHandleHass.cpp | 1 + src/MqttHandleInverter.cpp | 1 + src/WebApi_ws_live.cpp | 1 + webapp/src/locales/de.json | 5 ++++- webapp/src/locales/en.json | 5 ++++- webapp/src/locales/fr.json | 5 ++++- webapp/src/types/LiveDataStatus.ts | 1 + webapp/src/views/HomeView.vue | 12 ++++++++++++ 12 files changed, 43 insertions(+), 7 deletions(-) diff --git a/lib/Hoymiles/src/HoymilesRadio_CMT.cpp b/lib/Hoymiles/src/HoymilesRadio_CMT.cpp index bbd31f21..f6d19588 100644 --- a/lib/Hoymiles/src/HoymilesRadio_CMT.cpp +++ b/lib/Hoymiles/src/HoymilesRadio_CMT.cpp @@ -169,7 +169,7 @@ void HoymilesRadio_CMT::loop() dumpBuf(f.fragment, f.len, false); Hoymiles.getMessageOutput()->printf("| %d dBm\r\n", f.rssi); - inv->addRxFragment(f.fragment, f.len); + inv->addRxFragment(f.fragment, f.len, f.rssi); } else { Hoymiles.getMessageOutput()->println("Inverter Not found!"); } diff --git a/lib/Hoymiles/src/HoymilesRadio_NRF.cpp b/lib/Hoymiles/src/HoymilesRadio_NRF.cpp index 4bf104ad..bd496afe 100644 --- a/lib/Hoymiles/src/HoymilesRadio_NRF.cpp +++ b/lib/Hoymiles/src/HoymilesRadio_NRF.cpp @@ -80,7 +80,7 @@ void HoymilesRadio_NRF::loop() dumpBuf(f.fragment, f.len, false); Hoymiles.getMessageOutput()->printf("| %d dBm\r\n", f.rssi); - inv->addRxFragment(f.fragment, f.len); + inv->addRxFragment(f.fragment, f.len, f.rssi); } else { Hoymiles.getMessageOutput()->println("Inverter Not found!"); } diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.cpp b/lib/Hoymiles/src/inverters/InverterAbstract.cpp index 3e51bbd7..7fe522f3 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.cpp +++ b/lib/Hoymiles/src/inverters/InverterAbstract.cpp @@ -137,6 +137,11 @@ bool InverterAbstract::getClearEventlogOnMidnight() const return _clearEventlogOnMidnight; } +int8_t InverterAbstract::getLastRssi() const +{ + return _lastRssi; +} + bool InverterAbstract::sendChangeChannelRequest() { return false; @@ -185,8 +190,10 @@ void InverterAbstract::clearRxFragmentBuffer() _rxFragmentRetransmitCnt = 0; } -void InverterAbstract::addRxFragment(const uint8_t fragment[], const uint8_t len) +void InverterAbstract::addRxFragment(const uint8_t fragment[], const uint8_t len, const int8_t rssi) { + _lastRssi = rssi; + if (len < 11) { Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) fragment too short\r\n", __FILE__, __LINE__); return; diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.h b/lib/Hoymiles/src/inverters/InverterAbstract.h index f139fab3..2f49f912 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.h +++ b/lib/Hoymiles/src/inverters/InverterAbstract.h @@ -61,8 +61,10 @@ public: void setClearEventlogOnMidnight(const bool enabled); bool getClearEventlogOnMidnight() const; + int8_t getLastRssi() const; + void clearRxFragmentBuffer(); - void addRxFragment(const uint8_t fragment[], const uint8_t len); + void addRxFragment(const uint8_t fragment[], const uint8_t len, const int8_t rssi); uint8_t verifyAllFragments(CommandAbstract& cmd); void performDailyTask(); @@ -131,6 +133,8 @@ private: bool _zeroYieldDayOnMidnight = false; bool _clearEventlogOnMidnight = false; + int8_t _lastRssi = 0; + std::unique_ptr _alarmLogParser; std::unique_ptr _devInfoParser; std::unique_ptr _gridProfileParser; diff --git a/src/MqttHandleHass.cpp b/src/MqttHandleHass.cpp index 7afa87da..6491c9ba 100644 --- a/src/MqttHandleHass.cpp +++ b/src/MqttHandleHass.cpp @@ -97,6 +97,7 @@ void MqttHandleHassClass::publishConfig() publishInverterSensor(inv, "RX Fail Receive Partial", "radio/rx_fail_partial", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); publishInverterSensor(inv, "RX Fail Receive Corrupt", "radio/rx_fail_corrupt", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); publishInverterSensor(inv, "TX Re-Request Fragment", "radio/tx_re_request", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterSensor(inv, "RSSI", "radio/rssi", "dBm", "", DEVICE_CLS_SIGNAL_STRENGTH, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); // Loop all channels for (auto& t : inv->Statistics()->getChannelTypes()) { diff --git a/src/MqttHandleInverter.cpp b/src/MqttHandleInverter.cpp index 8e720663..6185f193 100644 --- a/src/MqttHandleInverter.cpp +++ b/src/MqttHandleInverter.cpp @@ -50,6 +50,7 @@ void MqttHandleInverterClass::loop() MqttSettings.publish(subtopic + "/radio/rx_fail_nothing", String(inv->RadioStats.RxFailNoAnswer)); MqttSettings.publish(subtopic + "/radio/rx_fail_partial", String(inv->RadioStats.RxFailPartialAnswer)); MqttSettings.publish(subtopic + "/radio/rx_fail_corrupt", String(inv->RadioStats.RxFailCorruptData)); + MqttSettings.publish(subtopic + "/radio/rssi", String(inv->getLastRssi())); if (inv->DevInfo()->getLastUpdate() > 0) { // Bootloader Version diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index 29c204a3..a50a792a 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -153,6 +153,7 @@ void WebApiWsLiveClass::generateInverterCommonJsonResponse(JsonObject& root, std root["radio_stats"]["rx_fail_nothing"] = inv->RadioStats.RxFailNoAnswer; root["radio_stats"]["rx_fail_partial"] = inv->RadioStats.RxFailPartialAnswer; root["radio_stats"]["rx_fail_corrupt"] = inv->RadioStats.RxFailCorruptData; + root["radio_stats"]["rssi"] = inv->getLastRssi(); } void WebApiWsLiveClass::generateInverterChannelJsonResponse(JsonObject& root, std::shared_ptr inv) diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 995f6ec7..0bc17768 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -150,7 +150,10 @@ "RxFailCorrupt": "Empfang Fehler: Beschädigt empfangen", "TxReRequest": "Gesendete Fragment Wiederanforderungen", "StatsReset": "Statistiken zurücksetzen", - "StatsResetting": "Zurücksetzen..." + "StatsResetting": "Zurücksetzen...", + "Rssi": "RSSI des zuletzt empfangenen Paketes", + "RssiHint": "HM-Wechselrichter unterstützen nur RSSI-Werte < -64 dBm und > -64 dBm. In diesem Fall wird -80 dBm und -30 dBm angezeigt.", + "dBm": "{dbm} dBm" }, "eventlog": { "Start": "Beginn", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index cf3c74f0..f760178f 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -150,7 +150,10 @@ "RxFailCorrupt": "RX Fail: Receive Corrupt", "TxReRequest": "TX Re-Request Fragment", "StatsReset": "Reset Statistics", - "StatsResetting": "Resetting..." + "StatsResetting": "Resetting...", + "Rssi": "RSSI of last received packet", + "RssiHint": "HM inverters only support RSSI values < -64 dBm and > -64 dBm. In this case, -80 dbm and -30 dbm is shown.", + "dBm": "{dbm} dBm" }, "eventlog": { "Start": "Start", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index fcb22a19..023fc099 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -150,7 +150,10 @@ "RxFailCorrupt": "RX Fail: Receive Corrupt", "TxReRequest": "TX Re-Request Fragment", "StatsReset": "Reset Statistics", - "StatsResetting": "Resetting..." + "StatsResetting": "Resetting...", + "Rssi": "RSSI of last received packet", + "RssiHint": "HM inverters only support RSSI values < -64 dBm and > -64 dBm. In this case, -80 dbm and -30 dbm is shown.", + "dBm": "{dbm} dBm" }, "eventlog": { "Start": "Départ", diff --git a/webapp/src/types/LiveDataStatus.ts b/webapp/src/types/LiveDataStatus.ts index db95897c..4f681070 100644 --- a/webapp/src/types/LiveDataStatus.ts +++ b/webapp/src/types/LiveDataStatus.ts @@ -28,6 +28,7 @@ export interface RadioStatistics { rx_fail_nothing: number; rx_fail_partial: number; rx_fail_corrupt: number; + rssi: number; } export interface Inverter { diff --git a/webapp/src/views/HomeView.vue b/webapp/src/views/HomeView.vue index 0aef275a..ef8fa8ef 100644 --- a/webapp/src/views/HomeView.vue +++ b/webapp/src/views/HomeView.vue @@ -291,6 +291,16 @@ {{ $n(inverter.radio_stats.tx_re_request) }} + + + {{ $t('home.Rssi') }} + + + + {{ $t('home.dBm', { dbm: $n(inverter.radio_stats.rssi) }) }} + + +