From d3f95000e23e6284836d5f808ea927f51bc1cab9 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Sat, 18 Nov 2023 22:34:55 +0100 Subject: [PATCH 01/89] Optimize Sun data calculation --- include/SunPosition.h | 8 +++---- src/SunPosition.cpp | 55 ++++++++++++++++--------------------------- 2 files changed, 23 insertions(+), 40 deletions(-) diff --git a/include/SunPosition.h b/include/SunPosition.h index 26caab7b..1e1508d3 100644 --- a/include/SunPosition.h +++ b/include/SunPosition.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include +#include #include class SunPositionClass { @@ -19,16 +19,14 @@ public: private: void updateSunData(); bool checkRecalcDayChanged(); - bool getDoRecalc(); + bool getSunTime(struct tm* info, uint32_t offset); - SunSet _sun; bool _isSunsetAvailable = true; uint32_t _sunriseMinutes = 0; uint32_t _sunsetMinutes = 0; bool _isValidInfo = false; - bool _doRecalc = true; - std::mutex _recalcLock; + std::atomic_bool _doRecalc = true; uint32_t _lastSunPositionCalculatedYMD = 0; }; diff --git a/src/SunPosition.cpp b/src/SunPosition.cpp index 0d4d419b..ccbaeb6b 100644 --- a/src/SunPosition.cpp +++ b/src/SunPosition.cpp @@ -19,7 +19,7 @@ void SunPositionClass::init() void SunPositionClass::loop() { - if (getDoRecalc() || checkRecalcDayChanged()) { + if (_doRecalc || checkRecalcDayChanged()) { updateSunData(); } } @@ -43,16 +43,9 @@ bool SunPositionClass::isSunsetAvailable() void SunPositionClass::setDoRecalc(bool doRecalc) { - std::lock_guard lock(_recalcLock); _doRecalc = doRecalc; } -bool SunPositionClass::getDoRecalc() -{ - std::lock_guard lock(_recalcLock); - return _doRecalc; -} - bool SunPositionClass::checkRecalcDayChanged() { time_t now; @@ -64,10 +57,7 @@ bool SunPositionClass::checkRecalcDayChanged() uint32_t ymd; ymd = (timeinfo.tm_year << 9) | (timeinfo.tm_mon << 5) | timeinfo.tm_mday; - if (_lastSunPositionCalculatedYMD != ymd) { - return true; - } - return false; + return _lastSunPositionCalculatedYMD != ymd; } void SunPositionClass::updateSunData() @@ -82,15 +72,12 @@ void SunPositionClass::updateSunData() if (!gotLocalTime) { _sunriseMinutes = 0; _sunsetMinutes = 0; + _isSunsetAvailable = true; _isValidInfo = false; return; } CONFIG_T const& config = Configuration.get(); - int offset = Utils::getTimezoneOffset() / 3600; - - _sun.setPosition(config.Ntp_Latitude, config.Ntp_Longitude, offset); - _sun.setCurrentDate(1900 + timeinfo.tm_year, timeinfo.tm_mon + 1, timeinfo.tm_mday); double sunset_type; switch (config.Ntp_SunsetType) { @@ -108,15 +95,21 @@ void SunPositionClass::updateSunData() break; } - double sunriseRaw = _sun.calcCustomSunrise(sunset_type); - double sunsetRaw = _sun.calcCustomSunset(sunset_type); + int offset = Utils::getTimezoneOffset() / 3600; + + SunSet sun; + sun.setPosition(config.Ntp_Latitude, config.Ntp_Longitude, offset); + sun.setCurrentDate(1900 + timeinfo.tm_year, timeinfo.tm_mon + 1, timeinfo.tm_mday); + + double sunriseRaw = sun.calcCustomSunrise(sunset_type); + double sunsetRaw = sun.calcCustomSunset(sunset_type); // If no sunset/sunrise exists (e.g. astronomical calculation in summer) // assume it's day period if (std::isnan(sunriseRaw) || std::isnan(sunsetRaw)) { - _isSunsetAvailable = false; _sunriseMinutes = 0; _sunsetMinutes = 0; + _isSunsetAvailable = false; _isValidInfo = false; return; } @@ -128,7 +121,7 @@ void SunPositionClass::updateSunData() _isValidInfo = true; } -bool SunPositionClass::sunsetTime(struct tm* info) +bool SunPositionClass::getSunTime(struct tm* info, uint32_t offset) { // Get today's date time_t aTime = time(NULL); @@ -137,7 +130,7 @@ bool SunPositionClass::sunsetTime(struct tm* info) struct tm tm; localtime_r(&aTime, &tm); tm.tm_sec = 0; - tm.tm_min = _sunsetMinutes; + tm.tm_min = offset; tm.tm_hour = 0; tm.tm_isdst = -1; time_t midnight = mktime(&tm); @@ -146,20 +139,12 @@ bool SunPositionClass::sunsetTime(struct tm* info) return _isValidInfo; } +bool SunPositionClass::sunsetTime(struct tm* info) +{ + return getSunTime(info, _sunsetMinutes); +} + bool SunPositionClass::sunriseTime(struct tm* info) { - // Get today's date - time_t aTime = time(NULL); - - // Set the time to midnight - struct tm tm; - localtime_r(&aTime, &tm); - tm.tm_sec = 0; - tm.tm_min = _sunriseMinutes; - tm.tm_hour = 0; - tm.tm_isdst = -1; - time_t midnight = mktime(&tm); - - localtime_r(&midnight, info); - return _isValidInfo; + return getSunTime(info, _sunriseMinutes); } From 6bafd734d7030b4db54dc4341365a9528c3f6d9d Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Sat, 18 Nov 2023 22:35:58 +0100 Subject: [PATCH 02/89] Remove not required enum --- include/Led_Single.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/include/Led_Single.h b/include/Led_Single.h index a5c601bd..ed811824 100644 --- a/include/Led_Single.h +++ b/include/Led_Single.h @@ -6,13 +6,6 @@ #define LEDSINGLE_UPDATE_INTERVAL 2000 -enum eLedFunction { - CONNECTED_NETWORK, - CONNECTED_MQTT, - INV_REACHABLE, - INV_PRODUCING, -}; - class LedSingleClass { public: LedSingleClass(); From b85c53f476b3f9d3e9470b342d4df886c02c43c2 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Sun, 19 Nov 2023 14:53:26 +0100 Subject: [PATCH 03/89] Split config struct into different sub structs --- include/Configuration.h | 140 ++++++++++------- src/Configuration.cpp | 256 ++++++++++++++++---------------- src/InverterSettings.cpp | 12 +- src/Led_Single.cpp | 2 +- src/MqttHandleDtu.cpp | 2 +- src/MqttHandleHass.cpp | 12 +- src/MqttHandleInverter.cpp | 4 +- src/MqttHandleInverterTotal.cpp | 4 +- src/MqttSettings.cpp | 40 ++--- src/NetworkSettings.cpp | 40 ++--- src/NtpSettings.cpp | 4 +- src/SunPosition.cpp | 4 +- src/WebApi.cpp | 4 +- src/WebApi_device.cpp | 30 ++-- src/WebApi_dtu.cpp | 34 ++--- src/WebApi_mqtt.cpp | 124 ++++++++-------- src/WebApi_network.cpp | 76 +++++----- src/WebApi_ntp.cpp | 30 ++-- src/WebApi_security.cpp | 8 +- src/WebApi_sysstatus.cpp | 2 +- src/WebApi_ws_console.cpp | 4 +- src/WebApi_ws_live.cpp | 6 +- src/main.cpp | 16 +- 23 files changed, 440 insertions(+), 414 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index 39284cb8..7c5ce1f6 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -52,72 +52,98 @@ struct INVERTER_CONFIG_T { }; struct CONFIG_T { - uint32_t Cfg_Version; - uint32_t Cfg_SaveCount; + struct { + uint32_t Version; + uint32_t SaveCount; + } Cfg; - char WiFi_Ssid[WIFI_MAX_SSID_STRLEN + 1]; - char WiFi_Password[WIFI_MAX_PASSWORD_STRLEN + 1]; - uint8_t WiFi_Ip[4]; - uint8_t WiFi_Netmask[4]; - uint8_t WiFi_Gateway[4]; - uint8_t WiFi_Dns1[4]; - uint8_t WiFi_Dns2[4]; - bool WiFi_Dhcp; - char WiFi_Hostname[WIFI_MAX_HOSTNAME_STRLEN + 1]; - uint32_t WiFi_ApTimeout; + struct { + char Ssid[WIFI_MAX_SSID_STRLEN + 1]; + char Password[WIFI_MAX_PASSWORD_STRLEN + 1]; + uint8_t Ip[4]; + uint8_t Netmask[4]; + uint8_t Gateway[4]; + uint8_t Dns1[4]; + uint8_t Dns2[4]; + bool Dhcp; + char Hostname[WIFI_MAX_HOSTNAME_STRLEN + 1]; + uint32_t ApTimeout; + } WiFi; - bool Mdns_Enabled; + struct { + bool Enabled; + } Mdns; - char Ntp_Server[NTP_MAX_SERVER_STRLEN + 1]; - char Ntp_Timezone[NTP_MAX_TIMEZONE_STRLEN + 1]; - char Ntp_TimezoneDescr[NTP_MAX_TIMEZONEDESCR_STRLEN + 1]; - double Ntp_Longitude; - double Ntp_Latitude; - uint8_t Ntp_SunsetType; + struct { + char Server[NTP_MAX_SERVER_STRLEN + 1]; + char Timezone[NTP_MAX_TIMEZONE_STRLEN + 1]; + char TimezoneDescr[NTP_MAX_TIMEZONEDESCR_STRLEN + 1]; + double Longitude; + double Latitude; + uint8_t SunsetType; + } Ntp; - bool Mqtt_Enabled; - char Mqtt_Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1]; - uint32_t Mqtt_Port; - char Mqtt_Username[MQTT_MAX_USERNAME_STRLEN + 1]; - char Mqtt_Password[MQTT_MAX_PASSWORD_STRLEN + 1]; - char Mqtt_Topic[MQTT_MAX_TOPIC_STRLEN + 1]; - bool Mqtt_Retain; - char Mqtt_LwtTopic[MQTT_MAX_TOPIC_STRLEN + 1]; - char Mqtt_LwtValue_Online[MQTT_MAX_LWTVALUE_STRLEN + 1]; - char Mqtt_LwtValue_Offline[MQTT_MAX_LWTVALUE_STRLEN + 1]; - uint32_t Mqtt_PublishInterval; - bool Mqtt_CleanSession; + struct { + bool Enabled; + char Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1]; + uint32_t Port; + char Username[MQTT_MAX_USERNAME_STRLEN + 1]; + char Password[MQTT_MAX_PASSWORD_STRLEN + 1]; + char Topic[MQTT_MAX_TOPIC_STRLEN + 1]; + bool Retain; + uint32_t PublishInterval; + bool CleanSession; - bool Mqtt_Hass_Enabled; - bool Mqtt_Hass_Retain; - char Mqtt_Hass_Topic[MQTT_MAX_TOPIC_STRLEN + 1]; - bool Mqtt_Hass_IndividualPanels; - bool Mqtt_Hass_Expire; + struct { + char Topic[MQTT_MAX_TOPIC_STRLEN + 1]; + char Value_Online[MQTT_MAX_LWTVALUE_STRLEN + 1]; + char Value_Offline[MQTT_MAX_LWTVALUE_STRLEN + 1]; + } Lwt; - bool Mqtt_Tls; - char Mqtt_RootCaCert[MQTT_MAX_CERT_STRLEN + 1]; - bool Mqtt_TlsCertLogin; - char Mqtt_ClientCert[MQTT_MAX_CERT_STRLEN + 1]; - char Mqtt_ClientKey[MQTT_MAX_CERT_STRLEN + 1]; + struct { + bool Enabled; + bool Retain; + char Topic[MQTT_MAX_TOPIC_STRLEN + 1]; + bool IndividualPanels; + bool Expire; + } Hass; + + struct { + bool Enabled; + char RootCaCert[MQTT_MAX_CERT_STRLEN + 1]; + bool CertLogin; + char ClientCert[MQTT_MAX_CERT_STRLEN + 1]; + char ClientKey[MQTT_MAX_CERT_STRLEN + 1]; + } Tls; + } Mqtt; + + struct { + uint64_t Serial; + uint32_t PollInterval; + struct { + uint8_t PaLevel; + } Nrf; + struct { + int8_t PaLevel; + uint32_t Frequency; + } Cmt; + } Dtu; + + struct { + char Password[WIFI_MAX_PASSWORD_STRLEN + 1]; + bool AllowReadonly; + } Security; + + struct { + bool PowerSafe; + bool ScreenSaver; + uint8_t Rotation; + uint8_t Contrast; + uint8_t Language; + } Display; INVERTER_CONFIG_T Inverter[INV_MAX_COUNT]; - - uint64_t Dtu_Serial; - uint32_t Dtu_PollInterval; - uint8_t Dtu_NrfPaLevel; - int8_t Dtu_CmtPaLevel; - uint32_t Dtu_CmtFrequency; - - char Security_Password[WIFI_MAX_PASSWORD_STRLEN + 1]; - bool Security_AllowReadonly; - char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1]; - - bool Display_PowerSafe; - bool Display_ScreenSaver; - uint8_t Display_Rotation; - uint8_t Display_Contrast; - uint8_t Display_Language; }; class ConfigurationClass { diff --git a/src/Configuration.cpp b/src/Configuration.cpp index e90f7191..e88bc399 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -21,87 +21,87 @@ bool ConfigurationClass::write() if (!f) { return false; } - config.Cfg_SaveCount++; + config.Cfg.SaveCount++; DynamicJsonDocument doc(JSON_BUFFER_SIZE); JsonObject cfg = doc.createNestedObject("cfg"); - cfg["version"] = config.Cfg_Version; - cfg["save_count"] = config.Cfg_SaveCount; + cfg["version"] = config.Cfg.Version; + cfg["save_count"] = config.Cfg.SaveCount; JsonObject wifi = doc.createNestedObject("wifi"); - wifi["ssid"] = config.WiFi_Ssid; - wifi["password"] = config.WiFi_Password; - wifi["ip"] = IPAddress(config.WiFi_Ip).toString(); - wifi["netmask"] = IPAddress(config.WiFi_Netmask).toString(); - wifi["gateway"] = IPAddress(config.WiFi_Gateway).toString(); - wifi["dns1"] = IPAddress(config.WiFi_Dns1).toString(); - wifi["dns2"] = IPAddress(config.WiFi_Dns2).toString(); - wifi["dhcp"] = config.WiFi_Dhcp; - wifi["hostname"] = config.WiFi_Hostname; - wifi["aptimeout"] = config.WiFi_ApTimeout; + wifi["ssid"] = config.WiFi.Ssid; + wifi["password"] = config.WiFi.Password; + wifi["ip"] = IPAddress(config.WiFi.Ip).toString(); + wifi["netmask"] = IPAddress(config.WiFi.Netmask).toString(); + wifi["gateway"] = IPAddress(config.WiFi.Gateway).toString(); + wifi["dns1"] = IPAddress(config.WiFi.Dns1).toString(); + wifi["dns2"] = IPAddress(config.WiFi.Dns2).toString(); + wifi["dhcp"] = config.WiFi.Dhcp; + wifi["hostname"] = config.WiFi.Hostname; + wifi["aptimeout"] = config.WiFi.ApTimeout; JsonObject mdns = doc.createNestedObject("mdns"); - mdns["enabled"] = config.Mdns_Enabled; + mdns["enabled"] = config.Mdns.Enabled; JsonObject ntp = doc.createNestedObject("ntp"); - ntp["server"] = config.Ntp_Server; - ntp["timezone"] = config.Ntp_Timezone; - ntp["timezone_descr"] = config.Ntp_TimezoneDescr; - ntp["latitude"] = config.Ntp_Latitude; - ntp["longitude"] = config.Ntp_Longitude; - ntp["sunsettype"] = config.Ntp_SunsetType; + ntp["server"] = config.Ntp.Server; + ntp["timezone"] = config.Ntp.Timezone; + ntp["timezone_descr"] = config.Ntp.TimezoneDescr; + ntp["latitude"] = config.Ntp.Latitude; + ntp["longitude"] = config.Ntp.Longitude; + ntp["sunsettype"] = config.Ntp.SunsetType; JsonObject mqtt = doc.createNestedObject("mqtt"); - mqtt["enabled"] = config.Mqtt_Enabled; - mqtt["hostname"] = config.Mqtt_Hostname; - mqtt["port"] = config.Mqtt_Port; - mqtt["username"] = config.Mqtt_Username; - mqtt["password"] = config.Mqtt_Password; - mqtt["topic"] = config.Mqtt_Topic; - mqtt["retain"] = config.Mqtt_Retain; - mqtt["publish_interval"] = config.Mqtt_PublishInterval; - mqtt["clean_session"] = config.Mqtt_CleanSession; + mqtt["enabled"] = config.Mqtt.Enabled; + mqtt["hostname"] = config.Mqtt.Hostname; + mqtt["port"] = config.Mqtt.Port; + mqtt["username"] = config.Mqtt.Username; + mqtt["password"] = config.Mqtt.Password; + mqtt["topic"] = config.Mqtt.Topic; + mqtt["retain"] = config.Mqtt.Retain; + mqtt["publish_interval"] = config.Mqtt.PublishInterval; + mqtt["clean_session"] = config.Mqtt.CleanSession; JsonObject mqtt_lwt = mqtt.createNestedObject("lwt"); - mqtt_lwt["topic"] = config.Mqtt_LwtTopic; - mqtt_lwt["value_online"] = config.Mqtt_LwtValue_Online; - mqtt_lwt["value_offline"] = config.Mqtt_LwtValue_Offline; + mqtt_lwt["topic"] = config.Mqtt.Lwt.Topic; + mqtt_lwt["value_online"] = config.Mqtt.Lwt.Value_Online; + mqtt_lwt["value_offline"] = config.Mqtt.Lwt.Value_Offline; JsonObject mqtt_tls = mqtt.createNestedObject("tls"); - mqtt_tls["enabled"] = config.Mqtt_Tls; - mqtt_tls["root_ca_cert"] = config.Mqtt_RootCaCert; - mqtt_tls["certlogin"] = config.Mqtt_TlsCertLogin; - mqtt_tls["client_cert"] = config.Mqtt_ClientCert; - mqtt_tls["client_key"] = config.Mqtt_ClientKey; + mqtt_tls["enabled"] = config.Mqtt.Tls.Enabled; + mqtt_tls["root_ca_cert"] = config.Mqtt.Tls.RootCaCert; + mqtt_tls["certlogin"] = config.Mqtt.Tls.CertLogin; + mqtt_tls["client_cert"] = config.Mqtt.Tls.ClientCert; + mqtt_tls["client_key"] = config.Mqtt.Tls.ClientKey; JsonObject mqtt_hass = mqtt.createNestedObject("hass"); - mqtt_hass["enabled"] = config.Mqtt_Hass_Enabled; - mqtt_hass["retain"] = config.Mqtt_Hass_Retain; - mqtt_hass["topic"] = config.Mqtt_Hass_Topic; - mqtt_hass["individual_panels"] = config.Mqtt_Hass_IndividualPanels; - mqtt_hass["expire"] = config.Mqtt_Hass_Expire; + mqtt_hass["enabled"] = config.Mqtt.Hass.Enabled; + mqtt_hass["retain"] = config.Mqtt.Hass.Retain; + mqtt_hass["topic"] = config.Mqtt.Hass.Topic; + mqtt_hass["individual_panels"] = config.Mqtt.Hass.IndividualPanels; + mqtt_hass["expire"] = config.Mqtt.Hass.Expire; JsonObject dtu = doc.createNestedObject("dtu"); - dtu["serial"] = config.Dtu_Serial; - dtu["poll_interval"] = config.Dtu_PollInterval; - dtu["nrf_pa_level"] = config.Dtu_NrfPaLevel; - dtu["cmt_pa_level"] = config.Dtu_CmtPaLevel; - dtu["cmt_frequency"] = config.Dtu_CmtFrequency; + dtu["serial"] = config.Dtu.Serial; + dtu["poll_interval"] = config.Dtu.PollInterval; + dtu["nrf_pa_level"] = config.Dtu.Nrf.PaLevel; + dtu["cmt_pa_level"] = config.Dtu.Cmt.PaLevel; + dtu["cmt_frequency"] = config.Dtu.Cmt.Frequency; JsonObject security = doc.createNestedObject("security"); - security["password"] = config.Security_Password; - security["allow_readonly"] = config.Security_AllowReadonly; + security["password"] = config.Security.Password; + security["allow_readonly"] = config.Security.AllowReadonly; JsonObject device = doc.createNestedObject("device"); device["pinmapping"] = config.Dev_PinMapping; JsonObject display = device.createNestedObject("display"); - display["powersafe"] = config.Display_PowerSafe; - display["screensaver"] = config.Display_ScreenSaver; - display["rotation"] = config.Display_Rotation; - display["contrast"] = config.Display_Contrast; - display["language"] = config.Display_Language; + display["powersafe"] = config.Display.PowerSafe; + display["screensaver"] = config.Display.ScreenSaver; + display["rotation"] = config.Display.Rotation; + display["contrast"] = config.Display.Contrast; + display["language"] = config.Display.Language; JsonArray inverters = doc.createNestedArray("inverters"); for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { @@ -148,113 +148,113 @@ bool ConfigurationClass::read() } JsonObject cfg = doc["cfg"]; - config.Cfg_Version = cfg["version"] | CONFIG_VERSION; - config.Cfg_SaveCount = cfg["save_count"] | 0; + config.Cfg.Version = cfg["version"] | CONFIG_VERSION; + config.Cfg.SaveCount = cfg["save_count"] | 0; JsonObject wifi = doc["wifi"]; - strlcpy(config.WiFi_Ssid, wifi["ssid"] | WIFI_SSID, sizeof(config.WiFi_Ssid)); - strlcpy(config.WiFi_Password, wifi["password"] | WIFI_PASSWORD, sizeof(config.WiFi_Password)); - strlcpy(config.WiFi_Hostname, wifi["hostname"] | APP_HOSTNAME, sizeof(config.WiFi_Hostname)); + strlcpy(config.WiFi.Ssid, wifi["ssid"] | WIFI_SSID, sizeof(config.WiFi.Ssid)); + strlcpy(config.WiFi.Password, wifi["password"] | WIFI_PASSWORD, sizeof(config.WiFi.Password)); + strlcpy(config.WiFi.Hostname, wifi["hostname"] | APP_HOSTNAME, sizeof(config.WiFi.Hostname)); IPAddress wifi_ip; wifi_ip.fromString(wifi["ip"] | ""); - config.WiFi_Ip[0] = wifi_ip[0]; - config.WiFi_Ip[1] = wifi_ip[1]; - config.WiFi_Ip[2] = wifi_ip[2]; - config.WiFi_Ip[3] = wifi_ip[3]; + config.WiFi.Ip[0] = wifi_ip[0]; + config.WiFi.Ip[1] = wifi_ip[1]; + config.WiFi.Ip[2] = wifi_ip[2]; + config.WiFi.Ip[3] = wifi_ip[3]; IPAddress wifi_netmask; wifi_netmask.fromString(wifi["netmask"] | ""); - config.WiFi_Netmask[0] = wifi_netmask[0]; - config.WiFi_Netmask[1] = wifi_netmask[1]; - config.WiFi_Netmask[2] = wifi_netmask[2]; - config.WiFi_Netmask[3] = wifi_netmask[3]; + config.WiFi.Netmask[0] = wifi_netmask[0]; + config.WiFi.Netmask[1] = wifi_netmask[1]; + config.WiFi.Netmask[2] = wifi_netmask[2]; + config.WiFi.Netmask[3] = wifi_netmask[3]; IPAddress wifi_gateway; wifi_gateway.fromString(wifi["gateway"] | ""); - config.WiFi_Gateway[0] = wifi_gateway[0]; - config.WiFi_Gateway[1] = wifi_gateway[1]; - config.WiFi_Gateway[2] = wifi_gateway[2]; - config.WiFi_Gateway[3] = wifi_gateway[3]; + config.WiFi.Gateway[0] = wifi_gateway[0]; + config.WiFi.Gateway[1] = wifi_gateway[1]; + config.WiFi.Gateway[2] = wifi_gateway[2]; + config.WiFi.Gateway[3] = wifi_gateway[3]; IPAddress wifi_dns1; wifi_dns1.fromString(wifi["dns1"] | ""); - config.WiFi_Dns1[0] = wifi_dns1[0]; - config.WiFi_Dns1[1] = wifi_dns1[1]; - config.WiFi_Dns1[2] = wifi_dns1[2]; - config.WiFi_Dns1[3] = wifi_dns1[3]; + config.WiFi.Dns1[0] = wifi_dns1[0]; + config.WiFi.Dns1[1] = wifi_dns1[1]; + config.WiFi.Dns1[2] = wifi_dns1[2]; + config.WiFi.Dns1[3] = wifi_dns1[3]; IPAddress wifi_dns2; wifi_dns2.fromString(wifi["dns2"] | ""); - config.WiFi_Dns2[0] = wifi_dns2[0]; - config.WiFi_Dns2[1] = wifi_dns2[1]; - config.WiFi_Dns2[2] = wifi_dns2[2]; - config.WiFi_Dns2[3] = wifi_dns2[3]; + config.WiFi.Dns2[0] = wifi_dns2[0]; + config.WiFi.Dns2[1] = wifi_dns2[1]; + config.WiFi.Dns2[2] = wifi_dns2[2]; + config.WiFi.Dns2[3] = wifi_dns2[3]; - config.WiFi_Dhcp = wifi["dhcp"] | WIFI_DHCP; - config.WiFi_ApTimeout = wifi["aptimeout"] | ACCESS_POINT_TIMEOUT; + config.WiFi.Dhcp = wifi["dhcp"] | WIFI_DHCP; + config.WiFi.ApTimeout = wifi["aptimeout"] | ACCESS_POINT_TIMEOUT; JsonObject mdns = doc["mdns"]; - config.Mdns_Enabled = mdns["enabled"] | MDNS_ENABLED; + config.Mdns.Enabled = mdns["enabled"] | MDNS_ENABLED; JsonObject ntp = doc["ntp"]; - strlcpy(config.Ntp_Server, ntp["server"] | NTP_SERVER, sizeof(config.Ntp_Server)); - strlcpy(config.Ntp_Timezone, ntp["timezone"] | NTP_TIMEZONE, sizeof(config.Ntp_Timezone)); - strlcpy(config.Ntp_TimezoneDescr, ntp["timezone_descr"] | NTP_TIMEZONEDESCR, sizeof(config.Ntp_TimezoneDescr)); - config.Ntp_Latitude = ntp["latitude"] | NTP_LATITUDE; - config.Ntp_Longitude = ntp["longitude"] | NTP_LONGITUDE; - config.Ntp_SunsetType = ntp["sunsettype"] | NTP_SUNSETTYPE; + strlcpy(config.Ntp.Server, ntp["server"] | NTP_SERVER, sizeof(config.Ntp.Server)); + strlcpy(config.Ntp.Timezone, ntp["timezone"] | NTP_TIMEZONE, sizeof(config.Ntp.Timezone)); + strlcpy(config.Ntp.TimezoneDescr, ntp["timezone_descr"] | NTP_TIMEZONEDESCR, sizeof(config.Ntp.TimezoneDescr)); + config.Ntp.Latitude = ntp["latitude"] | NTP_LATITUDE; + config.Ntp.Longitude = ntp["longitude"] | NTP_LONGITUDE; + config.Ntp.SunsetType = ntp["sunsettype"] | NTP_SUNSETTYPE; JsonObject mqtt = doc["mqtt"]; - config.Mqtt_Enabled = mqtt["enabled"] | MQTT_ENABLED; - strlcpy(config.Mqtt_Hostname, mqtt["hostname"] | MQTT_HOST, sizeof(config.Mqtt_Hostname)); - config.Mqtt_Port = mqtt["port"] | MQTT_PORT; - strlcpy(config.Mqtt_Username, mqtt["username"] | MQTT_USER, sizeof(config.Mqtt_Username)); - strlcpy(config.Mqtt_Password, mqtt["password"] | MQTT_PASSWORD, sizeof(config.Mqtt_Password)); - strlcpy(config.Mqtt_Topic, mqtt["topic"] | MQTT_TOPIC, sizeof(config.Mqtt_Topic)); - config.Mqtt_Retain = mqtt["retain"] | MQTT_RETAIN; - config.Mqtt_PublishInterval = mqtt["publish_interval"] | MQTT_PUBLISH_INTERVAL; - config.Mqtt_CleanSession = mqtt["clean_session"] | MQTT_CLEAN_SESSION; + config.Mqtt.Enabled = mqtt["enabled"] | MQTT_ENABLED; + strlcpy(config.Mqtt.Hostname, mqtt["hostname"] | MQTT_HOST, sizeof(config.Mqtt.Hostname)); + config.Mqtt.Port = mqtt["port"] | MQTT_PORT; + strlcpy(config.Mqtt.Username, mqtt["username"] | MQTT_USER, sizeof(config.Mqtt.Username)); + strlcpy(config.Mqtt.Password, mqtt["password"] | MQTT_PASSWORD, sizeof(config.Mqtt.Password)); + strlcpy(config.Mqtt.Topic, mqtt["topic"] | MQTT_TOPIC, sizeof(config.Mqtt.Topic)); + config.Mqtt.Retain = mqtt["retain"] | MQTT_RETAIN; + config.Mqtt.PublishInterval = mqtt["publish_interval"] | MQTT_PUBLISH_INTERVAL; + config.Mqtt.CleanSession = mqtt["clean_session"] | MQTT_CLEAN_SESSION; JsonObject mqtt_lwt = mqtt["lwt"]; - strlcpy(config.Mqtt_LwtTopic, mqtt_lwt["topic"] | MQTT_LWT_TOPIC, sizeof(config.Mqtt_LwtTopic)); - strlcpy(config.Mqtt_LwtValue_Online, mqtt_lwt["value_online"] | MQTT_LWT_ONLINE, sizeof(config.Mqtt_LwtValue_Online)); - strlcpy(config.Mqtt_LwtValue_Offline, mqtt_lwt["value_offline"] | MQTT_LWT_OFFLINE, sizeof(config.Mqtt_LwtValue_Offline)); + strlcpy(config.Mqtt.Lwt.Topic, mqtt_lwt["topic"] | MQTT_LWT_TOPIC, sizeof(config.Mqtt.Lwt.Topic)); + strlcpy(config.Mqtt.Lwt.Value_Online, mqtt_lwt["value_online"] | MQTT_LWT_ONLINE, sizeof(config.Mqtt.Lwt.Value_Online)); + strlcpy(config.Mqtt.Lwt.Value_Offline, mqtt_lwt["value_offline"] | MQTT_LWT_OFFLINE, sizeof(config.Mqtt.Lwt.Value_Offline)); JsonObject mqtt_tls = mqtt["tls"]; - config.Mqtt_Tls = mqtt_tls["enabled"] | MQTT_TLS; - strlcpy(config.Mqtt_RootCaCert, mqtt_tls["root_ca_cert"] | MQTT_ROOT_CA_CERT, sizeof(config.Mqtt_RootCaCert)); - config.Mqtt_TlsCertLogin = mqtt_tls["certlogin"] | MQTT_TLSCERTLOGIN; - strlcpy(config.Mqtt_ClientCert, mqtt_tls["client_cert"] | MQTT_TLSCLIENTCERT, sizeof(config.Mqtt_ClientCert)); - strlcpy(config.Mqtt_ClientKey, mqtt_tls["client_key"] | MQTT_TLSCLIENTKEY, sizeof(config.Mqtt_ClientKey)); + config.Mqtt.Tls.Enabled = mqtt_tls["enabled"] | MQTT_TLS; + strlcpy(config.Mqtt.Tls.RootCaCert, mqtt_tls["root_ca_cert"] | MQTT_ROOT_CA_CERT, sizeof(config.Mqtt.Tls.RootCaCert)); + config.Mqtt.Tls.CertLogin = mqtt_tls["certlogin"] | MQTT_TLSCERTLOGIN; + strlcpy(config.Mqtt.Tls.ClientCert, mqtt_tls["client_cert"] | MQTT_TLSCLIENTCERT, sizeof(config.Mqtt.Tls.ClientCert)); + strlcpy(config.Mqtt.Tls.ClientKey, mqtt_tls["client_key"] | MQTT_TLSCLIENTKEY, sizeof(config.Mqtt.Tls.ClientKey)); JsonObject mqtt_hass = mqtt["hass"]; - config.Mqtt_Hass_Enabled = mqtt_hass["enabled"] | MQTT_HASS_ENABLED; - config.Mqtt_Hass_Retain = mqtt_hass["retain"] | MQTT_HASS_RETAIN; - config.Mqtt_Hass_Expire = mqtt_hass["expire"] | MQTT_HASS_EXPIRE; - config.Mqtt_Hass_IndividualPanels = mqtt_hass["individual_panels"] | MQTT_HASS_INDIVIDUALPANELS; - strlcpy(config.Mqtt_Hass_Topic, mqtt_hass["topic"] | MQTT_HASS_TOPIC, sizeof(config.Mqtt_Hass_Topic)); + config.Mqtt.Hass.Enabled = mqtt_hass["enabled"] | MQTT_HASS_ENABLED; + config.Mqtt.Hass.Retain = mqtt_hass["retain"] | MQTT_HASS_RETAIN; + config.Mqtt.Hass.Expire = mqtt_hass["expire"] | MQTT_HASS_EXPIRE; + config.Mqtt.Hass.IndividualPanels = mqtt_hass["individual_panels"] | MQTT_HASS_INDIVIDUALPANELS; + strlcpy(config.Mqtt.Hass.Topic, mqtt_hass["topic"] | MQTT_HASS_TOPIC, sizeof(config.Mqtt.Hass.Topic)); JsonObject dtu = doc["dtu"]; - config.Dtu_Serial = dtu["serial"] | DTU_SERIAL; - config.Dtu_PollInterval = dtu["poll_interval"] | DTU_POLL_INTERVAL; - config.Dtu_NrfPaLevel = dtu["nrf_pa_level"] | DTU_NRF_PA_LEVEL; - config.Dtu_CmtPaLevel = dtu["cmt_pa_level"] | DTU_CMT_PA_LEVEL; - config.Dtu_CmtFrequency = dtu["cmt_frequency"] | DTU_CMT_FREQUENCY; + config.Dtu.Serial = dtu["serial"] | DTU_SERIAL; + config.Dtu.PollInterval = dtu["poll_interval"] | DTU_POLL_INTERVAL; + config.Dtu.Nrf.PaLevel = dtu["nrf_pa_level"] | DTU_NRF_PA_LEVEL; + config.Dtu.Cmt.PaLevel = dtu["cmt_pa_level"] | DTU_CMT_PA_LEVEL; + config.Dtu.Cmt.Frequency = dtu["cmt_frequency"] | DTU_CMT_FREQUENCY; JsonObject security = doc["security"]; - strlcpy(config.Security_Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security_Password)); - config.Security_AllowReadonly = security["allow_readonly"] | SECURITY_ALLOW_READONLY; + strlcpy(config.Security.Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security.Password)); + config.Security.AllowReadonly = security["allow_readonly"] | SECURITY_ALLOW_READONLY; JsonObject device = doc["device"]; strlcpy(config.Dev_PinMapping, device["pinmapping"] | DEV_PINMAPPING, sizeof(config.Dev_PinMapping)); JsonObject display = device["display"]; - config.Display_PowerSafe = display["powersafe"] | DISPLAY_POWERSAFE; - config.Display_ScreenSaver = display["screensaver"] | DISPLAY_SCREENSAVER; - config.Display_Rotation = display["rotation"] | DISPLAY_ROTATION; - config.Display_Contrast = display["contrast"] | DISPLAY_CONTRAST; - config.Display_Language = display["language"] | DISPLAY_LANGUAGE; + config.Display.PowerSafe = display["powersafe"] | DISPLAY_POWERSAFE; + config.Display.ScreenSaver = display["screensaver"] | DISPLAY_SCREENSAVER; + config.Display.Rotation = display["rotation"] | DISPLAY_ROTATION; + config.Display.Contrast = display["contrast"] | DISPLAY_CONTRAST; + config.Display.Language = display["language"] | DISPLAY_LANGUAGE; JsonArray inverters = doc["inverters"]; for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { @@ -299,7 +299,7 @@ void ConfigurationClass::migrate() return; } - if (config.Cfg_Version < 0x00011700) { + if (config.Cfg.Version < 0x00011700) { JsonArray inverters = doc["inverters"]; for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { JsonObject inv = inverters[i].as(); @@ -311,19 +311,19 @@ void ConfigurationClass::migrate() } } - if (config.Cfg_Version < 0x00011800) { + if (config.Cfg.Version < 0x00011800) { JsonObject mqtt = doc["mqtt"]; - config.Mqtt_PublishInterval = mqtt["publish_invterval"]; + config.Mqtt.PublishInterval = mqtt["publish_invterval"]; } - if (config.Cfg_Version < 0x00011900) { + if (config.Cfg.Version < 0x00011900) { JsonObject dtu = doc["dtu"]; - config.Dtu_NrfPaLevel = dtu["pa_level"]; + config.Dtu.Nrf.PaLevel = dtu["pa_level"]; } f.close(); - config.Cfg_Version = CONFIG_VERSION; + config.Cfg.Version = CONFIG_VERSION; write(); read(); } diff --git a/src/InverterSettings.cpp b/src/InverterSettings.cpp index c5050bff..ce692bf1 100644 --- a/src/InverterSettings.cpp +++ b/src/InverterSettings.cpp @@ -46,19 +46,19 @@ void InverterSettingsClass::init() if (PinMapping.isValidCmt2300Config()) { Hoymiles.initCMT(pin.cmt_sdio, pin.cmt_clk, pin.cmt_cs, pin.cmt_fcs, pin.cmt_gpio2, pin.cmt_gpio3); MessageOutput.println(F(" Setting CMT target frequency... ")); - Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu_CmtFrequency); + Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency); } MessageOutput.println(" Setting radio PA level... "); - Hoymiles.getRadioNrf()->setPALevel((rf24_pa_dbm_e)config.Dtu_NrfPaLevel); - Hoymiles.getRadioCmt()->setPALevel(config.Dtu_CmtPaLevel); + Hoymiles.getRadioNrf()->setPALevel((rf24_pa_dbm_e)config.Dtu.Nrf.PaLevel); + Hoymiles.getRadioCmt()->setPALevel(config.Dtu.Cmt.PaLevel); MessageOutput.println(" Setting DTU serial... "); - Hoymiles.getRadioNrf()->setDtuSerial(config.Dtu_Serial); - Hoymiles.getRadioCmt()->setDtuSerial(config.Dtu_Serial); + Hoymiles.getRadioNrf()->setDtuSerial(config.Dtu.Serial); + Hoymiles.getRadioCmt()->setDtuSerial(config.Dtu.Serial); MessageOutput.println(" Setting poll interval... "); - Hoymiles.setPollInterval(config.Dtu_PollInterval); + Hoymiles.setPollInterval(config.Dtu.PollInterval); for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { if (config.Inverter[i].Serial > 0) { diff --git a/src/Led_Single.cpp b/src/Led_Single.cpp index 09658c85..24aabbd8 100644 --- a/src/Led_Single.cpp +++ b/src/Led_Single.cpp @@ -52,7 +52,7 @@ void LedSingleClass::loop() } struct tm timeinfo; - if (getLocalTime(&timeinfo, 5) && (!config.Mqtt_Enabled || (config.Mqtt_Enabled && MqttSettings.getConnected()))) { + if (getLocalTime(&timeinfo, 5) && (!config.Mqtt.Enabled || (config.Mqtt.Enabled && MqttSettings.getConnected()))) { _ledState[0] = LedState_t::On; } diff --git a/src/MqttHandleDtu.cpp b/src/MqttHandleDtu.cpp index ee5ad417..000b6c57 100644 --- a/src/MqttHandleDtu.cpp +++ b/src/MqttHandleDtu.cpp @@ -22,7 +22,7 @@ void MqttHandleDtuClass::loop() const CONFIG_T& config = Configuration.get(); - if (millis() - _lastPublish > (config.Mqtt_PublishInterval * 1000)) { + if (millis() - _lastPublish > (config.Mqtt.PublishInterval * 1000)) { MqttSettings.publish("dtu/uptime", String(millis() / 1000)); MqttSettings.publish("dtu/ip", NetworkSettings.localIP().toString()); MqttSettings.publish("dtu/hostname", NetworkSettings.getHostname()); diff --git a/src/MqttHandleHass.cpp b/src/MqttHandleHass.cpp index dd2f5608..e5842240 100644 --- a/src/MqttHandleHass.cpp +++ b/src/MqttHandleHass.cpp @@ -37,7 +37,7 @@ void MqttHandleHassClass::forceUpdate() void MqttHandleHassClass::publishConfig() { - if (!Configuration.get().Mqtt_Hass_Enabled) { + if (!Configuration.get().Mqtt.Hass.Enabled) { return; } @@ -69,7 +69,7 @@ void MqttHandleHassClass::publishConfig() for (auto& c : inv->Statistics()->getChannelsByType(t)) { for (uint8_t f = 0; f < DEVICE_CLS_ASSIGN_LIST_LEN; f++) { bool clear = false; - if (t == TYPE_DC && !config.Mqtt_Hass_IndividualPanels) { + if (t == TYPE_DC && !config.Mqtt.Hass.IndividualPanels) { clear = true; } publishField(inv, t, c, deviceFieldAssignment[f], clear); @@ -133,8 +133,8 @@ void MqttHandleHassClass::publishField(std::shared_ptr inv, Ch JsonObject deviceObj = root.createNestedObject("dev"); createDeviceInfo(deviceObj, inv); - if (Configuration.get().Mqtt_Hass_Expire) { - root["exp_aft"] = Hoymiles.getNumInverters() * max(Hoymiles.PollInterval(), Configuration.get().Mqtt_PublishInterval) * inv->getReachableThreshold(); + if (Configuration.get().Mqtt.Hass.Expire) { + root["exp_aft"] = Hoymiles.getNumInverters() * max(Hoymiles.PollInterval(), Configuration.get().Mqtt.PublishInterval) * inv->getReachableThreshold(); } if (devCls != 0) { root["dev_cla"] = devCls; @@ -266,7 +266,7 @@ void MqttHandleHassClass::createDeviceInfo(JsonObject& object, std::shared_ptr (config.Mqtt_PublishInterval * 1000)) { + if (millis() - _lastPublish > (config.Mqtt.PublishInterval * 1000)) { // Loop all inverters for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); @@ -166,7 +166,7 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro char* serial_str; char* subtopic; char* setting; - char* rest = &token_topic[strlen(config.Mqtt_Topic)]; + char* rest = &token_topic[strlen(config.Mqtt.Topic)]; serial_str = strtok_r(rest, "/", &rest); subtopic = strtok_r(rest, "/", &rest); diff --git a/src/MqttHandleInverterTotal.cpp b/src/MqttHandleInverterTotal.cpp index ac8e6a4e..5e18acd4 100644 --- a/src/MqttHandleInverterTotal.cpp +++ b/src/MqttHandleInverterTotal.cpp @@ -12,7 +12,7 @@ MqttHandleInverterTotalClass MqttHandleInverterTotal; void MqttHandleInverterTotalClass::init() { - _lastPublish.set(Configuration.get().Mqtt_PublishInterval * 1000); + _lastPublish.set(Configuration.get().Mqtt.PublishInterval * 1000); } void MqttHandleInverterTotalClass::loop() @@ -30,6 +30,6 @@ void MqttHandleInverterTotalClass::loop() MqttSettings.publish("dc/irradiation", String(Datastore.getTotalDcIrradiation(), 3)); MqttSettings.publish("dc/is_valid", String(Datastore.getIsAllEnabledReachable())); - _lastPublish.set(Configuration.get().Mqtt_PublishInterval * 1000); + _lastPublish.set(Configuration.get().Mqtt.PublishInterval * 1000); } } diff --git a/src/MqttSettings.cpp b/src/MqttSettings.cpp index eeb92bb4..2a4ba262 100644 --- a/src/MqttSettings.cpp +++ b/src/MqttSettings.cpp @@ -30,7 +30,7 @@ void MqttSettingsClass::onMqttConnect(bool sessionPresent) { MessageOutput.println("Connected to MQTT."); const CONFIG_T& config = Configuration.get(); - publish(config.Mqtt_LwtTopic, config.Mqtt_LwtValue_Online); + publish(config.Mqtt.Lwt.Topic, config.Mqtt.Lwt.Value_Online); std::lock_guard lock(_clientLock); if (mqttClient != nullptr) { @@ -99,7 +99,7 @@ void MqttSettingsClass::onMqttMessage(const espMqttClientTypes::MessagePropertie void MqttSettingsClass::performConnect() { - if (NetworkSettings.isConnected() && Configuration.get().Mqtt_Enabled) { + if (NetworkSettings.isConnected() && Configuration.get().Mqtt.Enabled) { using std::placeholders::_1; using std::placeholders::_2; using std::placeholders::_3; @@ -114,29 +114,29 @@ void MqttSettingsClass::performConnect() MessageOutput.println("Connecting to MQTT..."); const CONFIG_T& config = Configuration.get(); - willTopic = getPrefix() + config.Mqtt_LwtTopic; + willTopic = getPrefix() + config.Mqtt.Lwt.Topic; clientId = NetworkSettings.getApName(); - if (config.Mqtt_Tls) { - static_cast(mqttClient)->setCACert(config.Mqtt_RootCaCert); - static_cast(mqttClient)->setServer(config.Mqtt_Hostname, config.Mqtt_Port); - if (config.Mqtt_TlsCertLogin) { - static_cast(mqttClient)->setCertificate(config.Mqtt_ClientCert); - static_cast(mqttClient)->setPrivateKey(config.Mqtt_ClientKey); + if (config.Mqtt.Tls.Enabled) { + static_cast(mqttClient)->setCACert(config.Mqtt.Tls.RootCaCert); + static_cast(mqttClient)->setServer(config.Mqtt.Hostname, config.Mqtt.Port); + if (config.Mqtt.Tls.CertLogin) { + static_cast(mqttClient)->setCertificate(config.Mqtt.Tls.ClientCert); + static_cast(mqttClient)->setPrivateKey(config.Mqtt.Tls.ClientKey); } else { - static_cast(mqttClient)->setCredentials(config.Mqtt_Username, config.Mqtt_Password); + static_cast(mqttClient)->setCredentials(config.Mqtt.Username, config.Mqtt.Password); } - static_cast(mqttClient)->setWill(willTopic.c_str(), 2, config.Mqtt_Retain, config.Mqtt_LwtValue_Offline); + static_cast(mqttClient)->setWill(willTopic.c_str(), 2, config.Mqtt.Retain, config.Mqtt.Lwt.Value_Offline); static_cast(mqttClient)->setClientId(clientId.c_str()); - static_cast(mqttClient)->setCleanSession(config.Mqtt_CleanSession); + static_cast(mqttClient)->setCleanSession(config.Mqtt.CleanSession); static_cast(mqttClient)->onConnect(std::bind(&MqttSettingsClass::onMqttConnect, this, _1)); static_cast(mqttClient)->onDisconnect(std::bind(&MqttSettingsClass::onMqttDisconnect, this, _1)); static_cast(mqttClient)->onMessage(std::bind(&MqttSettingsClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); } else { - static_cast(mqttClient)->setServer(config.Mqtt_Hostname, config.Mqtt_Port); - static_cast(mqttClient)->setCredentials(config.Mqtt_Username, config.Mqtt_Password); - static_cast(mqttClient)->setWill(willTopic.c_str(), 2, config.Mqtt_Retain, config.Mqtt_LwtValue_Offline); + static_cast(mqttClient)->setServer(config.Mqtt.Hostname, config.Mqtt.Port); + static_cast(mqttClient)->setCredentials(config.Mqtt.Username, config.Mqtt.Password); + static_cast(mqttClient)->setWill(willTopic.c_str(), 2, config.Mqtt.Retain, config.Mqtt.Lwt.Value_Offline); static_cast(mqttClient)->setClientId(clientId.c_str()); - static_cast(mqttClient)->setCleanSession(config.Mqtt_CleanSession); + static_cast(mqttClient)->setCleanSession(config.Mqtt.CleanSession); static_cast(mqttClient)->onConnect(std::bind(&MqttSettingsClass::onMqttConnect, this, _1)); static_cast(mqttClient)->onDisconnect(std::bind(&MqttSettingsClass::onMqttDisconnect, this, _1)); static_cast(mqttClient)->onMessage(std::bind(&MqttSettingsClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); @@ -148,7 +148,7 @@ void MqttSettingsClass::performConnect() void MqttSettingsClass::performDisconnect() { const CONFIG_T& config = Configuration.get(); - publish(config.Mqtt_LwtTopic, config.Mqtt_LwtValue_Offline); + publish(config.Mqtt.Lwt.Topic, config.Mqtt.Lwt.Value_Offline); std::lock_guard lock(_clientLock); if (mqttClient == nullptr) { return; @@ -177,7 +177,7 @@ bool MqttSettingsClass::getConnected() String MqttSettingsClass::getPrefix() { - return Configuration.get().Mqtt_Topic; + return Configuration.get().Mqtt.Topic; } void MqttSettingsClass::publish(const String& subtopic, const String& payload) @@ -188,7 +188,7 @@ void MqttSettingsClass::publish(const String& subtopic, const String& payload) String value = payload; value.trim(); - publishGeneric(topic, value, Configuration.get().Mqtt_Retain, 0); + publishGeneric(topic, value, Configuration.get().Mqtt.Retain, 0); } void MqttSettingsClass::publishGeneric(const String& topic, const String& payload, bool retain, uint8_t qos) @@ -216,7 +216,7 @@ void MqttSettingsClass::createMqttClientObject() mqttClient = nullptr; } const CONFIG_T& config = Configuration.get(); - if (config.Mqtt_Tls) { + if (config.Mqtt.Tls.Enabled) { mqttClient = static_cast(new espMqttClientSecure); } else { mqttClient = static_cast(new espMqttClient); diff --git a/src/NetworkSettings.cpp b/src/NetworkSettings.cpp index 7e725ff1..5d43ba5c 100644 --- a/src/NetworkSettings.cpp +++ b/src/NetworkSettings.cpp @@ -113,7 +113,7 @@ void NetworkSettingsClass::raiseEvent(network_event event) void NetworkSettingsClass::handleMDNS() { - bool mdnsEnabled = Configuration.get().Mdns_Enabled; + bool mdnsEnabled = Configuration.get().Mdns.Enabled; if (lastMdnsEnabled == mdnsEnabled) { return; @@ -146,7 +146,7 @@ void NetworkSettingsClass::setupMode() WiFi.mode(WIFI_AP_STA); String ssidString = getApName(); WiFi.softAPConfig(apIp, apIp, apNetmask); - WiFi.softAP((const char*)ssidString.c_str(), Configuration.get().Security_Password); + WiFi.softAP((const char*)ssidString.c_str(), Configuration.get().Security.Password); dnsServer->setErrorReplyCode(DNSReplyCode::NoError); dnsServer->start(DNS_PORT, "*", WiFi.softAPIP()); dnsServerStatus = true; @@ -170,7 +170,7 @@ void NetworkSettingsClass::enableAdminMode() { adminEnabled = true; adminTimeoutCounter = 0; - adminTimeoutCounterMax = Configuration.get().WiFi_ApTimeout * 60; + adminTimeoutCounterMax = Configuration.get().WiFi.ApTimeout * 60; setupMode(); } @@ -255,15 +255,15 @@ void NetworkSettingsClass::loop() void NetworkSettingsClass::applyConfig() { setHostname(); - if (!strcmp(Configuration.get().WiFi_Ssid, "")) { + if (!strcmp(Configuration.get().WiFi.Ssid, "")) { return; } MessageOutput.print("Configuring WiFi STA using "); - if (strcmp(WiFi.SSID().c_str(), Configuration.get().WiFi_Ssid) || strcmp(WiFi.psk().c_str(), Configuration.get().WiFi_Password)) { + if (strcmp(WiFi.SSID().c_str(), Configuration.get().WiFi.Ssid) || strcmp(WiFi.psk().c_str(), Configuration.get().WiFi.Password)) { MessageOutput.print("new credentials... "); WiFi.begin( - Configuration.get().WiFi_Ssid, - Configuration.get().WiFi_Password); + Configuration.get().WiFi.Ssid, + Configuration.get().WiFi.Password); } else { MessageOutput.print("existing credentials... "); WiFi.begin(); @@ -298,33 +298,33 @@ void NetworkSettingsClass::setHostname() void NetworkSettingsClass::setStaticIp() { if (_networkMode == network_mode::WiFi) { - if (Configuration.get().WiFi_Dhcp) { + if (Configuration.get().WiFi.Dhcp) { MessageOutput.print("Configuring WiFi STA DHCP IP... "); WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); MessageOutput.println("done"); } else { MessageOutput.print("Configuring WiFi STA static IP... "); WiFi.config( - IPAddress(Configuration.get().WiFi_Ip), - IPAddress(Configuration.get().WiFi_Gateway), - IPAddress(Configuration.get().WiFi_Netmask), - IPAddress(Configuration.get().WiFi_Dns1), - IPAddress(Configuration.get().WiFi_Dns2)); + IPAddress(Configuration.get().WiFi.Ip), + IPAddress(Configuration.get().WiFi.Gateway), + IPAddress(Configuration.get().WiFi.Netmask), + IPAddress(Configuration.get().WiFi.Dns1), + IPAddress(Configuration.get().WiFi.Dns2)); MessageOutput.println("done"); } } else if (_networkMode == network_mode::Ethernet) { - if (Configuration.get().WiFi_Dhcp) { + if (Configuration.get().WiFi.Ssid) { MessageOutput.print("Configuring Ethernet DHCP IP... "); ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); MessageOutput.println("done"); } else { MessageOutput.print("Configuring Ethernet static IP... "); ETH.config( - IPAddress(Configuration.get().WiFi_Ip), - IPAddress(Configuration.get().WiFi_Gateway), - IPAddress(Configuration.get().WiFi_Netmask), - IPAddress(Configuration.get().WiFi_Dns1), - IPAddress(Configuration.get().WiFi_Dns2)); + IPAddress(Configuration.get().WiFi.Ip), + IPAddress(Configuration.get().WiFi.Gateway), + IPAddress(Configuration.get().WiFi.Netmask), + IPAddress(Configuration.get().WiFi.Dns1), + IPAddress(Configuration.get().WiFi.Dns2)); MessageOutput.println("done"); } } @@ -408,7 +408,7 @@ String NetworkSettingsClass::getHostname() uint8_t pos = 0; uint32_t chipId = Utils::getChipId(); - snprintf(preparedHostname, WIFI_MAX_HOSTNAME_STRLEN + 1, config.WiFi_Hostname, chipId); + snprintf(preparedHostname, WIFI_MAX_HOSTNAME_STRLEN + 1, config.WiFi.Hostname, chipId); const char* pC = preparedHostname; while (*pC && pos < WIFI_MAX_HOSTNAME_STRLEN) { // while !null and not over length diff --git a/src/NtpSettings.cpp b/src/NtpSettings.cpp index ce043384..b89904f3 100644 --- a/src/NtpSettings.cpp +++ b/src/NtpSettings.cpp @@ -19,12 +19,12 @@ void NtpSettingsClass::init() void NtpSettingsClass::setServer() { - configTime(0, 0, Configuration.get().Ntp_Server); + configTime(0, 0, Configuration.get().Ntp.Server); } void NtpSettingsClass::setTimezone() { - setenv("TZ", Configuration.get().Ntp_Timezone, 1); + setenv("TZ", Configuration.get().Ntp.Timezone, 1); tzset(); } diff --git a/src/SunPosition.cpp b/src/SunPosition.cpp index ccbaeb6b..48e30820 100644 --- a/src/SunPosition.cpp +++ b/src/SunPosition.cpp @@ -80,7 +80,7 @@ void SunPositionClass::updateSunData() CONFIG_T const& config = Configuration.get(); double sunset_type; - switch (config.Ntp_SunsetType) { + switch (config.Ntp.SunsetType) { case 0: sunset_type = SunSet::SUNSET_OFFICIAL; break; @@ -98,7 +98,7 @@ void SunPositionClass::updateSunData() int offset = Utils::getTimezoneOffset() / 3600; SunSet sun; - sun.setPosition(config.Ntp_Latitude, config.Ntp_Longitude, offset); + sun.setPosition(config.Ntp.Latitude, config.Ntp.Longitude, offset); sun.setCurrentDate(1900 + timeinfo.tm_year, timeinfo.tm_mon + 1, timeinfo.tm_mday); double sunriseRaw = sun.calcCustomSunrise(sunset_type); diff --git a/src/WebApi.cpp b/src/WebApi.cpp index 511a3845..717c3525 100644 --- a/src/WebApi.cpp +++ b/src/WebApi.cpp @@ -67,7 +67,7 @@ void WebApiClass::loop() bool WebApiClass::checkCredentials(AsyncWebServerRequest* request) { CONFIG_T& config = Configuration.get(); - if (request->authenticate(AUTH_USERNAME, config.Security_Password)) { + if (request->authenticate(AUTH_USERNAME, config.Security.Password)) { return true; } @@ -85,7 +85,7 @@ bool WebApiClass::checkCredentials(AsyncWebServerRequest* request) bool WebApiClass::checkCredentialsReadonly(AsyncWebServerRequest* request) { CONFIG_T& config = Configuration.get(); - if (config.Security_AllowReadonly) { + if (config.Security.AllowReadonly) { return true; } else { return checkCredentials(request); diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index 507a7e16..4dfbd146 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -77,11 +77,11 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) ledPinObj["led1"] = pin.led[1]; JsonObject display = root.createNestedObject("display"); - display["rotation"] = config.Display_Rotation; - display["power_safe"] = config.Display_PowerSafe; - display["screensaver"] = config.Display_ScreenSaver; - display["contrast"] = config.Display_Contrast; - display["language"] = config.Display_Language; + display["rotation"] = config.Display.Rotation; + display["power_safe"] = config.Display.PowerSafe; + display["screensaver"] = config.Display.ScreenSaver; + display["contrast"] = config.Display.Contrast; + display["language"] = config.Display.Language; response->setLength(); request->send(response); @@ -148,17 +148,17 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) bool performRestart = root["curPin"]["name"].as() != config.Dev_PinMapping; strlcpy(config.Dev_PinMapping, root["curPin"]["name"].as().c_str(), sizeof(config.Dev_PinMapping)); - config.Display_Rotation = root["display"]["rotation"].as(); - config.Display_PowerSafe = root["display"]["power_safe"].as(); - config.Display_ScreenSaver = root["display"]["screensaver"].as(); - config.Display_Contrast = root["display"]["contrast"].as(); - config.Display_Language = root["display"]["language"].as(); + config.Display.Rotation = root["display"]["rotation"].as(); + config.Display.PowerSafe = root["display"]["power_safe"].as(); + config.Display.ScreenSaver = root["display"]["screensaver"].as(); + config.Display.Contrast = root["display"]["contrast"].as(); + config.Display.Language = root["display"]["language"].as(); - Display.setOrientation(config.Display_Rotation); - Display.enablePowerSafe = config.Display_PowerSafe; - Display.enableScreensaver = config.Display_ScreenSaver; - Display.setContrast(config.Display_Contrast); - Display.setLanguage(config.Display_Language); + Display.setOrientation(config.Display.Rotation); + Display.enablePowerSafe = config.Display.PowerSafe; + Display.enableScreensaver = config.Display.ScreenSaver; + Display.setContrast(config.Display.Contrast); + Display.setLanguage(config.Display.Language); Configuration.write(); diff --git a/src/WebApi_dtu.cpp b/src/WebApi_dtu.cpp index 1ae7c408..ea130b1f 100644 --- a/src/WebApi_dtu.cpp +++ b/src/WebApi_dtu.cpp @@ -36,15 +36,15 @@ void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request) // DTU Serial is read as HEX char buffer[sizeof(uint64_t) * 8 + 1]; snprintf(buffer, sizeof(buffer), "%0x%08x", - ((uint32_t)((config.Dtu_Serial >> 32) & 0xFFFFFFFF)), - ((uint32_t)(config.Dtu_Serial & 0xFFFFFFFF))); + ((uint32_t)((config.Dtu.Serial >> 32) & 0xFFFFFFFF)), + ((uint32_t)(config.Dtu.Serial & 0xFFFFFFFF))); root["serial"] = buffer; - root["pollinterval"] = config.Dtu_PollInterval; + root["pollinterval"] = config.Dtu.PollInterval; root["nrf_enabled"] = Hoymiles.getRadioNrf()->isInitialized(); - root["nrf_palevel"] = config.Dtu_NrfPaLevel; + root["nrf_palevel"] = config.Dtu.Nrf.PaLevel; root["cmt_enabled"] = Hoymiles.getRadioCmt()->isInitialized(); - root["cmt_palevel"] = config.Dtu_CmtPaLevel; - root["cmt_frequency"] = config.Dtu_CmtFrequency; + root["cmt_palevel"] = config.Dtu.Cmt.PaLevel; + root["cmt_frequency"] = config.Dtu.Cmt.Frequency; response->setLength(); request->send(response); @@ -149,11 +149,11 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) CONFIG_T& config = Configuration.get(); // Interpret the string as a hex value and convert it to uint64_t - config.Dtu_Serial = strtoll(root["serial"].as().c_str(), NULL, 16); - config.Dtu_PollInterval = root["pollinterval"].as(); - config.Dtu_NrfPaLevel = root["nrf_palevel"].as(); - config.Dtu_CmtPaLevel = root["cmt_palevel"].as(); - config.Dtu_CmtFrequency = root["cmt_frequency"].as(); + config.Dtu.Serial = strtoll(root["serial"].as().c_str(), NULL, 16); + config.Dtu.PollInterval = root["pollinterval"].as(); + config.Dtu.Nrf.PaLevel = root["nrf_palevel"].as(); + config.Dtu.Cmt.PaLevel = root["cmt_palevel"].as(); + config.Dtu.Cmt.Frequency = root["cmt_frequency"].as(); Configuration.write(); retMsg["type"] = "success"; @@ -163,10 +163,10 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) response->setLength(); request->send(response); - Hoymiles.getRadioNrf()->setPALevel((rf24_pa_dbm_e)config.Dtu_NrfPaLevel); - Hoymiles.getRadioCmt()->setPALevel(config.Dtu_CmtPaLevel); - Hoymiles.getRadioNrf()->setDtuSerial(config.Dtu_Serial); - Hoymiles.getRadioCmt()->setDtuSerial(config.Dtu_Serial); - Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu_CmtFrequency); - Hoymiles.setPollInterval(config.Dtu_PollInterval); + Hoymiles.getRadioNrf()->setPALevel((rf24_pa_dbm_e)config.Dtu.Nrf.PaLevel); + Hoymiles.getRadioCmt()->setPALevel(config.Dtu.Cmt.PaLevel); + Hoymiles.getRadioNrf()->setDtuSerial(config.Dtu.Serial); + Hoymiles.getRadioCmt()->setDtuSerial(config.Dtu.Serial); + Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency); + Hoymiles.setPollInterval(config.Dtu.PollInterval); } \ No newline at end of file diff --git a/src/WebApi_mqtt.cpp b/src/WebApi_mqtt.cpp index aedcd51a..d31b1e91 100644 --- a/src/WebApi_mqtt.cpp +++ b/src/WebApi_mqtt.cpp @@ -36,25 +36,25 @@ void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request) JsonObject root = response->getRoot(); const CONFIG_T& config = Configuration.get(); - root["mqtt_enabled"] = config.Mqtt_Enabled; - root["mqtt_hostname"] = config.Mqtt_Hostname; - root["mqtt_port"] = config.Mqtt_Port; - root["mqtt_username"] = config.Mqtt_Username; - root["mqtt_topic"] = config.Mqtt_Topic; + root["mqtt_enabled"] = config.Mqtt.Enabled; + root["mqtt_hostname"] = config.Mqtt.Hostname; + root["mqtt_port"] = config.Mqtt.Port; + root["mqtt_username"] = config.Mqtt.Username; + root["mqtt_topic"] = config.Mqtt.Topic; root["mqtt_connected"] = MqttSettings.getConnected(); - root["mqtt_retain"] = config.Mqtt_Retain; - root["mqtt_tls"] = config.Mqtt_Tls; - root["mqtt_root_ca_cert_info"] = getTlsCertInfo(config.Mqtt_RootCaCert); - root["mqtt_tls_cert_login"] = config.Mqtt_TlsCertLogin; - root["mqtt_client_cert_info"] = getTlsCertInfo(config.Mqtt_ClientCert); - root["mqtt_lwt_topic"] = String(config.Mqtt_Topic) + config.Mqtt_LwtTopic; - root["mqtt_publish_interval"] = config.Mqtt_PublishInterval; - root["mqtt_clean_session"] = config.Mqtt_CleanSession; - root["mqtt_hass_enabled"] = config.Mqtt_Hass_Enabled; - root["mqtt_hass_expire"] = config.Mqtt_Hass_Expire; - root["mqtt_hass_retain"] = config.Mqtt_Hass_Retain; - root["mqtt_hass_topic"] = config.Mqtt_Hass_Topic; - root["mqtt_hass_individualpanels"] = config.Mqtt_Hass_IndividualPanels; + root["mqtt_retain"] = config.Mqtt.Retain; + root["mqtt_tls"] = config.Mqtt.Tls.Enabled; + root["mqtt_root_ca_cert_info"] = getTlsCertInfo(config.Mqtt.Tls.RootCaCert); + root["mqtt_tls_cert_login"] = config.Mqtt.Tls.CertLogin; + root["mqtt_client_cert_info"] = getTlsCertInfo(config.Mqtt.Tls.ClientCert); + root["mqtt_lwt_topic"] = String(config.Mqtt.Topic) + config.Mqtt.Lwt.Topic; + root["mqtt_publish_interval"] = config.Mqtt.PublishInterval; + root["mqtt_clean_session"] = config.Mqtt.CleanSession; + root["mqtt_hass_enabled"] = config.Mqtt.Hass.Enabled; + root["mqtt_hass_expire"] = config.Mqtt.Hass.Expire; + root["mqtt_hass_retain"] = config.Mqtt.Hass.Retain; + root["mqtt_hass_topic"] = config.Mqtt.Hass.Topic; + root["mqtt_hass_individualpanels"] = config.Mqtt.Hass.IndividualPanels; response->setLength(); request->send(response); @@ -70,28 +70,28 @@ void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request) JsonObject root = response->getRoot(); const CONFIG_T& config = Configuration.get(); - root["mqtt_enabled"] = config.Mqtt_Enabled; - root["mqtt_hostname"] = config.Mqtt_Hostname; - root["mqtt_port"] = config.Mqtt_Port; - root["mqtt_username"] = config.Mqtt_Username; - root["mqtt_password"] = config.Mqtt_Password; - root["mqtt_topic"] = config.Mqtt_Topic; - root["mqtt_retain"] = config.Mqtt_Retain; - root["mqtt_tls"] = config.Mqtt_Tls; - root["mqtt_root_ca_cert"] = config.Mqtt_RootCaCert; - root["mqtt_tls_cert_login"] = config.Mqtt_TlsCertLogin; - root["mqtt_client_cert"] = config.Mqtt_ClientCert; - root["mqtt_client_key"] = config.Mqtt_ClientKey; - root["mqtt_lwt_topic"] = config.Mqtt_LwtTopic; - root["mqtt_lwt_online"] = config.Mqtt_LwtValue_Online; - root["mqtt_lwt_offline"] = config.Mqtt_LwtValue_Offline; - root["mqtt_publish_interval"] = config.Mqtt_PublishInterval; - root["mqtt_clean_session"] = config.Mqtt_CleanSession; - root["mqtt_hass_enabled"] = config.Mqtt_Hass_Enabled; - root["mqtt_hass_expire"] = config.Mqtt_Hass_Expire; - root["mqtt_hass_retain"] = config.Mqtt_Hass_Retain; - root["mqtt_hass_topic"] = config.Mqtt_Hass_Topic; - root["mqtt_hass_individualpanels"] = config.Mqtt_Hass_IndividualPanels; + root["mqtt_enabled"] = config.Mqtt.Enabled; + root["mqtt_hostname"] = config.Mqtt.Hostname; + root["mqtt_port"] = config.Mqtt.Port; + root["mqtt_username"] = config.Mqtt.Username; + root["mqtt_password"] = config.Mqtt.Password; + root["mqtt_topic"] = config.Mqtt.Topic; + root["mqtt_retain"] = config.Mqtt.Retain; + root["mqtt_tls"] = config.Mqtt.Tls.Enabled; + root["mqtt_root_ca_cert"] = config.Mqtt.Tls.RootCaCert; + root["mqtt_tls_cert_login"] = config.Mqtt.Tls.CertLogin; + root["mqtt_client_cert"] = config.Mqtt.Tls.ClientCert; + root["mqtt_client_key"] = config.Mqtt.Tls.ClientKey; + root["mqtt_lwt_topic"] = config.Mqtt.Lwt.Topic; + root["mqtt_lwt_online"] = config.Mqtt.CleanSession; + root["mqtt_lwt_offline"] = config.Mqtt.Lwt.Value_Offline; + root["mqtt_publish_interval"] = config.Mqtt.PublishInterval; + root["mqtt_clean_session"] = config.Mqtt.CleanSession; + root["mqtt_hass_enabled"] = config.Mqtt.Hass.Enabled; + root["mqtt_hass_expire"] = config.Mqtt.Hass.Expire; + root["mqtt_hass_retain"] = config.Mqtt.Hass.Retain; + root["mqtt_hass_topic"] = config.Mqtt.Hass.Topic; + root["mqtt_hass_individualpanels"] = config.Mqtt.Hass.IndividualPanels; response->setLength(); request->send(response); @@ -300,28 +300,28 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) } CONFIG_T& config = Configuration.get(); - config.Mqtt_Enabled = root["mqtt_enabled"].as(); - config.Mqtt_Retain = root["mqtt_retain"].as(); - config.Mqtt_Tls = root["mqtt_tls"].as(); - strlcpy(config.Mqtt_RootCaCert, root["mqtt_root_ca_cert"].as().c_str(), sizeof(config.Mqtt_RootCaCert)); - config.Mqtt_TlsCertLogin = root["mqtt_tls_cert_login"].as(); - strlcpy(config.Mqtt_ClientCert, root["mqtt_client_cert"].as().c_str(), sizeof(config.Mqtt_ClientCert)); - strlcpy(config.Mqtt_ClientKey, root["mqtt_client_key"].as().c_str(), sizeof(config.Mqtt_ClientKey)); - config.Mqtt_Port = root["mqtt_port"].as(); - strlcpy(config.Mqtt_Hostname, root["mqtt_hostname"].as().c_str(), sizeof(config.Mqtt_Hostname)); - strlcpy(config.Mqtt_Username, root["mqtt_username"].as().c_str(), sizeof(config.Mqtt_Username)); - strlcpy(config.Mqtt_Password, root["mqtt_password"].as().c_str(), sizeof(config.Mqtt_Password)); - strlcpy(config.Mqtt_Topic, root["mqtt_topic"].as().c_str(), sizeof(config.Mqtt_Topic)); - strlcpy(config.Mqtt_LwtTopic, root["mqtt_lwt_topic"].as().c_str(), sizeof(config.Mqtt_LwtTopic)); - strlcpy(config.Mqtt_LwtValue_Online, root["mqtt_lwt_online"].as().c_str(), sizeof(config.Mqtt_LwtValue_Online)); - strlcpy(config.Mqtt_LwtValue_Offline, root["mqtt_lwt_offline"].as().c_str(), sizeof(config.Mqtt_LwtValue_Offline)); - config.Mqtt_PublishInterval = root["mqtt_publish_interval"].as(); - config.Mqtt_CleanSession = root["mqtt_clean_session"].as(); - config.Mqtt_Hass_Enabled = root["mqtt_hass_enabled"].as(); - config.Mqtt_Hass_Expire = root["mqtt_hass_expire"].as(); - config.Mqtt_Hass_Retain = root["mqtt_hass_retain"].as(); - config.Mqtt_Hass_IndividualPanels = root["mqtt_hass_individualpanels"].as(); - strlcpy(config.Mqtt_Hass_Topic, root["mqtt_hass_topic"].as().c_str(), sizeof(config.Mqtt_Hass_Topic)); + config.Mqtt.Enabled = root["mqtt_enabled"].as(); + config.Mqtt.Retain = root["mqtt_retain"].as(); + config.Mqtt.Tls.Enabled = root["mqtt_tls"].as(); + strlcpy(config.Mqtt.Tls.RootCaCert, root["mqtt_root_ca_cert"].as().c_str(), sizeof(config.Mqtt.Tls.RootCaCert)); + config.Mqtt.Tls.CertLogin = root["mqtt_tls_cert_login"].as(); + strlcpy(config.Mqtt.Tls.ClientCert, root["mqtt_client_cert"].as().c_str(), sizeof(config.Mqtt.Tls.ClientCert)); + strlcpy(config.Mqtt.Tls.ClientKey, root["mqtt_client_key"].as().c_str(), sizeof(config.Mqtt.Tls.ClientKey)); + config.Mqtt.Port = root["mqtt_port"].as(); + strlcpy(config.Mqtt.Hostname, root["mqtt_hostname"].as().c_str(), sizeof(config.Mqtt.Hostname)); + strlcpy(config.Mqtt.Username, root["mqtt_username"].as().c_str(), sizeof(config.Mqtt.Username)); + strlcpy(config.Mqtt.Password, root["mqtt_password"].as().c_str(), sizeof(config.Mqtt.Password)); + strlcpy(config.Mqtt.Topic, root["mqtt_topic"].as().c_str(), sizeof(config.Mqtt.Topic)); + strlcpy(config.Mqtt.Lwt.Topic, root["mqtt_lwt_topic"].as().c_str(), sizeof(config.Mqtt.Lwt.Topic)); + strlcpy(config.Mqtt.Lwt.Value_Online, root["mqtt_lwt_online"].as().c_str(), sizeof(config.Mqtt.Lwt.Value_Online)); + strlcpy(config.Mqtt.Lwt.Value_Offline, root["mqtt_lwt_offline"].as().c_str(), sizeof(config.Mqtt.Lwt.Value_Offline)); + config.Mqtt.PublishInterval = root["mqtt_publish_interval"].as(); + config.Mqtt.CleanSession = root["mqtt_clean_session"].as(); + config.Mqtt.Hass.Enabled = root["mqtt_hass_enabled"].as(); + config.Mqtt.Hass.Expire = root["mqtt_hass_expire"].as(); + config.Mqtt.Hass.Retain = root["mqtt_hass_retain"].as(); + config.Mqtt.Hass.IndividualPanels = root["mqtt_hass_individualpanels"].as(); + strlcpy(config.Mqtt.Hass.Topic, root["mqtt_hass_topic"].as().c_str(), sizeof(config.Mqtt.Hass.Topic)); Configuration.write(); retMsg["type"] = "success"; diff --git a/src/WebApi_network.cpp b/src/WebApi_network.cpp index 849c5f8a..3f2eb0dc 100644 --- a/src/WebApi_network.cpp +++ b/src/WebApi_network.cpp @@ -66,17 +66,17 @@ void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request) JsonObject root = response->getRoot(); const CONFIG_T& config = Configuration.get(); - root["hostname"] = config.WiFi_Hostname; - root["dhcp"] = config.WiFi_Dhcp; - root["ipaddress"] = IPAddress(config.WiFi_Ip).toString(); - root["netmask"] = IPAddress(config.WiFi_Netmask).toString(); - root["gateway"] = IPAddress(config.WiFi_Gateway).toString(); - root["dns1"] = IPAddress(config.WiFi_Dns1).toString(); - root["dns2"] = IPAddress(config.WiFi_Dns2).toString(); - root["ssid"] = config.WiFi_Ssid; - root["password"] = config.WiFi_Password; - root["aptimeout"] = config.WiFi_ApTimeout; - root["mdnsenabled"] = config.Mdns_Enabled; + root["hostname"] = config.WiFi.Hostname; + root["dhcp"] = config.WiFi.Dhcp; + root["ipaddress"] = IPAddress(config.WiFi.Ip).toString(); + root["netmask"] = IPAddress(config.WiFi.Netmask).toString(); + root["gateway"] = IPAddress(config.WiFi.Gateway).toString(); + root["dns1"] = IPAddress(config.WiFi.Dns1).toString(); + root["dns2"] = IPAddress(config.WiFi.Dns2).toString(); + root["ssid"] = config.WiFi.Ssid; + root["password"] = config.WiFi.Password; + root["aptimeout"] = config.WiFi.ApTimeout; + root["mdnsenabled"] = config.Mdns.Enabled; response->setLength(); request->send(response); @@ -208,36 +208,36 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) } CONFIG_T& config = Configuration.get(); - config.WiFi_Ip[0] = ipaddress[0]; - config.WiFi_Ip[1] = ipaddress[1]; - config.WiFi_Ip[2] = ipaddress[2]; - config.WiFi_Ip[3] = ipaddress[3]; - config.WiFi_Netmask[0] = netmask[0]; - config.WiFi_Netmask[1] = netmask[1]; - config.WiFi_Netmask[2] = netmask[2]; - config.WiFi_Netmask[3] = netmask[3]; - config.WiFi_Gateway[0] = gateway[0]; - config.WiFi_Gateway[1] = gateway[1]; - config.WiFi_Gateway[2] = gateway[2]; - config.WiFi_Gateway[3] = gateway[3]; - config.WiFi_Dns1[0] = dns1[0]; - config.WiFi_Dns1[1] = dns1[1]; - config.WiFi_Dns1[2] = dns1[2]; - config.WiFi_Dns1[3] = dns1[3]; - config.WiFi_Dns2[0] = dns2[0]; - config.WiFi_Dns2[1] = dns2[1]; - config.WiFi_Dns2[2] = dns2[2]; - config.WiFi_Dns2[3] = dns2[3]; - strlcpy(config.WiFi_Ssid, root["ssid"].as().c_str(), sizeof(config.WiFi_Ssid)); - strlcpy(config.WiFi_Password, root["password"].as().c_str(), sizeof(config.WiFi_Password)); - strlcpy(config.WiFi_Hostname, root["hostname"].as().c_str(), sizeof(config.WiFi_Hostname)); + config.WiFi.Ip[0] = ipaddress[0]; + config.WiFi.Ip[1] = ipaddress[1]; + config.WiFi.Ip[2] = ipaddress[2]; + config.WiFi.Ip[3] = ipaddress[3]; + config.WiFi.Netmask[0] = netmask[0]; + config.WiFi.Netmask[1] = netmask[1]; + config.WiFi.Netmask[2] = netmask[2]; + config.WiFi.Netmask[3] = netmask[3]; + config.WiFi.Gateway[0] = gateway[0]; + config.WiFi.Gateway[1] = gateway[1]; + config.WiFi.Gateway[2] = gateway[2]; + config.WiFi.Gateway[3] = gateway[3]; + config.WiFi.Dns1[0] = dns1[0]; + config.WiFi.Dns1[1] = dns1[1]; + config.WiFi.Dns1[2] = dns1[2]; + config.WiFi.Dns1[3] = dns1[3]; + config.WiFi.Dns2[0] = dns2[0]; + config.WiFi.Dns2[1] = dns2[1]; + config.WiFi.Dns2[2] = dns2[2]; + config.WiFi.Dns2[3] = dns2[3]; + strlcpy(config.WiFi.Ssid, root["ssid"].as().c_str(), sizeof(config.WiFi.Ssid)); + strlcpy(config.WiFi.Password, root["password"].as().c_str(), sizeof(config.WiFi.Password)); + strlcpy(config.WiFi.Hostname, root["hostname"].as().c_str(), sizeof(config.WiFi.Hostname)); if (root["dhcp"].as()) { - config.WiFi_Dhcp = true; + config.WiFi.Dhcp = true; } else { - config.WiFi_Dhcp = false; + config.WiFi.Dhcp = false; } - config.WiFi_ApTimeout = root["aptimeout"].as(); - config.Mdns_Enabled = root["mdnsenabled"].as(); + config.WiFi.ApTimeout = root["aptimeout"].as(); + config.Mdns.Enabled = root["mdnsenabled"].as(); Configuration.write(); retMsg["type"] = "success"; diff --git a/src/WebApi_ntp.cpp b/src/WebApi_ntp.cpp index c0cfaa44..132b08ef 100644 --- a/src/WebApi_ntp.cpp +++ b/src/WebApi_ntp.cpp @@ -38,9 +38,9 @@ void WebApiNtpClass::onNtpStatus(AsyncWebServerRequest* request) JsonObject root = response->getRoot(); const CONFIG_T& config = Configuration.get(); - root["ntp_server"] = config.Ntp_Server; - root["ntp_timezone"] = config.Ntp_Timezone; - root["ntp_timezone_descr"] = config.Ntp_TimezoneDescr; + root["ntp_server"] = config.Ntp.Server; + root["ntp_timezone"] = config.Ntp.Timezone; + root["ntp_timezone_descr"] = config.Ntp.TimezoneDescr; struct tm timeinfo; if (!getLocalTime(&timeinfo, 5)) { @@ -83,12 +83,12 @@ void WebApiNtpClass::onNtpAdminGet(AsyncWebServerRequest* request) JsonObject root = response->getRoot(); const CONFIG_T& config = Configuration.get(); - root["ntp_server"] = config.Ntp_Server; - root["ntp_timezone"] = config.Ntp_Timezone; - root["ntp_timezone_descr"] = config.Ntp_TimezoneDescr; - root["longitude"] = config.Ntp_Longitude; - root["latitude"] = config.Ntp_Latitude; - root["sunsettype"] = config.Ntp_SunsetType; + root["ntp_server"] = config.Ntp.Server; + root["ntp_timezone"] = config.Ntp.Timezone; + root["ntp_timezone_descr"] = config.Ntp.TimezoneDescr; + root["longitude"] = config.Ntp.Longitude; + root["latitude"] = config.Ntp.Latitude; + root["sunsettype"] = config.Ntp.SunsetType; response->setLength(); request->send(response); @@ -173,12 +173,12 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request) } CONFIG_T& config = Configuration.get(); - strlcpy(config.Ntp_Server, root["ntp_server"].as().c_str(), sizeof(config.Ntp_Server)); - strlcpy(config.Ntp_Timezone, root["ntp_timezone"].as().c_str(), sizeof(config.Ntp_Timezone)); - strlcpy(config.Ntp_TimezoneDescr, root["ntp_timezone_descr"].as().c_str(), sizeof(config.Ntp_TimezoneDescr)); - config.Ntp_Latitude = root["latitude"].as(); - config.Ntp_Longitude = root["longitude"].as(); - config.Ntp_SunsetType = root["sunsettype"].as(); + strlcpy(config.Ntp.Server, root["ntp_server"].as().c_str(), sizeof(config.Ntp.Server)); + strlcpy(config.Ntp.Timezone, root["ntp_timezone"].as().c_str(), sizeof(config.Ntp.Timezone)); + strlcpy(config.Ntp.TimezoneDescr, root["ntp_timezone_descr"].as().c_str(), sizeof(config.Ntp.TimezoneDescr)); + config.Ntp.Latitude = root["latitude"].as(); + config.Ntp.Longitude = root["longitude"].as(); + config.Ntp.SunsetType = root["sunsettype"].as(); Configuration.write(); retMsg["type"] = "success"; diff --git a/src/WebApi_security.cpp b/src/WebApi_security.cpp index a2221f9b..9e45be06 100644 --- a/src/WebApi_security.cpp +++ b/src/WebApi_security.cpp @@ -34,8 +34,8 @@ void WebApiSecurityClass::onSecurityGet(AsyncWebServerRequest* request) JsonObject root = response->getRoot(); const CONFIG_T& config = Configuration.get(); - root["password"] = config.Security_Password; - root["allow_readonly"] = config.Security_AllowReadonly; + root["password"] = config.Security.Password; + root["allow_readonly"] = config.Security.AllowReadonly; response->setLength(); request->send(response); @@ -99,8 +99,8 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request) } CONFIG_T& config = Configuration.get(); - strlcpy(config.Security_Password, root["password"].as().c_str(), sizeof(config.Security_Password)); - config.Security_AllowReadonly = root["allow_readonly"].as(); + strlcpy(config.Security.Password, root["password"].as().c_str(), sizeof(config.Security.Password)); + config.Security.AllowReadonly = root["allow_readonly"].as(); Configuration.write(); retMsg["type"] = "success"; diff --git a/src/WebApi_sysstatus.cpp b/src/WebApi_sysstatus.cpp index fbb392d9..eff4ca54 100644 --- a/src/WebApi_sysstatus.cpp +++ b/src/WebApi_sysstatus.cpp @@ -61,7 +61,7 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request) reason = ResetReason.get_reset_reason_verbose(1); root["resetreason_1"] = reason; - root["cfgsavecount"] = Configuration.get().Cfg_SaveCount; + root["cfgsavecount"] = Configuration.get().Cfg.SaveCount; char version[16]; snprintf(version, sizeof(version), "%d.%d.%d", CONFIG_VERSION >> 24 & 0xff, CONFIG_VERSION >> 16 & 0xff, CONFIG_VERSION >> 8 & 0xff); diff --git a/src/WebApi_ws_console.cpp b/src/WebApi_ws_console.cpp index 2837fc39..6e522431 100644 --- a/src/WebApi_ws_console.cpp +++ b/src/WebApi_ws_console.cpp @@ -26,10 +26,10 @@ void WebApiWsConsoleClass::loop() if (millis() - _lastWsCleanup > 1000) { _ws.cleanupClients(); - if (Configuration.get().Security_AllowReadonly) { + if (Configuration.get().Security.AllowReadonly) { _ws.setAuthentication("", ""); } else { - _ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security_Password); + _ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password); } _lastWsCleanup = millis(); diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index 7224de84..d0a88b73 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -71,10 +71,10 @@ void WebApiWsLiveClass::loop() if (buffer) { serializeJson(root, buffer); - if (Configuration.get().Security_AllowReadonly) { + if (Configuration.get().Security.AllowReadonly) { _ws.setAuthentication("", ""); } else { - _ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security_Password); + _ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password); } _ws.textAll(buffer); @@ -172,7 +172,7 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root) struct tm timeinfo; hintObj["time_sync"] = !getLocalTime(&timeinfo, 5); hintObj["radio_problem"] = (Hoymiles.getRadioNrf()->isInitialized() && (!Hoymiles.getRadioNrf()->isConnected() || !Hoymiles.getRadioNrf()->isPVariant())) || (Hoymiles.getRadioCmt()->isInitialized() && (!Hoymiles.getRadioCmt()->isConnected())); - if (!strcmp(Configuration.get().Security_Password, ACCESS_POINT_PASSWORD)) { + if (!strcmp(Configuration.get().Security.Password, ACCESS_POINT_PASSWORD)) { hintObj["default_password"] = true; } else { hintObj["default_password"] = false; diff --git a/src/main.cpp b/src/main.cpp index d12a3572..bc6cb86a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -61,7 +61,7 @@ void setup() MessageOutput.print("failed... "); } } - if (Configuration.get().Cfg_Version != CONFIG_VERSION) { + if (Configuration.get().Cfg.Version != CONFIG_VERSION) { MessageOutput.print("migrated... "); Configuration.migrate(); } @@ -116,11 +116,11 @@ void setup() pin.display_clk, pin.display_cs, pin.display_reset); - Display.setOrientation(config.Display_Rotation); - Display.enablePowerSafe = config.Display_PowerSafe; - Display.enableScreensaver = config.Display_ScreenSaver; - Display.setContrast(config.Display_Contrast); - Display.setLanguage(config.Display_Language); + Display.setOrientation(config.Display.Rotation); + Display.enablePowerSafe = config.Display.PowerSafe; + Display.enableScreensaver = config.Display.ScreenSaver; + Display.setContrast(config.Display.Contrast); + Display.setLanguage(config.Display.Language); Display.setStartupDisplay(); MessageOutput.println("done"); @@ -131,13 +131,13 @@ void setup() // Check for default DTU serial MessageOutput.print("Check for default DTU serial... "); - if (config.Dtu_Serial == DTU_SERIAL) { + if (config.Dtu.Serial == DTU_SERIAL) { MessageOutput.print("generate serial based on ESP chip id: "); uint64_t dtuId = Utils::generateDtuSerial(); MessageOutput.printf("%0x%08x... ", ((uint32_t)((dtuId >> 32) & 0xFFFFFFFF)), ((uint32_t)(dtuId & 0xFFFFFFFF))); - config.Dtu_Serial = dtuId; + config.Dtu.Serial = dtuId; Configuration.write(); } MessageOutput.println("done"); From ee4811bbe75d572f454945f573c814dc8c30142c Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Sun, 19 Nov 2023 16:17:10 +0100 Subject: [PATCH 04/89] Feature: Allow configuration of LWT QoS --- include/Configuration.h | 1 + include/WebApi_errors.h | 1 + include/defaults.h | 1 + src/Configuration.cpp | 2 ++ src/MqttSettings.cpp | 2 +- src/WebApi_mqtt.cpp | 12 ++++++++++++ webapp/src/locales/de.json | 5 +++++ webapp/src/locales/en.json | 5 +++++ webapp/src/locales/fr.json | 5 +++++ webapp/src/types/MqttConfig.ts | 1 + webapp/src/views/MqttAdminView.vue | 18 ++++++++++++++++++ 11 files changed, 52 insertions(+), 1 deletion(-) diff --git a/include/Configuration.h b/include/Configuration.h index 7c5ce1f6..e2ed0b3b 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -98,6 +98,7 @@ struct CONFIG_T { char Topic[MQTT_MAX_TOPIC_STRLEN + 1]; char Value_Online[MQTT_MAX_LWTVALUE_STRLEN + 1]; char Value_Offline[MQTT_MAX_LWTVALUE_STRLEN + 1]; + uint8_t Qos; } Lwt; struct { diff --git a/include/WebApi_errors.h b/include/WebApi_errors.h index ac91941e..31ddae03 100644 --- a/include/WebApi_errors.h +++ b/include/WebApi_errors.h @@ -56,6 +56,7 @@ enum WebApiError { MqttPublishInterval, MqttHassTopicLength, MqttHassTopicCharacter, + MqttLwtQos, NetworkBase = 8000, NetworkIpInvalid, diff --git a/include/defaults.h b/include/defaults.h index 2ee00630..264e6661 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -75,6 +75,7 @@ #define MQTT_LWT_TOPIC "dtu/status" #define MQTT_LWT_ONLINE "online" #define MQTT_LWT_OFFLINE "offline" +#define MQTT_LWT_QOS 2U #define MQTT_PUBLISH_INTERVAL 5U #define MQTT_CLEAN_SESSION true diff --git a/src/Configuration.cpp b/src/Configuration.cpp index e88bc399..ffb70f2d 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -67,6 +67,7 @@ bool ConfigurationClass::write() mqtt_lwt["topic"] = config.Mqtt.Lwt.Topic; mqtt_lwt["value_online"] = config.Mqtt.Lwt.Value_Online; mqtt_lwt["value_offline"] = config.Mqtt.Lwt.Value_Offline; + mqtt_lwt["qos"] = config.Mqtt.Lwt.Qos; JsonObject mqtt_tls = mqtt.createNestedObject("tls"); mqtt_tls["enabled"] = config.Mqtt.Tls.Enabled; @@ -220,6 +221,7 @@ bool ConfigurationClass::read() strlcpy(config.Mqtt.Lwt.Topic, mqtt_lwt["topic"] | MQTT_LWT_TOPIC, sizeof(config.Mqtt.Lwt.Topic)); strlcpy(config.Mqtt.Lwt.Value_Online, mqtt_lwt["value_online"] | MQTT_LWT_ONLINE, sizeof(config.Mqtt.Lwt.Value_Online)); strlcpy(config.Mqtt.Lwt.Value_Offline, mqtt_lwt["value_offline"] | MQTT_LWT_OFFLINE, sizeof(config.Mqtt.Lwt.Value_Offline)); + config.Mqtt.Lwt.Qos = mqtt_lwt["qos"] | MQTT_LWT_QOS; JsonObject mqtt_tls = mqtt["tls"]; config.Mqtt.Tls.Enabled = mqtt_tls["enabled"] | MQTT_TLS; diff --git a/src/MqttSettings.cpp b/src/MqttSettings.cpp index 2a4ba262..e864437d 100644 --- a/src/MqttSettings.cpp +++ b/src/MqttSettings.cpp @@ -134,7 +134,7 @@ void MqttSettingsClass::performConnect() } else { static_cast(mqttClient)->setServer(config.Mqtt.Hostname, config.Mqtt.Port); static_cast(mqttClient)->setCredentials(config.Mqtt.Username, config.Mqtt.Password); - static_cast(mqttClient)->setWill(willTopic.c_str(), 2, config.Mqtt.Retain, config.Mqtt.Lwt.Value_Offline); + static_cast(mqttClient)->setWill(willTopic.c_str(), config.Mqtt.Lwt.Qos, config.Mqtt.Retain, config.Mqtt.Lwt.Value_Offline); static_cast(mqttClient)->setClientId(clientId.c_str()); static_cast(mqttClient)->setCleanSession(config.Mqtt.CleanSession); static_cast(mqttClient)->onConnect(std::bind(&MqttSettingsClass::onMqttConnect, this, _1)); diff --git a/src/WebApi_mqtt.cpp b/src/WebApi_mqtt.cpp index d31b1e91..340642b9 100644 --- a/src/WebApi_mqtt.cpp +++ b/src/WebApi_mqtt.cpp @@ -85,6 +85,7 @@ void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request) root["mqtt_lwt_topic"] = config.Mqtt.Lwt.Topic; root["mqtt_lwt_online"] = config.Mqtt.CleanSession; root["mqtt_lwt_offline"] = config.Mqtt.Lwt.Value_Offline; + root["mqtt_lwt_qos"] = config.Mqtt.Lwt.Qos; root["mqtt_publish_interval"] = config.Mqtt.PublishInterval; root["mqtt_clean_session"] = config.Mqtt.CleanSession; root["mqtt_hass_enabled"] = config.Mqtt.Hass.Enabled; @@ -150,6 +151,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) && root.containsKey("mqtt_lwt_topic") && root.containsKey("mqtt_lwt_online") && root.containsKey("mqtt_lwt_offline") + && root.containsKey("mqtt_lwt_qos") && root.containsKey("mqtt_publish_interval") && root.containsKey("mqtt_clean_session") && root.containsKey("mqtt_hass_enabled") @@ -269,6 +271,15 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) return; } + if (root["mqtt_lwt_qos"].as() > 2) { + retMsg["message"] = "LWT QoS must not be greater than " STR(2) "!"; + retMsg["code"] = WebApiError::MqttLwtQos; + retMsg["param"]["max"] = 2; + response->setLength(); + request->send(response); + return; + } + if (root["mqtt_publish_interval"].as() < 5 || root["mqtt_publish_interval"].as() > 65535) { retMsg["message"] = "Publish interval must be a number between 5 and 65535!"; retMsg["code"] = WebApiError::MqttPublishInterval; @@ -315,6 +326,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) strlcpy(config.Mqtt.Lwt.Topic, root["mqtt_lwt_topic"].as().c_str(), sizeof(config.Mqtt.Lwt.Topic)); strlcpy(config.Mqtt.Lwt.Value_Online, root["mqtt_lwt_online"].as().c_str(), sizeof(config.Mqtt.Lwt.Value_Online)); strlcpy(config.Mqtt.Lwt.Value_Offline, root["mqtt_lwt_offline"].as().c_str(), sizeof(config.Mqtt.Lwt.Value_Offline)); + config.Mqtt.Lwt.Qos = root["mqtt_lwt_qos"].as(); config.Mqtt.PublishInterval = root["mqtt_publish_interval"].as(); config.Mqtt.CleanSession = root["mqtt_clean_session"].as(); config.Mqtt.Hass.Enabled = root["mqtt_hass_enabled"].as(); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 62e7548b..e14e281e 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -73,6 +73,7 @@ "7013": "Veröffentlichungsintervall muss zwischen {min} und {max} sein!", "7014": "Hass-Topic darf nicht länger als {max} Zeichen sein!", "7015": "Hass-Topic darf keine Leerzeichen enthalten!", + "7016": "LWT QOS darf icht größer als {max} sein!", "8001": "IP-Adresse ist ungültig!", "8002": "Netzmaske ist ungültig!", "8003": "Standardgateway ist ungültig!", @@ -437,6 +438,10 @@ "LwtOnlineHint": "Nachricht, die im LWT-Topic veröffentlicht wird, wenn OpenDTU online ist", "LwtOffline": "LWT-Offline-Nachricht:", "LwtOfflineHint": "Nachricht, die im LWT-Topic veröffentlicht wird, wenn OpenDTU offline ist", + "LwtQos": "QoS (Quality of Service):", + "QOS0": "0 (Höchstens einmal)", + "QOS1": "1 (Mindestens einmal)", + "QOS2": "2 (Exakt einmal)", "HassParameters": "Home Assistant MQTT-Auto-Discovery-Parameter", "HassPrefixTopic": "Präfix Topic:", "HassPrefixTopicHint": "The prefix for the discovery topic", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 40458b17..4a76935d 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -73,6 +73,7 @@ "7013": "Publish interval must be a number between {min} and {max}!", "7014": "Hass topic must not longer then {max} characters!", "7015": "Hass topic must not contain space characters!", + "7016": "LWT QOS must not greater then {max}!", "8001": "IP address is invalid!", "8002": "Netmask is invalid!", "8003": "Gateway is invalid!", @@ -437,6 +438,10 @@ "LwtOnlineHint": "Message that will be published to LWT topic when online", "LwtOffline": "LWT Offline message:", "LwtOfflineHint": "Message that will be published to LWT topic when offline", + "LwtQos": "QoS (Quality of Service):", + "QOS0": "0 (At most once)", + "QOS1": "1 (At least once)", + "QOS2": "2 (Exactly once)", "HassParameters": "Home Assistant MQTT Auto Discovery Parameters", "HassPrefixTopic": "Prefix Topic:", "HassPrefixTopicHint": "The prefix for the discovery topic", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index e5a48eb1..e3afc29f 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -73,6 +73,7 @@ "7013": "L'intervalle de publication doit être un nombre compris entre {min} et {max} !", "7014": "Le sujet Hass ne doit pas dépasser {max} caractères !", "7015": "Le sujet Hass ne doit pas contenir d'espace !", + "7016": "LWT QOS ne doit pas être supérieur à {max}!", "8001": "L'adresse IP n'est pas valide !", "8002": "Le masque de réseau n'est pas valide !", "8003": "La passerelle n'est pas valide !", @@ -437,6 +438,10 @@ "LwtOnlineHint": "Message qui sera publié sur le sujet LWT lorsqu'il sera en ligne", "LwtOffline": "Message hors ligne de LWT", "LwtOfflineHint": "Message qui sera publié sur le sujet LWT lorsqu'il sera hors ligne", + "LwtQos": "QoS (Quality of Service):", + "QOS0": "0 (Au maximum une fois)", + "QOS1": "1 (Au moins une fois)", + "QOS2": "2 (Exactement une fois)", "HassParameters": "Paramètres de découverte automatique MQTT de Home Assistant", "HassPrefixTopic": "Préfixe du sujet", "HassPrefixTopicHint": "Le préfixe de découverte du sujet", diff --git a/webapp/src/types/MqttConfig.ts b/webapp/src/types/MqttConfig.ts index f2d847b8..c61b770a 100644 --- a/webapp/src/types/MqttConfig.ts +++ b/webapp/src/types/MqttConfig.ts @@ -16,6 +16,7 @@ export interface MqttConfig { mqtt_lwt_topic: string; mqtt_lwt_online: string; mqtt_lwt_offline: string; + mqtt_lwt_qos: number; mqtt_hass_enabled: boolean; mqtt_hass_expire: boolean; mqtt_hass_retain: boolean; diff --git a/webapp/src/views/MqttAdminView.vue b/webapp/src/views/MqttAdminView.vue index d8c03b7f..fadc8d2a 100644 --- a/webapp/src/views/MqttAdminView.vue +++ b/webapp/src/views/MqttAdminView.vue @@ -98,6 +98,19 @@ v-model="mqttConfigList.mqtt_lwt_offline" type="text" maxlength="20" :placeholder="$t('mqttadmin.LwtOfflineHint')"/> + +
+ +
+ +
+
Date: Sun, 19 Nov 2023 17:00:26 +0100 Subject: [PATCH 05/89] Made resetreason methods static --- lib/ResetReason/src/ResetReason.cpp | 8 +++----- lib/ResetReason/src/ResetReason.h | 10 ++++------ src/WebApi_sysstatus.cpp | 4 ++-- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/ResetReason/src/ResetReason.cpp b/lib/ResetReason/src/ResetReason.cpp index b00dab79..74bf3a38 100644 --- a/lib/ResetReason/src/ResetReason.cpp +++ b/lib/ResetReason/src/ResetReason.cpp @@ -20,7 +20,7 @@ #include "rom/rtc.h" #endif -String ResetReasonClass::get_reset_reason_verbose(uint8_t cpu_id) +String ResetReason::get_reset_reason_verbose(uint8_t cpu_id) { RESET_REASON reason; reason = rtc_get_reset_reason(cpu_id); @@ -86,7 +86,7 @@ String ResetReasonClass::get_reset_reason_verbose(uint8_t cpu_id) return reason_str; } -String ResetReasonClass::get_reset_reason_short(uint8_t cpu_id) +String ResetReason::get_reset_reason_short(uint8_t cpu_id) { RESET_REASON reason; reason = rtc_get_reset_reason(cpu_id); @@ -150,6 +150,4 @@ String ResetReasonClass::get_reset_reason_short(uint8_t cpu_id) } return reason_str; -} - -ResetReasonClass ResetReason; +} \ No newline at end of file diff --git a/lib/ResetReason/src/ResetReason.h b/lib/ResetReason/src/ResetReason.h index 34427bfa..52356b52 100644 --- a/lib/ResetReason/src/ResetReason.h +++ b/lib/ResetReason/src/ResetReason.h @@ -3,10 +3,8 @@ #include -class ResetReasonClass { +class ResetReason { public: - String get_reset_reason_verbose(uint8_t cpu_id); - String get_reset_reason_short(uint8_t cpu_id); -}; - -extern ResetReasonClass ResetReason; \ No newline at end of file + static String get_reset_reason_verbose(uint8_t cpu_id); + static String get_reset_reason_short(uint8_t cpu_id); +}; \ No newline at end of file diff --git a/src/WebApi_sysstatus.cpp b/src/WebApi_sysstatus.cpp index eff4ca54..7a3be0f6 100644 --- a/src/WebApi_sysstatus.cpp +++ b/src/WebApi_sysstatus.cpp @@ -55,10 +55,10 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request) root["chipcores"] = ESP.getChipCores(); String reason; - reason = ResetReason.get_reset_reason_verbose(0); + reason = ResetReason::get_reset_reason_verbose(0); root["resetreason_0"] = reason; - reason = ResetReason.get_reset_reason_verbose(1); + reason = ResetReason::get_reset_reason_verbose(1); root["resetreason_1"] = reason; root["cfgsavecount"] = Configuration.get().Cfg.SaveCount; From 1de3b4816619b7675fbb9917c287590086eafe10 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Wed, 22 Nov 2023 20:21:25 +0100 Subject: [PATCH 06/89] Feature: Implement offset cache for "YieldDay" Thanks to @broth-itk for the idea! Fix: #1258 #1397 --- include/Configuration.h | 1 + lib/Hoymiles/src/Hoymiles.cpp | 1 + lib/Hoymiles/src/parser/StatisticsParser.cpp | 46 ++++++++++++++++++++ lib/Hoymiles/src/parser/StatisticsParser.h | 7 +++ src/Configuration.cpp | 2 + src/InverterSettings.cpp | 1 + src/WebApi_inverter.cpp | 3 ++ webapp/src/locales/de.json | 4 +- webapp/src/locales/en.json | 4 +- webapp/src/locales/fr.json | 4 +- webapp/src/views/InverterAdminView.vue | 6 +++ 11 files changed, 76 insertions(+), 3 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index e2ed0b3b..5e74c3a7 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -48,6 +48,7 @@ struct INVERTER_CONFIG_T { uint8_t ReachableThreshold; bool ZeroRuntimeDataIfUnrechable; bool ZeroYieldDayOnMidnight; + bool YieldDayCorrection; CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT]; }; diff --git a/lib/Hoymiles/src/Hoymiles.cpp b/lib/Hoymiles/src/Hoymiles.cpp index d138e4f1..a4558ac6 100644 --- a/lib/Hoymiles/src/Hoymiles.cpp +++ b/lib/Hoymiles/src/Hoymiles.cpp @@ -134,6 +134,7 @@ void HoymilesClass::loop() if (inv->getZeroYieldDayOnMidnight()) { inv->Statistics()->zeroDailyData(); } + inv->Statistics()->resetYieldDayCorrection(); } lastWeekDay = currentWeekDay; diff --git a/lib/Hoymiles/src/parser/StatisticsParser.cpp b/lib/Hoymiles/src/parser/StatisticsParser.cpp index 71c1ebbd..84f19299 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.cpp +++ b/lib/Hoymiles/src/parser/StatisticsParser.cpp @@ -82,6 +82,8 @@ void StatisticsParser::clearBuffer() { memset(_payloadStatistic, 0, STATISTIC_PACKET_SIZE); _statisticLength = 0; + + memset(_lastYieldDay, 0, sizeof(_lastYieldDay)); } void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len) @@ -94,6 +96,31 @@ void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t _statisticLength += len; } +void StatisticsParser::endAppendFragment() +{ + Parser::endAppendFragment(); + + if (!_enableYieldDayCorrection) { + resetYieldDayCorrection(); + return; + } + + for (auto& c : getChannelsByType(TYPE_DC)) { + // check if current yield day is smaller then last cached yield day + if (getChannelFieldValue(TYPE_DC, c, FLD_YD) < _lastYieldDay[static_cast(c)]) { + // currently all values are zero --> Add last known values to offset + Hoymiles.getMessageOutput()->printf("Yield Day reset detected!\r\n"); + + setChannelFieldOffset(TYPE_DC, c, FLD_YD, + getChannelFieldOffset(TYPE_DC, c, FLD_YD) + _lastYieldDay[static_cast(c)]); + + _lastYieldDay[static_cast(c)] = 0; + } else { + _lastYieldDay[static_cast(c)] = getChannelFieldValue(TYPE_DC, c, FLD_YD); + } + } +} + const byteAssign_t* StatisticsParser::getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) { for (uint8_t i = 0; i < _byteAssignmentSize; i++) { @@ -329,6 +356,16 @@ void StatisticsParser::setLastUpdateFromInternal(uint32_t lastUpdate) _lastUpdateFromInternal = lastUpdate; } +bool StatisticsParser::getYieldDayCorrection() +{ + return _enableYieldDayCorrection; +} + +void StatisticsParser::setYieldDayCorrection(bool enabled) +{ + _enableYieldDayCorrection = enabled; +} + void StatisticsParser::zeroFields(const FieldId_t* fields) { // Loop all channels @@ -344,6 +381,15 @@ void StatisticsParser::zeroFields(const FieldId_t* fields) setLastUpdateFromInternal(millis()); } +void StatisticsParser::resetYieldDayCorrection() +{ + // new day detected, reset counters + for (auto& c : getChannelsByType(TYPE_DC)) { + setChannelFieldOffset(TYPE_DC, c, FLD_YD, 0); + _lastYieldDay[static_cast(c)] = 0; + } +} + static float calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0) { float yield = 0; diff --git a/lib/Hoymiles/src/parser/StatisticsParser.h b/lib/Hoymiles/src/parser/StatisticsParser.h index da291004..367858a2 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.h +++ b/lib/Hoymiles/src/parser/StatisticsParser.h @@ -106,6 +106,7 @@ public: StatisticsParser(); void clearBuffer(); void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); + void endAppendFragment(); void setByteAssignment(const byteAssign_t* byteAssignment, uint8_t size); @@ -140,6 +141,7 @@ public: void zeroRuntimeData(); void zeroDailyData(); + void resetYieldDayCorrection(); // Update time when new data from the inverter is received void setLastUpdate(uint32_t lastUpdate); @@ -148,6 +150,8 @@ public: uint32_t getLastUpdateFromInternal(); void setLastUpdateFromInternal(uint32_t lastUpdate); + bool getYieldDayCorrection(); + void setYieldDayCorrection(bool enabled); private: void zeroFields(const FieldId_t* fields); @@ -162,4 +166,7 @@ private: uint32_t _rxFailureCount = 0; uint32_t _lastUpdateFromInternal = 0; + + bool _enableYieldDayCorrection = false; + float _lastYieldDay[CH_CNT]; }; \ No newline at end of file diff --git a/src/Configuration.cpp b/src/Configuration.cpp index ffb70f2d..8874a57f 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -117,6 +117,7 @@ bool ConfigurationClass::write() inv["reachable_threshold"] = config.Inverter[i].ReachableThreshold; inv["zero_runtime"] = config.Inverter[i].ZeroRuntimeDataIfUnrechable; inv["zero_day"] = config.Inverter[i].ZeroYieldDayOnMidnight; + inv["yieldday_correction"] = config.Inverter[i].YieldDayCorrection; JsonArray channel = inv.createNestedArray("channel"); for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { @@ -272,6 +273,7 @@ bool ConfigurationClass::read() config.Inverter[i].ReachableThreshold = inv["reachable_threshold"] | REACHABLE_THRESHOLD; config.Inverter[i].ZeroRuntimeDataIfUnrechable = inv["zero_runtime"] | false; config.Inverter[i].ZeroYieldDayOnMidnight = inv["zero_day"] | false; + config.Inverter[i].YieldDayCorrection = inv["yieldday_correction"] | false; JsonArray channel = inv["channel"]; for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { diff --git a/src/InverterSettings.cpp b/src/InverterSettings.cpp index ce692bf1..425ecd6f 100644 --- a/src/InverterSettings.cpp +++ b/src/InverterSettings.cpp @@ -74,6 +74,7 @@ void InverterSettingsClass::init() inv->setReachableThreshold(config.Inverter[i].ReachableThreshold); inv->setZeroValuesIfUnreachable(config.Inverter[i].ZeroRuntimeDataIfUnrechable); inv->setZeroYieldDayOnMidnight(config.Inverter[i].ZeroYieldDayOnMidnight); + inv->Statistics()->setYieldDayCorrection(config.Inverter[i].YieldDayCorrection); for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { inv->Statistics()->setStringMaxPower(c, config.Inverter[i].channel[c].MaxChannelPower); inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast(c), FLD_YT, config.Inverter[i].channel[c].YieldTotalOffset); diff --git a/src/WebApi_inverter.cpp b/src/WebApi_inverter.cpp index d5ea9b45..3ec01b9c 100644 --- a/src/WebApi_inverter.cpp +++ b/src/WebApi_inverter.cpp @@ -61,6 +61,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request) obj["reachable_threshold"] = config.Inverter[i].ReachableThreshold; obj["zero_runtime"] = config.Inverter[i].ZeroRuntimeDataIfUnrechable; obj["zero_day"] = config.Inverter[i].ZeroYieldDayOnMidnight; + obj["yieldday_correction"] = config.Inverter[i].YieldDayCorrection; auto inv = Hoymiles.getInverterBySerial(config.Inverter[i].Serial); uint8_t max_channels; @@ -288,6 +289,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) inverter.ReachableThreshold = root["reachable_threshold"] | REACHABLE_THRESHOLD; inverter.ZeroRuntimeDataIfUnrechable = root["zero_runtime"] | false; inverter.ZeroYieldDayOnMidnight = root["zero_day"] | false; + inverter.YieldDayCorrection = root["yieldday_correction"] | false; arrayCount++; } @@ -321,6 +323,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) inv->setReachableThreshold(inverter.ReachableThreshold); inv->setZeroValuesIfUnreachable(inverter.ZeroRuntimeDataIfUnrechable); inv->setZeroYieldDayOnMidnight(inverter.ZeroYieldDayOnMidnight); + inv->Statistics()->setYieldDayCorrection(inverter.YieldDayCorrection); for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { inv->Statistics()->setStringMaxPower(c, inverter.channel[c].MaxChannelPower); inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast(c), FLD_YT, inverter.channel[c].YieldTotalOffset); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index e14e281e..81ac5b40 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -494,7 +494,9 @@ "Cancel": "@:maintenancereboot.Cancel", "Save": "@:dtuadmin.Save", "DeleteMsg": "Soll der Wechselrichter \"{name}\" mit der Seriennummer {serial} wirklich gelöscht werden?", - "Delete": "Löschen" + "Delete": "Löschen", + "YieldDayCorrection": "Tagesertragskorrektur", + "YieldDayCorrectionHint": "Summiert den Tagesertrag, auch wenn der Wechselrichter neu gestartet wird. Der Wert wird um Mitternacht zurückgesetzt" }, "configadmin": { "ConfigManagement": "Konfigurationsverwaltung", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 4a76935d..67faba0f 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -494,7 +494,9 @@ "Cancel": "@:maintenancereboot.Cancel", "Save": "@:dtuadmin.Save", "DeleteMsg": "Are you sure you want to delete the inverter \"{name}\" with serial number {serial}?", - "Delete": "Delete" + "Delete": "Delete", + "YieldDayCorrection": "Yield Day Correction", + "YieldDayCorrectionHint": "Sum up daily yield even if the inverter is restarted. Value will be reset at midnight" }, "configadmin": { "ConfigManagement": "Config Management", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index e3afc29f..006ab015 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -494,7 +494,9 @@ "Cancel": "@:maintenancereboot.Cancel", "Save": "@:dtuadmin.Save", "DeleteMsg": "Êtes-vous sûr de vouloir supprimer l'onduleur \"{name}\" avec le numéro de série \"{serial}\" ?", - "Delete": "Supprimer" + "Delete": "Supprimer", + "YieldDayCorrection": "Yield Day Correction", + "YieldDayCorrectionHint": "Sum up daily yield even if the inverter is restarted. Value will be reset at midnight" }, "configadmin": { "ConfigManagement": "Gestion de la configuration", diff --git a/webapp/src/views/InverterAdminView.vue b/webapp/src/views/InverterAdminView.vue index d1f199ad..300abdbf 100644 --- a/webapp/src/views/InverterAdminView.vue +++ b/webapp/src/views/InverterAdminView.vue @@ -192,6 +192,11 @@ v-model="selectedInverterData.zero_day" type="checkbox" :tooltip="$t('inverteradmin.ZeroDayHint')" wide/> + + @@ -269,6 +274,7 @@ declare interface Inverter { reachable_threshold: number; zero_runtime: boolean; zero_day: boolean; + yieldday_correction: boolean; channel: Array; } From 203e871c4a47be32d6a9c34267d0b58baf1dd745 Mon Sep 17 00:00:00 2001 From: Pierre Kancir Date: Thu, 16 Nov 2023 17:15:18 +0100 Subject: [PATCH 07/89] Add Esp32-Stick-PoE-A --- docs/DeviceProfiles/esp32_stick_poe_a.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 docs/DeviceProfiles/esp32_stick_poe_a.json diff --git a/docs/DeviceProfiles/esp32_stick_poe_a.json b/docs/DeviceProfiles/esp32_stick_poe_a.json new file mode 100644 index 00000000..92c81745 --- /dev/null +++ b/docs/DeviceProfiles/esp32_stick_poe_a.json @@ -0,0 +1,22 @@ +[ + { + "name": "Esp32-Stick-PoE-A", + "nrf24": { + "miso": 2, + "mosi": 15, + "clk": 14, + "irq": 34, + "en": 12, + "cs": 4 + }, + "eth": { + "enabled": true, + "phy_addr": 1, + "power": -1, + "mdc": 23, + "mdio": 18, + "type": 0, + "clk_mode": 3 + } + } +] From b158a5682ec57b8d7808899c29e1c215d26ccb45 Mon Sep 17 00:00:00 2001 From: Pierre Kancir Date: Wed, 22 Nov 2023 22:58:37 +0100 Subject: [PATCH 08/89] remove broken LilyGO_T_ETH_POE config, use device profile instead --- platformio.ini | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/platformio.ini b/platformio.ini index 6fcbb882..cbb76a6f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -148,25 +148,6 @@ build_flags = ${env.build_flags} -DOPENDTU_ETHERNET -[env:LilyGO_T_ETH_POE] -; http://www.lilygo.cn/claprod_view.aspx?TypeId=21&Id=1344&FId=t28:21:28 -board = esp32dev -build_flags = ${env.build_flags} - -DHOYMILES_PIN_MISO=2 - -DHOYMILES_PIN_MOSI=15 - -DHOYMILES_PIN_SCLK=14 - -DHOYMILES_PIN_IRQ=34 - -DHOYMILES_PIN_CE=12 - -DHOYMILES_PIN_CS=4 - -DOPENDTU_ETHERNET - -DETH_CLK_MODE=ETH_CLOCK_GPIO17_OUT - -DETH_POWER_PIN=-1 - -DETH_TYPE=ETH_PHY_LAN8720 - -DETH_ADDR=0 - -DETH_MDC_PIN=23 - -DETH_MDIO_PIN=18 - - [env:esp_s3_12k_kit] ; https://www.waveshare.com/wiki/NodeMCU-ESP-S3-12K-Kit board = esp32-s3-devkitc-1 From dff6da9a5f82e7931129064a4b95c09c623e0464 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Thu, 23 Nov 2023 22:32:01 +0100 Subject: [PATCH 09/89] Feature: High resolution Icon and PWA (Progressive Web App) functionality Fix: #1289 --- platformio.ini | 9 ++++++++- src/WebApi_webapp.cpp | 9 ++++++++- webapp/index.html | 1 + webapp/public/favicon.png | Bin 682 -> 4590 bytes webapp/public/site.webmanifest | 13 +++++++++++++ 5 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 webapp/public/site.webmanifest diff --git a/platformio.ini b/platformio.ini index 6fcbb882..10ce4571 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,6 @@ framework = arduino platform = espressif32@6.3.2 build_flags = - -DCOMPONENT_EMBED_FILES=webapp_dist/index.html.gz:webapp_dist/zones.json.gz:webapp_dist/favicon.ico:webapp_dist/favicon.png:webapp_dist/js/app.js.gz -DPIOENV=\"$PIOENV\" -Wall -Wextra -Werror -std=c++17 @@ -45,6 +44,14 @@ extra_scripts = board_build.partitions = partitions_custom.csv board_build.filesystem = littlefs +board_build.embed_files = + webapp_dist/index.html.gz + webapp_dist/zones.json.gz + webapp_dist/favicon.ico + webapp_dist/favicon.png + webapp_dist/js/app.js.gz + webapp_dist/site.webmanifest + monitor_filters = esp32_exception_decoder, time, log2file, colorize monitor_speed = 115200 upload_protocol = esptool diff --git a/src/WebApi_webapp.cpp b/src/WebApi_webapp.cpp index 90516ad6..a4712996 100644 --- a/src/WebApi_webapp.cpp +++ b/src/WebApi_webapp.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ #include "WebApi_webapp.h" @@ -9,12 +9,14 @@ extern const uint8_t file_favicon_ico_start[] asm("_binary_webapp_dist_favicon_i extern const uint8_t file_favicon_png_start[] asm("_binary_webapp_dist_favicon_png_start"); extern const uint8_t file_zones_json_start[] asm("_binary_webapp_dist_zones_json_gz_start"); extern const uint8_t file_app_js_start[] asm("_binary_webapp_dist_js_app_js_gz_start"); +extern const uint8_t file_site_webmanifest_start[] asm("_binary_webapp_dist_site_webmanifest_start"); extern const uint8_t file_index_html_end[] asm("_binary_webapp_dist_index_html_gz_end"); extern const uint8_t file_favicon_ico_end[] asm("_binary_webapp_dist_favicon_ico_end"); extern const uint8_t file_favicon_png_end[] asm("_binary_webapp_dist_favicon_png_end"); extern const uint8_t file_zones_json_end[] asm("_binary_webapp_dist_zones_json_gz_end"); extern const uint8_t file_app_js_end[] asm("_binary_webapp_dist_js_app_js_gz_end"); +extern const uint8_t file_site_webmanifest_end[] asm("_binary_webapp_dist_site_webmanifest_end"); void WebApiWebappClass::init(AsyncWebServer* server) { @@ -54,6 +56,11 @@ void WebApiWebappClass::init(AsyncWebServer* server) request->send(response); }); + _server->on("/site.webmanifest", HTTP_GET, [](AsyncWebServerRequest* request) { + AsyncWebServerResponse* response = request->beginResponse_P(200, "application/json", file_site_webmanifest_start, file_site_webmanifest_end - file_site_webmanifest_start); + request->send(response); + }); + _server->on("/js/app.js", HTTP_GET, [](AsyncWebServerRequest* request) { #ifdef AUTO_GIT_HASH // check client If-None-Match header vs ETag/AUTO_GIT_HASH diff --git a/webapp/index.html b/webapp/index.html index 39a94a03..2d84d6f5 100644 --- a/webapp/index.html +++ b/webapp/index.html @@ -5,6 +5,7 @@ + OpenDTU diff --git a/webapp/public/favicon.png b/webapp/public/favicon.png index 3378b661323215355b212542a9fe59b610adeae1..278aac84f197d64e420e057aa7ba62c94c82e512 100644 GIT binary patch literal 4590 zcmai%2QXaS+rU>#^bl5AL`x8nAS4JP%j#nF-XmCbl|_{3i5f)jJ<+2ih!RBfPOw3A zS)CAd7ytXt&Oh_ceBXTEnYqt>%I|rebM86!&fEwMbww(22ssD@qEc3ZX#w}Re=brY zK#rtdj|6Va9`Xhr+AcO8UY2gpL0(>7_Ylqw?$0cd&+oao*=FoWK>&yhsbuI50)Z+2 zxd?7Bt8#)sgvT0cI`WE0wab5v#l=Oy!eX&F9PVH4%d@jHyonD1=K1+Kp4r{q-QVBG zM*%+o;NhdABLKiNKy++u?BwJGj{(k(jt)S_!^_LdfPQdr07xM8w-?X?&;PFy$o;?l zK&gQVpPikZ{{DVE49o#Y!PjqYZU*$vpFjUCa&d71=s*@QFc{3>;2_?Emw2q9q2c)W z7+46t;Pmt~FiHG8fh;_8czF1?CP1#Ntl&-jQ<|Hb`x{+fU!Rzm7#bP^Jhip8_(q+b zoj}&s*4F0cCZ5FC|CR$=i%VBmmw$I*VH)(_f<)6KJTiYlS5u=b+=x-fsN}Jc(}b42uaj?!CpC=lfP<8j zFj9+#Mu6~ML&2m`O5GH@*Q=FGkzTdyA8mTB&9h4h%^S`QoDQAV$q@&Og+zZKc@@^d z@{5H&XVEIoN+a~kt>N%47Mh$iqG3j53^Pq$%R~JLtFbAI6EukoPdL}QKnUlG;~@o< zW@15=Xi;S7R>G28PwcSkR^+Osj8SVt91jdff-}5-u01P>nTaojCx=gykz}h}DXuX@ zNGb1zez_VLJw2F;9zwVWz17GIak|0~onuYicK!UrkIcKMox&bZmHd6unr&nCCnojj zV%opGSL*XDelNR7d28fv>)A$3E)m@%Jv3>tpRhbYOA!`W=ZqW8ubKt92M+#ui_^>4 zmZE_uPoe~3%elZH3z2Q|cGzc0Wob6Q%TdFP*y)c1ep|JCUgEwTy)&n5t@KHl)g;E- zH?jx&o4E9G8_xaxUGq=tN}ChE`f?I{HzpvsYEPj{EKi{Qf|H)Jv8`~tS#Y~-+ZTsB z5;(D!b38D7wRRKTG5iB<3rW|0>5M!!UCy<5qSo4ua=Tf6rR9WE^g*tg#A=okhrGNC zx7L$RHlfGTf8xZJ?jOKx9;%k3ISvkCPG2c& z|4hwBz8Vp$33*^HS6=;#!I*J8(%*pt&iSf7@jeqKBrH9H^j2cRQz>FPZ;9d-1omZ^ zRN-N9%?l{vhskfZmT}iiK3DH2{%4r$k`-?%pVLjOjdhdT`!jXRX29awhn#%acp7r@g4Mo_BSirbmP7jXt42zg19dZ1sY< z=LHI~jpfJew>GRJP$(K(OWD@un}fVxesBL`q#B#yDDqWBJuvsVHimGji;lgQt~~iuYSCF_|}NJIrfVpeU(e2Ld&}-4q4xROO6aHZEd%>#Bb5#m|k(K zJYsRySstqKczGo2+gUV|711vbimCeFJ*h&7$>NwtF^M@DpFYiSa<>~dhXp-ZJ6^TB zybXL?g?V`5ySlpW%+BUxy%wZHHO2!dFNflPIIvu$XUbgJThI0S;_KDVaEyTOVC-a{ z{n}iVvCLA2o+%SDJpb8q$E87Tjb*C+Y8J;zmYLEX@ryJ4EG7H5v7~cIsHhq*CkB20 zf)qTxHL1+ECDqPNt+4)Qx!v6FdMP&@ie`GEJ{&GgfP)(+NZ9jP8x2)56JN$0f* z@`6vK;X#`}$aVVY8{*2ou^W;;E#+OfyP-+(6Q;Mn=NXa-E&gfyPES@_DO16p`caIx zs%iw`(gTOd)H@3w-7)G8eUG>PRos}=h_yG)EbMsqW(xexV= zQ$fxhh?=0f{WGT9X4T}WadZ&Qn`4`QRGcZpV6*H;C=?=Se0J0*fN3neW1iT6l2U7Y zLDZF;JW6J_>nny2{gn2735qJX#?XBfHq9@pu)t;S#W+EK=H)^yk9(t5Bl=MehWi9Hc507-g?~i`N`1j9O zJ$vFD?2`T9o@D@iSM@@A;}#@v-6KIquY28p!6e>$S?0k6Y)xb@P}4|@-}&T~(V?Xv zfyhUHco_w}z+?}G)H5|`BB->z*8F=@>1lhwf>pD)#Emt(M}uZyCZ%Ce3fj>!c%E^k zgyI8No&%1Z&zE>WlqTJe-M2|OyYIDmVc*2-yA17v`w~6+jK7E)c1?q#S7FcOXk%i& zcSGz67ZIJsgHC^zq|ItX7pKJ0&!M21JW>Nt(LG%OO9T`BjV{SXS5mP&<^}!@^z{!P zGlLH6kpDt3KYO-$@L43E8jO&+D{GChwJzO=;5lRYNxTRI}osBpasGK};bN zUppi<5g2VL#!oOHY8cm>l2f14l$zx){UOGaE-K?q{F=6cj3u4V^_a}Ebz~kMio5%$RYSiza+=daXaUn{6s?Wma{*^?IF|ES=2~IzcXt` z)Z0e0ZAwORU#+mX2C2wE;ggdHiZ}U~Acl(;Q(Y@-4{G)mBvem$Xri=8Jq<@cFDcEQlew_)j~H19NB7!JNFj?fv$ zKGKr*47X-FJU4!!Qem~(?g=g5bVX6%Sgr`$WNO^K6CJ~J$qb%-*=7eiUUS>ISbJXH zJtpFoK$|d* zLTXm(oVcm|Hq4Hd+A|fZ(@J?{SWpS01p9yl{P_7Cm%j8l3yQ+f3>*`iVVYfil|k#BUP|tkYe= znvx40cPt*%lPBda`UEDh79=ei&+aBLdeAI|gLN@xG=WRS@K%dbnj5FWZVW^cx@%sA z2PP#~9>}Vq^E?$fUa3RPy@}#E%_V?b80r!&G@(ENXRRm3!1inDZA1WGqIyUOKoB~c>{ zCn2{sm|0HO5kG(HGA!>RZOC{I(yJ4l6==) zA71vcDIBUK^qnZse=Lp{{D;rh%`ID9G8q)iteGqm__BOcG=nfk@vz<> zt~r)HE8{c337&UE)9x%C(YksKzl{IW&Tjn=GT)jDFQh3Skvtc`leBS#^!gM+P zNf@yWUDMqvA?9V9&5!hc(OV9k)?v%OR4!8Ny)t1%gpE-zt+nhMv8!FPtvf;k#97hk z8-c-R35J&--OrBS`O9)^SUgHXDr4u~*%!TsXWHGpv*6*pe$wtlVadl=lYjYH*>Bu} zl(u`;j=~Tj9_k#qPH`?s`C8+auBEGb91C?GV`4p<=oIpVX1_g%a9oRs3&xt-t;@MC zE9yhK&!aHg(kScvAO|JKQM{jxY8lc zV!UfO>WV{(fy%cM4dxeglJ1d| z|2bOPKRDRem}cnn!@pLPAyAP1a}Df6i7-Wo!jD%*uhY5>yBs`9j&9wO7>T3K58BY2 zZ;9G7x5+9kn8<~fP%D!`y47kE%Z29=_M3I1?jQ8wPg7>YzEfqMttom$YES!o&@)Z#MRl1Gk3*~BCEZ$$U_33MdYLw5sA-4oh3wlq z(hBF@>u0Ba`{8?Hh8U-fo1O#&5Wl8YqQEG0?|0v?DW)6E5-m4XuL_^ddN5dH@A&ZV%P2{|{3M@0?65+R zzD#$huokB~SVmV`JzXIt>wML$Tkx3Yc1Y}7*P?l{03AtJT4RzxI-h<_!=egl{XkU>KF z^h5+}t&1^w5rN)AMB!rv0PxKGeY@RoyG zB!GHK6>3jNMf-P0;F;Mm^JDI7qN*y__q~fT&XRFW0io-jEVgNPu;m^l(wtDw;O zQztS30W=ed*X$XPl;&jiTcKrYboHmx>DR#lAmR8Y4jc}LvP!6yLbZ16{@hxlom1oN z3$uA*y0vzYDaIH8fSFzXP2S&i-C!+8BJrqenlbVv={RO~#u(^J7n>86N%Qx7rGl~~ zA0rnLpELUN`K&#@0nq5A??lSeq`Uxtgy$b4*zI-$nE*iF_miZ&-|s(dG-l8Ny;kPT z{K$CwF(b+2C|Wj%%jL3l&8IPEg4f7f*b#+JopLRD2Yrf(z?q8wkc>e!?@P~)W-U35ZvO;%Mm>D);IdSt?j#+LNBR2t(lcCg zx==iHwypz!%N!zWt$a%FFLC29!5>!qrv0I!+Z<&kMWS9bd_m}EwaZ%1oM=&P!7MD%nnDD5^s8I#}p|6T?E0Pw3|@?Jmc Qz5oCK07*qoM6N<$f~lH1Y5)KL diff --git a/webapp/public/site.webmanifest b/webapp/public/site.webmanifest new file mode 100644 index 00000000..3be24609 --- /dev/null +++ b/webapp/public/site.webmanifest @@ -0,0 +1,13 @@ +{ + "name": "OpenDTU", + "short_name": "OpenDTU", + "display": "standalone", + "orientation": "portrait", + "icons": [ + { + "src": "/favicon.png", + "sizes": "144x144", + "type": "image/png" + } + ] +} \ No newline at end of file From 94170545ed19a08b5bda37e1de8146e84193974d Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Thu, 23 Nov 2023 22:34:18 +0100 Subject: [PATCH 10/89] webapp: Update dependencies --- webapp/package.json | 16 +++---- webapp/yarn.lock | 108 ++++++++++++++++++++++---------------------- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index c781b53e..15b6ccc6 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -19,27 +19,27 @@ "sortablejs": "^1.15.0", "spark-md5": "^3.0.2", "vue": "^3.3.8", - "vue-i18n": "^9.7.0", + "vue-i18n": "^9.7.1", "vue-router": "^4.2.5" }, "devDependencies": { "@intlify/unplugin-vue-i18n": "^1.5.0", - "@rushstack/eslint-patch": "^1.5.1", + "@rushstack/eslint-patch": "^1.6.0", "@tsconfig/node18": "^18.2.2", - "@types/bootstrap": "^5.2.9", - "@types/node": "^20.9.0", - "@types/sortablejs": "^1.15.5", + "@types/bootstrap": "^5.2.10", + "@types/node": "^20.9.4", + "@types/sortablejs": "^1.15.7", "@types/spark-md5": "^3.0.4", "@vitejs/plugin-vue": "^4.5.0", "@vue/eslint-config-typescript": "^12.0.0", "@vue/tsconfig": "^0.4.0", - "eslint": "^8.53.0", + "eslint": "^8.54.0", "eslint-plugin-vue": "^9.18.1", "npm-run-all": "^4.1.5", "sass": "^1.69.5", "terser": "^5.24.0", - "typescript": "^5.2.2", - "vite": "^5.0.0", + "typescript": "^5.3.2", + "vite": "^5.0.2", "vite-plugin-compression": "^0.5.1", "vite-plugin-css-injected-by-js": "^3.3.0", "vue-tsc": "^1.8.22" diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 0a6eabe7..2f6f0fdd 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -171,10 +171,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.53.0": - version "8.53.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.53.0.tgz#bea56f2ed2b5baea164348ff4d5a879f6f81f20d" - integrity sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w== +"@eslint/js@8.54.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.54.0.tgz#4fab9a2ff7860082c304f750e94acd644cf984cf" + integrity sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ== "@humanwhocodes/config-array@^0.11.13": version "0.11.13" @@ -211,20 +211,20 @@ source-map-js "^1.0.1" yaml-eslint-parser "^1.2.2" -"@intlify/core-base@9.7.0": - version "9.7.0" - resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.7.0.tgz#329f4225365187d9fbe7a3b5c5d8e897fa703b30" - integrity sha512-1tBnfnCI23jXqGW15cagCjn2GgD487VST1dMG8P5LRzrSfx+kUzqFyTrjMNIwgq1tVaF4HnDpFMUuyrzTLKphw== +"@intlify/core-base@9.7.1": + version "9.7.1" + resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.7.1.tgz#d31315a8e9cf027678b65a4155287143ab91b8fd" + integrity sha512-jPJTeECEhqQ7g//8g3Fb79j5SzSSRqlFCWD6pcX94uMLXU+L1m07gVZnnvzoJBnaMyJHiiwxOqZVfvu6rQfLvw== dependencies: - "@intlify/message-compiler" "9.7.0" - "@intlify/shared" "9.7.0" + "@intlify/message-compiler" "9.7.1" + "@intlify/shared" "9.7.1" -"@intlify/message-compiler@9.7.0": - version "9.7.0" - resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.7.0.tgz#6371127c5a2a4f50ec59728f85a7786e3478c931" - integrity sha512-/YdZCio2L2tCM5bZ2eMHbSEIQNPh1QqvZIOLI/yCVKXLscis7O0SsR2nmuU/DfCJ3iSeI8juw82C2wLvfsAeww== +"@intlify/message-compiler@9.7.1": + version "9.7.1" + resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.7.1.tgz#2cd5f26c6b9f19afffd62e69d192cc4e9e51ba9a" + integrity sha512-HfIr2Hn/K7b0Zv4kGqkxAxwtipyxAwhI9a3krN5cuhH/G9gkaik7of1PdzjR3Mix43t2onBiKYQyaU7mo7e0aA== dependencies: - "@intlify/shared" "9.7.0" + "@intlify/shared" "9.7.1" source-map-js "^1.0.2" "@intlify/message-compiler@^9.4.0": @@ -240,10 +240,10 @@ resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.4.0.tgz#4a78d462fc82433db900981e12eb5b1aae3d6085" integrity sha512-AFqymip2kToqA0B6KZPg5jSrdcVHoli9t/VhGKE2iiMq9utFuMoGdDC/JOCIZgwxo6aXAk86QyU2XtzEoMuZ6A== -"@intlify/shared@9.7.0": - version "9.7.0" - resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.7.0.tgz#96166a54b781997db92259772e9621d3f7dff9a5" - integrity sha512-PUkEuk//YKu4CHS5ah3mNa3XL/+TZj6rAY/6yYN+GCNFd2u+uWUkeuwE4Q6t8dydRWlErOePHHS0KyNoof/oBw== +"@intlify/shared@9.7.1": + version "9.7.1" + resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.7.1.tgz#a4902421aacda2d716981eb9528aef0163c7bb0a" + integrity sha512-CBKnHzlUYGrk5QII9q4nElAQKO5cX1rRx8VmSWXltyOZjbkGHXYQTHULn6KwRi+CypuBCfmPkyPBHMzosypIeg== "@intlify/unplugin-vue-i18n@^1.5.0": version "1.5.0" @@ -408,20 +408,20 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.4.1.tgz#8311b77e6cce322865ba12ada8c3779369610d18" integrity sha512-eAhItDX9yQtZVM3yvXS/VR3qPqcnXvnLyx1pLXl4JzyNMBNO3KC986t/iAg2zcMzpAp9JSvxB5VZGnBiNoA98w== -"@rushstack/eslint-patch@^1.5.1": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz#5f1b518ec5fa54437c0b7c4a821546c64fed6922" - integrity sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA== +"@rushstack/eslint-patch@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.6.0.tgz#1898e7a7b943680d757417a47fb10f5fcc230b39" + integrity sha512-2/U3GXA6YiPYQDLGwtGlnNgKYBSwCFIHf8Y9LUY5VATHdtbLlU0Y1R3QoBnT0aB4qv/BEiVVsj7LJXoQCgJ2vA== "@tsconfig/node18@^18.2.2": version "18.2.2" resolved "https://registry.yarnpkg.com/@tsconfig/node18/-/node18-18.2.2.tgz#81fb16ecff0d400b1cbadbf76713b50f331029ce" integrity sha512-d6McJeGsuoRlwWZmVIeE8CUA27lu6jLjvv1JzqmpsytOYYbVi1tHZEnwCNVOXnj4pyLvneZlFlpXUK+X9wBWyw== -"@types/bootstrap@^5.2.9": - version "5.2.9" - resolved "https://registry.yarnpkg.com/@types/bootstrap/-/bootstrap-5.2.9.tgz#5040df5d8d12cb9fb6268a33b8d87234af15e09a" - integrity sha512-Fcg4nORBKaVUAG4F0ePWcatWQVfr3NAT9XIN+hl1PaiAwb4tq55+iua9R3exsbB3yyfhyQlHYg2foTlW86J+RA== +"@types/bootstrap@^5.2.10": + version "5.2.10" + resolved "https://registry.yarnpkg.com/@types/bootstrap/-/bootstrap-5.2.10.tgz#58506463bccc6602bc051487ad8d3a6458f94c6c" + integrity sha512-F2X+cd6551tep0MvVZ6nM8v7XgGN/twpdNDjqS1TUM7YFNEtQYWk+dKAnH+T1gr6QgCoGMPl487xw/9hXooa2g== dependencies: "@popperjs/core" "^2.9.2" @@ -435,10 +435,10 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== -"@types/node@^20.9.0": - version "20.9.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.0.tgz#bfcdc230583aeb891cf51e73cfdaacdd8deae298" - integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw== +"@types/node@^20.9.4": + version "20.9.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.4.tgz#cc8f970e869c26834bdb7ed480b30ede622d74c7" + integrity sha512-wmyg8HUhcn6ACjsn8oKYjkN/zUzQeNtMy44weTJSM6p4MMzEOuKbA3OjJ267uPCOW7Xex9dyrNTful8XTQYoDA== dependencies: undici-types "~5.26.4" @@ -447,10 +447,10 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.1.tgz#0480eeb7221eb9bc398ad7432c9d7e14b1a5a367" integrity sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg== -"@types/sortablejs@^1.15.5": - version "1.15.5" - resolved "https://registry.yarnpkg.com/@types/sortablejs/-/sortablejs-1.15.5.tgz#c59e51765bc53c920192de0d0202f75b7ce4cb3f" - integrity sha512-qqqbEFbB1EZt08I1Ok2BA3Sx0zlI8oizdIguMsajk4Yo/iHgXhCb3GM6N09JOJqT9xIMYM9LTFy8vit3RNY71Q== +"@types/sortablejs@^1.15.7": + version "1.15.7" + resolved "https://registry.yarnpkg.com/@types/sortablejs/-/sortablejs-1.15.7.tgz#11f85e98fce2854708e5c6d6011f7a236d79ae9f" + integrity sha512-PvgWCx1Lbgm88FdQ6S7OGvLIjWS66mudKPlfdrWil0TjsO5zmoZmzoKiiwRShs1dwPgrlkr0N4ewuy0/+QUXYQ== "@types/spark-md5@^3.0.4": version "3.0.4" @@ -1190,15 +1190,15 @@ eslint-visitor-keys@^3.4.1: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== -eslint@^8.53.0: - version "8.53.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.53.0.tgz#14f2c8244298fcae1f46945459577413ba2697ce" - integrity sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag== +eslint@^8.54.0: + version "8.54.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.54.0.tgz#588e0dd4388af91a2e8fa37ea64924074c783537" + integrity sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" "@eslint/eslintrc" "^2.1.3" - "@eslint/js" "8.53.0" + "@eslint/js" "8.54.0" "@humanwhocodes/config-array" "^0.11.13" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -2482,10 +2482,10 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -typescript@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" - integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== +typescript@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" + integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== ufo@^1.1.2: version "1.1.2" @@ -2556,10 +2556,10 @@ vite-plugin-css-injected-by-js@^3.3.0: resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.3.0.tgz#c19480a9e42a95c5bced976a9dde1446f9bd91ff" integrity sha512-xG+jyHNCmUqi/TXp6q88wTJGeAOrNLSyUUTp4qEQ9QZLGcHWQQsCsSSKa59rPMQr8sOzfzmWDd8enGqfH/dBew== -vite@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.0.tgz#3bfb65acda2a97127e4fa240156664a1f234ce08" - integrity sha512-ESJVM59mdyGpsiNAeHQOR/0fqNoOyWPYesFto8FFZugfmhdHx8Fzd8sF3Q/xkVhZsyOxHfdM7ieiVAorI9RjFw== +vite@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.2.tgz#3c94627dace83b9bf04b64eaf618038e30fb95c0" + integrity sha512-6CCq1CAJCNM1ya2ZZA7+jS2KgnhbzvxakmlIjN24cF/PXhRMzpM/z8QgsVJA/Dm5fWUWnVEsmtBoMhmerPxT0g== dependencies: esbuild "^0.19.3" postcss "^8.4.31" @@ -2580,13 +2580,13 @@ vue-eslint-parser@^9.3.1: lodash "^4.17.21" semver "^7.3.6" -vue-i18n@^9.7.0: - version "9.7.0" - resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.7.0.tgz#c88592ade72c651d6879895244d348f2892c5646" - integrity sha512-8Z8kSz9U2juzuAf+6mjW1HTd5pIlYuFJZkC+HvYOglFdpzwc2rTUGjxKwN8xGdtGur1MFnyJ44TSr+TksJtY8A== +vue-i18n@^9.7.1: + version "9.7.1" + resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.7.1.tgz#41393b066f6ff69d9be2ba31a59b043263c77289" + integrity sha512-A6DzWqJQMdzBj+392+g3zIgGV0FnFC7o/V+txs5yIALANEZzY6ZV8hM2wvZR3nTbQI7dntAmzBHMeoEteJO0kQ== dependencies: - "@intlify/core-base" "9.7.0" - "@intlify/shared" "9.7.0" + "@intlify/core-base" "9.7.1" + "@intlify/shared" "9.7.1" "@vue/devtools-api" "^6.5.0" vue-router@^4.2.5: From a7c9c2df1ade264cd56c10a06c8dea604a3cfb85 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 20 Nov 2023 20:50:01 +0100 Subject: [PATCH 11/89] Initialize TaskScheduler --- include/Scheduler.h | 6 ++++++ platformio.ini | 2 ++ src/Scheduler.cpp | 7 +++++++ src/main.cpp | 4 ++++ 4 files changed, 19 insertions(+) create mode 100644 include/Scheduler.h create mode 100644 src/Scheduler.cpp diff --git a/include/Scheduler.h b/include/Scheduler.h new file mode 100644 index 00000000..44f1b51d --- /dev/null +++ b/include/Scheduler.h @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +extern Scheduler scheduler; \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 10ce4571..8bf5b01d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -23,6 +23,7 @@ platform = espressif32@6.3.2 build_flags = -DPIOENV=\"$PIOENV\" + -D_TASK_STD_FUNCTION=1 -Wall -Wextra -Werror -std=c++17 -std=gnu++17 @@ -36,6 +37,7 @@ lib_deps = nrf24/RF24 @ ^1.4.8 olikraus/U8g2 @ ^2.35.7 buelowp/sunset @ ^1.1.7 + https://github.com/arkhipenko/TaskScheduler#testing extra_scripts = pre:pio-scripts/auto_firmware_version.py diff --git a/src/Scheduler.cpp b/src/Scheduler.cpp new file mode 100644 index 00000000..79dfd9c8 --- /dev/null +++ b/src/Scheduler.cpp @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Thomas Basler and others + */ +#include "Scheduler.h" + +Scheduler scheduler; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index bc6cb86a..2976cbab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,12 +16,14 @@ #include "NetworkSettings.h" #include "NtpSettings.h" #include "PinMapping.h" +#include "Scheduler.h" #include "SunPosition.h" #include "Utils.h" #include "WebApi.h" #include "defaults.h" #include #include +#include void setup() { @@ -149,6 +151,8 @@ void setup() void loop() { + scheduler.execute(); + NetworkSettings.loop(); yield(); InverterSettings.loop(); From 12031ed09ec65b949151feb7494537f67b79710b Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 20 Nov 2023 20:57:14 +0100 Subject: [PATCH 12/89] Migrate SunPosition to TaskScheduler --- include/SunPosition.h | 7 +++++-- src/SunPosition.cpp | 6 +++++- src/main.cpp | 4 +--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/include/SunPosition.h b/include/SunPosition.h index 1e1508d3..81414c97 100644 --- a/include/SunPosition.h +++ b/include/SunPosition.h @@ -1,14 +1,14 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include #include class SunPositionClass { public: SunPositionClass(); - void init(); - void loop(); + void init(Scheduler* scheduler); bool isDayPeriod(); bool isSunsetAvailable(); @@ -17,10 +17,13 @@ public: void setDoRecalc(bool doRecalc); private: + void loop(); void updateSunData(); bool checkRecalcDayChanged(); bool getSunTime(struct tm* info, uint32_t offset); + Task _loopTask; + bool _isSunsetAvailable = true; uint32_t _sunriseMinutes = 0; uint32_t _sunsetMinutes = 0; diff --git a/src/SunPosition.cpp b/src/SunPosition.cpp index 48e30820..5e10d982 100644 --- a/src/SunPosition.cpp +++ b/src/SunPosition.cpp @@ -13,8 +13,12 @@ SunPositionClass::SunPositionClass() { } -void SunPositionClass::init() +void SunPositionClass::init(Scheduler* scheduler) { + scheduler->addTask(_loopTask); + _loopTask.setCallback(std::bind(&SunPositionClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); } void SunPositionClass::loop() diff --git a/src/main.cpp b/src/main.cpp index 2976cbab..6385b37b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -93,7 +93,7 @@ void setup() // Initialize SunPosition MessageOutput.print("Initialize SunPosition... "); - SunPosition.init(); + SunPosition.init(&scheduler); MessageOutput.println("done"); // Initialize MqTT @@ -171,8 +171,6 @@ void loop() yield(); Display.loop(); yield(); - SunPosition.loop(); - yield(); MessageOutput.loop(); yield(); LedSingle.loop(); From c045b5df48e50dd4167dc4a7740d8dafb85e6ef4 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 20 Nov 2023 21:06:07 +0100 Subject: [PATCH 13/89] Migrate Datastore to TaskScheduler --- include/Datastore.h | 10 +-- src/Datastore.cpp | 156 +++++++++++++++++++++++--------------------- src/main.cpp | 4 +- 3 files changed, 87 insertions(+), 83 deletions(-) diff --git a/include/Datastore.h b/include/Datastore.h index 6e4c0396..b667f9d0 100644 --- a/include/Datastore.h +++ b/include/Datastore.h @@ -1,13 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include +#include #include class DatastoreClass { public: - void init(); - void loop(); + void init(Scheduler* scheduler); // Sum of yield total of all enabled inverters, a inverter which is just disabled at night is also included float getTotalAcYieldTotalEnabled(); @@ -58,7 +57,10 @@ public: bool getIsAllEnabledReachable(); private: - TimeoutHelper _updateTimeout; + void loop(); + + Task _loopTask; + std::mutex _mutex; float _totalAcYieldTotalEnabled = 0; diff --git a/src/Datastore.cpp b/src/Datastore.cpp index 4ff67b80..b0be1f62 100644 --- a/src/Datastore.cpp +++ b/src/Datastore.cpp @@ -8,105 +8,109 @@ DatastoreClass Datastore; -void DatastoreClass::init() +void DatastoreClass::init(Scheduler* scheduler) { - _updateTimeout.set(1000); + scheduler->addTask(_loopTask); + _loopTask.setCallback(std::bind(&DatastoreClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.setInterval(1 * TASK_SECOND); + _loopTask.enable(); } void DatastoreClass::loop() { - if (Hoymiles.isAllRadioIdle() && _updateTimeout.occured()) { + if (!Hoymiles.isAllRadioIdle()) { + _loopTask.forceNextIteration(); + return; + } - uint8_t isProducing = 0; - uint8_t isReachable = 0; - uint8_t pollEnabledCount = 0; + uint8_t isProducing = 0; + uint8_t isReachable = 0; + uint8_t pollEnabledCount = 0; - std::lock_guard lock(_mutex); + std::lock_guard lock(_mutex); - _totalAcYieldTotalEnabled = 0; - _totalAcYieldTotalDigits = 0; + _totalAcYieldTotalEnabled = 0; + _totalAcYieldTotalDigits = 0; - _totalAcYieldDayEnabled = 0; - _totalAcYieldDayDigits = 0; + _totalAcYieldDayEnabled = 0; + _totalAcYieldDayDigits = 0; - _totalAcPowerEnabled = 0; - _totalAcPowerDigits = 0; + _totalAcPowerEnabled = 0; + _totalAcPowerDigits = 0; - _totalDcPowerEnabled = 0; - _totalDcPowerDigits = 0; + _totalDcPowerEnabled = 0; + _totalDcPowerDigits = 0; - _totalDcPowerIrradiation = 0; - _totalDcIrradiationInstalled = 0; + _totalDcPowerIrradiation = 0; + _totalDcIrradiationInstalled = 0; - _isAllEnabledProducing = true; - _isAllEnabledReachable = true; + _isAllEnabledProducing = true; + _isAllEnabledReachable = true; - for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { - auto inv = Hoymiles.getInverterByPos(i); - if (inv == nullptr) { - continue; - } + for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { + auto inv = Hoymiles.getInverterByPos(i); + if (inv == nullptr) { + continue; + } - auto cfg = Configuration.getInverterConfig(inv->serial()); - if (cfg == nullptr) { - continue; - } + auto cfg = Configuration.getInverterConfig(inv->serial()); + if (cfg == nullptr) { + continue; + } + if (inv->getEnablePolling()) { + pollEnabledCount++; + } + + if (inv->isProducing()) { + isProducing++; + } else { if (inv->getEnablePolling()) { - pollEnabledCount++; - } - - if (inv->isProducing()) { - isProducing++; - } else { - if (inv->getEnablePolling()) { - _isAllEnabledProducing = false; - } - } - - if (inv->isReachable()) { - isReachable++; - } else { - if (inv->getEnablePolling()) { - _isAllEnabledReachable = false; - } - } - - for (auto& c : inv->Statistics()->getChannelsByType(TYPE_AC)) { - if (cfg->Poll_Enable) { - _totalAcYieldTotalEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YT); - _totalAcYieldDayEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YD); - - _totalAcYieldTotalDigits = max(_totalAcYieldTotalDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YT)); - _totalAcYieldDayDigits = max(_totalAcYieldDayDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YD)); - } - if (inv->getEnablePolling()) { - _totalAcPowerEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_PAC); - _totalAcPowerDigits = max(_totalAcPowerDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_PAC)); - } - } - - for (auto& c : inv->Statistics()->getChannelsByType(TYPE_DC)) { - if (inv->getEnablePolling()) { - _totalDcPowerEnabled += inv->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC); - _totalDcPowerDigits = max(_totalDcPowerDigits, inv->Statistics()->getChannelFieldDigits(TYPE_DC, c, FLD_PDC)); - - if (inv->Statistics()->getStringMaxPower(c) > 0) { - _totalDcPowerIrradiation += inv->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC); - _totalDcIrradiationInstalled += inv->Statistics()->getStringMaxPower(c); - } - } + _isAllEnabledProducing = false; } } - _isAtLeastOneProducing = isProducing > 0; - _isAtLeastOneReachable = isReachable > 0; - _isAtLeastOnePollEnabled = pollEnabledCount > 0; + if (inv->isReachable()) { + isReachable++; + } else { + if (inv->getEnablePolling()) { + _isAllEnabledReachable = false; + } + } - _totalDcIrradiation = _totalDcIrradiationInstalled > 0 ? _totalDcPowerIrradiation / _totalDcIrradiationInstalled * 100.0f : 0; + for (auto& c : inv->Statistics()->getChannelsByType(TYPE_AC)) { + if (cfg->Poll_Enable) { + _totalAcYieldTotalEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YT); + _totalAcYieldDayEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YD); - _updateTimeout.reset(); + _totalAcYieldTotalDigits = max(_totalAcYieldTotalDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YT)); + _totalAcYieldDayDigits = max(_totalAcYieldDayDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_YD)); + } + if (inv->getEnablePolling()) { + _totalAcPowerEnabled += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_PAC); + _totalAcPowerDigits = max(_totalAcPowerDigits, inv->Statistics()->getChannelFieldDigits(TYPE_AC, c, FLD_PAC)); + } + } + + for (auto& c : inv->Statistics()->getChannelsByType(TYPE_DC)) { + if (inv->getEnablePolling()) { + _totalDcPowerEnabled += inv->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC); + _totalDcPowerDigits = max(_totalDcPowerDigits, inv->Statistics()->getChannelFieldDigits(TYPE_DC, c, FLD_PDC)); + + if (inv->Statistics()->getStringMaxPower(c) > 0) { + _totalDcPowerIrradiation += inv->Statistics()->getChannelFieldValue(TYPE_DC, c, FLD_PDC); + _totalDcIrradiationInstalled += inv->Statistics()->getStringMaxPower(c); + } + } + } } + + _isAtLeastOneProducing = isProducing > 0; + _isAtLeastOneReachable = isReachable > 0; + _isAtLeastOnePollEnabled = pollEnabledCount > 0; + + _totalDcIrradiation = _totalDcIrradiationInstalled > 0 ? _totalDcPowerIrradiation / _totalDcIrradiationInstalled * 100.0f : 0; } float DatastoreClass::getTotalAcYieldTotalEnabled() diff --git a/src/main.cpp b/src/main.cpp index 6385b37b..a7b1fbf4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -146,7 +146,7 @@ void setup() InverterSettings.init(); - Datastore.init(); + Datastore.init(&scheduler); } void loop() @@ -157,8 +157,6 @@ void loop() yield(); InverterSettings.loop(); yield(); - Datastore.loop(); - yield(); MqttHandleDtu.loop(); yield(); MqttHandleInverter.loop(); From 98c30d104262ae54d42164ff428ba07549e75565 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 20 Nov 2023 21:28:27 +0100 Subject: [PATCH 14/89] Migrate MqttHandleInverterTotal to TaskSchedule --- include/MqttHandleInverterTotal.h | 9 +++++---- src/MqttHandleInverterTotal.cpp | 30 +++++++++++++++++------------- src/main.cpp | 4 +--- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/include/MqttHandleInverterTotal.h b/include/MqttHandleInverterTotal.h index fa4ce4b6..1ff9567d 100644 --- a/include/MqttHandleInverterTotal.h +++ b/include/MqttHandleInverterTotal.h @@ -1,15 +1,16 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include +#include class MqttHandleInverterTotalClass { public: - void init(); - void loop(); + void init(Scheduler* scheduler); private: - TimeoutHelper _lastPublish; + void loop(); + + Task _loopTask; }; extern MqttHandleInverterTotalClass MqttHandleInverterTotal; \ No newline at end of file diff --git a/src/MqttHandleInverterTotal.cpp b/src/MqttHandleInverterTotal.cpp index 5e18acd4..45b2c9bb 100644 --- a/src/MqttHandleInverterTotal.cpp +++ b/src/MqttHandleInverterTotal.cpp @@ -10,26 +10,30 @@ MqttHandleInverterTotalClass MqttHandleInverterTotal; -void MqttHandleInverterTotalClass::init() +void MqttHandleInverterTotalClass::init(Scheduler* scheduler) { - _lastPublish.set(Configuration.get().Mqtt.PublishInterval * 1000); + scheduler->addTask(_loopTask); + _loopTask.setCallback(std::bind(&MqttHandleInverterTotalClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND); + _loopTask.enable(); } void MqttHandleInverterTotalClass::loop() { + // Update interval from config + _loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND); + if (!MqttSettings.getConnected() || !Hoymiles.isAllRadioIdle()) { + _loopTask.forceNextIteration(); return; } - if (_lastPublish.occured()) { - MqttSettings.publish("ac/power", String(Datastore.getTotalAcPowerEnabled(), Datastore.getTotalAcPowerDigits())); - MqttSettings.publish("ac/yieldtotal", String(Datastore.getTotalAcYieldTotalEnabled(), Datastore.getTotalAcYieldTotalDigits())); - MqttSettings.publish("ac/yieldday", String(Datastore.getTotalAcYieldDayEnabled(), Datastore.getTotalAcYieldDayDigits())); - MqttSettings.publish("ac/is_valid", String(Datastore.getIsAllEnabledReachable())); - MqttSettings.publish("dc/power", String(Datastore.getTotalDcPowerEnabled(), Datastore.getTotalDcPowerDigits())); - MqttSettings.publish("dc/irradiation", String(Datastore.getTotalDcIrradiation(), 3)); - MqttSettings.publish("dc/is_valid", String(Datastore.getIsAllEnabledReachable())); - - _lastPublish.set(Configuration.get().Mqtt.PublishInterval * 1000); - } + MqttSettings.publish("ac/power", String(Datastore.getTotalAcPowerEnabled(), Datastore.getTotalAcPowerDigits())); + MqttSettings.publish("ac/yieldtotal", String(Datastore.getTotalAcYieldTotalEnabled(), Datastore.getTotalAcYieldTotalDigits())); + MqttSettings.publish("ac/yieldday", String(Datastore.getTotalAcYieldDayEnabled(), Datastore.getTotalAcYieldDayDigits())); + MqttSettings.publish("ac/is_valid", String(Datastore.getIsAllEnabledReachable())); + MqttSettings.publish("dc/power", String(Datastore.getTotalDcPowerEnabled(), Datastore.getTotalDcPowerDigits())); + MqttSettings.publish("dc/irradiation", String(Datastore.getTotalDcIrradiation(), 3)); + MqttSettings.publish("dc/is_valid", String(Datastore.getIsAllEnabledReachable())); } diff --git a/src/main.cpp b/src/main.cpp index a7b1fbf4..b2072eec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -101,7 +101,7 @@ void setup() MqttSettings.init(); MqttHandleDtu.init(); MqttHandleInverter.init(); - MqttHandleInverterTotal.init(); + MqttHandleInverterTotal.init(&scheduler); MqttHandleHass.init(); MessageOutput.println("done"); @@ -161,8 +161,6 @@ void loop() yield(); MqttHandleInverter.loop(); yield(); - MqttHandleInverterTotal.loop(); - yield(); MqttHandleHass.loop(); yield(); WebApi.loop(); From 48a27fbfadabef97bbbb82a076aa4518f1251d13 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 20 Nov 2023 21:31:58 +0100 Subject: [PATCH 15/89] Migrate MqttHandleHass to TaskScheduler --- include/MqttHandleHass.h | 7 +++++-- src/MqttHandleHass.cpp | 6 +++++- src/main.cpp | 4 +--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/include/MqttHandleHass.h b/include/MqttHandleHass.h index 5bf7e71e..27f6e911 100644 --- a/include/MqttHandleHass.h +++ b/include/MqttHandleHass.h @@ -3,6 +3,7 @@ #include #include +#include // mqtt discovery device classes enum { @@ -50,12 +51,12 @@ const byteAssign_fieldDeviceClass_t deviceFieldAssignment[] = { class MqttHandleHassClass { public: - void init(); - void loop(); + void init(Scheduler* scheduler); void publishConfig(); void forceUpdate(); private: + void loop(); void publish(const String& subtopic, const String& payload); void publishField(std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel, byteAssign_fieldDeviceClass_t fieldType, bool clear = false); void publishInverterButton(std::shared_ptr inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload); @@ -63,6 +64,8 @@ private: void publishInverterBinarySensor(std::shared_ptr inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off); void createDeviceInfo(JsonObject& object, std::shared_ptr inv); + Task _loopTask; + bool _wasConnected = false; bool _updateForced = false; }; diff --git a/src/MqttHandleHass.cpp b/src/MqttHandleHass.cpp index e5842240..5880fd4e 100644 --- a/src/MqttHandleHass.cpp +++ b/src/MqttHandleHass.cpp @@ -9,8 +9,12 @@ MqttHandleHassClass MqttHandleHass; -void MqttHandleHassClass::init() +void MqttHandleHassClass::init(Scheduler* scheduler) { + scheduler->addTask(_loopTask); + _loopTask.setCallback(std::bind(&MqttHandleHassClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); } void MqttHandleHassClass::loop() diff --git a/src/main.cpp b/src/main.cpp index b2072eec..793a4850 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -102,7 +102,7 @@ void setup() MqttHandleDtu.init(); MqttHandleInverter.init(); MqttHandleInverterTotal.init(&scheduler); - MqttHandleHass.init(); + MqttHandleHass.init(&scheduler); MessageOutput.println("done"); // Initialize WebApi @@ -161,8 +161,6 @@ void loop() yield(); MqttHandleInverter.loop(); yield(); - MqttHandleHass.loop(); - yield(); WebApi.loop(); yield(); Display.loop(); From 5c501f879fa71dc68bad7abb7e8f545a61c8903c Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 20 Nov 2023 21:35:56 +0100 Subject: [PATCH 16/89] Migrate MqttHandleDtu to TaskScheduler --- include/MqttHandleDtu.h | 8 +++++--- src/MqttHandleDtu.cpp | 28 +++++++++++++++------------- src/main.cpp | 4 +--- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/include/MqttHandleDtu.h b/include/MqttHandleDtu.h index fb566345..e580542c 100644 --- a/include/MqttHandleDtu.h +++ b/include/MqttHandleDtu.h @@ -1,15 +1,17 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include class MqttHandleDtuClass { public: - void init(); - void loop(); + void init(Scheduler* scheduler); private: - uint32_t _lastPublish = 0; + void loop(); + + Task _loopTask; }; extern MqttHandleDtuClass MqttHandleDtu; \ No newline at end of file diff --git a/src/MqttHandleDtu.cpp b/src/MqttHandleDtu.cpp index 000b6c57..feff6318 100644 --- a/src/MqttHandleDtu.cpp +++ b/src/MqttHandleDtu.cpp @@ -10,27 +10,29 @@ MqttHandleDtuClass MqttHandleDtu; -void MqttHandleDtuClass::init() +void MqttHandleDtuClass::init(Scheduler* scheduler) { + scheduler->addTask(_loopTask); + _loopTask.setCallback(std::bind(&MqttHandleDtuClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND); + _loopTask.enable(); } void MqttHandleDtuClass::loop() { + _loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND); + if (!MqttSettings.getConnected() || !Hoymiles.isAllRadioIdle()) { + _loopTask.forceNextIteration(); return; } - const CONFIG_T& config = Configuration.get(); - - if (millis() - _lastPublish > (config.Mqtt.PublishInterval * 1000)) { - MqttSettings.publish("dtu/uptime", String(millis() / 1000)); - MqttSettings.publish("dtu/ip", NetworkSettings.localIP().toString()); - MqttSettings.publish("dtu/hostname", NetworkSettings.getHostname()); - if (NetworkSettings.NetworkMode() == network_mode::WiFi) { - MqttSettings.publish("dtu/rssi", String(WiFi.RSSI())); - MqttSettings.publish("dtu/bssid", String(WiFi.BSSIDstr())); - } - - _lastPublish = millis(); + MqttSettings.publish("dtu/uptime", String(millis() / 1000)); + MqttSettings.publish("dtu/ip", NetworkSettings.localIP().toString()); + MqttSettings.publish("dtu/hostname", NetworkSettings.getHostname()); + if (NetworkSettings.NetworkMode() == network_mode::WiFi) { + MqttSettings.publish("dtu/rssi", String(WiFi.RSSI())); + MqttSettings.publish("dtu/bssid", String(WiFi.BSSIDstr())); } } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 793a4850..da7f74d5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -99,7 +99,7 @@ void setup() // Initialize MqTT MessageOutput.print("Initialize MqTT... "); MqttSettings.init(); - MqttHandleDtu.init(); + MqttHandleDtu.init(&scheduler); MqttHandleInverter.init(); MqttHandleInverterTotal.init(&scheduler); MqttHandleHass.init(&scheduler); @@ -157,8 +157,6 @@ void loop() yield(); InverterSettings.loop(); yield(); - MqttHandleDtu.loop(); - yield(); MqttHandleInverter.loop(); yield(); WebApi.loop(); From 524483451fd98097a1fee0fb1eb01f881b876ef2 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 20 Nov 2023 21:42:58 +0100 Subject: [PATCH 17/89] Migrate MqttHandleInverter to TaskScheduler --- include/MqttHandleInverter.h | 9 +-- src/MqttHandleInverter.cpp | 119 ++++++++++++++++++----------------- src/main.cpp | 4 +- 3 files changed, 67 insertions(+), 65 deletions(-) diff --git a/include/MqttHandleInverter.h b/include/MqttHandleInverter.h index 0194bacf..874e3e9b 100644 --- a/include/MqttHandleInverter.h +++ b/include/MqttHandleInverter.h @@ -3,22 +3,23 @@ #include "Configuration.h" #include -#include +#include #include class MqttHandleInverterClass { public: - void init(); - void loop(); + void init(Scheduler* scheduler); static String getTopic(std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); private: + void loop(); void publishField(std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total); + Task _loopTask; + uint32_t _lastPublishStats[INV_MAX_COUNT] = { 0 }; - uint32_t _lastPublish = 0; FieldId_t _publishFields[14] = { FLD_UDC, diff --git a/src/MqttHandleInverter.cpp b/src/MqttHandleInverter.cpp index 700b75b5..b0d1d74c 100644 --- a/src/MqttHandleInverter.cpp +++ b/src/MqttHandleInverter.cpp @@ -18,7 +18,7 @@ MqttHandleInverterClass MqttHandleInverter; -void MqttHandleInverterClass::init() +void MqttHandleInverterClass::init(Scheduler* scheduler) { using std::placeholders::_1; using std::placeholders::_2; @@ -34,90 +34,93 @@ void MqttHandleInverterClass::init() MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_POWER).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_RESTART).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); + + scheduler->addTask(_loopTask); + _loopTask.setCallback(std::bind(&MqttHandleInverterClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND); + _loopTask.enable(); } void MqttHandleInverterClass::loop() { + _loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND); + if (!MqttSettings.getConnected() || !Hoymiles.isAllRadioIdle()) { + _loopTask.forceNextIteration(); return; } - const CONFIG_T& config = Configuration.get(); + // Loop all inverters + for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { + auto inv = Hoymiles.getInverterByPos(i); - if (millis() - _lastPublish > (config.Mqtt.PublishInterval * 1000)) { - // Loop all inverters - for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { - auto inv = Hoymiles.getInverterByPos(i); + String subtopic = inv->serialString(); - String subtopic = inv->serialString(); + // Name + MqttSettings.publish(subtopic + "/name", inv->name()); - // Name - MqttSettings.publish(subtopic + "/name", inv->name()); + if (inv->DevInfo()->getLastUpdate() > 0) { + // Bootloader Version + MqttSettings.publish(subtopic + "/device/bootloaderversion", String(inv->DevInfo()->getFwBootloaderVersion())); - if (inv->DevInfo()->getLastUpdate() > 0) { - // Bootloader Version - MqttSettings.publish(subtopic + "/device/bootloaderversion", String(inv->DevInfo()->getFwBootloaderVersion())); + // Firmware Version + MqttSettings.publish(subtopic + "/device/fwbuildversion", String(inv->DevInfo()->getFwBuildVersion())); - // Firmware Version - MqttSettings.publish(subtopic + "/device/fwbuildversion", String(inv->DevInfo()->getFwBuildVersion())); + // Firmware Build DateTime + char timebuffer[32]; + const time_t t = inv->DevInfo()->getFwBuildDateTime(); + std::strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", gmtime(&t)); + MqttSettings.publish(subtopic + "/device/fwbuilddatetime", String(timebuffer)); - // Firmware Build DateTime - char timebuffer[32]; - const time_t t = inv->DevInfo()->getFwBuildDateTime(); - std::strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", gmtime(&t)); - MqttSettings.publish(subtopic + "/device/fwbuilddatetime", String(timebuffer)); + // Hardware part number + MqttSettings.publish(subtopic + "/device/hwpartnumber", String(inv->DevInfo()->getHwPartNumber())); - // Hardware part number - MqttSettings.publish(subtopic + "/device/hwpartnumber", String(inv->DevInfo()->getHwPartNumber())); + // Hardware version + MqttSettings.publish(subtopic + "/device/hwversion", inv->DevInfo()->getHwVersion()); + } - // Hardware version - MqttSettings.publish(subtopic + "/device/hwversion", inv->DevInfo()->getHwVersion()); + if (inv->SystemConfigPara()->getLastUpdate() > 0) { + // Limit + MqttSettings.publish(subtopic + "/status/limit_relative", String(inv->SystemConfigPara()->getLimitPercent())); + + uint16_t maxpower = inv->DevInfo()->getMaxPower(); + if (maxpower > 0) { + MqttSettings.publish(subtopic + "/status/limit_absolute", String(inv->SystemConfigPara()->getLimitPercent() * maxpower / 100)); } + } - if (inv->SystemConfigPara()->getLastUpdate() > 0) { - // Limit - MqttSettings.publish(subtopic + "/status/limit_relative", String(inv->SystemConfigPara()->getLimitPercent())); + MqttSettings.publish(subtopic + "/status/reachable", String(inv->isReachable())); + MqttSettings.publish(subtopic + "/status/producing", String(inv->isProducing())); - uint16_t maxpower = inv->DevInfo()->getMaxPower(); - if (maxpower > 0) { - MqttSettings.publish(subtopic + "/status/limit_absolute", String(inv->SystemConfigPara()->getLimitPercent() * maxpower / 100)); - } - } + if (inv->Statistics()->getLastUpdate() > 0) { + MqttSettings.publish(subtopic + "/status/last_update", String(std::time(0) - (millis() - inv->Statistics()->getLastUpdate()) / 1000)); + } else { + MqttSettings.publish(subtopic + "/status/last_update", String(0)); + } - MqttSettings.publish(subtopic + "/status/reachable", String(inv->isReachable())); - MqttSettings.publish(subtopic + "/status/producing", String(inv->isProducing())); + uint32_t lastUpdateInternal = inv->Statistics()->getLastUpdateFromInternal(); + if (inv->Statistics()->getLastUpdate() > 0 && (lastUpdateInternal != _lastPublishStats[i])) { + _lastPublishStats[i] = lastUpdateInternal; - if (inv->Statistics()->getLastUpdate() > 0) { - MqttSettings.publish(subtopic + "/status/last_update", String(std::time(0) - (millis() - inv->Statistics()->getLastUpdate()) / 1000)); - } else { - MqttSettings.publish(subtopic + "/status/last_update", String(0)); - } - - uint32_t lastUpdateInternal = inv->Statistics()->getLastUpdateFromInternal(); - if (inv->Statistics()->getLastUpdate() > 0 && (lastUpdateInternal != _lastPublishStats[i])) { - _lastPublishStats[i] = lastUpdateInternal; - - // Loop all channels - for (auto& t : inv->Statistics()->getChannelTypes()) { - for (auto& c : inv->Statistics()->getChannelsByType(t)) { - if (t == TYPE_DC) { - INVERTER_CONFIG_T* inv_cfg = Configuration.getInverterConfig(inv->serial()); - if (inv_cfg != nullptr) { - // TODO(tbnobody) - MqttSettings.publish(inv->serialString() + "/" + String(static_cast(c) + 1) + "/name", inv_cfg->channel[c].Name); - } - } - for (uint8_t f = 0; f < sizeof(_publishFields) / sizeof(FieldId_t); f++) { - publishField(inv, t, c, _publishFields[f]); + // Loop all channels + for (auto& t : inv->Statistics()->getChannelTypes()) { + for (auto& c : inv->Statistics()->getChannelsByType(t)) { + if (t == TYPE_DC) { + INVERTER_CONFIG_T* inv_cfg = Configuration.getInverterConfig(inv->serial()); + if (inv_cfg != nullptr) { + // TODO(tbnobody) + MqttSettings.publish(inv->serialString() + "/" + String(static_cast(c) + 1) + "/name", inv_cfg->channel[c].Name); } } + for (uint8_t f = 0; f < sizeof(_publishFields) / sizeof(FieldId_t); f++) { + publishField(inv, t, c, _publishFields[f]); + } } } - - yield(); } - _lastPublish = millis(); + yield(); } } diff --git a/src/main.cpp b/src/main.cpp index da7f74d5..a4874e8a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -100,7 +100,7 @@ void setup() MessageOutput.print("Initialize MqTT... "); MqttSettings.init(); MqttHandleDtu.init(&scheduler); - MqttHandleInverter.init(); + MqttHandleInverter.init(&scheduler); MqttHandleInverterTotal.init(&scheduler); MqttHandleHass.init(&scheduler); MessageOutput.println("done"); @@ -157,8 +157,6 @@ void loop() yield(); InverterSettings.loop(); yield(); - MqttHandleInverter.loop(); - yield(); WebApi.loop(); yield(); Display.loop(); From 7881d955bd4b700ded17e833746e7fcacca60870 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 20 Nov 2023 21:47:44 +0100 Subject: [PATCH 18/89] Migrate LedSingle to TaskScheduler --- include/Led_Single.h | 8 ++++++-- src/Led_Single.cpp | 7 ++++++- src/main.cpp | 4 +--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/include/Led_Single.h b/include/Led_Single.h index ed811824..e01fdfe0 100644 --- a/include/Led_Single.h +++ b/include/Led_Single.h @@ -2,6 +2,7 @@ #pragma once #include "PinMapping.h" +#include #include #define LEDSINGLE_UPDATE_INTERVAL 2000 @@ -9,13 +10,16 @@ class LedSingleClass { public: LedSingleClass(); - void init(); - void loop(); + void init(Scheduler* scheduler); void turnAllOff(); void turnAllOn(); private: + void loop(); + + Task _loopTask; + enum class LedState_t { On, Off, diff --git a/src/Led_Single.cpp b/src/Led_Single.cpp index 24aabbd8..43825619 100644 --- a/src/Led_Single.cpp +++ b/src/Led_Single.cpp @@ -16,7 +16,7 @@ LedSingleClass::LedSingleClass() { } -void LedSingleClass::init() +void LedSingleClass::init(Scheduler* scheduler) { _blinkTimeout.set(500); _updateTimeout.set(LEDSINGLE_UPDATE_INTERVAL); @@ -33,6 +33,11 @@ void LedSingleClass::init() _ledState[i] = LedState_t::Off; } + + scheduler->addTask(_loopTask); + _loopTask.setCallback(std::bind(&LedSingleClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); } void LedSingleClass::loop() diff --git a/src/main.cpp b/src/main.cpp index a4874e8a..9e3404af 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -128,7 +128,7 @@ void setup() // Initialize Single LEDs MessageOutput.print("Initialize LEDs... "); - LedSingle.init(); + LedSingle.init(&scheduler); MessageOutput.println("done"); // Check for default DTU serial @@ -163,6 +163,4 @@ void loop() yield(); MessageOutput.loop(); yield(); - LedSingle.loop(); - yield(); } \ No newline at end of file From 150141103710433d91f2bcee352099987962db5a Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 20 Nov 2023 21:51:51 +0100 Subject: [PATCH 19/89] Migrate NetworkSettings to TaskScheduler --- include/NetworkSettings.h | 8 ++++++-- src/NetworkSettings.cpp | 7 ++++++- src/main.cpp | 4 +--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/include/NetworkSettings.h b/include/NetworkSettings.h index fa94c8b3..6bd99ec8 100644 --- a/include/NetworkSettings.h +++ b/include/NetworkSettings.h @@ -2,6 +2,7 @@ #pragma once #include +#include #include #include @@ -38,8 +39,7 @@ typedef struct NetworkEventCbList { class NetworkSettingsClass { public: NetworkSettingsClass(); - void init(); - void loop(); + void init(Scheduler* scheduler); void applyConfig(); void enableAdminMode(); String getApName(); @@ -57,11 +57,15 @@ public: void raiseEvent(network_event event); private: + void loop(); void setHostname(); void setStaticIp(); void handleMDNS(); void setupMode(); void NetworkEvent(WiFiEvent_t event); + + Task _loopTask; + bool adminEnabled = true; bool forceDisconnection = false; uint32_t adminTimeoutCounter = 0; diff --git a/src/NetworkSettings.cpp b/src/NetworkSettings.cpp index 5d43ba5c..9d22f175 100644 --- a/src/NetworkSettings.cpp +++ b/src/NetworkSettings.cpp @@ -18,7 +18,7 @@ NetworkSettingsClass::NetworkSettingsClass() dnsServer.reset(new DNSServer()); } -void NetworkSettingsClass::init() +void NetworkSettingsClass::init(Scheduler* scheduler) { using std::placeholders::_1; @@ -27,6 +27,11 @@ void NetworkSettingsClass::init() WiFi.onEvent(std::bind(&NetworkSettingsClass::NetworkEvent, this, _1)); setupMode(); + + scheduler->addTask(_loopTask); + _loopTask.setCallback(std::bind(&NetworkSettingsClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); } void NetworkSettingsClass::NetworkEvent(WiFiEvent_t event) diff --git a/src/main.cpp b/src/main.cpp index 9e3404af..63bc83cb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -82,7 +82,7 @@ void setup() // Initialize WiFi MessageOutput.print("Initialize Network... "); - NetworkSettings.init(); + NetworkSettings.init(&scheduler); MessageOutput.println("done"); NetworkSettings.applyConfig(); @@ -153,8 +153,6 @@ void loop() { scheduler.execute(); - NetworkSettings.loop(); - yield(); InverterSettings.loop(); yield(); WebApi.loop(); From 77779a1ed9358f3184d2bfd2b5b66c1c1a18d921 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 20 Nov 2023 21:55:00 +0100 Subject: [PATCH 20/89] Migrate InverterSettings to TaskScheduler --- include/InverterSettings.h | 8 ++++++-- src/InverterSettings.cpp | 7 ++++++- src/main.cpp | 4 +--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/include/InverterSettings.h b/include/InverterSettings.h index 6375dfcf..8de7253e 100644 --- a/include/InverterSettings.h +++ b/include/InverterSettings.h @@ -1,16 +1,20 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include #define INVERTER_UPDATE_SETTINGS_INTERVAL 60000l class InverterSettingsClass { public: - void init(); - void loop(); + void init(Scheduler* scheduler); private: + void loop(); + + Task _loopTask; + uint32_t _lastUpdate = 0; }; diff --git a/src/InverterSettings.cpp b/src/InverterSettings.cpp index 425ecd6f..22b331aa 100644 --- a/src/InverterSettings.cpp +++ b/src/InverterSettings.cpp @@ -25,7 +25,7 @@ InverterSettingsClass InverterSettings; -void InverterSettingsClass::init() +void InverterSettingsClass::init(Scheduler* scheduler) { const CONFIG_T& config = Configuration.get(); const PinMapping_t& pin = PinMapping.get(); @@ -87,6 +87,11 @@ void InverterSettingsClass::init() } else { MessageOutput.println("Invalid pin config"); } + + scheduler->addTask(_loopTask); + _loopTask.setCallback(std::bind(&InverterSettingsClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); } void InverterSettingsClass::loop() diff --git a/src/main.cpp b/src/main.cpp index 63bc83cb..eade5842 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -144,7 +144,7 @@ void setup() } MessageOutput.println("done"); - InverterSettings.init(); + InverterSettings.init(&scheduler); Datastore.init(&scheduler); } @@ -153,8 +153,6 @@ void loop() { scheduler.execute(); - InverterSettings.loop(); - yield(); WebApi.loop(); yield(); Display.loop(); From ad1f1b690c98c8b6af7c592572e5be6d3a0f1c12 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 20 Nov 2023 21:57:43 +0100 Subject: [PATCH 21/89] Migrate MessageOutput to TaskScheduler --- include/MessageOutput.h | 9 +++++++-- src/MessageOutput.cpp | 8 ++++++++ src/main.cpp | 3 +-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/include/MessageOutput.h b/include/MessageOutput.h index 7c56a6f4..3a30bff3 100644 --- a/include/MessageOutput.h +++ b/include/MessageOutput.h @@ -4,18 +4,23 @@ #include #include #include +#include #include #define BUFFER_SIZE 500 class MessageOutputClass : public Print { public: - void loop(); + void init(Scheduler* scheduler); size_t write(uint8_t c) override; - size_t write(const uint8_t *buffer, size_t size) override; + size_t write(const uint8_t* buffer, size_t size) override; void register_ws_output(AsyncWebSocket* output); private: + void loop(); + + Task _loopTask; + AsyncWebSocket* _ws = NULL; char _buffer[BUFFER_SIZE]; uint16_t _buff_pos = 0; diff --git a/src/MessageOutput.cpp b/src/MessageOutput.cpp index 23c644bc..abe28313 100644 --- a/src/MessageOutput.cpp +++ b/src/MessageOutput.cpp @@ -8,6 +8,14 @@ MessageOutputClass MessageOutput; +void MessageOutputClass::init(Scheduler* scheduler) +{ + scheduler->addTask(_loopTask); + _loopTask.setCallback(std::bind(&MessageOutputClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); +} + void MessageOutputClass::register_ws_output(AsyncWebSocket* output) { _ws = output; diff --git a/src/main.cpp b/src/main.cpp index eade5842..48c9aa04 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,6 +36,7 @@ void setup() while (!Serial) yield(); #endif + MessageOutput.init(&scheduler); MessageOutput.println(); MessageOutput.println("Starting OpenDTU"); @@ -157,6 +158,4 @@ void loop() yield(); Display.loop(); yield(); - MessageOutput.loop(); - yield(); } \ No newline at end of file From ab8679e7b9e6d131938bf81571e79ed1a9755ba9 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 20 Nov 2023 22:02:44 +0100 Subject: [PATCH 22/89] Migrate Display_Graphic to TaskScheduler --- include/Display_Graphic.h | 8 +-- src/Display_Graphic.cpp | 108 ++++++++++++++++++++------------------ src/main.cpp | 3 +- 3 files changed, 62 insertions(+), 57 deletions(-) diff --git a/include/Display_Graphic.h b/include/Display_Graphic.h index 9fe202c4..3aec4f27 100644 --- a/include/Display_Graphic.h +++ b/include/Display_Graphic.h @@ -2,6 +2,7 @@ #pragma once #include "defaults.h" +#include #include enum DisplayType_t { @@ -16,8 +17,7 @@ public: DisplayGraphicClass(); ~DisplayGraphicClass(); - void init(DisplayType_t type, uint8_t data, uint8_t clk, uint8_t cs, uint8_t reset); - void loop(); + void init(Scheduler* scheduler, DisplayType_t type, uint8_t data, uint8_t clk, uint8_t cs, uint8_t reset); void setContrast(uint8_t contrast); void setStatus(bool turnOn); void setOrientation(uint8_t rotation = DISPLAY_ROTATION); @@ -28,10 +28,13 @@ public: bool enableScreensaver = true; private: + void loop(); void printText(const char* text, uint8_t line); void calcLineHeights(); void setFont(uint8_t line); + Task _loopTask; + U8G2* _display; bool _displayTurnedOn; @@ -41,7 +44,6 @@ private: uint8_t _mExtra; uint16_t _period = 1000; uint16_t _interval = 60000; // interval at which to power save (milliseconds) - uint32_t _lastDisplayUpdate = 0; uint32_t _previousMillis = 0; char _fmtText[32]; bool _isLarge = false; diff --git a/src/Display_Graphic.cpp b/src/Display_Graphic.cpp index 26991cb5..aec3d3b0 100644 --- a/src/Display_Graphic.cpp +++ b/src/Display_Graphic.cpp @@ -39,7 +39,7 @@ DisplayGraphicClass::~DisplayGraphicClass() delete _display; } -void DisplayGraphicClass::init(DisplayType_t type, uint8_t data, uint8_t clk, uint8_t cs, uint8_t reset) +void DisplayGraphicClass::init(Scheduler* scheduler, DisplayType_t type, uint8_t data, uint8_t clk, uint8_t cs, uint8_t reset) { _display_type = type; if (_display_type > DisplayType_t::None) { @@ -49,6 +49,12 @@ void DisplayGraphicClass::init(DisplayType_t type, uint8_t data, uint8_t clk, ui setContrast(DISPLAY_CONTRAST); setStatus(true); } + + scheduler->addTask(_loopTask); + _loopTask.setCallback(std::bind(&DisplayGraphicClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.setInterval(_period); + _loopTask.enable(); } void DisplayGraphicClass::calcLineHeights() @@ -137,62 +143,60 @@ void DisplayGraphicClass::loop() return; } - if ((millis() - _lastDisplayUpdate) > _period) { + _loopTask.setInterval(_period); - _display->clearBuffer(); - bool displayPowerSave = false; + _display->clearBuffer(); + bool displayPowerSave = false; - //=====> Actual Production ========== - if (Datastore.getIsAtLeastOneReachable()) { - displayPowerSave = false; - if (Datastore.getTotalAcPowerEnabled() > 999) { - snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_kw[_display_language], (Datastore.getTotalAcPowerEnabled() / 1000)); - } else { - snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_w[_display_language], Datastore.getTotalAcPowerEnabled()); - } - printText(_fmtText, 0); - _previousMillis = millis(); - } - //<======================= - - //=====> Offline =========== - else { - printText(i18n_offline[_display_language], 0); - // check if it's time to enter power saving mode - if (millis() - _previousMillis >= (_interval * 2)) { - displayPowerSave = enablePowerSafe; - } - } - //<======================= - - //=====> Today & Total Production ======= - snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_wh[_display_language], Datastore.getTotalAcYieldDayEnabled()); - printText(_fmtText, 1); - - snprintf(_fmtText, sizeof(_fmtText), i18n_yield_total_kwh[_display_language], Datastore.getTotalAcYieldTotalEnabled()); - printText(_fmtText, 2); - //<======================= - - //=====> IP or Date-Time ======== - if (!(_mExtra % 10) && NetworkSettings.localIP()) { - printText(NetworkSettings.localIP().toString().c_str(), 3); + //=====> Actual Production ========== + if (Datastore.getIsAtLeastOneReachable()) { + displayPowerSave = false; + if (Datastore.getTotalAcPowerEnabled() > 999) { + snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_kw[_display_language], (Datastore.getTotalAcPowerEnabled() / 1000)); } else { - // Get current time - time_t now = time(nullptr); - strftime(_fmtText, sizeof(_fmtText), i18n_date_format[_display_language], localtime(&now)); - printText(_fmtText, 3); + snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_w[_display_language], Datastore.getTotalAcPowerEnabled()); } - _display->sendBuffer(); - - _mExtra++; - _lastDisplayUpdate = millis(); - - if (!_displayTurnedOn) { - displayPowerSave = true; - } - - _display->setPowerSave(displayPowerSave); + printText(_fmtText, 0); + _previousMillis = millis(); } + //<======================= + + //=====> Offline =========== + else { + printText(i18n_offline[_display_language], 0); + // check if it's time to enter power saving mode + if (millis() - _previousMillis >= (_interval * 2)) { + displayPowerSave = enablePowerSafe; + } + } + //<======================= + + //=====> Today & Total Production ======= + snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_wh[_display_language], Datastore.getTotalAcYieldDayEnabled()); + printText(_fmtText, 1); + + snprintf(_fmtText, sizeof(_fmtText), i18n_yield_total_kwh[_display_language], Datastore.getTotalAcYieldTotalEnabled()); + printText(_fmtText, 2); + //<======================= + + //=====> IP or Date-Time ======== + if (!(_mExtra % 10) && NetworkSettings.localIP()) { + printText(NetworkSettings.localIP().toString().c_str(), 3); + } else { + // Get current time + time_t now = time(nullptr); + strftime(_fmtText, sizeof(_fmtText), i18n_date_format[_display_language], localtime(&now)); + printText(_fmtText, 3); + } + _display->sendBuffer(); + + _mExtra++; + + if (!_displayTurnedOn) { + displayPowerSave = true; + } + + _display->setPowerSave(displayPowerSave); } void DisplayGraphicClass::setContrast(uint8_t contrast) diff --git a/src/main.cpp b/src/main.cpp index 48c9aa04..2a1b5927 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -114,6 +114,7 @@ void setup() // Initialize Display MessageOutput.print("Initialize Display... "); Display.init( + &scheduler, static_cast(pin.display_type), pin.display_data, pin.display_clk, @@ -156,6 +157,4 @@ void loop() WebApi.loop(); yield(); - Display.loop(); - yield(); } \ No newline at end of file From 80d534e0451613351142da16b77ccc40746985ab Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 20 Nov 2023 22:07:07 +0100 Subject: [PATCH 23/89] Migrate WebApi to TaskScheduler --- include/WebApi.h | 8 ++++++-- src/WebApi.cpp | 7 ++++++- src/main.cpp | 5 +---- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/include/WebApi.h b/include/WebApi.h index b61091de..b96184b3 100644 --- a/include/WebApi.h +++ b/include/WebApi.h @@ -22,12 +22,12 @@ #include "WebApi_ws_console.h" #include "WebApi_ws_live.h" #include +#include class WebApiClass { public: WebApiClass(); - void init(); - void loop(); + void init(Scheduler* scheduler); static bool checkCredentials(AsyncWebServerRequest* request); static bool checkCredentialsReadonly(AsyncWebServerRequest* request); @@ -35,6 +35,10 @@ public: static void sendTooManyRequests(AsyncWebServerRequest* request); private: + void loop(); + + Task _loopTask; + AsyncWebServer _server; AsyncEventSource _events; diff --git a/src/WebApi.cpp b/src/WebApi.cpp index 717c3525..2c5cc803 100644 --- a/src/WebApi.cpp +++ b/src/WebApi.cpp @@ -13,7 +13,7 @@ WebApiClass::WebApiClass() { } -void WebApiClass::init() +void WebApiClass::init(Scheduler* scheduler) { _server.addHandler(&_events); @@ -39,6 +39,11 @@ void WebApiClass::init() _webApiWsLive.init(&_server); _server.begin(); + + scheduler->addTask(_loopTask); + _loopTask.setCallback(std::bind(&WebApiClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); } void WebApiClass::loop() diff --git a/src/main.cpp b/src/main.cpp index 2a1b5927..e7dd14d2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -108,7 +108,7 @@ void setup() // Initialize WebApi MessageOutput.print("Initialize WebApi... "); - WebApi.init(); + WebApi.init(&scheduler); MessageOutput.println("done"); // Initialize Display @@ -154,7 +154,4 @@ void setup() void loop() { scheduler.execute(); - - WebApi.loop(); - yield(); } \ No newline at end of file From 134fefa30e5370d9060d9feb73b1025a7658eb52 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 20 Nov 2023 22:25:07 +0100 Subject: [PATCH 24/89] Split InverterSettings into multiple tasks --- include/InverterSettings.h | 8 +++---- src/InverterSettings.cpp | 47 ++++++++++++++++++++++---------------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/include/InverterSettings.h b/include/InverterSettings.h index 8de7253e..0f83386b 100644 --- a/include/InverterSettings.h +++ b/include/InverterSettings.h @@ -11,11 +11,11 @@ public: void init(Scheduler* scheduler); private: - void loop(); + void settingsLoop(); + void hoyLoop(); - Task _loopTask; - - uint32_t _lastUpdate = 0; + Task _settingsTask; + Task _hoyTask; }; extern InverterSettingsClass InverterSettings; diff --git a/src/InverterSettings.cpp b/src/InverterSettings.cpp index 22b331aa..40ad9286 100644 --- a/src/InverterSettings.cpp +++ b/src/InverterSettings.cpp @@ -88,31 +88,38 @@ void InverterSettingsClass::init(Scheduler* scheduler) MessageOutput.println("Invalid pin config"); } - scheduler->addTask(_loopTask); - _loopTask.setCallback(std::bind(&InverterSettingsClass::loop, this)); - _loopTask.setIterations(TASK_FOREVER); - _loopTask.enable(); + scheduler->addTask(_hoyTask); + _hoyTask.setCallback(std::bind(&InverterSettingsClass::hoyLoop, this)); + _hoyTask.setIterations(TASK_FOREVER); + _hoyTask.enable(); + + scheduler->addTask(_settingsTask); + _settingsTask.setCallback(std::bind(&InverterSettingsClass::settingsLoop, this)); + _settingsTask.setIterations(TASK_FOREVER); + _settingsTask.setInterval(INVERTER_UPDATE_SETTINGS_INTERVAL); + _settingsTask.enable(); } -void InverterSettingsClass::loop() +void InverterSettingsClass::settingsLoop() { - if (millis() - _lastUpdate > INVERTER_UPDATE_SETTINGS_INTERVAL) { - const CONFIG_T& config = Configuration.get(); + const CONFIG_T& config = Configuration.get(); - for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { - auto const& inv_cfg = config.Inverter[i]; - if (inv_cfg.Serial == 0) { - continue; - } - auto inv = Hoymiles.getInverterBySerial(inv_cfg.Serial); - if (inv == nullptr) { - continue; - } - - inv->setEnablePolling(inv_cfg.Poll_Enable && (SunPosition.isDayPeriod() || inv_cfg.Poll_Enable_Night)); - inv->setEnableCommands(inv_cfg.Command_Enable && (SunPosition.isDayPeriod() || inv_cfg.Command_Enable_Night)); + for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { + auto const& inv_cfg = config.Inverter[i]; + if (inv_cfg.Serial == 0) { + continue; + } + auto inv = Hoymiles.getInverterBySerial(inv_cfg.Serial); + if (inv == nullptr) { + continue; } - } + inv->setEnablePolling(inv_cfg.Poll_Enable && (SunPosition.isDayPeriod() || inv_cfg.Poll_Enable_Night)); + inv->setEnableCommands(inv_cfg.Command_Enable && (SunPosition.isDayPeriod() || inv_cfg.Command_Enable_Night)); + } + } + +void InverterSettingsClass::hoyLoop() +{ Hoymiles.loop(); } From 0db5b2eb9ac102ed114d2ac5b09b3b9a325e16e6 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 20 Nov 2023 22:26:55 +0100 Subject: [PATCH 25/89] Calculate SunPosition only every 5 seconds --- src/SunPosition.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SunPosition.cpp b/src/SunPosition.cpp index 5e10d982..578e2e34 100644 --- a/src/SunPosition.cpp +++ b/src/SunPosition.cpp @@ -18,6 +18,7 @@ void SunPositionClass::init(Scheduler* scheduler) scheduler->addTask(_loopTask); _loopTask.setCallback(std::bind(&SunPositionClass::loop, this)); _loopTask.setIterations(TASK_FOREVER); + _loopTask.setInterval(5 * TASK_SECOND); _loopTask.enable(); } From f8f79c816a0a223cdf5fef2beb2f0664bde750ec Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Tue, 21 Nov 2023 21:30:58 +0100 Subject: [PATCH 26/89] Split LedSingle into multiple tasks --- include/Led_Single.h | 8 ++++---- src/Led_Single.cpp | 35 +++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/include/Led_Single.h b/include/Led_Single.h index e01fdfe0..9622600c 100644 --- a/include/Led_Single.h +++ b/include/Led_Single.h @@ -16,9 +16,11 @@ public: void turnAllOn(); private: - void loop(); + void setLoop(); + void outputLoop(); - Task _loopTask; + Task _setTask; + Task _outputTask; enum class LedState_t { On, @@ -28,9 +30,7 @@ private: LedState_t _ledState[PINMAPPING_LED_COUNT]; LedState_t _allState; - TimeoutHelper _updateTimeout; TimeoutHelper _blinkTimeout; - uint8_t _ledActive = 0; }; extern LedSingleClass LedSingle; \ No newline at end of file diff --git a/src/Led_Single.cpp b/src/Led_Single.cpp index 43825619..40d48579 100644 --- a/src/Led_Single.cpp +++ b/src/Led_Single.cpp @@ -18,8 +18,9 @@ LedSingleClass::LedSingleClass() void LedSingleClass::init(Scheduler* scheduler) { + bool ledActive = false; + _blinkTimeout.set(500); - _updateTimeout.set(LEDSINGLE_UPDATE_INTERVAL); turnAllOn(); auto& pin = PinMapping.get(); @@ -28,25 +29,29 @@ void LedSingleClass::init(Scheduler* scheduler) if (pin.led[i] >= 0) { pinMode(pin.led[i], OUTPUT); digitalWrite(pin.led[i], LOW); - _ledActive++; + ledActive = true; } _ledState[i] = LedState_t::Off; } - scheduler->addTask(_loopTask); - _loopTask.setCallback(std::bind(&LedSingleClass::loop, this)); - _loopTask.setIterations(TASK_FOREVER); - _loopTask.enable(); + if (ledActive) { + scheduler->addTask(_outputTask); + _outputTask.setCallback(std::bind(&LedSingleClass::outputLoop, this)); + _outputTask.setIterations(TASK_FOREVER); + _outputTask.enable(); + + scheduler->addTask(_setTask); + _setTask.setCallback(std::bind(&LedSingleClass::setLoop, this)); + _setTask.setInterval(LEDSINGLE_UPDATE_INTERVAL * TASK_MILLISECOND); + _setTask.setIterations(TASK_FOREVER); + _setTask.enable(); + } } -void LedSingleClass::loop() +void LedSingleClass::setLoop() { - if (_ledActive == 0) { - return; - } - - if (_updateTimeout.occured() && _allState == LedState_t::On) { + if (_allState == LedState_t::On) { const CONFIG_T& config = Configuration.get(); // Update network status @@ -73,12 +78,14 @@ void LedSingleClass::loop() } } - _updateTimeout.reset(); - } else if (_updateTimeout.occured() && _allState == LedState_t::Off) { + } else if (_allState == LedState_t::Off) { _ledState[0] = LedState_t::Off; _ledState[1] = LedState_t::Off; } +} +void LedSingleClass::outputLoop() +{ auto& pin = PinMapping.get(); for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { From 0fd4c603d524944ec49f459fe798cc642bb0264b Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Thu, 23 Nov 2023 22:55:25 +0100 Subject: [PATCH 27/89] Upgrade espMqttClient from 1.4.5 to 1.5.0 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 8bf5b01d..597b875f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -33,7 +33,7 @@ build_unflags = lib_deps = https://github.com/yubox-node-org/ESPAsyncWebServer bblanchon/ArduinoJson @ ^6.21.3 - https://github.com/bertmelis/espMqttClient.git#v1.4.5 + https://github.com/bertmelis/espMqttClient.git#v1.5.0 nrf24/RF24 @ ^1.4.8 olikraus/U8g2 @ ^2.35.7 buelowp/sunset @ ^1.1.7 From 3dc91449aad0d350cbf86991a38f06ce5b082454 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Fri, 24 Nov 2023 17:57:44 +0100 Subject: [PATCH 28/89] Doc: Correct amount of MPP-Tracker --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d27d23f4..89ed2b69 100644 --- a/README.md +++ b/README.md @@ -53,9 +53,9 @@ Like to show your own build? Just send me a Pull Request. | Hoymiles HMS-1600-4T | CMT2300A | 4 | 4 | 1 | | Hoymiles HMS-1800-4T | CMT2300A | 4 | 4 | 1 | | Hoymiles HMS-2000-4T | CMT2300A | 4 | 4 | 1 | -| Hoymiles HMT-1600-4T | CMT2300A | 4 | 4 | 3 | -| Hoymiles HMT-1800-4T | CMT2300A | 4 | 4 | 3 | -| Hoymiles HMT-2000-4T | CMT2300A | 4 | 4 | 3 | +| Hoymiles HMT-1600-4T | CMT2300A | 4 | 2 | 3 | +| Hoymiles HMT-1800-4T | CMT2300A | 4 | 2 | 3 | +| Hoymiles HMT-2000-4T | CMT2300A | 4 | 2 | 3 | | Hoymiles HMT-1800-6T | CMT2300A | 6 | 3 | 3 | | Hoymiles HMT-2250-6T | CMT2300A | 6 | 3 | 3 | | Solenso SOL-H350 | NRF24L01+ | 1 | 1 | 1 | From bcc647fd5137bbe0b6595ee15bbb075c202875f7 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Fri, 24 Nov 2023 18:10:08 +0100 Subject: [PATCH 29/89] Added HMT-1600-4T and HMT-1800-4T to DevInfoParser Fix #1524 --- lib/Hoymiles/src/parser/DevInfoParser.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Hoymiles/src/parser/DevInfoParser.cpp b/lib/Hoymiles/src/parser/DevInfoParser.cpp index bc28ce39..5ae5d5da 100644 --- a/lib/Hoymiles/src/parser/DevInfoParser.cpp +++ b/lib/Hoymiles/src/parser/DevInfoParser.cpp @@ -45,8 +45,11 @@ const devInfo_t devInfo[] = { { { 0x10, 0x12, 0x71, ALL }, 2000, "HMS-2000" }, // 01 { { 0x10, 0x22, 0x71, ALL }, 2000, "HMS-2000" }, // 10 - { { 0x10, 0x33, 0x11, ALL }, 1800, "HMT-1800" }, // 01 - { { 0x10, 0x33, 0x31, ALL }, 2250, "HMT-2250" } // 01 + { { 0x10, 0x32, 0x41, ALL }, 1600, "HMT-1600-4T" }, // 00 + { { 0x10, 0x32, 0x51, ALL }, 1800, "HMT-1800-4T" }, // 00 + + { { 0x10, 0x33, 0x11, ALL }, 1800, "HMT-1800-6T" }, // 01 + { { 0x10, 0x33, 0x31, ALL }, 2250, "HMT-2250-6T" } // 01 }; DevInfoParser::DevInfoParser() From 8cf31729df66fbd42b76dcc0ef1d1954784baefc Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Fri, 24 Nov 2023 18:11:08 +0100 Subject: [PATCH 30/89] Adjusted inverter names for HMS-1600/1800/2000-4T --- lib/Hoymiles/src/inverters/HMS_4CH.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Hoymiles/src/inverters/HMS_4CH.cpp b/lib/Hoymiles/src/inverters/HMS_4CH.cpp index ffdc2055..5d15b0af 100644 --- a/lib/Hoymiles/src/inverters/HMS_4CH.cpp +++ b/lib/Hoymiles/src/inverters/HMS_4CH.cpp @@ -61,7 +61,7 @@ bool HMS_4CH::isValidSerial(uint64_t serial) String HMS_4CH::typeName() { - return "HMS-1600/1800/2000"; + return "HMS-1600/1800/2000-4T"; } const byteAssign_t* HMS_4CH::getByteAssignment() From d0397c821ff64528be4edbe35df192a969525348 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Fri, 24 Nov 2023 18:17:11 +0100 Subject: [PATCH 31/89] Add channel count to description of detected inverter type (DevInfoParser) --- lib/Hoymiles/src/parser/DevInfoParser.cpp | 56 +++++++++++------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/Hoymiles/src/parser/DevInfoParser.cpp b/lib/Hoymiles/src/parser/DevInfoParser.cpp index 5ae5d5da..1de00092 100644 --- a/lib/Hoymiles/src/parser/DevInfoParser.cpp +++ b/lib/Hoymiles/src/parser/DevInfoParser.cpp @@ -15,35 +15,35 @@ typedef struct { } devInfo_t; const devInfo_t devInfo[] = { - { { 0x10, 0x10, 0x10, ALL }, 300, "HM-300" }, - { { 0x10, 0x10, 0x20, ALL }, 350, "HM-350" }, - { { 0x10, 0x10, 0x30, ALL }, 400, "HM-400" }, - { { 0x10, 0x10, 0x40, ALL }, 400, "HM-400" }, - { { 0x10, 0x11, 0x10, ALL }, 600, "HM-600" }, - { { 0x10, 0x11, 0x20, ALL }, 700, "HM-700" }, - { { 0x10, 0x11, 0x30, ALL }, 800, "HM-800" }, - { { 0x10, 0x11, 0x40, ALL }, 800, "HM-800" }, - { { 0x10, 0x12, 0x10, ALL }, 1200, "HM-1200" }, - { { 0x10, 0x02, 0x30, ALL }, 1500, "MI-1500 Gen3" }, - { { 0x10, 0x12, 0x30, ALL }, 1500, "HM-1500" }, - { { 0x10, 0x10, 0x10, 0x15 }, static_cast(300 * 0.7), "HM-300" }, // HM-300 factory limitted to 70% + { { 0x10, 0x10, 0x10, ALL }, 300, "HM-300-1T" }, + { { 0x10, 0x10, 0x20, ALL }, 350, "HM-350-1T" }, + { { 0x10, 0x10, 0x30, ALL }, 400, "HM-400-1T" }, + { { 0x10, 0x10, 0x40, ALL }, 400, "HM-400-1T" }, + { { 0x10, 0x11, 0x10, ALL }, 600, "HM-600-2T" }, + { { 0x10, 0x11, 0x20, ALL }, 700, "HM-700-2T" }, + { { 0x10, 0x11, 0x30, ALL }, 800, "HM-800-2T" }, + { { 0x10, 0x11, 0x40, ALL }, 800, "HM-800-2T" }, + { { 0x10, 0x12, 0x10, ALL }, 1200, "HM-1200-4T" }, + { { 0x10, 0x02, 0x30, ALL }, 1500, "MI-1500-4T Gen3" }, + { { 0x10, 0x12, 0x30, ALL }, 1500, "HM-1500-4T" }, + { { 0x10, 0x10, 0x10, 0x15 }, static_cast(300 * 0.7), "HM-300-1T" }, // HM-300 factory limitted to 70% - { { 0x10, 0x20, 0x21, ALL }, 350, "HMS-350" }, // 00 - { { 0x10, 0x20, 0x41, ALL }, 400, "HMS-400" }, // 00 - { { 0x10, 0x10, 0x51, ALL }, 450, "HMS-450" }, // 01 - { { 0x10, 0x10, 0x71, ALL }, 500, "HMS-500" }, // 02 - { { 0x10, 0x20, 0x71, ALL }, 500, "HMS-500 v2" }, // 02 - { { 0x10, 0x21, 0x11, ALL }, 600, "HMS-600" }, // 01 - { { 0x10, 0x21, 0x41, ALL }, 800, "HMS-800" }, // 00 - { { 0x10, 0x11, 0x51, ALL }, 900, "HMS-900" }, // 01 - { { 0x10, 0x21, 0x51, ALL }, 900, "HMS-900" }, // 03 - { { 0x10, 0x21, 0x71, ALL }, 1000, "HMS-1000" }, // 05 - { { 0x10, 0x11, 0x71, ALL }, 1000, "HMS-1000" }, // 01 - { { 0x10, 0x22, 0x41, ALL }, 1600, "HMS-1600" }, // 4 - { { 0x10, 0x12, 0x51, ALL }, 1800, "HMS-1800" }, // 01 - { { 0x10, 0x22, 0x51, ALL }, 1800, "HMS-1800" }, // 16 - { { 0x10, 0x12, 0x71, ALL }, 2000, "HMS-2000" }, // 01 - { { 0x10, 0x22, 0x71, ALL }, 2000, "HMS-2000" }, // 10 + { { 0x10, 0x20, 0x21, ALL }, 350, "HMS-350-1T" }, // 00 + { { 0x10, 0x20, 0x41, ALL }, 400, "HMS-400-1T" }, // 00 + { { 0x10, 0x10, 0x51, ALL }, 450, "HMS-450-1T" }, // 01 + { { 0x10, 0x10, 0x71, ALL }, 500, "HMS-500-1T" }, // 02 + { { 0x10, 0x20, 0x71, ALL }, 500, "HMS-500-1T v2" }, // 02 + { { 0x10, 0x21, 0x11, ALL }, 600, "HMS-600-2T" }, // 01 + { { 0x10, 0x21, 0x41, ALL }, 800, "HMS-800-2T" }, // 00 + { { 0x10, 0x11, 0x51, ALL }, 900, "HMS-900-2T" }, // 01 + { { 0x10, 0x21, 0x51, ALL }, 900, "HMS-900-2T" }, // 03 + { { 0x10, 0x21, 0x71, ALL }, 1000, "HMS-1000-2T" }, // 05 + { { 0x10, 0x11, 0x71, ALL }, 1000, "HMS-1000-2T" }, // 01 + { { 0x10, 0x22, 0x41, ALL }, 1600, "HMS-1600-4T" }, // 4 + { { 0x10, 0x12, 0x51, ALL }, 1800, "HMS-1800-4T" }, // 01 + { { 0x10, 0x22, 0x51, ALL }, 1800, "HMS-1800-4T" }, // 16 + { { 0x10, 0x12, 0x71, ALL }, 2000, "HMS-2000-4T" }, // 01 + { { 0x10, 0x22, 0x71, ALL }, 2000, "HMS-2000-4T" }, // 10 { { 0x10, 0x32, 0x41, ALL }, 1600, "HMT-1600-4T" }, // 00 { { 0x10, 0x32, 0x51, ALL }, 1800, "HMT-1800-4T" }, // 00 From 3b6e9343d4921d1b2257f3be71ae438222a9c0f3 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Sat, 25 Nov 2023 12:45:18 +0100 Subject: [PATCH 32/89] Adjust device web api endpoint for dynamic led count --- src/WebApi_device.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index 4dfbd146..af5f7d30 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -73,8 +73,9 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) displayPinObj["reset"] = pin.display_reset; JsonObject ledPinObj = curPin.createNestedObject("led"); - ledPinObj["led0"] = pin.led[0]; - ledPinObj["led1"] = pin.led[1]; + for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { + ledPinObj["led" + String(i)] = pin.led[i]; + } JsonObject display = root.createNestedObject("display"); display["rotation"] = config.Display.Rotation; From 9ae791edd423e0b9e379ed60de04f24b34ee215c Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Thu, 7 Dec 2023 12:46:38 +0100 Subject: [PATCH 33/89] Feature: Added ability to change the brightness of the LEDs Based on the idea of @moritzlerch with several modifications like pwmTable and structure --- include/Configuration.h | 5 ++ include/Led_Single.h | 7 ++- include/defaults.h | 2 + src/Configuration.cpp | 12 ++++ src/Led_Single.cpp | 87 ++++++++++++++++++++-------- src/WebApi_device.cpp | 12 ++++ webapp/src/locales/de.json | 7 ++- webapp/src/locales/en.json | 3 + webapp/src/locales/fr.json | 3 + webapp/src/types/DeviceConfig.ts | 5 ++ webapp/src/views/DeviceAdminView.vue | 58 ++++++++++++++++++- 11 files changed, 171 insertions(+), 30 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index 5e74c3a7..c0df7713 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include "PinMapping.h" #include #define CONFIG_FILENAME "/config.json" @@ -144,6 +145,10 @@ struct CONFIG_T { uint8_t Language; } Display; + struct { + uint8_t Brightness; + } Led_Single[PINMAPPING_LED_COUNT]; + INVERTER_CONFIG_T Inverter[INV_MAX_COUNT]; char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1]; }; diff --git a/include/Led_Single.h b/include/Led_Single.h index 9622600c..b4cabdef 100644 --- a/include/Led_Single.h +++ b/include/Led_Single.h @@ -19,6 +19,8 @@ private: void setLoop(); void outputLoop(); + void setLed(uint8_t ledNo, bool ledState); + Task _setTask; Task _outputTask; @@ -28,8 +30,9 @@ private: Blink, }; - LedState_t _ledState[PINMAPPING_LED_COUNT]; - LedState_t _allState; + LedState_t _ledMode[PINMAPPING_LED_COUNT]; + LedState_t _allMode; + bool _ledStateCurrent[PINMAPPING_LED_COUNT]; TimeoutHelper _blinkTimeout; }; diff --git a/include/defaults.h b/include/defaults.h index 264e6661..36f2cfb4 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -100,3 +100,5 @@ #define DISPLAY_LANGUAGE 0U #define REACHABLE_THRESHOLD 2U + +#define LED_BRIGHTNESS 100U \ No newline at end of file diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 8874a57f..8e7fe8f4 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -104,6 +104,12 @@ bool ConfigurationClass::write() display["contrast"] = config.Display.Contrast; display["language"] = config.Display.Language; + JsonArray leds = device.createNestedArray("led"); + for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { + JsonObject led = leds.createNestedObject(); + led["brightness"] = config.Led_Single[i].Brightness; + } + JsonArray inverters = doc.createNestedArray("inverters"); for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { JsonObject inv = inverters.createNestedObject(); @@ -259,6 +265,12 @@ bool ConfigurationClass::read() config.Display.Contrast = display["contrast"] | DISPLAY_CONTRAST; config.Display.Language = display["language"] | DISPLAY_LANGUAGE; + JsonArray leds = device["led"]; + for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { + JsonObject led = leds[i].as(); + config.Led_Single[i].Brightness = led["brightness"] | LED_BRIGHTNESS; + } + JsonArray inverters = doc["inverters"]; for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { JsonObject inv = inverters[i].as(); diff --git a/src/Led_Single.cpp b/src/Led_Single.cpp index 40d48579..21eb3bf6 100644 --- a/src/Led_Single.cpp +++ b/src/Led_Single.cpp @@ -12,6 +12,31 @@ LedSingleClass LedSingle; +/* + The table is calculated using the following formula + (See https://www.mikrocontroller.net/articles/LED-Fading) + a = Step count: 101 --> 0 - 100 + b = PWM resolution: 256: 0 - 255 + y = Calculated value of index x: + y = 0 if x = 0 + y = pow(2, log2(b-1) * (x+1) / a) if x > 0 +*/ +const uint8_t pwmTable[] = { + 0, + 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, + 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, + 6, 6, 6, 7, 7, 8, 8, 8, 9, 9, + 10, 11, 11, 12, 12, 13, 14, 15, 16, 16, + 17, 18, 19, 20, 22, 23, 24, 25, 27, 28, + 30, 32, 33, 35, 37, 39, 42, 44, 47, 49, + 52, 55, 58, 61, 65, 68, 72, 76, 81, 85, + 90, 95, 100, 106, 112, 118, 125, 132, 139, 147, + 156, 164, 174, 183, 194, 205, 216, 228, 241, 255 +}; + +#define LED_OFF 0 + LedSingleClass::LedSingleClass() { } @@ -28,11 +53,11 @@ void LedSingleClass::init(Scheduler* scheduler) if (pin.led[i] >= 0) { pinMode(pin.led[i], OUTPUT); - digitalWrite(pin.led[i], LOW); + setLed(i, false); ledActive = true; } - _ledState[i] = LedState_t::Off; + _ledMode[i] = LedState_t::Off; } if (ledActive) { @@ -51,58 +76,52 @@ void LedSingleClass::init(Scheduler* scheduler) void LedSingleClass::setLoop() { - if (_allState == LedState_t::On) { + if (_allMode == LedState_t::On) { const CONFIG_T& config = Configuration.get(); // Update network status - _ledState[0] = LedState_t::Off; + _ledMode[0] = LedState_t::Off; if (NetworkSettings.isConnected()) { - _ledState[0] = LedState_t::Blink; + _ledMode[0] = LedState_t::Blink; } struct tm timeinfo; if (getLocalTime(&timeinfo, 5) && (!config.Mqtt.Enabled || (config.Mqtt.Enabled && MqttSettings.getConnected()))) { - _ledState[0] = LedState_t::On; + _ledMode[0] = LedState_t::On; } // Update inverter status - _ledState[1] = LedState_t::Off; + _ledMode[1] = LedState_t::Off; if (Hoymiles.getNumInverters() && Datastore.getIsAtLeastOnePollEnabled()) { // set LED status if (Datastore.getIsAllEnabledReachable() && Datastore.getIsAllEnabledProducing()) { - _ledState[1] = LedState_t::On; + _ledMode[1] = LedState_t::On; } if (Datastore.getIsAllEnabledReachable() && !Datastore.getIsAllEnabledProducing()) { - _ledState[1] = LedState_t::Blink; + _ledMode[1] = LedState_t::Blink; } } - } else if (_allState == LedState_t::Off) { - _ledState[0] = LedState_t::Off; - _ledState[1] = LedState_t::Off; + } else if (_allMode == LedState_t::Off) { + _ledMode[0] = LedState_t::Off; + _ledMode[1] = LedState_t::Off; } } void LedSingleClass::outputLoop() { - auto& pin = PinMapping.get(); for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { - - if (pin.led[i] < 0) { - continue; - } - - switch (_ledState[i]) { + switch (_ledMode[i]) { case LedState_t::Off: - digitalWrite(pin.led[i], LOW); + setLed(i, false); break; case LedState_t::On: - digitalWrite(pin.led[i], HIGH); + setLed(i, true); break; case LedState_t::Blink: if (_blinkTimeout.occured()) { - digitalWrite(pin.led[i], !digitalRead(pin.led[i])); + setLed(i, !_ledStateCurrent[i]); _blinkTimeout.reset(); } break; @@ -110,12 +129,32 @@ void LedSingleClass::outputLoop() } } +void LedSingleClass::setLed(uint8_t ledNo, bool ledState) +{ + const auto& pin = PinMapping.get(); + const auto& config = Configuration.get(); + + if (pin.led[ledNo] < 0) { + return; + } + + const uint32_t currentPWM = ledcRead(analogGetChannel(pin.led[ledNo])); + const uint32_t targetPWM = ledState ? pwmTable[config.Led_Single[ledNo].Brightness] : LED_OFF; + + if (currentPWM == targetPWM) { + return; + } + + analogWrite(pin.led[ledNo], targetPWM); + _ledStateCurrent[ledNo] = ledState; +} + void LedSingleClass::turnAllOff() { - _allState = LedState_t::Off; + _allMode = LedState_t::Off; } void LedSingleClass::turnAllOn() { - _allState = LedState_t::On; + _allMode = LedState_t::On; } diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index af5f7d30..9008f94e 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -84,6 +84,12 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) display["contrast"] = config.Display.Contrast; display["language"] = config.Display.Language; + JsonArray leds = root.createNestedArray("led"); + for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { + JsonObject led = leds.createNestedObject(); + led["brightness"] = config.Led_Single[i].Brightness; + } + response->setLength(); request->send(response); } @@ -155,6 +161,12 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) config.Display.Contrast = root["display"]["contrast"].as(); config.Display.Language = root["display"]["language"].as(); + for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { + Serial.println(root["led"][i]["brightness"].as()); + config.Led_Single[i].Brightness = root["led"][i]["brightness"].as(); + config.Led_Single[i].Brightness = min(100, config.Led_Single[i].Brightness); + } + Display.setOrientation(config.Display.Rotation); Display.enablePowerSafe = config.Display.PowerSafe; Display.enableScreensaver = config.Display.ScreenSaver; diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 81ac5b40..fe94668d 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -574,12 +574,15 @@ "Rotation": "Rotation:", "rot0": "Keine Rotation", "rot90": "90 Grad Drehung", + "rot180": "180 Grad Drehung", + "rot270": "270 Grad Drehung", "DisplayLanguage": "Displaysprache:", "en": "Englisch", "de": "Deutsch", "fr": "Französisch", - "rot180": "180 Grad Drehung", - "rot270": "270 Grad Drehung", + "Leds": "LEDs", + "EqualBrightness": "Gleiche Helligkeit:", + "LedBrightness": "LED {led} Helligkeit ({brightness}):", "Save": "@:dtuadmin.Save" }, "pininfo": { diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 67faba0f..ec284f79 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -580,6 +580,9 @@ "en": "English", "de": "German", "fr": "French", + "Leds": "LEDs", + "EqualBrightness": "Equal brightness:", + "LedBrightness": "LED {led} brightness ({brightness}):", "Save": "@:dtuadmin.Save" }, "pininfo": { diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index 006ab015..8f96264a 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -580,6 +580,9 @@ "en": "Anglais", "de": "Allemand", "fr": "Français", + "Leds": "LEDs", + "EqualBrightness": "Même luminosité:", + "LedBrightness": "LED {led} luminosité ({brightness}):", "Save": "@:dtuadmin.Save" }, "pininfo": { diff --git a/webapp/src/types/DeviceConfig.ts b/webapp/src/types/DeviceConfig.ts index 8b77c5b7..07115269 100644 --- a/webapp/src/types/DeviceConfig.ts +++ b/webapp/src/types/DeviceConfig.ts @@ -8,7 +8,12 @@ export interface Display { language: number; } +export interface Led { + brightness: number; +} + export interface DeviceConfig { curPin: Device; display: Display; + led: Array; } \ No newline at end of file diff --git a/webapp/src/views/DeviceAdminView.vue b/webapp/src/views/DeviceAdminView.vue index 2af0e50e..bcf3d40b 100644 --- a/webapp/src/views/DeviceAdminView.vue +++ b/webapp/src/views/DeviceAdminView.vue @@ -13,6 +13,8 @@ }} + + + @@ -109,7 +134,7 @@ import BasePage from '@/components/BasePage.vue'; import BootstrapAlert from "@/components/BootstrapAlert.vue"; import InputElement from '@/components/InputElement.vue'; import PinInfo from '@/components/PinInfo.vue'; -import type { DeviceConfig } from "@/types/DeviceConfig"; +import type { DeviceConfig, Led } from "@/types/DeviceConfig"; import type { PinMapping, Device } from "@/types/PinMapping"; import { authHeader, handleResponse } from '@/utils/authentication'; import { defineComponent } from 'vue'; @@ -130,6 +155,7 @@ export default defineComponent({ alertMessage: "", alertType: "info", showAlert: false, + equalBrightnessCheckVal: false, displayRotationList: [ { key: 0, value: 'rot0' }, { key: 1, value: 'rot90' }, @@ -147,6 +173,14 @@ export default defineComponent({ this.getDeviceConfig(); this.getPinMappingList(); }, + watch: { + equalBrightnessCheckVal: function(val) { + if (!val) { + return; + } + this.deviceConfigList.led.every(v => v.brightness = this.deviceConfigList.led[0].brightness); + } + }, methods: { getPinMappingList() { this.pinMappingLoading = true; @@ -180,7 +214,10 @@ export default defineComponent({ this.deviceConfigList = data; this.dataLoading = false; } - ); + ) + .then(() => { + this.equalBrightnessCheckVal = this.isEqualBrightness(); + }); }, savePinConfig(e: Event) { e.preventDefault(); @@ -202,6 +239,23 @@ export default defineComponent({ } ); }, + getLedIdFromNumber(ledNo: number) : string { + return 'inputLED' + ledNo + 'Brightness'; + }, + getNumberFromLedId(id: string): number { + return parseInt(id.replace("inputLED", "").replace("Brightness", "")); + }, + isEqualBrightness(): boolean { + const allEqual = (arr : Led[]) => arr.every(v => v.brightness === arr[0].brightness); + return allEqual(this.deviceConfigList.led); + }, + syncSliders(event: Event) { + if (!this.equalBrightnessCheckVal) { + return; + } + const srcId = this.getNumberFromLedId((event.target as Element).id); + this.deviceConfigList.led.every(v => v.brightness = this.deviceConfigList.led[srcId].brightness); + } }, }); \ No newline at end of file From 08d45f2751a43b86a95868334e6045d297cf1002 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Thu, 7 Dec 2023 12:51:21 +0100 Subject: [PATCH 34/89] webapp: Update dependencies --- webapp/package.json | 20 ++--- webapp/yarn.lock | 195 ++++++++++++++++++++++++-------------------- 2 files changed, 115 insertions(+), 100 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index 15b6ccc6..0425689c 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -16,10 +16,10 @@ "bootstrap": "^5.3.2", "bootstrap-icons-vue": "^1.11.1", "mitt": "^3.0.1", - "sortablejs": "^1.15.0", + "sortablejs": "^1.15.1", "spark-md5": "^3.0.2", "vue": "^3.3.8", - "vue-i18n": "^9.7.1", + "vue-i18n": "^9.8.0", "vue-router": "^4.2.5" }, "devDependencies": { @@ -27,21 +27,21 @@ "@rushstack/eslint-patch": "^1.6.0", "@tsconfig/node18": "^18.2.2", "@types/bootstrap": "^5.2.10", - "@types/node": "^20.9.4", + "@types/node": "^20.10.4", "@types/sortablejs": "^1.15.7", "@types/spark-md5": "^3.0.4", - "@vitejs/plugin-vue": "^4.5.0", + "@vitejs/plugin-vue": "^4.5.1", "@vue/eslint-config-typescript": "^12.0.0", "@vue/tsconfig": "^0.4.0", - "eslint": "^8.54.0", - "eslint-plugin-vue": "^9.18.1", + "eslint": "^8.55.0", + "eslint-plugin-vue": "^9.19.2", "npm-run-all": "^4.1.5", "sass": "^1.69.5", - "terser": "^5.24.0", - "typescript": "^5.3.2", - "vite": "^5.0.2", + "terser": "^5.25.0", + "typescript": "^5.3.3", + "vite": "^5.0.6", "vite-plugin-compression": "^0.5.1", "vite-plugin-css-injected-by-js": "^3.3.0", - "vue-tsc": "^1.8.22" + "vue-tsc": "^1.8.25" } } diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 2f6f0fdd..4b313c9f 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -156,10 +156,10 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8" integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== -"@eslint/eslintrc@^2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.3.tgz#797470a75fe0fbd5a53350ee715e85e87baff22d" - integrity sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA== +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -171,10 +171,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.54.0": - version "8.54.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.54.0.tgz#4fab9a2ff7860082c304f750e94acd644cf984cf" - integrity sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ== +"@eslint/js@8.55.0": + version "8.55.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.55.0.tgz#b721d52060f369aa259cf97392403cb9ce892ec6" + integrity sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA== "@humanwhocodes/config-array@^0.11.13": version "0.11.13" @@ -211,20 +211,20 @@ source-map-js "^1.0.1" yaml-eslint-parser "^1.2.2" -"@intlify/core-base@9.7.1": - version "9.7.1" - resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.7.1.tgz#d31315a8e9cf027678b65a4155287143ab91b8fd" - integrity sha512-jPJTeECEhqQ7g//8g3Fb79j5SzSSRqlFCWD6pcX94uMLXU+L1m07gVZnnvzoJBnaMyJHiiwxOqZVfvu6rQfLvw== +"@intlify/core-base@9.8.0": + version "9.8.0" + resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.8.0.tgz#969ca59f55084e23e968ec0bfe71678774e568ec" + integrity sha512-UxaSZVZ1DwqC/CltUZrWZNaWNhfmKtfyV4BJSt/Zt4Or/fZs1iFj0B+OekYk1+MRHfIOe3+x00uXGQI4PbO/9g== dependencies: - "@intlify/message-compiler" "9.7.1" - "@intlify/shared" "9.7.1" + "@intlify/message-compiler" "9.8.0" + "@intlify/shared" "9.8.0" -"@intlify/message-compiler@9.7.1": - version "9.7.1" - resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.7.1.tgz#2cd5f26c6b9f19afffd62e69d192cc4e9e51ba9a" - integrity sha512-HfIr2Hn/K7b0Zv4kGqkxAxwtipyxAwhI9a3krN5cuhH/G9gkaik7of1PdzjR3Mix43t2onBiKYQyaU7mo7e0aA== +"@intlify/message-compiler@9.8.0": + version "9.8.0" + resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.8.0.tgz#587d69b302f9b8130a4a949b0ab4add519761787" + integrity sha512-McnYWhcoYmDJvssVu6QGR0shqlkJuL1HHdi5lK7fNqvQqRYaQ4lSLjYmZxwc8tRNMdIe9/KUKfyPxU9M6yCtNQ== dependencies: - "@intlify/shared" "9.7.1" + "@intlify/shared" "9.8.0" source-map-js "^1.0.2" "@intlify/message-compiler@^9.4.0": @@ -240,10 +240,10 @@ resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.4.0.tgz#4a78d462fc82433db900981e12eb5b1aae3d6085" integrity sha512-AFqymip2kToqA0B6KZPg5jSrdcVHoli9t/VhGKE2iiMq9utFuMoGdDC/JOCIZgwxo6aXAk86QyU2XtzEoMuZ6A== -"@intlify/shared@9.7.1": - version "9.7.1" - resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.7.1.tgz#a4902421aacda2d716981eb9528aef0163c7bb0a" - integrity sha512-CBKnHzlUYGrk5QII9q4nElAQKO5cX1rRx8VmSWXltyOZjbkGHXYQTHULn6KwRi+CypuBCfmPkyPBHMzosypIeg== +"@intlify/shared@9.8.0": + version "9.8.0" + resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.8.0.tgz#62adf8f6ef67c8eba6cf8d521e248f3503f237d3" + integrity sha512-TmgR0RCLjzrSo+W3wT0ALf9851iFMlVI9EYNGeWvZFUQTAJx0bvfsMlPdgVtV1tDNRiAfhkFsMKu6jtUY1ZLKQ== "@intlify/unplugin-vue-i18n@^1.5.0": version "1.5.0" @@ -435,10 +435,10 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== -"@types/node@^20.9.4": - version "20.9.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.4.tgz#cc8f970e869c26834bdb7ed480b30ede622d74c7" - integrity sha512-wmyg8HUhcn6ACjsn8oKYjkN/zUzQeNtMy44weTJSM6p4MMzEOuKbA3OjJ267uPCOW7Xex9dyrNTful8XTQYoDA== +"@types/node@^20.10.4": + version "20.10.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.4.tgz#b246fd84d55d5b1b71bf51f964bd514409347198" + integrity sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg== dependencies: undici-types "~5.26.4" @@ -547,31 +547,31 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@vitejs/plugin-vue@^4.5.0": - version "4.5.0" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.5.0.tgz#b4569fcb1faac054eba4f5efc1aaf4d39f4379e5" - integrity sha512-a2WSpP8X8HTEww/U00bU4mX1QpLINNuz/2KMNpLsdu3BzOpak3AGI1CJYBTXcc4SPhaD0eNRUp7IyQK405L5dQ== +"@vitejs/plugin-vue@^4.5.1": + version "4.5.1" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.5.1.tgz#84815bfeb46928c03a9ed765e4a8425c22345e15" + integrity sha512-DaUzYFr+2UGDG7VSSdShKa9sIWYBa1LL8KC0MNOf2H5LjcTPjob0x8LbkqXWmAtbANJCkpiQTj66UVcQkN2s3g== -"@volar/language-core@1.10.7", "@volar/language-core@~1.10.5": - version "1.10.7" - resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.10.7.tgz#9d555bf0a3ca652c525651baba5ecf8a55cf3471" - integrity sha512-6+WI7HGqWCsKJ/bms4V45WP7eDeoGxDtLjYPrHB7QkIWVkRLIeGPzzBoonZz9kERM+Kld3W89Y+IlICejVAKhA== +"@volar/language-core@1.11.1", "@volar/language-core@~1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.11.1.tgz#ecdf12ea8dc35fb8549e517991abcbf449a5ad4f" + integrity sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw== dependencies: - "@volar/source-map" "1.10.7" + "@volar/source-map" "1.11.1" -"@volar/source-map@1.10.7", "@volar/source-map@~1.10.5": - version "1.10.7" - resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-1.10.7.tgz#f2b5c6b99f3fc91c10d4013eaeb083fbbf4b9e0d" - integrity sha512-anA254XO0lmmeu0p/kvgPOCkrVpqNIHWMvEkPX70PSk4ntg0iBzN/f0Kip6deXvibl6v14Q3Z8RihWrZwdZEEQ== +"@volar/source-map@1.11.1", "@volar/source-map@~1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-1.11.1.tgz#535b0328d9e2b7a91dff846cab4058e191f4452f" + integrity sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg== dependencies: muggle-string "^0.3.1" -"@volar/typescript@~1.10.5": - version "1.10.7" - resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-1.10.7.tgz#2ed47e3260d4161445099ba89c7471fbc51133b6" - integrity sha512-2hvA3vjXVUn1vOpsP/nWLnE5DUmY6YKQhvDRoZVfBrnWwIo0ySxdTUP4XieXGGgSk43xJaeU1zqQS/3Wfm7QgA== +"@volar/typescript@~1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-1.11.1.tgz#ba86c6f326d88e249c7f5cfe4b765be3946fd627" + integrity sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ== dependencies: - "@volar/language-core" "1.10.7" + "@volar/language-core" "1.11.1" path-browserify "^1.0.1" "@vue/compiler-core@3.2.47": @@ -690,18 +690,19 @@ "@typescript-eslint/parser" "^6.7.0" vue-eslint-parser "^9.3.1" -"@vue/language-core@1.8.22": - version "1.8.22" - resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-1.8.22.tgz#1ef62645fb9b1f830c6c84a5586e49e74727b1e3" - integrity sha512-bsMoJzCrXZqGsxawtUea1cLjUT9dZnDsy5TuZ+l1fxRMzUGQUG9+Ypq4w//CqpWmrx7nIAJpw2JVF/t258miRw== +"@vue/language-core@1.8.25": + version "1.8.25" + resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-1.8.25.tgz#b44b4e3c244ba9b1b79cccf9eb7b046535a4676f" + integrity sha512-NJk/5DnAZlpvXX8BdWmHI45bWGLViUaS3R/RMrmFSvFMSbJKuEODpM4kR0F0Ofv5SFzCWuNiMhxameWpVdQsnA== dependencies: - "@volar/language-core" "~1.10.5" - "@volar/source-map" "~1.10.5" + "@volar/language-core" "~1.11.1" + "@volar/source-map" "~1.11.1" "@vue/compiler-dom" "^3.3.0" "@vue/shared" "^3.3.0" computeds "^0.0.1" minimatch "^9.0.3" muggle-string "^0.3.1" + path-browserify "^1.0.1" vue-template-compiler "^2.7.14" "@vue/reactivity-transform@3.2.47": @@ -1146,10 +1147,10 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-plugin-vue@^9.18.1: - version "9.18.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.18.1.tgz#73cf29df7450ce5913296465f8d1dc545344920c" - integrity sha512-7hZFlrEgg9NIzuVik2I9xSnJA5RsmOfueYgsUGUokEDLJ1LHtxO0Pl4duje1BriZ/jDWb+44tcIlC3yi0tdlZg== +eslint-plugin-vue@^9.19.2: + version "9.19.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.19.2.tgz#7ab83a001a1ac8bccae013c5b9cb5d2c644fb376" + integrity sha512-CPDqTOG2K4Ni2o4J5wixkLVNwgctKXFu6oBpVJlpNq7f38lh9I80pRTouZSJ2MAebPJlINU/KTFSXyQfBUlymA== dependencies: "@eslint-community/eslint-utils" "^4.4.0" natural-compare "^1.4.0" @@ -1190,15 +1191,15 @@ eslint-visitor-keys@^3.4.1: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== -eslint@^8.54.0: - version "8.54.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.54.0.tgz#588e0dd4388af91a2e8fa37ea64924074c783537" - integrity sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA== +eslint@^8.55.0: + version "8.55.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.55.0.tgz#078cb7b847d66f2c254ea1794fa395bf8e7e03f8" + integrity sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.3" - "@eslint/js" "8.54.0" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.55.0" "@humanwhocodes/config-array" "^0.11.13" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -1908,6 +1909,11 @@ nanoid@^3.3.6: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -2137,6 +2143,15 @@ postcss@^8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.4.32: + version "8.4.32" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.32.tgz#1dac6ac51ab19adb21b8b34fd2d93a86440ef6c9" + integrity sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -2314,10 +2329,10 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -sortablejs@^1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.0.tgz#53230b8aa3502bb77a29e2005808ffdb4a5f7e2a" - integrity sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w== +sortablejs@^1.15.1: + version "1.15.1" + resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.1.tgz#9a35f52cdff449fb42ea8ecf222f3468d76e0a47" + integrity sha512-P5Cjvb0UG1ZVNiDPj/n4V+DinttXG6K8n7vM/HQf0C25K3YKQTQY6fsr/sEGsJGpQ9exmPxluHxKBc0mLKU1lQ== "source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2: version "1.0.2" @@ -2436,10 +2451,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.24.0: - version "5.24.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.24.0.tgz#4ae50302977bca4831ccc7b4fef63a3c04228364" - integrity sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw== +terser@^5.25.0: + version "5.25.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.25.0.tgz#6579b4cca45b08bf0fdaa1a04605fd5860dfb2ac" + integrity sha512-we0I9SIsfvNUMP77zC9HG+MylwYYsGFSBG8qm+13oud2Yh+O104y614FRbyjpxys16jZwot72Fpi827YvGzuqg== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -2482,10 +2497,10 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -typescript@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" - integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== +typescript@^5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== ufo@^1.1.2: version "1.1.2" @@ -2556,13 +2571,13 @@ vite-plugin-css-injected-by-js@^3.3.0: resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.3.0.tgz#c19480a9e42a95c5bced976a9dde1446f9bd91ff" integrity sha512-xG+jyHNCmUqi/TXp6q88wTJGeAOrNLSyUUTp4qEQ9QZLGcHWQQsCsSSKa59rPMQr8sOzfzmWDd8enGqfH/dBew== -vite@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.2.tgz#3c94627dace83b9bf04b64eaf618038e30fb95c0" - integrity sha512-6CCq1CAJCNM1ya2ZZA7+jS2KgnhbzvxakmlIjN24cF/PXhRMzpM/z8QgsVJA/Dm5fWUWnVEsmtBoMhmerPxT0g== +vite@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.6.tgz#f9e13503a4c5ccd67312c67803dec921f3bdea7c" + integrity sha512-MD3joyAEBtV7QZPl2JVVUai6zHms3YOmLR+BpMzLlX2Yzjfcc4gTgNi09d/Rua3F4EtC8zdwPU8eQYyib4vVMQ== dependencies: esbuild "^0.19.3" - postcss "^8.4.31" + postcss "^8.4.32" rollup "^4.2.0" optionalDependencies: fsevents "~2.3.3" @@ -2580,13 +2595,13 @@ vue-eslint-parser@^9.3.1: lodash "^4.17.21" semver "^7.3.6" -vue-i18n@^9.7.1: - version "9.7.1" - resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.7.1.tgz#41393b066f6ff69d9be2ba31a59b043263c77289" - integrity sha512-A6DzWqJQMdzBj+392+g3zIgGV0FnFC7o/V+txs5yIALANEZzY6ZV8hM2wvZR3nTbQI7dntAmzBHMeoEteJO0kQ== +vue-i18n@^9.8.0: + version "9.8.0" + resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.8.0.tgz#54339daf377a31b234b027c5158e774728b6bc24" + integrity sha512-Izho+6PYjejsTq2mzjcRdBZ5VLRQoSuuexvR8029h5CpN03FYqiqBrShMyf2I1DKkN6kw/xmujcbvC+4QybpsQ== dependencies: - "@intlify/core-base" "9.7.1" - "@intlify/shared" "9.7.1" + "@intlify/core-base" "9.8.0" + "@intlify/shared" "9.8.0" "@vue/devtools-api" "^6.5.0" vue-router@^4.2.5: @@ -2604,13 +2619,13 @@ vue-template-compiler@^2.7.14: de-indent "^1.0.2" he "^1.2.0" -vue-tsc@^1.8.22: - version "1.8.22" - resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.8.22.tgz#421e73c38b50802a6716ca32ed87b5970c867323" - integrity sha512-j9P4kHtW6eEE08aS5McFZE/ivmipXy0JzrnTgbomfABMaVKx37kNBw//irL3+LlE3kOo63XpnRigyPC3w7+z+A== +vue-tsc@^1.8.25: + version "1.8.25" + resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.8.25.tgz#90cd03e71d28c5c4a8068167b232eb97cc96b77f" + integrity sha512-lHsRhDc/Y7LINvYhZ3pv4elflFADoEOo67vfClAfF2heVHpHmVquLSjojgCSIwzA4F0Pc4vowT/psXCYcfk+iQ== dependencies: - "@volar/typescript" "~1.10.5" - "@vue/language-core" "1.8.22" + "@volar/typescript" "~1.11.1" + "@vue/language-core" "1.8.25" semver "^7.5.4" vue@^3.3.8: From c0a185394cf653520d1bca1376d3073faf419251 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Thu, 7 Dec 2023 12:56:45 +0100 Subject: [PATCH 35/89] Update olikraus/U8g2 from 2.35.7 to 2.35.8 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 597b875f..5ed8cef9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -35,7 +35,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/bertmelis/espMqttClient.git#v1.5.0 nrf24/RF24 @ ^1.4.8 - olikraus/U8g2 @ ^2.35.7 + olikraus/U8g2 @ ^2.35.8 buelowp/sunset @ ^1.1.7 https://github.com/arkhipenko/TaskScheduler#testing From e9a55cf36161182112cdff4b7e07eb60c7f57f2f Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Thu, 7 Dec 2023 14:09:32 +0100 Subject: [PATCH 36/89] Remove not required onWebsocketEvent --- include/WebApi_ws_console.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/WebApi_ws_console.h b/include/WebApi_ws_console.h index 81df81e9..1d0fe10c 100644 --- a/include/WebApi_ws_console.h +++ b/include/WebApi_ws_console.h @@ -10,8 +10,6 @@ public: void loop(); private: - void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); - AsyncWebServer* _server; AsyncWebSocket _ws; From e0c07b9bcf41e9073d5d3edc621b0550083f42de Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Thu, 7 Dec 2023 14:13:58 +0100 Subject: [PATCH 37/89] Remove code nesting --- lib/Hoymiles/src/Hoymiles.cpp | 172 +++++++++++++++++----------------- 1 file changed, 87 insertions(+), 85 deletions(-) diff --git a/lib/Hoymiles/src/Hoymiles.cpp b/lib/Hoymiles/src/Hoymiles.cpp index a4558ac6..937be53c 100644 --- a/lib/Hoymiles/src/Hoymiles.cpp +++ b/lib/Hoymiles/src/Hoymiles.cpp @@ -40,105 +40,107 @@ void HoymilesClass::loop() _radioNrf->loop(); _radioCmt->loop(); - if (getNumInverters() > 0) { - if (millis() - _lastPoll > (_pollInterval * 1000)) { - static uint8_t inverterPos = 0; + if (getNumInverters() == 0) { + return; + } - std::shared_ptr iv = getInverterByPos(inverterPos); - if ((iv == nullptr) || ((iv != nullptr) && (!iv->getRadio()->isInitialized()))) { - if (++inverterPos >= getNumInverters()) { - inverterPos = 0; - } + if (millis() - _lastPoll > (_pollInterval * 1000)) { + static uint8_t inverterPos = 0; + + std::shared_ptr iv = getInverterByPos(inverterPos); + if ((iv == nullptr) || ((iv != nullptr) && (!iv->getRadio()->isInitialized()))) { + if (++inverterPos >= getNumInverters()) { + inverterPos = 0; + } + } + + if (iv != nullptr && iv->getRadio()->isInitialized() && iv->getRadio()->isQueueEmpty()) { + + if (iv->getZeroValuesIfUnreachable() && !iv->isReachable()) { + Hoymiles.getMessageOutput()->println("Set runtime data to zero"); + iv->Statistics()->zeroRuntimeData(); } - if (iv != nullptr && iv->getRadio()->isInitialized() && iv->getRadio()->isQueueEmpty()) { + if (iv->getEnablePolling() || iv->getEnableCommands()) { + _messageOutput->print("Fetch inverter: "); + _messageOutput->println(iv->serial(), HEX); - if (iv->getZeroValuesIfUnreachable() && !iv->isReachable()) { - Hoymiles.getMessageOutput()->println("Set runtime data to zero"); - iv->Statistics()->zeroRuntimeData(); + if (!iv->isReachable()) { + iv->sendChangeChannelRequest(); } - if (iv->getEnablePolling() || iv->getEnableCommands()) { - _messageOutput->print("Fetch inverter: "); - _messageOutput->println(iv->serial(), HEX); + iv->sendStatsRequest(); - if (!iv->isReachable()) { - iv->sendChangeChannelRequest(); - } + // Fetch event log + bool force = iv->EventLog()->getLastAlarmRequestSuccess() == CMD_NOK; + iv->sendAlarmLogRequest(force); - iv->sendStatsRequest(); - - // Fetch event log - bool force = iv->EventLog()->getLastAlarmRequestSuccess() == CMD_NOK; - iv->sendAlarmLogRequest(force); - - // Fetch limit - if (((millis() - iv->SystemConfigPara()->getLastUpdateRequest() > HOY_SYSTEM_CONFIG_PARA_POLL_INTERVAL) - && (millis() - iv->SystemConfigPara()->getLastUpdateCommand() > HOY_SYSTEM_CONFIG_PARA_POLL_MIN_DURATION))) { - _messageOutput->println("Request SystemConfigPara"); - iv->sendSystemConfigParaRequest(); - } - - // Set limit if required - if (iv->SystemConfigPara()->getLastLimitCommandSuccess() == CMD_NOK) { - _messageOutput->println("Resend ActivePowerControl"); - iv->resendActivePowerControlRequest(); - } - - // Set power status if required - if (iv->PowerCommand()->getLastPowerCommandSuccess() == CMD_NOK) { - _messageOutput->println("Resend PowerCommand"); - iv->resendPowerControlRequest(); - } - - // Fetch dev info (but first fetch stats) - if (iv->Statistics()->getLastUpdate() > 0) { - bool invalidDevInfo = !iv->DevInfo()->containsValidData() - && iv->DevInfo()->getLastUpdateAll() > 0 - && iv->DevInfo()->getLastUpdateSimple() > 0; - - if (invalidDevInfo) { - _messageOutput->println("DevInfo: No Valid Data"); - } - - if ((iv->DevInfo()->getLastUpdateAll() == 0) - || (iv->DevInfo()->getLastUpdateSimple() == 0) - || invalidDevInfo) { - _messageOutput->println("Request device info"); - iv->sendDevInfoRequest(); - } - } - - // Fetch grid profile - if (iv->Statistics()->getLastUpdate() > 0 && iv->GridProfile()->getLastUpdate() == 0) { - iv->sendGridOnProFileParaRequest(); - } - - _lastPoll = millis(); + // Fetch limit + if (((millis() - iv->SystemConfigPara()->getLastUpdateRequest() > HOY_SYSTEM_CONFIG_PARA_POLL_INTERVAL) + && (millis() - iv->SystemConfigPara()->getLastUpdateCommand() > HOY_SYSTEM_CONFIG_PARA_POLL_MIN_DURATION))) { + _messageOutput->println("Request SystemConfigPara"); + iv->sendSystemConfigParaRequest(); } - if (++inverterPos >= getNumInverters()) { - inverterPos = 0; + // Set limit if required + if (iv->SystemConfigPara()->getLastLimitCommandSuccess() == CMD_NOK) { + _messageOutput->println("Resend ActivePowerControl"); + iv->resendActivePowerControlRequest(); } + + // Set power status if required + if (iv->PowerCommand()->getLastPowerCommandSuccess() == CMD_NOK) { + _messageOutput->println("Resend PowerCommand"); + iv->resendPowerControlRequest(); + } + + // Fetch dev info (but first fetch stats) + if (iv->Statistics()->getLastUpdate() > 0) { + bool invalidDevInfo = !iv->DevInfo()->containsValidData() + && iv->DevInfo()->getLastUpdateAll() > 0 + && iv->DevInfo()->getLastUpdateSimple() > 0; + + if (invalidDevInfo) { + _messageOutput->println("DevInfo: No Valid Data"); + } + + if ((iv->DevInfo()->getLastUpdateAll() == 0) + || (iv->DevInfo()->getLastUpdateSimple() == 0) + || invalidDevInfo) { + _messageOutput->println("Request device info"); + iv->sendDevInfoRequest(); + } + } + + // Fetch grid profile + if (iv->Statistics()->getLastUpdate() > 0 && iv->GridProfile()->getLastUpdate() == 0) { + iv->sendGridOnProFileParaRequest(); + } + + _lastPoll = millis(); } - // Perform housekeeping of all inverters on day change - int8_t currentWeekDay = Utils::getWeekDay(); - static int8_t lastWeekDay = -1; - if (lastWeekDay == -1) { + if (++inverterPos >= getNumInverters()) { + inverterPos = 0; + } + } + + // Perform housekeeping of all inverters on day change + int8_t currentWeekDay = Utils::getWeekDay(); + static int8_t lastWeekDay = -1; + if (lastWeekDay == -1) { + lastWeekDay = currentWeekDay; + } else { + if (currentWeekDay != lastWeekDay) { + + for (auto& inv : _inverters) { + if (inv->getZeroYieldDayOnMidnight()) { + inv->Statistics()->zeroDailyData(); + } + inv->Statistics()->resetYieldDayCorrection(); + } + lastWeekDay = currentWeekDay; - } else { - if (currentWeekDay != lastWeekDay) { - - for (auto& inv : _inverters) { - if (inv->getZeroYieldDayOnMidnight()) { - inv->Statistics()->zeroDailyData(); - } - inv->Statistics()->resetYieldDayCorrection(); - } - - lastWeekDay = currentWeekDay; - } } } } From 8b5d406a4fc6dab91da54eccaed8bd0b14cd7957 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Thu, 7 Dec 2023 15:19:04 +0100 Subject: [PATCH 38/89] Introduce several const statements --- include/WebApi_ws_live.h | 2 +- lib/Hoymiles/src/Hoymiles.cpp | 6 +++--- lib/Hoymiles/src/Hoymiles.h | 2 +- lib/Hoymiles/src/HoymilesRadio.cpp | 2 +- lib/Hoymiles/src/HoymilesRadio.h | 2 +- lib/Hoymiles/src/commands/MultiDataCommand.cpp | 4 ++-- .../src/commands/RealTimeRunDataCommand.cpp | 6 +++--- .../src/commands/SystemConfigParaCommand.cpp | 6 +++--- lib/Hoymiles/src/parser/AlarmLogParser.cpp | 6 +++--- lib/Hoymiles/src/parser/DevInfoParser.cpp | 17 +++++++---------- lib/Hoymiles/src/parser/StatisticsParser.cpp | 17 ++++++++--------- src/WebApi_ws_live.cpp | 2 +- src/main.cpp | 6 +++--- 13 files changed, 37 insertions(+), 41 deletions(-) diff --git a/include/WebApi_ws_live.h b/include/WebApi_ws_live.h index 1e664920..ab7d1a43 100644 --- a/include/WebApi_ws_live.h +++ b/include/WebApi_ws_live.h @@ -14,7 +14,7 @@ public: private: void generateJsonResponse(JsonVariant& root); void addField(JsonObject& root, uint8_t idx, std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, String topic = ""); - void addTotalField(JsonObject& root, String name, float value, String unit, uint8_t digits); + void addTotalField(JsonObject& root, const String& name, float value, const String& unit, uint8_t digits); void onLivedataStatus(AsyncWebServerRequest* request); void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); diff --git a/lib/Hoymiles/src/Hoymiles.cpp b/lib/Hoymiles/src/Hoymiles.cpp index 937be53c..05a12ef3 100644 --- a/lib/Hoymiles/src/Hoymiles.cpp +++ b/lib/Hoymiles/src/Hoymiles.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ #include "Hoymiles.h" #include "Utils.h" @@ -126,7 +126,7 @@ void HoymilesClass::loop() } // Perform housekeeping of all inverters on day change - int8_t currentWeekDay = Utils::getWeekDay(); + const int8_t currentWeekDay = Utils::getWeekDay(); static int8_t lastWeekDay = -1; if (lastWeekDay == -1) { lastWeekDay = currentWeekDay; @@ -198,7 +198,7 @@ std::shared_ptr HoymilesClass::getInverterBySerial(uint64_t se return nullptr; } -std::shared_ptr HoymilesClass::getInverterByFragment(fragment_t* fragment) +std::shared_ptr HoymilesClass::getInverterByFragment(const fragment_t* fragment) { if (fragment->len <= 4) { return nullptr; diff --git a/lib/Hoymiles/src/Hoymiles.h b/lib/Hoymiles/src/Hoymiles.h index 11b84763..7b37efc3 100644 --- a/lib/Hoymiles/src/Hoymiles.h +++ b/lib/Hoymiles/src/Hoymiles.h @@ -26,7 +26,7 @@ public: std::shared_ptr addInverter(const char* name, uint64_t serial); std::shared_ptr getInverterByPos(uint8_t pos); std::shared_ptr getInverterBySerial(uint64_t serial); - std::shared_ptr getInverterByFragment(fragment_t* fragment); + std::shared_ptr getInverterByFragment(const fragment_t* fragment); void removeInverterBySerial(uint64_t serial); size_t getNumInverters(); diff --git a/lib/Hoymiles/src/HoymilesRadio.cpp b/lib/Hoymiles/src/HoymilesRadio.cpp index 4afc447a..e1e101db 100644 --- a/lib/Hoymiles/src/HoymilesRadio.cpp +++ b/lib/Hoymiles/src/HoymilesRadio.cpp @@ -28,7 +28,7 @@ serial_u HoymilesRadio::convertSerialToRadioId(serial_u serial) return radioId; } -bool HoymilesRadio::checkFragmentCrc(fragment_t* fragment) +bool HoymilesRadio::checkFragmentCrc(const fragment_t* fragment) { uint8_t crc = crc8(fragment->fragment, fragment->len - 1); return (crc == fragment->fragment[fragment->len - 1]); diff --git a/lib/Hoymiles/src/HoymilesRadio.h b/lib/Hoymiles/src/HoymilesRadio.h index fa2f6945..69ef1e91 100644 --- a/lib/Hoymiles/src/HoymilesRadio.h +++ b/lib/Hoymiles/src/HoymilesRadio.h @@ -31,7 +31,7 @@ protected: static serial_u convertSerialToRadioId(serial_u serial); void dumpBuf(const uint8_t buf[], uint8_t len, bool appendNewline = true); - bool checkFragmentCrc(fragment_t* fragment); + bool checkFragmentCrc(const fragment_t* fragment); virtual void sendEsbPacket(CommandAbstract* cmd) = 0; void sendRetransmitPacket(uint8_t fragment_id); void sendLastPacketAgain(); diff --git a/lib/Hoymiles/src/commands/MultiDataCommand.cpp b/lib/Hoymiles/src/commands/MultiDataCommand.cpp index 39a0d4c6..39640609 100644 --- a/lib/Hoymiles/src/commands/MultiDataCommand.cpp +++ b/lib/Hoymiles/src/commands/MultiDataCommand.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ #include "MultiDataCommand.h" #include "crc.h" @@ -88,7 +88,7 @@ bool MultiDataCommand::handleResponse(InverterAbstract* inverter, fragment_t fra void MultiDataCommand::udpateCRC() { - uint16_t crc = crc16(&_payload[10], 14); // From data_type till password + const uint16_t crc = crc16(&_payload[10], 14); // From data_type till password _payload[24] = (uint8_t)(crc >> 8); _payload[25] = (uint8_t)(crc); } diff --git a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp index 3f0aed36..1be41f9f 100644 --- a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp +++ b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ #include "RealTimeRunDataCommand.h" #include "Hoymiles.h" @@ -29,8 +29,8 @@ bool RealTimeRunDataCommand::handleResponse(InverterAbstract* inverter, fragment // Check if at least all required bytes are received // In case of low power in the inverter it occours that some incomplete fragments // with a valid CRC are received. - uint8_t fragmentsSize = getTotalFragmentSize(fragment, max_fragment_id); - uint8_t expectedSize = inverter->Statistics()->getExpectedByteCount(); + const uint8_t fragmentsSize = getTotalFragmentSize(fragment, max_fragment_id); + const uint8_t expectedSize = inverter->Statistics()->getExpectedByteCount(); if (fragmentsSize < expectedSize) { Hoymiles.getMessageOutput()->printf("ERROR in %s: Received fragment size: %d, min expected size: %d\r\n", getCommandName().c_str(), fragmentsSize, expectedSize); diff --git a/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp b/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp index 5e238a59..ca42f48f 100644 --- a/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp +++ b/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ #include "SystemConfigParaCommand.h" #include "Hoymiles.h" @@ -29,8 +29,8 @@ bool SystemConfigParaCommand::handleResponse(InverterAbstract* inverter, fragmen // Check if at least all required bytes are received // In case of low power in the inverter it occours that some incomplete fragments // with a valid CRC are received. - uint8_t fragmentsSize = getTotalFragmentSize(fragment, max_fragment_id); - uint8_t expectedSize = inverter->SystemConfigPara()->getExpectedByteCount(); + const uint8_t fragmentsSize = getTotalFragmentSize(fragment, max_fragment_id); + const uint8_t expectedSize = inverter->SystemConfigPara()->getExpectedByteCount(); if (fragmentsSize < expectedSize) { Hoymiles.getMessageOutput()->printf("ERROR in %s: Received fragment size: %d, min expected size: %d\r\n", getCommandName().c_str(), fragmentsSize, expectedSize); diff --git a/lib/Hoymiles/src/parser/AlarmLogParser.cpp b/lib/Hoymiles/src/parser/AlarmLogParser.cpp index fe2d2bab..41ce4cd7 100644 --- a/lib/Hoymiles/src/parser/AlarmLogParser.cpp +++ b/lib/Hoymiles/src/parser/AlarmLogParser.cpp @@ -216,13 +216,13 @@ void AlarmLogParser::setMessageType(AlarmMessageType_t type) void AlarmLogParser::getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry, AlarmMessageLocale_t locale) { - uint8_t entryStartOffset = 2 + entryId * ALARM_LOG_ENTRY_SIZE; + const uint8_t entryStartOffset = 2 + entryId * ALARM_LOG_ENTRY_SIZE; - int timezoneOffset = getTimezoneOffset(); + const int timezoneOffset = getTimezoneOffset(); HOY_SEMAPHORE_TAKE(); - uint32_t wcode = (uint16_t)_payloadAlarmLog[entryStartOffset] << 8 | _payloadAlarmLog[entryStartOffset + 1]; + const uint32_t wcode = (uint16_t)_payloadAlarmLog[entryStartOffset] << 8 | _payloadAlarmLog[entryStartOffset + 1]; uint32_t startTimeOffset = 0; if (((wcode >> 13) & 0x01) == 1) { startTimeOffset = 12 * 60 * 60; diff --git a/lib/Hoymiles/src/parser/DevInfoParser.cpp b/lib/Hoymiles/src/parser/DevInfoParser.cpp index 1de00092..52edd8c1 100644 --- a/lib/Hoymiles/src/parser/DevInfoParser.cpp +++ b/lib/Hoymiles/src/parser/DevInfoParser.cpp @@ -116,7 +116,7 @@ void DevInfoParser::setLastUpdateSimple(uint32_t lastUpdate) uint16_t DevInfoParser::getFwBuildVersion() { HOY_SEMAPHORE_TAKE(); - uint16_t ret = (((uint16_t)_payloadDevInfoAll[0]) << 8) | _payloadDevInfoAll[1]; + const uint16_t ret = (((uint16_t)_payloadDevInfoAll[0]) << 8) | _payloadDevInfoAll[1]; HOY_SEMAPHORE_GIVE(); return ret; } @@ -140,19 +140,16 @@ time_t DevInfoParser::getFwBuildDateTime() uint16_t DevInfoParser::getFwBootloaderVersion() { HOY_SEMAPHORE_TAKE(); - uint16_t ret = (((uint16_t)_payloadDevInfoAll[8]) << 8) | _payloadDevInfoAll[9]; + const uint16_t ret = (((uint16_t)_payloadDevInfoAll[8]) << 8) | _payloadDevInfoAll[9]; HOY_SEMAPHORE_GIVE(); return ret; } uint32_t DevInfoParser::getHwPartNumber() { - uint16_t hwpn_h; - uint16_t hwpn_l; - HOY_SEMAPHORE_TAKE(); - hwpn_h = (((uint16_t)_payloadDevInfoSimple[2]) << 8) | _payloadDevInfoSimple[3]; - hwpn_l = (((uint16_t)_payloadDevInfoSimple[4]) << 8) | _payloadDevInfoSimple[5]; + const uint16_t hwpn_h = (((uint16_t)_payloadDevInfoSimple[2]) << 8) | _payloadDevInfoSimple[3]; + const uint16_t hwpn_l = (((uint16_t)_payloadDevInfoSimple[4]) << 8) | _payloadDevInfoSimple[5]; HOY_SEMAPHORE_GIVE(); return ((uint32_t)hwpn_h << 16) | ((uint32_t)hwpn_l); @@ -169,7 +166,7 @@ String DevInfoParser::getHwVersion() uint16_t DevInfoParser::getMaxPower() { - uint8_t idx = getDevIdx(); + const uint8_t idx = getDevIdx(); if (idx == 0xff) { return 0; } @@ -178,7 +175,7 @@ uint16_t DevInfoParser::getMaxPower() String DevInfoParser::getHwModelName() { - uint8_t idx = getDevIdx(); + const uint8_t idx = getDevIdx(); if (idx == 0xff) { return ""; } @@ -187,7 +184,7 @@ String DevInfoParser::getHwModelName() bool DevInfoParser::containsValidData() { - time_t t = getFwBuildDateTime(); + const time_t t = getFwBuildDateTime(); struct tm info; localtime_r(&t, &info); diff --git a/lib/Hoymiles/src/parser/StatisticsParser.cpp b/lib/Hoymiles/src/parser/StatisticsParser.cpp index 84f19299..b436f430 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.cpp +++ b/lib/Hoymiles/src/parser/StatisticsParser.cpp @@ -144,15 +144,13 @@ fieldSettings_t* StatisticsParser::getSettingByChannelField(ChannelType_t type, float StatisticsParser::getChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) { const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId); - fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId); - if (pos == NULL) { return 0; } uint8_t ptr = pos->start; - uint8_t end = ptr + pos->num; - uint16_t div = pos->div; + const uint8_t end = ptr + pos->num; + const uint16_t div = pos->div; if (CMD_CALC != div) { // Value is a static value @@ -174,6 +172,8 @@ float StatisticsParser::getChannelFieldValue(ChannelType_t type, ChannelNum_t ch } result /= static_cast(div); + + const fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId); if (setting != NULL && _statisticLength > 0) { result += setting->offset; } @@ -189,20 +189,19 @@ float StatisticsParser::getChannelFieldValue(ChannelType_t type, ChannelNum_t ch bool StatisticsParser::setChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float value) { const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId); - fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId); - if (pos == NULL) { return false; } uint8_t ptr = pos->start + pos->num - 1; - uint8_t end = pos->start; - uint16_t div = pos->div; + const uint8_t end = pos->start; + const uint16_t div = pos->div; if (CMD_CALC == div) { return false; } + const fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId); if (setting != NULL) { value -= setting->offset; } @@ -260,7 +259,7 @@ uint8_t StatisticsParser::getChannelFieldDigits(ChannelType_t type, ChannelNum_t float StatisticsParser::getChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) { - fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId); + const fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId); if (setting != NULL) { return setting->offset; } diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index d0a88b73..41ebb84b 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -196,7 +196,7 @@ void WebApiWsLiveClass::addField(JsonObject& root, uint8_t idx, std::shared_ptr< } } -void WebApiWsLiveClass::addTotalField(JsonObject& root, String name, float value, String unit, uint8_t digits) +void WebApiWsLiveClass::addTotalField(JsonObject& root, const String& name, float value, const String& unit, uint8_t digits) { root[name]["v"] = value; root[name]["u"] = unit; diff --git a/src/main.cpp b/src/main.cpp index e7dd14d2..cf9f4fa8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,7 +68,7 @@ void setup() MessageOutput.print("migrated... "); Configuration.migrate(); } - CONFIG_T& config = Configuration.get(); + auto& config = Configuration.get(); MessageOutput.println("done"); // Load PinMapping @@ -78,7 +78,7 @@ void setup() } else { MessageOutput.print("using default config "); } - const PinMapping_t& pin = PinMapping.get(); + const auto& pin = PinMapping.get(); MessageOutput.println("done"); // Initialize WiFi @@ -137,7 +137,7 @@ void setup() MessageOutput.print("Check for default DTU serial... "); if (config.Dtu.Serial == DTU_SERIAL) { MessageOutput.print("generate serial based on ESP chip id: "); - uint64_t dtuId = Utils::generateDtuSerial(); + const uint64_t dtuId = Utils::generateDtuSerial(); MessageOutput.printf("%0x%08x... ", ((uint32_t)((dtuId >> 32) & 0xFFFFFFFF)), ((uint32_t)(dtuId & 0xFFFFFFFF))); From b937532505d2074112cc3af26f5e815db434ed99 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Thu, 7 Dec 2023 15:21:24 +0100 Subject: [PATCH 39/89] Remove not required AsyncEventSource --- include/WebApi.h | 1 - src/WebApi.cpp | 3 --- 2 files changed, 4 deletions(-) diff --git a/include/WebApi.h b/include/WebApi.h index b96184b3..e6d1b0cd 100644 --- a/include/WebApi.h +++ b/include/WebApi.h @@ -40,7 +40,6 @@ private: Task _loopTask; AsyncWebServer _server; - AsyncEventSource _events; WebApiConfigClass _webApiConfig; WebApiDeviceClass _webApiDevice; diff --git a/src/WebApi.cpp b/src/WebApi.cpp index 2c5cc803..37fb5142 100644 --- a/src/WebApi.cpp +++ b/src/WebApi.cpp @@ -9,14 +9,11 @@ WebApiClass::WebApiClass() : _server(HTTP_PORT) - , _events("/events") { } void WebApiClass::init(Scheduler* scheduler) { - _server.addHandler(&_events); - _webApiConfig.init(&_server); _webApiDevice.init(&_server); _webApiDevInfo.init(&_server); From c9508d26607f288471938f7e82b1073a8d047e5d Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Thu, 7 Dec 2023 20:26:11 +0100 Subject: [PATCH 40/89] Doc: Added byte specification to each command --- .../commands/ActivePowerControlCommand.cpp | 22 ++++++++++++++-- .../src/commands/AlarmDataCommand.cpp | 20 +++++++++++++- .../src/commands/ChannelChangeCommand.cpp | 14 ++++++++++ lib/Hoymiles/src/commands/CommandAbstract.cpp | 26 ++++++++++++++++++- .../src/commands/DevControlCommand.cpp | 19 +++++++++++++- .../src/commands/DevInfoAllCommand.cpp | 18 ++++++++++++- .../src/commands/DevInfoSimpleCommand.cpp | 18 ++++++++++++- .../src/commands/GridOnProFilePara.cpp | 18 ++++++++++++- .../src/commands/MultiDataCommand.cpp | 23 ++++++++++++++++ .../src/commands/PowerControlCommand.cpp | 21 ++++++++++++++- .../src/commands/RealTimeRunDataCommand.cpp | 16 ++++++++++++ .../src/commands/RequestFrameCommand.cpp | 20 +++++++++++++- .../src/commands/SingleDataCommand.cpp | 17 +++++++++++- .../src/commands/SystemConfigParaCommand.cpp | 16 ++++++++++++ 14 files changed, 257 insertions(+), 11 deletions(-) diff --git a/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp b/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp index 78bcd55e..cfbd75cf 100644 --- a/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp +++ b/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp @@ -1,7 +1,25 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ + +/* +This command is used to send a limit to the inverter. + +Derives from DevControlCommand. + +Command structure: +SCmd: Sub-Command ID. Is always 0x0b +Limit: limit to be set in the inverter +Type: absolute / relative and persistant/non-persistant + +00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +------------------------------------------------------------------------------------------------------------------- + |<------ CRC16 ------>| +51 71 60 35 46 80 12 23 04 81 0b 00 00 00 00 00 00 00 00 -- -- -- -- -- -- -- -- -- -- -- -- -- +^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^ ^^^^^ ^^^^^ ^^ +ID Target Addr Source Addr Cmd SCmd ? Limit Type CRC16 CRC8 +*/ #include "ActivePowerControlCommand.h" #include "inverters/InverterAbstract.h" @@ -17,7 +35,7 @@ ActivePowerControlCommand::ActivePowerControlCommand(uint64_t target_address, ui _payload[14] = 0x00; _payload[15] = 0x00; - udpateCRC(CRC_SIZE); // 2 byte crc + udpateCRC(CRC_SIZE); // 6 byte crc _payload_size = 18; diff --git a/lib/Hoymiles/src/commands/AlarmDataCommand.cpp b/lib/Hoymiles/src/commands/AlarmDataCommand.cpp index 574e0be2..49382c65 100644 --- a/lib/Hoymiles/src/commands/AlarmDataCommand.cpp +++ b/lib/Hoymiles/src/commands/AlarmDataCommand.cpp @@ -1,7 +1,25 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ + +/* +This command is used to fetch the eventlog from the inverter. + +Derives from MultiDataCommand + +Command structure: +* DT: this specific command uses 0x11 +* AlarmId: The last event id received from the inverter or zero in case that no events + has been received yet. --> Not Implemented yet + +00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +----------------------------------------------------------------------------------------------------------------------- + |<------------------- CRC16 --------------------->| +15 71 60 35 46 80 12 23 04 80 11 00 65 72 06 B8 00 00 00 00 00 00 00 00 00 00 00 -- -- -- -- -- +^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^ +ID Target Addr Source Addr Idx DT ? Time Gap AlarmId Password CRC16 CRC8 +*/ #include "AlarmDataCommand.h" #include "inverters/InverterAbstract.h" diff --git a/lib/Hoymiles/src/commands/ChannelChangeCommand.cpp b/lib/Hoymiles/src/commands/ChannelChangeCommand.cpp index 139bbea3..c7e9dcde 100644 --- a/lib/Hoymiles/src/commands/ChannelChangeCommand.cpp +++ b/lib/Hoymiles/src/commands/ChannelChangeCommand.cpp @@ -2,6 +2,20 @@ /* * Copyright (C) 2023 Thomas Basler and others */ + +/* +Derives from CommandAbstract. Special command to set frequency channel on HMS/HMT inverters. + +Command structure: +* ID: fixed identifier and everytime 0x56 +* CH: Channel to which the inverter will be switched to + +00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +----------------------------------------------------------------------------------------------------------------- +56 71 60 35 46 80 12 23 04 02 15 21 00 14 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^ +ID Target Addr Source Addr ? ? ? CH ? CRC8 +*/ #include "ChannelChangeCommand.h" ChannelChangeCommand::ChannelChangeCommand(uint64_t target_address, uint64_t router_address, uint8_t channel) diff --git a/lib/Hoymiles/src/commands/CommandAbstract.cpp b/lib/Hoymiles/src/commands/CommandAbstract.cpp index 78d8d07d..16213064 100644 --- a/lib/Hoymiles/src/commands/CommandAbstract.cpp +++ b/lib/Hoymiles/src/commands/CommandAbstract.cpp @@ -1,7 +1,31 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ + +/* +Command structure: +* Each package has a maximum of 32 bytes +* Target Address: the address of the inverter. Has to be read as hex value +* Source Address the address of the dtu itself. Has to be read as hex value +* CRC8: a crc8 checksum added to the end of the payload containing all valid data. + Each sub-commmand has to set it's own payload size. + +Conversion of Target Addr: +Inverter Serial Number: (0x)116171603546 +Target Address: 71 60 35 46 + +Conversion of Source Addr: +DTU Serial Number: (0x)199980122304 +Source Address: 80 12 23 04 + +00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +----------------------------------------------------------------------------------------------------- +|<------------- CRC8 ------------>| +00 71 60 35 46 80 12 23 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ + Target Addr Source Addr CRC8 +*/ #include "CommandAbstract.h" #include "crc.h" #include diff --git a/lib/Hoymiles/src/commands/DevControlCommand.cpp b/lib/Hoymiles/src/commands/DevControlCommand.cpp index fce935b8..d57b3deb 100644 --- a/lib/Hoymiles/src/commands/DevControlCommand.cpp +++ b/lib/Hoymiles/src/commands/DevControlCommand.cpp @@ -1,8 +1,25 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ + +/* +Derives from CommandAbstract. Has a variable length. + +Command structure: +* ID: fixed identifier and everytime 0x51 +* Cmd: Fixed at 0x81 for these types of commands +* Payload: dynamic amount of bytes +* CRC16: calcuclated over the highlighted amount of bytes + +00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +------------------------------------------------------------------------------------------------------------- + |<->| CRC16 +51 71 60 35 46 80 12 23 04 81 00 00 00 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^^^^ ^^ ^^ +ID Target Addr Source Addr Cmd Payload CRC16 CRC8 +*/ #include "DevControlCommand.h" #include "crc.h" diff --git a/lib/Hoymiles/src/commands/DevInfoAllCommand.cpp b/lib/Hoymiles/src/commands/DevInfoAllCommand.cpp index b175822e..6820ba66 100644 --- a/lib/Hoymiles/src/commands/DevInfoAllCommand.cpp +++ b/lib/Hoymiles/src/commands/DevInfoAllCommand.cpp @@ -1,7 +1,23 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ + +/* +This command is used to fetch firmware information from the inverter. + +Derives from MultiDataCommand + +Command structure: +* DT: this specific command uses 0x01 + +00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +----------------------------------------------------------------------------------------------------------------------- + |<------------------- CRC16 --------------------->| +15 71 60 35 46 80 12 23 04 80 01 00 65 72 06 B8 00 00 00 00 00 00 00 00 00 00 00 -- -- -- -- -- +^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^ +ID Target Addr Source Addr Idx DT ? Time Gap Password CRC16 CRC8 +*/ #include "DevInfoAllCommand.h" #include "inverters/InverterAbstract.h" diff --git a/lib/Hoymiles/src/commands/DevInfoSimpleCommand.cpp b/lib/Hoymiles/src/commands/DevInfoSimpleCommand.cpp index 09d5a467..3caccf77 100644 --- a/lib/Hoymiles/src/commands/DevInfoSimpleCommand.cpp +++ b/lib/Hoymiles/src/commands/DevInfoSimpleCommand.cpp @@ -1,7 +1,23 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ + +/* +This command is used to fetch hardware information from the inverter. + +Derives from MultiDataCommand + +Command structure: +* DT: this specific command uses 0x00 + +00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +----------------------------------------------------------------------------------------------------------------------- + |<------------------- CRC16 --------------------->| +15 71 60 35 46 80 12 23 04 80 00 00 65 72 06 B8 00 00 00 00 00 00 00 00 00 00 00 -- -- -- -- -- +^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^ +ID Target Addr Source Addr Idx DT ? Time Gap Password CRC16 CRC8 +*/ #include "DevInfoSimpleCommand.h" #include "inverters/InverterAbstract.h" diff --git a/lib/Hoymiles/src/commands/GridOnProFilePara.cpp b/lib/Hoymiles/src/commands/GridOnProFilePara.cpp index e9171672..5af8e46f 100644 --- a/lib/Hoymiles/src/commands/GridOnProFilePara.cpp +++ b/lib/Hoymiles/src/commands/GridOnProFilePara.cpp @@ -1,7 +1,23 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ + +/* +This command is used to fetch the grid profile from the inverter. + +Derives from MultiDataCommand + +Command structure: +* DT: this specific command uses 0x02 + +00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +----------------------------------------------------------------------------------------------------------------------- + |<------------------- CRC16 --------------------->| +15 71 60 35 46 80 12 23 04 80 02 00 65 72 06 B8 00 00 00 00 00 00 00 00 00 00 00 -- -- -- -- -- +^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^ +ID Target Addr Source Addr Idx DT ? Time Gap Password CRC16 CRC8 +*/ #include "GridOnProFilePara.h" #include "Hoymiles.h" #include "inverters/InverterAbstract.h" diff --git a/lib/Hoymiles/src/commands/MultiDataCommand.cpp b/lib/Hoymiles/src/commands/MultiDataCommand.cpp index 39640609..3b9e1652 100644 --- a/lib/Hoymiles/src/commands/MultiDataCommand.cpp +++ b/lib/Hoymiles/src/commands/MultiDataCommand.cpp @@ -2,6 +2,29 @@ /* * Copyright (C) 2022-2023 Thomas Basler and others */ + +/* +Derives from CommandAbstract. Has a fixed length of 26 bytes. + +Command structure: +* ID: fixed identifier and everytime 0x15 +* Idx: the counter of sequencial packages to send. Currently it's only 0x80 + because all request requests only consist of one package. +* DT: repressents the data type and specifies which sub-command to be fetched +* Time: represents the current unix timestamp as hex format. The time on the inverter is synced to the sent time. + Can be calculated e.g. using the following command + echo "obase=16; $(date --date='2023-12-07 18:54:00' +%s)" | bc +* Gap: always 0x0 +* Password: currently always 0x0 +* CRC16: calcuclated over the highlighted amount of bytes + +00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +----------------------------------------------------------------------------------------------------------------------- + |<------------------- CRC16 --------------------->| +15 71 60 35 46 80 12 23 04 80 00 00 65 72 06 B8 00 00 00 00 00 00 00 00 00 00 00 -- -- -- -- -- +^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^ +ID Target Addr Source Addr Idx DT ? Time Gap Password CRC16 CRC8 +*/ #include "MultiDataCommand.h" #include "crc.h" diff --git a/lib/Hoymiles/src/commands/PowerControlCommand.cpp b/lib/Hoymiles/src/commands/PowerControlCommand.cpp index 522ad5f2..6698c842 100644 --- a/lib/Hoymiles/src/commands/PowerControlCommand.cpp +++ b/lib/Hoymiles/src/commands/PowerControlCommand.cpp @@ -1,7 +1,26 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ + +/* +This command is used to power cycle the inverter. + +Derives from DevControlCommand. + +Command structure: +SCmd: Sub-Command ID + 00 --> Turn On + 01 --> Turn Off + 02 --> Restart + +00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +--------------------------------------------------------------------------------------------------------------- + |<--->| CRC16 +51 71 60 35 46 80 12 23 04 81 00 00 00 00 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^ ^^ +ID Target Addr Source Addr Cmd SCmd ? CRC16 CRC8 +*/ #include "PowerControlCommand.h" #include "inverters/InverterAbstract.h" diff --git a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp index 1be41f9f..ccca0870 100644 --- a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp +++ b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp @@ -2,6 +2,22 @@ /* * Copyright (C) 2022-2023 Thomas Basler and others */ + +/* +This command is used to fetch live run time data from the inverter. + +Derives from MultiDataCommand + +Command structure: +* DT: this specific command uses 0x0b + +00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +----------------------------------------------------------------------------------------------------------------------- + |<------------------- CRC16 --------------------->| +15 71 60 35 46 80 12 23 04 80 0b 00 65 72 06 B8 00 00 00 00 00 00 00 00 00 00 00 -- -- -- -- -- +^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^ +ID Target Addr Source Addr Idx DT ? Time Gap Password CRC16 CRC8 +*/ #include "RealTimeRunDataCommand.h" #include "Hoymiles.h" #include "inverters/InverterAbstract.h" diff --git a/lib/Hoymiles/src/commands/RequestFrameCommand.cpp b/lib/Hoymiles/src/commands/RequestFrameCommand.cpp index e2bfb766..0e379c3f 100644 --- a/lib/Hoymiles/src/commands/RequestFrameCommand.cpp +++ b/lib/Hoymiles/src/commands/RequestFrameCommand.cpp @@ -1,7 +1,25 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ + +/* +This command is used to re-request a specific fragment returned by a MultiDataCommand from the inverter. + +Derives from SingleDataCommand. Has a fixed length of 10 bytes. + +Command structure: +* ID: fixed identifier and everytime 0x15 +* Idx: the counter of sequencial packages to send. Currently it's only 0x80 + because all request requests only consist of one package. +* Frm: is set to the fragment id to re-request. "Or" operation with 0x80 is applied to the frame. + +00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +--------------------------------------------------------------------------------------------------------- +15 71 60 35 46 80 12 23 04 85 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ +ID Target Addr Source Addr Frm CRC8 +*/ #include "RequestFrameCommand.h" RequestFrameCommand::RequestFrameCommand(uint64_t target_address, uint64_t router_address, uint8_t frame_no) diff --git a/lib/Hoymiles/src/commands/SingleDataCommand.cpp b/lib/Hoymiles/src/commands/SingleDataCommand.cpp index 636ee87a..49155481 100644 --- a/lib/Hoymiles/src/commands/SingleDataCommand.cpp +++ b/lib/Hoymiles/src/commands/SingleDataCommand.cpp @@ -1,7 +1,22 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ + +/* +This command is used to send simple commands, containing only one payload, to the inverter. + +Derives from CommandAbstract. + +Command structure: +* ID: fixed identifier and everytime 0x15 + +00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +--------------------------------------------------------------------------------------------------------- +15 71 60 35 46 80 12 23 04 00 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ +ID Target Addr Source Addr CRC8 +*/ #include "SingleDataCommand.h" SingleDataCommand::SingleDataCommand(uint64_t target_address, uint64_t router_address) diff --git a/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp b/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp index ca42f48f..48a56238 100644 --- a/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp +++ b/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp @@ -2,6 +2,22 @@ /* * Copyright (C) 2022-2023 Thomas Basler and others */ + +/* +This command is used to fetch current set limits from the inverter. + +Derives from MultiDataCommand + +Command structure: +* DT: this specific command uses 0x05 + +00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +----------------------------------------------------------------------------------------------------------------------- + |<------------------- CRC16 --------------------->| +15 71 60 35 46 80 12 23 04 80 05 00 65 72 06 B8 00 00 00 00 00 00 00 00 00 00 00 -- -- -- -- -- +^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^ +ID Target Addr Source Addr Idx DT ? Time Gap Password CRC16 CRC8 +*/ #include "SystemConfigParaCommand.h" #include "Hoymiles.h" #include "inverters/InverterAbstract.h" From 00bc631e87f16fb94bcf72d98b7f8e49eba83bbc Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Sat, 9 Dec 2023 11:12:37 +0100 Subject: [PATCH 41/89] Feature: Added basic Grid Profile parser which shows the used profile and version Other values are still outstanding. --- lib/Hoymiles/src/parser/GridProfileParser.cpp | 27 +++++++++++++++++++ lib/Hoymiles/src/parser/GridProfileParser.h | 12 +++++++++ src/WebApi_gridprofile.cpp | 3 +++ webapp/src/components/GridProfile.vue | 13 +++++++++ webapp/src/locales/de.json | 2 ++ webapp/src/locales/en.json | 2 ++ webapp/src/locales/fr.json | 2 ++ webapp/src/types/GridProfileStatus.ts | 2 ++ 8 files changed, 63 insertions(+) diff --git a/lib/Hoymiles/src/parser/GridProfileParser.cpp b/lib/Hoymiles/src/parser/GridProfileParser.cpp index 35f7689d..aa456a8e 100644 --- a/lib/Hoymiles/src/parser/GridProfileParser.cpp +++ b/lib/Hoymiles/src/parser/GridProfileParser.cpp @@ -6,6 +6,16 @@ #include "../Hoymiles.h" #include +const std::array GridProfileParser::_profileTypes = { { + { 0x02, 0x00, "no data (yet)" }, + { 0x03, 0x00, "Germany - DE_VDE4105_2018" }, + { 0x0a, 0x00, "European - EN 50549-1:2019" }, + { 0x0c, 0x00, "AT Tor - EU_EN50438" }, + { 0x0d, 0x04, "France" }, + { 0x12, 0x00, "Poland" }, + { 0x37, 0x00, "Swiss - CH_NA EEA-NE7-CH2020" }, +} }; + GridProfileParser::GridProfileParser() : Parser() { @@ -28,6 +38,23 @@ void GridProfileParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t _gridProfileLength += len; } +String GridProfileParser::getProfileName() +{ + for (auto& ptype : _profileTypes) { + if (ptype.lIdx == _payloadGridProfile[0] && ptype.hIdx == _payloadGridProfile[1]) { + return ptype.Name; + } + } + return "Unknown"; +} + +String GridProfileParser::getProfileVersion() +{ + char buffer[10]; + snprintf(buffer, sizeof(buffer), "%d.%d.%d", (_payloadGridProfile[2] >> 4) & 0x0f, _payloadGridProfile[2] & 0x0f, _payloadGridProfile[3]); + return buffer; +} + std::vector GridProfileParser::getRawData() { std::vector ret; diff --git a/lib/Hoymiles/src/parser/GridProfileParser.h b/lib/Hoymiles/src/parser/GridProfileParser.h index c2af52f8..30653716 100644 --- a/lib/Hoymiles/src/parser/GridProfileParser.h +++ b/lib/Hoymiles/src/parser/GridProfileParser.h @@ -3,6 +3,13 @@ #include "Parser.h" #define GRID_PROFILE_SIZE 141 +#define PROFILE_TYPE_COUNT 7 + +typedef struct { + uint8_t lIdx; + uint8_t hIdx; + const char* Name; +} ProfileType_t; class GridProfileParser : public Parser { public: @@ -10,9 +17,14 @@ public: void clearBuffer(); void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); + String getProfileName(); + String getProfileVersion(); + std::vector getRawData(); private: uint8_t _payloadGridProfile[GRID_PROFILE_SIZE] = {}; uint8_t _gridProfileLength = 0; + + static const std::array _profileTypes; }; \ No newline at end of file diff --git a/src/WebApi_gridprofile.cpp b/src/WebApi_gridprofile.cpp index c9d2adb8..599c54e4 100644 --- a/src/WebApi_gridprofile.cpp +++ b/src/WebApi_gridprofile.cpp @@ -42,6 +42,9 @@ void WebApiGridProfileClass::onGridProfileStatus(AsyncWebServerRequest* request) auto data = inv->GridProfile()->getRawData(); copyArray(&data[0], data.size(), raw); + + root["name"] = inv->GridProfile()->getProfileName(); + root["version"] = inv->GridProfile()->getProfileVersion(); } response->setLength(); diff --git a/webapp/src/components/GridProfile.vue b/webapp/src/components/GridProfile.vue index 31a14133..840e897e 100644 --- a/webapp/src/components/GridProfile.vue +++ b/webapp/src/components/GridProfile.vue @@ -6,6 +6,19 @@