diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9dc0c0df..dd88f54c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -106,7 +106,7 @@ jobs: - name: Rename Factory Firmware run: mv .pio/build/${{ matrix.environment }}/firmware.factory.bin .pio/build/${{ matrix.environment }}/opendtu-onbattery-${{ matrix.environment }}.factory.bin - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: opendtu-onbattery-${{ matrix.environment }} path: | @@ -149,7 +149,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: path: artifacts/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 012b4c28..2ce88dd1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,62 @@ "C_Cpp.clang_format_style": "WebKit", "files.associations": { "*.tcc": "cpp", - "algorithm": "cpp" + "algorithm": "cpp", + "array": "cpp", + "atomic": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "list": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "functional": "cpp", + "iterator": "cpp", + "map": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "regex": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp", + "variant": "cpp" } } \ No newline at end of file diff --git a/docs/DeviceProfiles/CASmo-DTU.json b/docs/DeviceProfiles/CASmo-DTU.json new file mode 100644 index 00000000..0de52e1d --- /dev/null +++ b/docs/DeviceProfiles/CASmo-DTU.json @@ -0,0 +1,20 @@ +[ + { + "name": "CASmo-DTU", + "links": [ + {"name": "Information", "url": "https://casmo.info/product-details/?product=2"} + ], + "nrf24": { + "miso": 19, + "mosi": 23, + "clk": 18, + "irq": 16, + "en": 4, + "cs": 5 + }, + "led": { + "led0": 25, + "led1": 26 + } + } +] \ No newline at end of file diff --git a/docs/DeviceProfiles/blinkyparts_esp32.json b/docs/DeviceProfiles/blinkyparts_esp32.json index 0ee922bf..617777f5 100644 --- a/docs/DeviceProfiles/blinkyparts_esp32.json +++ b/docs/DeviceProfiles/blinkyparts_esp32.json @@ -1,6 +1,12 @@ [ { "name": "NRF, LEDs, Display", + "links": [ + {"name": "Information", "url": "https://shop.blinkyparts.com/de/OpenDTU-NRF-Deine-Auswertung-fuer-deine-Balkonsolaranlage-kompatibel-zu-Hoymiles-HM-Serie-NRF-Modul/blink237542"}, + {"name": "Manual DE", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_de.pdf"}, + {"name": "Manual EN", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_en.pdf"}, + {"name": "Schematic", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/ibom.html"} + ], "nrf24": { "miso": 19, "mosi": 23, @@ -21,6 +27,12 @@ }, { "name": "CMT, LEDs, Display", + "links": [ + {"name": "Information", "url": "https://shop.blinkyparts.com/de/OpenDTU-CMT-Deine-Auswertung-fuer-deine-Balkonsolaranlage-kompatibel-zu-Hoymiles-HMS-und-HMT-Serie-CMT-Modul/blink238342"}, + {"name": "Manual DE", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_de.pdf"}, + {"name": "Manual EN", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_en.pdf"}, + {"name": "Schematic", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/ibom.html"} + ], "nrf24": { "miso": -1, "mosi": -1, @@ -49,6 +61,12 @@ }, { "name": "NRF, Display", + "links": [ + {"name": "Information", "url": "https://shop.blinkyparts.com/de/OpenDTU-NRF-Deine-Auswertung-fuer-deine-Balkonsolaranlage-kompatibel-zu-Hoymiles-HM-Serie-NRF-Modul/blink237542"}, + {"name": "Manual DE", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_de.pdf"}, + {"name": "Manual EN", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_en.pdf"}, + {"name": "Schematic", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/ibom.html"} + ], "nrf24": { "miso": 19, "mosi": 23, @@ -65,6 +83,12 @@ }, { "name": "CMT, Display", + "links": [ + {"name": "Information", "url": "https://shop.blinkyparts.com/de/OpenDTU-CMT-Deine-Auswertung-fuer-deine-Balkonsolaranlage-kompatibel-zu-Hoymiles-HMS-und-HMT-Serie-CMT-Modul/blink238342"}, + {"name": "Manual DE", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_de.pdf"}, + {"name": "Manual EN", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_en.pdf"}, + {"name": "Schematic", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/ibom.html"} + ], "nrf24": { "miso": -1, "mosi": -1, @@ -89,6 +113,12 @@ }, { "name": "NRF, LEDs", + "links": [ + {"name": "Information", "url": "https://shop.blinkyparts.com/de/OpenDTU-NRF-Deine-Auswertung-fuer-deine-Balkonsolaranlage-kompatibel-zu-Hoymiles-HM-Serie-NRF-Modul/blink237542"}, + {"name": "Manual DE", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_de.pdf"}, + {"name": "Manual EN", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_en.pdf"}, + {"name": "Schematic", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/ibom.html"} + ], "nrf24": { "miso": 19, "mosi": 23, @@ -104,6 +134,12 @@ }, { "name": "CMT, LEDs", + "links": [ + {"name": "Information", "url": "https://shop.blinkyparts.com/de/OpenDTU-CMT-Deine-Auswertung-fuer-deine-Balkonsolaranlage-kompatibel-zu-Hoymiles-HMS-und-HMT-Serie-CMT-Modul/blink238342"}, + {"name": "Manual DE", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_de.pdf"}, + {"name": "Manual EN", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_en.pdf"}, + {"name": "Schematic", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/ibom.html"} + ], "nrf24": { "miso": -1, "mosi": -1, @@ -127,6 +163,12 @@ }, { "name": "NRF", + "links": [ + {"name": "Information", "url": "https://shop.blinkyparts.com/de/OpenDTU-NRF-Deine-Auswertung-fuer-deine-Balkonsolaranlage-kompatibel-zu-Hoymiles-HM-Serie-NRF-Modul/blink237542"}, + {"name": "Manual DE", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_de.pdf"}, + {"name": "Manual EN", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_en.pdf"}, + {"name": "Schematic", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/ibom.html"} + ], "nrf24": { "miso": 19, "mosi": 23, @@ -138,6 +180,12 @@ }, { "name": "CMT", + "links": [ + {"name": "Information", "url": "https://shop.blinkyparts.com/de/OpenDTU-CMT-Deine-Auswertung-fuer-deine-Balkonsolaranlage-kompatibel-zu-Hoymiles-HMS-und-HMT-Serie-CMT-Modul/blink238342"}, + {"name": "Manual DE", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_de.pdf"}, + {"name": "Manual EN", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/manual/OpenDTU_Breakout_en.pdf"}, + {"name": "Schematic", "url": "https://binary-kitchen.github.io/SolderingTutorial/OpenDTU_Breakout/ibom.html"} + ], "nrf24": { "miso": -1, "mosi": -1, diff --git a/docs/DeviceProfiles/esp32_stick_poe_a.json b/docs/DeviceProfiles/esp32_stick_poe_a.json new file mode 100644 index 00000000..aca95d6b --- /dev/null +++ b/docs/DeviceProfiles/esp32_stick_poe_a.json @@ -0,0 +1,25 @@ +[ + { + "name": "Esp32-Stick-PoE-A", + "links": [ + {"name": "Information", "url": "https://github.com/allexoK/Esp32-Stick-Boards-Docs"} + ], + "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 + } + } +] diff --git a/docs/DeviceProfiles/lilygo_ttgo_t-internet_poe.json b/docs/DeviceProfiles/lilygo_ttgo_t-internet_poe.json index 538f3000..b5bb4ace 100644 --- a/docs/DeviceProfiles/lilygo_ttgo_t-internet_poe.json +++ b/docs/DeviceProfiles/lilygo_ttgo_t-internet_poe.json @@ -1,6 +1,9 @@ [ { "name": "LILYGO TTGO T-Internet-POE", + "links": [ + {"name": "Datasheet", "url": "https://www.lilygo.cc/products/t-internet-poe"} + ], "nrf24": { "miso": 2, "mosi": 15, @@ -21,6 +24,9 @@ }, { "name": "LILYGO TTGO T-Internet-POE, nrf24 direct solder", + "links": [ + {"name": "Datasheet", "url": "https://www.lilygo.cc/products/t-internet-poe"} + ], "nrf24": { "miso": 12, "mosi": 4, @@ -41,6 +47,9 @@ }, { "name": "LILYGO TTGO T-Internet-POE, nrf24 direct solder, SSD1306", + "links": [ + {"name": "Datasheet", "url": "https://www.lilygo.cc/products/t-internet-poe"} + ], "nrf24": { "miso": 12, "mosi": 4, diff --git a/docs/DeviceProfiles/olimex_esp32_evb.json b/docs/DeviceProfiles/olimex_esp32_evb.json index 9b66926d..ea0a8065 100644 --- a/docs/DeviceProfiles/olimex_esp32_evb.json +++ b/docs/DeviceProfiles/olimex_esp32_evb.json @@ -1,6 +1,9 @@ [ { "name": "Olimex ESP32-EVB", + "links": [ + { "name": "Datasheet", "url": "https://www.olimex.com/Products/IoT/ESP32/ESP32-EVB/open-source-hardware" } + ], "nrf24": { "miso": 15, "mosi": 2, diff --git a/docs/DeviceProfiles/olimex_esp32_poe.json b/docs/DeviceProfiles/olimex_esp32_poe.json index e43dff24..27f8242f 100644 --- a/docs/DeviceProfiles/olimex_esp32_poe.json +++ b/docs/DeviceProfiles/olimex_esp32_poe.json @@ -1,6 +1,9 @@ [ { "name": "Olimex ESP32-POE", + "links": [ + {"name": "Datasheet", "url": "https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware"} + ], "nrf24": { "miso": 15, "mosi": 2, @@ -21,6 +24,9 @@ }, { "name": "Olimex ESP32-POE with SSD1306", + "links": [ + {"name": "Datasheet", "url": "https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware"} + ], "nrf24": { "miso": 15, "mosi": 2, @@ -46,6 +52,9 @@ }, { "name": "Olimex ESP32-POE with SH1106", + "links": [ + {"name": "Datasheet", "url": "https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware"} + ], "nrf24": { "miso": 15, "mosi": 2, diff --git a/docs/DeviceProfiles/wt32-eth01.json b/docs/DeviceProfiles/wt32-eth01.json index e8aee231..388db209 100644 --- a/docs/DeviceProfiles/wt32-eth01.json +++ b/docs/DeviceProfiles/wt32-eth01.json @@ -1,6 +1,9 @@ [ { "name": "WT32-ETH01", + "links": [ + {"name": "Datasheet", "url": "http://www.wireless-tag.com/portfolio/wt32-eth01/"} + ], "nrf24": { "miso": 4, "mosi": 2, @@ -21,6 +24,9 @@ }, { "name": "WT32-ETH01 with SSD1306", + "links": [ + {"name": "Datasheet", "url": "http://www.wireless-tag.com/portfolio/wt32-eth01/"} + ], "nrf24": { "miso": 4, "mosi": 2, diff --git a/include/Battery.h b/include/Battery.h index 7e929034..ffb6f47d 100644 --- a/include/Battery.h +++ b/include/Battery.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "BatteryStats.h" @@ -19,12 +20,15 @@ class BatteryProvider { class BatteryClass { public: - void init(); - void loop(); + void init(Scheduler&); + void updateSettings(); std::shared_ptr getStats() const; - private: + void loop(); + + Task _loopTask; + uint32_t _lastMqttPublish = 0; mutable std::mutex _mutex; std::unique_ptr _upProvider = nullptr; diff --git a/include/Configuration.h b/include/Configuration.h index 14fc6a73..38b915bd 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" @@ -57,6 +58,7 @@ struct INVERTER_CONFIG_T { uint8_t ReachableThreshold; bool ZeroRuntimeDataIfUnrechable; bool ZeroYieldDayOnMidnight; + bool YieldDayCorrection; CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT]; }; @@ -74,128 +76,171 @@ struct POWERMETER_HTTP_PHASE_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; + + struct { + bool Enabled; + char Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1]; + bool VerboseLogging; + 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; + + struct { + 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 { + 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; + bool VerboseLogging; + } 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; + uint32_t DiagramDuration; + } Display; + + struct { + uint8_t Brightness; + } Led_Single[PINMAPPING_LED_COUNT]; + + struct { + bool Enabled; + bool VerboseLogging; + bool UpdatesOnly; + } Vedirect; + + struct { + bool Enabled; + bool VerboseLogging; + uint32_t Interval; + uint32_t Source; + char MqttTopicPowerMeter1[MQTT_MAX_TOPIC_STRLEN + 1]; + char MqttTopicPowerMeter2[MQTT_MAX_TOPIC_STRLEN + 1]; + char MqttTopicPowerMeter3[MQTT_MAX_TOPIC_STRLEN + 1]; + uint32_t SdmBaudrate; + uint32_t SdmAddress; + uint32_t HttpInterval; + bool HttpIndividualRequests; + POWERMETER_HTTP_PHASE_CONFIG_T Http_Phase[POWERMETER_MAX_PHASES]; + } PowerMeter; + + struct { + bool Enabled; + bool VerboseLogging; + bool SolarPassThroughEnabled; + uint8_t SolarPassThroughLosses; + uint8_t BatteryDrainStategy; + uint32_t Interval; + bool IsInverterBehindPowerMeter; + uint8_t InverterId; + uint8_t InverterChannelId; + int32_t TargetPowerConsumption; + int32_t TargetPowerConsumptionHysteresis; + int32_t LowerPowerLimit; + int32_t UpperPowerLimit; + uint32_t BatterySocStartThreshold; + uint32_t BatterySocStopThreshold; + float VoltageStartThreshold; + float VoltageStopThreshold; + float VoltageLoadCorrectionFactor; + int8_t RestartHour; + uint32_t FullSolarPassThroughSoc; + float FullSolarPassThroughStartVoltage; + float FullSolarPassThroughStopVoltage; + } PowerLimiter; + + struct { + bool Enabled; + bool VerboseLogging; + uint8_t Provider; + uint8_t JkBmsInterface; + uint8_t JkBmsPollingInterval; + } Battery; + + struct { + bool Enabled; + uint32_t CAN_Controller_Frequency; + bool Auto_Power_Enabled; + float Auto_Power_Voltage_Limit; + float Auto_Power_Enable_Voltage_Limit; + float Auto_Power_Lower_Power_Limit; + float Auto_Power_Upper_Power_Limit; + } Huawei; - bool Mqtt_Enabled; - char Mqtt_Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1]; - bool Mqtt_VerboseLogging; - 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; INVERTER_CONFIG_T Inverter[INV_MAX_COUNT]; - - uint64_t Dtu_Serial; - uint32_t Dtu_PollInterval; - bool Dtu_VerboseLogging; - uint8_t Dtu_NrfPaLevel; - int8_t Dtu_CmtPaLevel; - uint32_t Dtu_CmtFrequency; - - 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; - - 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]; - - bool Vedirect_Enabled; - bool Vedirect_VerboseLogging; - bool Vedirect_UpdatesOnly; - - bool PowerMeter_Enabled; - bool PowerMeter_VerboseLogging; - uint32_t PowerMeter_Interval; - uint32_t PowerMeter_Source; - char PowerMeter_MqttTopicPowerMeter1[MQTT_MAX_TOPIC_STRLEN + 1]; - char PowerMeter_MqttTopicPowerMeter2[MQTT_MAX_TOPIC_STRLEN + 1]; - char PowerMeter_MqttTopicPowerMeter3[MQTT_MAX_TOPIC_STRLEN + 1]; - uint32_t PowerMeter_SdmBaudrate; - uint32_t PowerMeter_SdmAddress; - uint32_t PowerMeter_HttpInterval; - bool PowerMeter_HttpIndividualRequests; - POWERMETER_HTTP_PHASE_CONFIG_T Powermeter_Http_Phase[POWERMETER_MAX_PHASES]; - - bool PowerLimiter_Enabled; - bool PowerLimiter_VerboseLogging; - bool PowerLimiter_SolarPassThroughEnabled; - uint8_t PowerLimiter_SolarPassThroughLosses; - uint8_t PowerLimiter_BatteryDrainStategy; - uint32_t PowerLimiter_Interval; - bool PowerLimiter_IsInverterBehindPowerMeter; - uint8_t PowerLimiter_InverterId; - uint8_t PowerLimiter_InverterChannelId; - int32_t PowerLimiter_TargetPowerConsumption; - int32_t PowerLimiter_TargetPowerConsumptionHysteresis; - int32_t PowerLimiter_LowerPowerLimit; - int32_t PowerLimiter_UpperPowerLimit; - uint32_t PowerLimiter_BatterySocStartThreshold; - uint32_t PowerLimiter_BatterySocStopThreshold; - float PowerLimiter_VoltageStartThreshold; - float PowerLimiter_VoltageStopThreshold; - float PowerLimiter_VoltageLoadCorrectionFactor; - int8_t PowerLimiter_RestartHour; - uint32_t PowerLimiter_FullSolarPassThroughSoc; - float PowerLimiter_FullSolarPassThroughStartVoltage; - float PowerLimiter_FullSolarPassThroughStopVoltage; - - bool Battery_Enabled; - bool Battery_VerboseLogging; - uint8_t Battery_Provider; - uint8_t Battery_JkBmsInterface; - uint8_t Battery_JkBmsPollingInterval; - - bool Huawei_Enabled; - uint32_t Huawei_CAN_Controller_Frequency; - bool Huawei_Auto_Power_Enabled; - float Huawei_Auto_Power_Voltage_Limit; - float Huawei_Auto_Power_Enable_Voltage_Limit; - float Huawei_Auto_Power_Lower_Power_Limit; - float Huawei_Auto_Power_Upper_Power_Limit; - - 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 { @@ -207,7 +252,7 @@ public: CONFIG_T& get(); INVERTER_CONFIG_T* getFreeInverterSlot(); - INVERTER_CONFIG_T* getInverterConfig(uint64_t serial); + INVERTER_CONFIG_T* getInverterConfig(const uint64_t serial); }; extern ConfigurationClass Configuration; \ No newline at end of file diff --git a/include/Datastore.h b/include/Datastore.h index 6e4c0396..351a822e 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/include/Display_Graphic.h b/include/Display_Graphic.h index 9fe202c4..1d620e54 100644 --- a/include/Display_Graphic.h +++ b/include/Display_Graphic.h @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include "Display_Graphic_Diagram.h" #include "defaults.h" +#include #include enum DisplayType_t { @@ -16,23 +18,28 @@ public: DisplayGraphicClass(); ~DisplayGraphicClass(); - void init(DisplayType_t type, uint8_t data, uint8_t clk, uint8_t cs, uint8_t reset); - void loop(); - void setContrast(uint8_t contrast); - void setStatus(bool turnOn); - void setOrientation(uint8_t rotation = DISPLAY_ROTATION); - void setLanguage(uint8_t language); + void init(Scheduler& scheduler, const DisplayType_t type, const uint8_t data, const uint8_t clk, const uint8_t cs, const uint8_t reset); + void setContrast(const uint8_t contrast); + void setStatus(const bool turnOn); + void setOrientation(const uint8_t rotation = DISPLAY_ROTATION); + void setLanguage(const uint8_t language); void setStartupDisplay(); + DisplayGraphicDiagramClass& Diagram(); + bool enablePowerSafe = true; bool enableScreensaver = true; private: - void printText(const char* text, uint8_t line); + void loop(); + void printText(const char* text, const uint8_t line); void calcLineHeights(); - void setFont(uint8_t line); + void setFont(const uint8_t line); + + Task _loopTask; U8G2* _display; + DisplayGraphicDiagramClass _diagram; bool _displayTurnedOn; @@ -41,7 +48,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/include/Display_Graphic_Diagram.h b/include/Display_Graphic_Diagram.h new file mode 100644 index 00000000..0626436c --- /dev/null +++ b/include/Display_Graphic_Diagram.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include +#include + +#define CHART_HEIGHT 20 // chart area hight in pixels +#define CHART_WIDTH 47 // chart area width in pixels +#define DIAG_POSX 80 // position were Diag is drawn at +#define DIAG_POSY 0 + +class DisplayGraphicDiagramClass { +public: + DisplayGraphicDiagramClass(); + + void init(Scheduler& scheduler, U8G2* display); + void redraw(); + + void updatePeriod(); + +private: + void averageLoop(); + void dataPointLoop(); + + static uint32_t getSecondsPerDot(); + + Task _averageTask; + Task _dataPointTask; + + U8G2* _display = nullptr; + std::array _graphValues = {}; + uint8_t _graphValuesCount = 0; + + float _iRunningAverage = 0; + uint16_t _iRunningAverageCnt = 0; + + uint8_t _graphPosX = DIAG_POSX; +}; \ No newline at end of file diff --git a/include/Huawei_can.h b/include/Huawei_can.h index 32e2a776..db97bd01 100644 --- a/include/Huawei_can.h +++ b/include/Huawei_can.h @@ -5,6 +5,7 @@ #include "SPI.h" #include #include +#include #ifndef HUAWEI_PIN_MISO #define HUAWEI_PIN_MISO 12 @@ -118,8 +119,8 @@ private: class HuaweiCanClass { public: - void init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs, uint8_t huawei_power); - void loop(); + void init(Scheduler& scheduler, uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs, uint8_t huawei_power); + void updateSettings(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs, uint8_t huawei_power); void setValue(float in, uint8_t parameterType); void setMode(uint8_t mode); @@ -128,9 +129,12 @@ public: bool getAutoPowerStatus(); private: + void loop(); void processReceivedParameters(); void _setValue(float in, uint8_t parameterType); + Task _loopTask; + TaskHandle_t _HuaweiCanCommunicationTaskHdl = NULL; bool _initialized = false; uint8_t _huaweiPower; // Power pin diff --git a/include/InverterSettings.h b/include/InverterSettings.h index 6375dfcf..aad05ed7 100644 --- a/include/InverterSettings.h +++ b/include/InverterSettings.h @@ -1,17 +1,21 @@ // 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: - uint32_t _lastUpdate = 0; + void settingsLoop(); + void hoyLoop(); + + Task _settingsTask; + Task _hoyTask; }; extern InverterSettingsClass InverterSettings; diff --git a/include/Led_Single.h b/include/Led_Single.h index a5c601bd..9404152b 100644 --- a/include/Led_Single.h +++ b/include/Led_Single.h @@ -2,38 +2,38 @@ #pragma once #include "PinMapping.h" +#include #include #define LEDSINGLE_UPDATE_INTERVAL 2000 -enum eLedFunction { - CONNECTED_NETWORK, - CONNECTED_MQTT, - INV_REACHABLE, - INV_PRODUCING, -}; - class LedSingleClass { public: LedSingleClass(); - void init(); - void loop(); + void init(Scheduler& scheduler); void turnAllOff(); void turnAllOn(); private: + void setLoop(); + void outputLoop(); + + void setLed(const uint8_t ledNo, const bool ledState); + + Task _setTask; + Task _outputTask; + enum class LedState_t { On, Off, Blink, }; - LedState_t _ledState[PINMAPPING_LED_COUNT]; - LedState_t _allState; - TimeoutHelper _updateTimeout; + LedState_t _ledMode[PINMAPPING_LED_COUNT]; + LedState_t _allMode; + bool _ledStateCurrent[PINMAPPING_LED_COUNT]; TimeoutHelper _blinkTimeout; - uint8_t _ledActive = 0; }; extern LedSingleClass LedSingle; \ No newline at end of file diff --git a/include/MessageOutput.h b/include/MessageOutput.h index 7ff745eb..94f915a5 100644 --- a/include/MessageOutput.h +++ b/include/MessageOutput.h @@ -2,34 +2,32 @@ #pragma once #include -#include -#include +#include +#include +#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: - using message_t = std::vector; + void loop(); - // we keep a buffer for every task and only write complete lines to the - // serial output and then move them to be pushed through the websocket. - // this way we prevent mangling of messages from different contexts. - std::unordered_map _task_messages; - std::queue _lines; + Task _loopTask; AsyncWebSocket* _ws = nullptr; + char _buffer[BUFFER_SIZE]; + uint16_t _buff_pos = 0; + uint32_t _lastSend = 0; + bool _forceSend = false; std::mutex _msgLock; - - void serialWrite(message_t const& m); }; extern MessageOutputClass MessageOutput; \ No newline at end of file diff --git a/include/MqttHandleDtu.h b/include/MqttHandleDtu.h index fb566345..01e17985 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/include/MqttHandleHass.h b/include/MqttHandleHass.h index 5bf7e71e..41f7bf8c 100644 --- a/include/MqttHandleHass.h +++ b/include/MqttHandleHass.h @@ -3,6 +3,7 @@ #include #include +#include // mqtt discovery device classes enum { @@ -50,18 +51,29 @@ 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 publishDtuSensor(const char* name, const char* device_class, const char* category, const char* icon, const char* unit_of_measure, const char* subTopic); + void publishDtuBinarySensor(const char* name, const char* device_class, const char* category, const char* payload_on, const char* payload_off, const char* subTopic = ""); + void publishInverterField(std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const 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); - void publishInverterNumber(std::shared_ptr inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, int16_t min = 1, int16_t max = 100); + void publishInverterNumber(std::shared_ptr inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, const int16_t min = 1, const int16_t max = 100); 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); + + static void createInverterInfo(DynamicJsonDocument& doc, std::shared_ptr inv); + static void createDtuInfo(DynamicJsonDocument& doc); + + static void createDeviceInfo(DynamicJsonDocument& doc, const String& name, const String& identifiers, const String& configuration_url, const String& manufacturer, const String& model, const String& sw_version, const String& via_device = ""); + + static String getDtuUniqueId(); + static String getDtuUrl(); + + Task _loopTask; bool _wasConnected = false; bool _updateForced = false; diff --git a/include/MqttHandleHuawei.h b/include/MqttHandleHuawei.h index bdb014c0..e25a82e0 100644 --- a/include/MqttHandleHuawei.h +++ b/include/MqttHandleHuawei.h @@ -4,15 +4,18 @@ #include "Configuration.h" #include #include +#include class MqttHandleHuaweiClass { public: - void init(); - void loop(); + void init(Scheduler& scheduler); private: + void loop(); 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; uint32_t _lastPublish; diff --git a/include/MqttHandleInverter.h b/include/MqttHandleInverter.h index 0194bacf..3925935f 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); + static String getTopic(std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId); private: - 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); + void loop(); + void publishField(std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId); + void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, const size_t len, const size_t index, const 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/include/MqttHandleInverterTotal.h b/include/MqttHandleInverterTotal.h index fa4ce4b6..19317818 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/include/MqttHandlePowerLimiter.h b/include/MqttHandlePowerLimiter.h index d52d202d..b81917db 100644 --- a/include/MqttHandlePowerLimiter.h +++ b/include/MqttHandlePowerLimiter.h @@ -3,15 +3,18 @@ #include "Configuration.h" #include +#include class MqttHandlePowerLimiterClass { public: - void init(); - void loop(); + void init(Scheduler& scheduler); private: + void loop(); void onCmdMode(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; uint32_t _lastPublish; diff --git a/include/MqttHandlePylontechHass.h b/include/MqttHandlePylontechHass.h index 318b970b..64f5a841 100644 --- a/include/MqttHandlePylontechHass.h +++ b/include/MqttHandlePylontechHass.h @@ -2,20 +2,23 @@ #pragma once #include +#include class MqttHandlePylontechHassClass { 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 publishBinarySensor(const char* caption, const char* icon, const char* subTopic, const char* payload_on, const char* payload_off); void publishSensor(const char* caption, const char* icon, const char* subTopic, const char* deviceClass = NULL, const char* stateClass = NULL, const char* unitOfMeasurement = NULL); void createDeviceInfo(JsonObject& object); + Task _loopTask; + bool _wasConnected = false; bool _updateForced = false; String serial = "0001"; // pseudo-serial, can be replaced in future with real serialnumber diff --git a/include/MqttHandleVedirect.h b/include/MqttHandleVedirect.h index 79c04fb2..571ee1e6 100644 --- a/include/MqttHandleVedirect.h +++ b/include/MqttHandleVedirect.h @@ -4,6 +4,7 @@ #include "VeDirectMpptController.h" #include "Configuration.h" #include +#include #ifndef VICTRON_PIN_RX #define VICTRON_PIN_RX 22 @@ -15,12 +16,14 @@ class MqttHandleVedirectClass { public: - void init(); - void loop(); + void init(Scheduler& scheduler); + void forceUpdate(); private: - + void loop(); VeDirectMpptController::veMpptStruct _kvFrame{}; + Task _loopTask; + // point of time in millis() when updated values will be published uint32_t _nextPublishUpdatesOnly = 0; diff --git a/include/MqttHandleVedirectHass.h b/include/MqttHandleVedirectHass.h index ca22a73f..577f08d6 100644 --- a/include/MqttHandleVedirectHass.h +++ b/include/MqttHandleVedirectHass.h @@ -3,20 +3,23 @@ #include #include "VeDirectMpptController.h" +#include class MqttHandleVedirectHassClass { 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 publishBinarySensor(const char* caption, const char* icon, const char* subTopic, const char* payload_on, const char* payload_off); void publishSensor(const char* caption, const char* icon, const char* subTopic, const char* deviceClass = NULL, const char* stateClass = NULL, const char* unitOfMeasurement = NULL); void createDeviceInfo(JsonObject& object); + Task _loopTask; + bool _wasConnected = false; bool _updateForced = false; }; diff --git a/include/MqttSettings.h b/include/MqttSettings.h index af48ea5e..87538568 100644 --- a/include/MqttSettings.h +++ b/include/MqttSettings.h @@ -15,29 +15,27 @@ public: void performReconnect(); bool getConnected(); void publish(const String& subtopic, const String& payload); - void publishGeneric(const String& topic, const String& payload, bool retain, uint8_t qos = 0); + void publishGeneric(const String& topic, const String& payload, const bool retain, const uint8_t qos = 0); - void subscribe(const String& topic, uint8_t qos, const espMqttClientTypes::OnMessageCallback& cb); + void subscribe(const String& topic, const uint8_t qos, const espMqttClientTypes::OnMessageCallback& cb); void unsubscribe(const String& topic); - String getPrefix(); + String getPrefix() const; private: void NetworkEvent(network_event event); void onMqttDisconnect(espMqttClientTypes::DisconnectReason reason); - void onMqttConnect(bool sessionPresent); - void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total); + void onMqttConnect(const bool sessionPresent); + void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, const size_t len, const size_t index, const size_t total); void performConnect(); void performDisconnect(); void createMqttClientObject(); - MqttClient* mqttClient = nullptr; - String clientId; - String willTopic; - Ticker mqttReconnectTimer; + MqttClient* _mqttClient = nullptr; + Ticker _mqttReconnectTimer; MqttSubscribeParser _mqttSubscribeParser; std::mutex _clientLock; bool _verboseLogging = true; diff --git a/include/NetworkSettings.h b/include/NetworkSettings.h index fa94c8b3..40ddc914 100644 --- a/include/NetworkSettings.h +++ b/include/NetworkSettings.h @@ -2,6 +2,7 @@ #pragma once #include +#include #include #include @@ -29,7 +30,7 @@ typedef struct NetworkEventCbList { network_event event; NetworkEventCbList() - : cb(NULL) + : cb(nullptr) , event(network_event::NETWORK_UNKNOWN) { } @@ -38,46 +39,50 @@ typedef struct NetworkEventCbList { class NetworkSettingsClass { public: NetworkSettingsClass(); - void init(); - void loop(); + void init(Scheduler& scheduler); void applyConfig(); void enableAdminMode(); - String getApName(); + String getApName() const; - IPAddress localIP(); - IPAddress subnetMask(); - IPAddress gatewayIP(); - IPAddress dnsIP(uint8_t dns_no = 0); - String macAddress(); + IPAddress localIP() const; + IPAddress subnetMask() const; + IPAddress gatewayIP() const; + IPAddress dnsIP(const uint8_t dns_no = 0) const; + String macAddress() const; static String getHostname(); - bool isConnected(); - network_mode NetworkMode(); + bool isConnected() const; + network_mode NetworkMode() const; - bool onEvent(NetworkEventCb cbEvent, network_event event = network_event::NETWORK_EVENT_MAX); - void raiseEvent(network_event event); + bool onEvent(NetworkEventCb cbEvent, const network_event event = network_event::NETWORK_EVENT_MAX); + void raiseEvent(const network_event event); private: + void loop(); void setHostname(); void setStaticIp(); void handleMDNS(); void setupMode(); - void NetworkEvent(WiFiEvent_t event); - bool adminEnabled = true; - bool forceDisconnection = false; - uint32_t adminTimeoutCounter = 0; - uint32_t adminTimeoutCounterMax = 0; - uint32_t connectTimeoutTimer = 0; - uint32_t connectRedoTimer = 0; - uint32_t lastTimerCall = 0; - const byte DNS_PORT = 53; - IPAddress apIp; - IPAddress apNetmask; - std::unique_ptr dnsServer; - bool dnsServerStatus = false; + void NetworkEvent(const WiFiEvent_t event); + + Task _loopTask; + + static constexpr byte DNS_PORT = 53; + + bool _adminEnabled = true; + bool _forceDisconnection = false; + uint32_t _adminTimeoutCounter = 0; + uint32_t _adminTimeoutCounterMax = 0; + uint32_t _connectTimeoutTimer = 0; + uint32_t _connectRedoTimer = 0; + uint32_t _lastTimerCall = 0; + IPAddress _apIp; + IPAddress _apNetmask; + std::unique_ptr _dnsServer; + bool _dnsServerStatus = false; network_mode _networkMode = network_mode::Undefined; bool _ethConnected = false; std::vector _cbEventList; - bool lastMdnsEnabled = false; + bool _lastMdnsEnabled = false; }; extern NetworkSettingsClass NetworkSettings; \ No newline at end of file diff --git a/include/PinMapping.h b/include/PinMapping.h index c9541c4b..30c56402 100644 --- a/include/PinMapping.h +++ b/include/PinMapping.h @@ -59,9 +59,9 @@ public: bool init(const String& deviceMapping); PinMapping_t& get(); - bool isValidNrf24Config(); - bool isValidCmt2300Config(); - bool isValidEthConfig(); + bool isValidNrf24Config() const; + bool isValidCmt2300Config() const; + bool isValidEthConfig() const; bool isValidHuaweiConfig(); private: diff --git a/include/PowerLimiter.h b/include/PowerLimiter.h index c2b92d46..40d8aa89 100644 --- a/include/PowerLimiter.h +++ b/include/PowerLimiter.h @@ -7,6 +7,7 @@ #include #include #include +#include #define PL_UI_STATE_INACTIVE 0 #define PL_UI_STATE_CHARGING 1 @@ -47,8 +48,7 @@ public: Stable, }; - void init(); - void loop(); + void init(Scheduler& scheduler); uint8_t getPowerLimiterState(); int32_t getLastRequestedPowerLimit(); @@ -63,6 +63,10 @@ public: void calcNextInverterRestart(); private: + void loop(); + + Task _loopTask; + int32_t _lastRequestedPowerLimit = 0; uint32_t _lastPowerLimitMillis = 0; uint32_t _shutdownTimeout = 0; diff --git a/include/PowerMeter.h b/include/PowerMeter.h index fce5c832..f6d29f2f 100644 --- a/include/PowerMeter.h +++ b/include/PowerMeter.h @@ -8,6 +8,7 @@ #include #include "SDM.h" #include "sml.h" +#include #ifndef SDM_RX_PIN #define SDM_RX_PIN 13 @@ -36,17 +37,19 @@ public: SOURCE_HTTP = 3, SOURCE_SML = 4 }; - void init(); - void loop(); + void init(Scheduler& scheduler); float getPowerTotal(bool forceUpdate = true); uint32_t getLastPowerMeterUpdate(); private: + void loop(); void mqtt(); void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total); + Task _loopTask; + bool _verboseLogging = true; uint32_t _lastPowerMeterCheck; // Used in Power limiter for safety check 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/include/SunPosition.h b/include/SunPosition.h index 26caab7b..49c9be4f 100644 --- a/include/SunPosition.h +++ b/include/SunPosition.h @@ -1,34 +1,35 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include +#include +#include #include class SunPositionClass { public: SunPositionClass(); - void init(); - void loop(); + void init(Scheduler& scheduler); - bool isDayPeriod(); - bool isSunsetAvailable(); - bool sunsetTime(struct tm* info); - bool sunriseTime(struct tm* info); - void setDoRecalc(bool doRecalc); + bool isDayPeriod() const; + bool isSunsetAvailable() const; + bool sunsetTime(struct tm* info) const; + bool sunriseTime(struct tm* info) const; + void setDoRecalc(const bool doRecalc); private: + void loop(); void updateSunData(); - bool checkRecalcDayChanged(); - bool getDoRecalc(); + bool checkRecalcDayChanged() const; + bool getSunTime(struct tm* info, const uint32_t offset) const; + + Task _loopTask; - 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/include/VictronMppt.h b/include/VictronMppt.h index d64fbb4c..091ddb00 100644 --- a/include/VictronMppt.h +++ b/include/VictronMppt.h @@ -5,14 +5,15 @@ #include #include "VeDirectMpptController.h" +#include class VictronMpptClass { public: VictronMpptClass() = default; ~VictronMpptClass() = default; - void init(); - void loop(); + void init(Scheduler& scheduler); + void updateSettings(); bool isDataValid() const; @@ -35,11 +36,14 @@ public: double getYieldDay() const; private: + void loop(); VictronMpptClass(VictronMpptClass const& other) = delete; VictronMpptClass(VictronMpptClass&& other) = delete; VictronMpptClass& operator=(VictronMpptClass const& other) = delete; VictronMpptClass& operator=(VictronMpptClass&& other) = delete; + Task _loopTask; + mutable std::mutex _mutex; using controller_t = std::unique_ptr; std::vector _controllers; diff --git a/include/WebApi.h b/include/WebApi.h index 40e66e33..b41fd6b9 100644 --- a/include/WebApi.h +++ b/include/WebApi.h @@ -30,12 +30,12 @@ #include "WebApi_Huawei.h" #include "WebApi_ws_battery.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); @@ -43,8 +43,11 @@ public: static void sendTooManyRequests(AsyncWebServerRequest* request); private: + void loop(); + + Task _loopTask; + AsyncWebServer _server; - AsyncEventSource _events; WebApiBatteryClass _webApiBattery; WebApiConfigClass _webApiConfig; diff --git a/include/WebApi_Huawei.h b/include/WebApi_Huawei.h index 1eed6139..7484a7ff 100644 --- a/include/WebApi_Huawei.h +++ b/include/WebApi_Huawei.h @@ -6,7 +6,7 @@ class WebApiHuaweiClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); void getJsonData(JsonObject& root); private: diff --git a/include/WebApi_battery.h b/include/WebApi_battery.h index bd43bb92..2d85d5a1 100644 --- a/include/WebApi_battery.h +++ b/include/WebApi_battery.h @@ -6,7 +6,7 @@ class WebApiBatteryClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_config.h b/include/WebApi_config.h index e022af65..edc8b291 100644 --- a/include/WebApi_config.h +++ b/include/WebApi_config.h @@ -5,7 +5,7 @@ class WebApiConfigClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_device.h b/include/WebApi_device.h index ae76edd0..9fca20fd 100644 --- a/include/WebApi_device.h +++ b/include/WebApi_device.h @@ -5,7 +5,7 @@ class WebApiDeviceClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_devinfo.h b/include/WebApi_devinfo.h index 0b8471e7..6e5602ba 100644 --- a/include/WebApi_devinfo.h +++ b/include/WebApi_devinfo.h @@ -5,7 +5,7 @@ class WebApiDevInfoClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_dtu.h b/include/WebApi_dtu.h index 4dcc235e..45f58d32 100644 --- a/include/WebApi_dtu.h +++ b/include/WebApi_dtu.h @@ -5,7 +5,7 @@ class WebApiDtuClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: 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/WebApi_eventlog.h b/include/WebApi_eventlog.h index 311b5289..ccc1658c 100644 --- a/include/WebApi_eventlog.h +++ b/include/WebApi_eventlog.h @@ -5,7 +5,7 @@ class WebApiEventlogClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_firmware.h b/include/WebApi_firmware.h index f99b248d..1b7e9236 100644 --- a/include/WebApi_firmware.h +++ b/include/WebApi_firmware.h @@ -5,7 +5,7 @@ class WebApiFirmwareClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_gridprofile.h b/include/WebApi_gridprofile.h index cf78cf64..87816599 100644 --- a/include/WebApi_gridprofile.h +++ b/include/WebApi_gridprofile.h @@ -5,11 +5,12 @@ class WebApiGridProfileClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: void onGridProfileStatus(AsyncWebServerRequest* request); + void onGridProfileRawdata(AsyncWebServerRequest* request); AsyncWebServer* _server; }; \ No newline at end of file diff --git a/include/WebApi_inverter.h b/include/WebApi_inverter.h index 9f2b0673..a8605153 100644 --- a/include/WebApi_inverter.h +++ b/include/WebApi_inverter.h @@ -5,7 +5,7 @@ class WebApiInverterClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_limit.h b/include/WebApi_limit.h index 026f7ef8..c2d1a7d7 100644 --- a/include/WebApi_limit.h +++ b/include/WebApi_limit.h @@ -5,7 +5,7 @@ class WebApiLimitClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_maintenance.h b/include/WebApi_maintenance.h index dd791537..db6dcf19 100644 --- a/include/WebApi_maintenance.h +++ b/include/WebApi_maintenance.h @@ -5,7 +5,7 @@ class WebApiMaintenanceClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_mqtt.h b/include/WebApi_mqtt.h index 91f73679..00a2b0b7 100644 --- a/include/WebApi_mqtt.h +++ b/include/WebApi_mqtt.h @@ -7,7 +7,7 @@ class WebApiMqttClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_network.h b/include/WebApi_network.h index 693bf583..47ef8d9a 100644 --- a/include/WebApi_network.h +++ b/include/WebApi_network.h @@ -5,7 +5,7 @@ class WebApiNetworkClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_ntp.h b/include/WebApi_ntp.h index fae87811..153aeeec 100644 --- a/include/WebApi_ntp.h +++ b/include/WebApi_ntp.h @@ -5,7 +5,7 @@ class WebApiNtpClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_power.h b/include/WebApi_power.h index f8912c0f..faed5c4e 100644 --- a/include/WebApi_power.h +++ b/include/WebApi_power.h @@ -5,7 +5,7 @@ class WebApiPowerClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_powerlimiter.h b/include/WebApi_powerlimiter.h index d4fb392a..a983a51b 100644 --- a/include/WebApi_powerlimiter.h +++ b/include/WebApi_powerlimiter.h @@ -6,7 +6,7 @@ class WebApiPowerLimiterClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_powermeter.h b/include/WebApi_powermeter.h index 4651dfbb..01b0d4ae 100644 --- a/include/WebApi_powermeter.h +++ b/include/WebApi_powermeter.h @@ -6,7 +6,7 @@ class WebApiPowerMeterClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_prometheus.h b/include/WebApi_prometheus.h index b03f8178..9fbf6e22 100644 --- a/include/WebApi_prometheus.h +++ b/include/WebApi_prometheus.h @@ -7,15 +7,15 @@ class WebApiPrometheusClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: void onPrometheusMetricsGet(AsyncWebServerRequest* request); - void addField(AsyncResponseStream* stream, String& serial, uint8_t idx, std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, const char* metricName, const char* channelName = NULL); + void addField(AsyncResponseStream* stream, const String& serial, const uint8_t idx, std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, const char* metricName, const char* channelName = nullptr); - void addPanelInfo(AsyncResponseStream* stream, String& serial, uint8_t idx, std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel); + void addPanelInfo(AsyncResponseStream* stream, const String& serial, const uint8_t idx, std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel); AsyncWebServer* _server; diff --git a/include/WebApi_security.h b/include/WebApi_security.h index 37c56fae..66e92101 100644 --- a/include/WebApi_security.h +++ b/include/WebApi_security.h @@ -5,7 +5,7 @@ class WebApiSecurityClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_sysstatus.h b/include/WebApi_sysstatus.h index 9b22a835..f63edd2c 100644 --- a/include/WebApi_sysstatus.h +++ b/include/WebApi_sysstatus.h @@ -5,7 +5,7 @@ class WebApiSysstatusClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_vedirect.h b/include/WebApi_vedirect.h index 1dd3a3fa..ed194117 100644 --- a/include/WebApi_vedirect.h +++ b/include/WebApi_vedirect.h @@ -6,7 +6,7 @@ class WebApiVedirectClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_webapp.h b/include/WebApi_webapp.h index ad161438..da50d963 100644 --- a/include/WebApi_webapp.h +++ b/include/WebApi_webapp.h @@ -5,7 +5,7 @@ class WebApiWebappClass { public: - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_ws_Huawei.h b/include/WebApi_ws_Huawei.h index 9ace7e88..8e61b7f7 100644 --- a/include/WebApi_ws_Huawei.h +++ b/include/WebApi_ws_Huawei.h @@ -8,7 +8,7 @@ class WebApiWsHuaweiLiveClass { public: WebApiWsHuaweiLiveClass(); - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_ws_battery.h b/include/WebApi_ws_battery.h index 5fe5b228..3454d71b 100644 --- a/include/WebApi_ws_battery.h +++ b/include/WebApi_ws_battery.h @@ -7,7 +7,7 @@ class WebApiWsBatteryLiveClass { public: WebApiWsBatteryLiveClass(); - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/WebApi_ws_console.h b/include/WebApi_ws_console.h index 81df81e9..4eea2c6a 100644 --- a/include/WebApi_ws_console.h +++ b/include/WebApi_ws_console.h @@ -6,12 +6,10 @@ class WebApiWsConsoleClass { public: WebApiWsConsoleClass(); - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: - void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); - AsyncWebServer* _server; AsyncWebSocket _ws; diff --git a/include/WebApi_ws_live.h b/include/WebApi_ws_live.h index 1e664920..37c7fbde 100644 --- a/include/WebApi_ws_live.h +++ b/include/WebApi_ws_live.h @@ -8,13 +8,13 @@ class WebApiWsLiveClass { public: WebApiWsLiveClass(); - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); 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 addField(JsonObject& root, uint8_t idx, std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, String topic = ""); + void addTotalField(JsonObject& root, const String& name, const float value, const String& unit, const 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/include/WebApi_ws_vedirect_live.h b/include/WebApi_ws_vedirect_live.h index d084e74e..3797b9b0 100644 --- a/include/WebApi_ws_vedirect_live.h +++ b/include/WebApi_ws_vedirect_live.h @@ -8,7 +8,7 @@ class WebApiWsVedirectLiveClass { public: WebApiWsVedirectLiveClass(); - void init(AsyncWebServer* server); + void init(AsyncWebServer& server); void loop(); private: diff --git a/include/defaults.h b/include/defaults.h index 40cd9b9b..9ec38406 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 @@ -97,6 +98,7 @@ #define DISPLAY_ROTATION 2U #define DISPLAY_CONTRAST 60U #define DISPLAY_LANGUAGE 0U +#define DISPLAY_DIAGRAM_DURATION (10UL * 60UL * 60UL) #define REACHABLE_THRESHOLD 2U @@ -146,3 +148,7 @@ #define HUAWEI_AUTO_POWER_UPPER_POWER_LIMIT 2000 #define VERBOSE_LOGGING true + +#define LED_BRIGHTNESS 100U + +#define MAX_INVERTER_LIMIT 2250 \ No newline at end of file diff --git a/lib/Frozen/AUTHORS b/lib/Frozen/AUTHORS new file mode 100644 index 00000000..d83d0f86 --- /dev/null +++ b/lib/Frozen/AUTHORS @@ -0,0 +1,3 @@ +serge-sans-paille +Jérôme Dumesnil +Chris Beck diff --git a/lib/Frozen/LICENSE b/lib/Frozen/LICENSE new file mode 100644 index 00000000..5b4b9bdc --- /dev/null +++ b/lib/Frozen/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 Quarkslab + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/lib/Frozen/README.rst b/lib/Frozen/README.rst new file mode 100644 index 00000000..5a1ceb9f --- /dev/null +++ b/lib/Frozen/README.rst @@ -0,0 +1,245 @@ +Frozen +###### + +.. image:: https://travis-ci.org/serge-sans-paille/frozen.svg?branch=master + :target: https://travis-ci.org/serge-sans-paille/frozen + +Header-only library that provides 0 cost initialization for immutable containers, fixed-size containers, and various algorithms. + +Frozen provides: + +- immutable (a.k.a. frozen), ``constexpr``-compatible versions of ``std::set``, + ``std::unordered_set``, ``std::map`` and ``std::unordered_map``. + +- fixed-capacity, ``constinit``-compatible versions of ``std::map`` and + ``std::unordered_map`` with immutable, compile-time selected keys mapped + to mutable values. + +- 0-cost initialization version of ``std::search`` for frozen needles using + Boyer-Moore or Knuth-Morris-Pratt algorithms. + + +The ``unordered_*`` containers are guaranteed *perfect* (a.k.a. no hash +collision) and the extra storage is linear with respect to the number of keys. + +Once initialized, the container keys cannot be updated, and in exchange, lookups +are faster. And initialization is free when ``constexpr`` or ``constinit`` is +used :-). + + +Installation +------------ + +Just copy the ``include/frozen`` directory somewhere and points to it using the ``-I`` flag. Alternatively, using CMake: + +.. code:: sh + + > mkdir build + > cd build + > cmake -D CMAKE_BUILD_TYPE=Release .. + > make install + + +Installation via CMake populates configuration files into the ``/usr/local/share`` +directory which can be consumed by CMake's ``find_package`` instrinsic function. + +Requirements +------------ + +A C++ compiler that supports C++14. Clang version 5 is a good pick, GCC version +6 lags behind in terms of ``constexpr`` compilation time (At least on my +setup), but compiles correctly. Visual Studio 2017 also works correctly! + +Note that gcc 5 isn't supported. (Here's an `old compat branch`_ where a small amount of stuff was ported.) + +.. _old compat branch: https://github.com/cbeck88/frozen/tree/gcc5-support + +Usage +----- + +Compiled with ``-std=c++14`` flag: + +.. code:: C++ + + #include + + constexpr frozen::set some_ints = {1,2,3,5}; + + constexpr bool letitgo = some_ints.count(8); + + extern int n; + bool letitgoooooo = some_ints.count(n); + + +As the constructor and some methods are ``constexpr``, it's also possible to write weird stuff like: + +.. code:: C++ + + #include + + template + std::enable_if_t< frozen::set{{1,11,111}}.count(N), int> foo(); + +String support is built-in: + +.. code:: C++ + + #include + #include + + constexpr frozen::unordered_map olaf = { + {"19", 19}, + {"31", 31}, + }; + constexpr auto val = olaf.at("19"); + +The associative containers have different functionality with and without ``constexpr``. +With ``constexpr``, frozen maps have immutable keys and values. Without ``constexpr``, the +values can be updated in runtime (the keys, however, remain immutable): + +.. code:: C++ + + + #include + #include + + static constinit frozen::unordered_map voice = { + {"Anna", "???"}, + {"Elsa", "???"} + }; + + int main() { + voice.at("Anna") = "Kristen"; + voice.at("Elsa") = "Idina"; + } + +You may also prefer a slightly more DRY initialization syntax: + +.. code:: C++ + + #include + + constexpr auto some_ints = frozen::make_set({1,2,3,5}); + +There are similar ``make_X`` functions for all frozen containers. + +Exception Handling +------------------ + +For compatibility with STL's API, Frozen may eventually throw exceptions, as in +``frozen::map::at``. If you build your code without exception support, or +define the ``FROZEN_NO_EXCEPTIONS`` macro variable, they will be turned into an +``std::abort``. + +Extending +--------- + +Just like the regular C++14 container, you can specialize the hash function, +the key equality comparator for ``unordered_*`` containers, and the comparison +functions for the ordered version. + +It's also possible to specialize the ``frozen::elsa`` structure used for +hashing. Note that unlike `std::hash`, the hasher also takes a seed in addition +to the value being hashed. + +.. code:: C++ + + template struct elsa { + // in case of collisions, different seeds are tried + constexpr std::size_t operator()(T const &value, std::size_t seed) const; + }; + +Ideally, the hash function should have nice statistical properties like *pairwise-independence*: + +If ``x`` and ``y`` are different values, the chance that ``elsa{}(x, seed) == elsa{}(y, seed)`` +should be very low for a random value of ``seed``. + +Note that frozen always ultimately produces a perfect hash function, and you will always have ``O(1)`` +lookup with frozen. It's just that if the input hasher performs poorly, the search will take longer and +your project will take longer to compile. + +Troubleshooting +--------------- + +If you hit a message like this: + +.. code:: none + + [...] + note: constexpr evaluation hit maximum step limit; possible infinite loop? + +Then either you've got a very big container and you should increase Clang's +thresholds, using ``-fconstexpr-steps=1000000000`` for instance, or the hash +functions used by frozen do not suit your data, and you should change them, as +in the following: + +.. code:: c++ + + struct olaf { + constexpr std::size_t operator()(frozen::string const &value, std::size_t seed) const { return seed ^ value[0];} + }; + + constexpr frozen::unordered_set hans = { "a", "b" }; + +Tests and Benchmarks +-------------------- + +Using hand-written Makefiles crafted with love and care: + +.. code:: sh + + > # running tests + > make -C tests check + > # running benchmarks + > make -C benchmarks GOOGLE_BENCHMARK_PREFIX= + +Using CMake to generate a static configuration build system: + +.. code:: sh + + > mkdir build + > cd build + > cmake -D CMAKE_BUILD_TYPE=Release \ + -D frozen.benchmark=ON \ + -G <"Unix Makefiles" or "Ninja"> .. + > # building the tests and benchmarks... + > make # ... with make + > ninja # ... with ninja + > cmake --build . # ... with cmake + > # running the tests... + > make test # ... with make + > ninja test # ... with ninja + > cmake --build . --target test # ... with cmake + > ctest # ... with ctest + > # running the benchmarks... + > make benchmark # ... with make + > ninja benchmark # ... with ninja + > cmake --build . --target benchmark # ... with cmake + +Using CMake to generate an IDE build system with test and benchmark targets + +.. code:: sh + + > mkdir build + > cd build + > cmake -D frozen.benchmark=ON -G <"Xcode" or "Visual Studio 15 2017"> .. + > # using cmake to drive the IDE build, test, and benchmark + > cmake --build . --config Release + > cmake --build . --target test + > cmake --build . --target benchmark + + +Credits +------- + +The perfect hashing is strongly inspired by the blog post `Throw away the keys: +Easy, Minimal Perfect Hashing `_. + +Thanks a lot to Jérôme Dumesnil for his high-quality reviews, and to Chris Beck +for his contributions on perfect hashing. + +Contact +------- + +Serge sans Paille ```` + diff --git a/lib/Frozen/frozen/CMakeLists.txt b/lib/Frozen/frozen/CMakeLists.txt new file mode 100644 index 00000000..185378d5 --- /dev/null +++ b/lib/Frozen/frozen/CMakeLists.txt @@ -0,0 +1,12 @@ +target_sources(frozen-headers INTERFACE + "${prefix}/frozen/algorithm.h" + "${prefix}/frozen/map.h" + "${prefix}/frozen/random.h" + "${prefix}/frozen/set.h" + "${prefix}/frozen/string.h" + "${prefix}/frozen/unordered_map.h" + "${prefix}/frozen/unordered_set.h" + "${prefix}/frozen/bits/algorithms.h" + "${prefix}/frozen/bits/basic_types.h" + "${prefix}/frozen/bits/elsa.h" + "${prefix}/frozen/bits/pmh.h") diff --git a/lib/Frozen/frozen/algorithm.h b/lib/Frozen/frozen/algorithm.h new file mode 100644 index 00000000..3abd529b --- /dev/null +++ b/lib/Frozen/frozen/algorithm.h @@ -0,0 +1,198 @@ +/* + * Frozen + * Copyright 2016 QuarksLab + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef FROZEN_LETITGO_ALGORITHM_H +#define FROZEN_LETITGO_ALGORITHM_H + +#include "frozen/bits/basic_types.h" +#include "frozen/bits/version.h" +#include "frozen/string.h" + +namespace frozen { + +// 'search' implementation if C++17 is not available +// https://en.cppreference.com/w/cpp/algorithm/search +template +ForwardIterator search(ForwardIterator first, ForwardIterator last, const Searcher & searcher) +{ + return searcher(first, last).first; +} + +// text book implementation from +// https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm + +template class knuth_morris_pratt_searcher { + bits::carray step_; + bits::carray needle_; + + static constexpr bits::carray + build_kmp_cache(char const (&needle)[size + 1]) { + std::ptrdiff_t cnd = 0; + bits::carray cache(-1); + for (std::size_t pos = 1; pos < size; ++pos) { + if (needle[pos] == needle[cnd]) { + cache[pos] = cache[cnd]; + cnd += 1; + } else { + cache[pos] = cnd; + cnd = cache[cnd]; + while (cnd >= 0 && needle[pos] != needle[cnd]) + cnd = cache[cnd]; + cnd += 1; + } + } + return cache; + } + +public: + constexpr knuth_morris_pratt_searcher(char const (&needle)[size + 1]) + : step_{build_kmp_cache(needle)}, needle_(needle) {} + + template + constexpr std::pair operator()(ForwardIterator first, ForwardIterator last) const { + std::size_t i = 0; + ForwardIterator iter = first; + while (iter != last) { + if (needle_[i] == *iter) { + if (i == (size - 1)) + return { iter - i, iter - i + size }; + ++i; + ++iter; + } else { + if (step_[i] > -1) { + i = step_[i]; + } else { + ++iter; + i = 0; + } + } + } + return { last, last }; + } +}; + +template +constexpr knuth_morris_pratt_searcher make_knuth_morris_pratt_searcher(char const (&needle)[N]) { + return {needle}; +} + +// text book implementation from +// https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore%E2%80%93Horspool_algorithm + +template class boyer_moore_searcher { + using skip_table_type = bits::carray; + using suffix_table_type = bits::carray; + + skip_table_type skip_table_; + suffix_table_type suffix_table_; + bits::carray needle_; + + constexpr auto build_skip_table(char const (&needle)[size + 1]) { + skip_table_type skip_table(size); + for (std::size_t i = 0; i < size - 1; ++i) + skip_table[needle[i]] -= i + 1; + return skip_table; + } + + constexpr bool is_prefix(char const (&needle)[size + 1], std::size_t pos) { + std::size_t suffixlen = size - pos; + + for (std::size_t i = 0; i < suffixlen; i++) { + if (needle[i] != needle[pos + i]) + return false; + } + return true; + } + + constexpr std::size_t suffix_length(char const (&needle)[size + 1], + std::size_t pos) { + // increment suffix length slen to the first mismatch or beginning + // of the word + for (std::size_t slen = 0; slen < pos ; slen++) + if (needle[pos - slen] != needle[size - 1 - slen]) + return slen; + + return pos; + } + + constexpr auto build_suffix_table(char const (&needle)[size + 1]) { + suffix_table_type suffix; + std::ptrdiff_t last_prefix_index = size - 1; + + // first loop + for (std::ptrdiff_t p = size - 1; p >= 0; p--) { + if (is_prefix(needle, p + 1)) + last_prefix_index = p + 1; + + suffix[p] = last_prefix_index + (size - 1 - p); + } + + // second loop + for (std::size_t p = 0; p < size - 1; p++) { + auto slen = suffix_length(needle, p); + if (needle[p - slen] != needle[size - 1 - slen]) + suffix[size - 1 - slen] = size - 1 - p + slen; + + } + return suffix; + } + +public: + constexpr boyer_moore_searcher(char const (&needle)[size + 1]) + : skip_table_{build_skip_table(needle)}, + suffix_table_{build_suffix_table(needle)}, + needle_(needle) {} + + template + constexpr std::pair operator()(RandomAccessIterator first, RandomAccessIterator last) const { + if (size == 0) + return { first, first }; + + if (size > size_t(last - first)) + return { last, last }; + + RandomAccessIterator iter = first + size - 1; + while (true) { + std::ptrdiff_t j = size - 1; + while (j > 0 && (*iter == needle_[j])) { + --iter; + --j; + } + if (j == 0 && *iter == needle_[0]) + return { iter, iter + size}; + + std::ptrdiff_t jump = std::max(skip_table_[*iter], suffix_table_[j]); + if (jump >= last - iter) + return { last, last }; + iter += jump; + } + } +}; + +template +constexpr boyer_moore_searcher make_boyer_moore_searcher(char const (&needle)[N]) { + return {needle}; +} + +} // namespace frozen + +#endif diff --git a/lib/Frozen/frozen/bits/algorithms.h b/lib/Frozen/frozen/bits/algorithms.h new file mode 100644 index 00000000..4efa61b2 --- /dev/null +++ b/lib/Frozen/frozen/bits/algorithms.h @@ -0,0 +1,235 @@ +/* + * Frozen + * Copyright 2016 QuarksLab + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef FROZEN_LETITGO_BITS_ALGORITHMS_H +#define FROZEN_LETITGO_BITS_ALGORITHMS_H + +#include "frozen/bits/basic_types.h" + +#include +#include + +namespace frozen { + +namespace bits { + +auto constexpr next_highest_power_of_two(std::size_t v) { + // https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + constexpr auto trip_count = std::numeric_limits::digits; + v--; + for(std::size_t i = 1; i < trip_count; i <<= 1) + v |= v >> i; + v++; + return v; +} + +template +auto constexpr log(T v) { + std::size_t n = 0; + while (v > 1) { + n += 1; + v >>= 1; + } + return n; +} + +constexpr std::size_t bit_weight(std::size_t n) { + return (n <= 8*sizeof(unsigned int)) + + (n <= 8*sizeof(unsigned long)) + + (n <= 8*sizeof(unsigned long long)) + + (n <= 128); +} + +unsigned int select_uint_least(std::integral_constant); +unsigned long select_uint_least(std::integral_constant); +unsigned long long select_uint_least(std::integral_constant); +template +unsigned long long select_uint_least(std::integral_constant) { + static_assert(N < 2, "unsupported type size"); + return {}; +} + + +template +using select_uint_least_t = decltype(select_uint_least(std::integral_constant())); + +template +constexpr auto min_element(Iter begin, const Iter end, + Compare const &compare) { + auto result = begin; + while (begin != end) { + if (compare(*begin, *result)) { + result = begin; + } + ++begin; + } + return result; +} + +template +constexpr void cswap(T &a, T &b) { + auto tmp = a; + a = b; + b = tmp; +} + +template +constexpr void cswap(std::pair & a, std::pair & b) { + cswap(a.first, b.first); + cswap(a.second, b.second); +} + +template +constexpr void cswap(std::tuple &a, std::tuple &b, std::index_sequence) { + using swallow = int[]; + (void) swallow{(cswap(std::get(a), std::get(b)), 0)...}; +} + +template +constexpr void cswap(std::tuple &a, std::tuple &b) { + cswap(a, b, std::make_index_sequence()); +} + +template +constexpr void iter_swap(Iter a, Iter b) { + cswap(*a, *b); +} + +template +constexpr Iterator partition(Iterator left, Iterator right, Compare const &compare) { + auto pivot = left + (right - left) / 2; + iter_swap(right, pivot); + pivot = right; + for (auto it = left; 0 < right - it; ++it) { + if (compare(*it, *pivot)) { + iter_swap(it, left); + left++; + } + } + iter_swap(pivot, left); + pivot = left; + return pivot; +} + +template +constexpr void quicksort(Iterator left, Iterator right, Compare const &compare) { + while (0 < right - left) { + auto new_pivot = bits::partition(left, right, compare); + quicksort(left, new_pivot, compare); + left = new_pivot + 1; + } +} + +template +constexpr Container quicksort(Container const &array, + Compare const &compare) { + Container res = array; + quicksort(res.begin(), res.end() - 1, compare); + return res; +} + +template struct LowerBound { + T const &value_; + Compare const &compare_; + constexpr LowerBound(T const &value, Compare const &compare) + : value_(value), compare_(compare) {} + + template + inline constexpr ForwardIt doit_fast(ForwardIt first, + std::integral_constant) { + return first; + } + + template + inline constexpr ForwardIt doit_fast(ForwardIt first, + std::integral_constant) { + auto constexpr step = N / 2; + static_assert(N/2 == N - N / 2 - 1, "power of two minus 1"); + auto it = first + step; + auto next_it = compare_(*it, value_) ? it + 1 : first; + return doit_fast(next_it, std::integral_constant{}); + } + + template + inline constexpr ForwardIt doitfirst(ForwardIt first, std::integral_constant, std::integral_constant) { + return doit_fast(first, std::integral_constant{}); + } + + template + inline constexpr ForwardIt doitfirst(ForwardIt first, std::integral_constant, std::integral_constant) { + auto constexpr next_power = next_highest_power_of_two(N); + auto constexpr next_start = next_power / 2 - 1; + auto it = first + next_start; + if (compare_(*it, value_)) { + auto constexpr next = N - next_start - 1; + return doitfirst(it + 1, std::integral_constant{}, std::integral_constant{}); + } + else + return doit_fast(first, std::integral_constant{}); + } + + template + inline constexpr ForwardIt doitfirst(ForwardIt first, std::integral_constant, std::integral_constant) { + return doit_fast(first, std::integral_constant{}); + } +}; + +template +constexpr ForwardIt lower_bound(ForwardIt first, const T &value, Compare const &compare) { + return LowerBound{value, compare}.doitfirst(first, std::integral_constant{}, std::integral_constant{}); +} + +template +constexpr bool binary_search(ForwardIt first, const T &value, + Compare const &compare) { + ForwardIt where = lower_bound(first, value, compare); + return (!(where == first + N) && !(compare(value, *where))); +} + + +template +constexpr bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2) +{ + for (; first1 != last1; ++first1, ++first2) { + if (!(*first1 == *first2)) { + return false; + } + } + return true; +} + +template +constexpr bool lexicographical_compare(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) +{ + for (; (first1 != last1) && (first2 != last2); ++first1, ++first2) { + if (*first1 < *first2) + return true; + if (*first2 < *first1) + return false; + } + return (first1 == last1) && (first2 != last2); +} + +} // namespace bits +} // namespace frozen + +#endif diff --git a/lib/Frozen/frozen/bits/basic_types.h b/lib/Frozen/frozen/bits/basic_types.h new file mode 100644 index 00000000..239270af --- /dev/null +++ b/lib/Frozen/frozen/bits/basic_types.h @@ -0,0 +1,198 @@ +/* + * Frozen + * Copyright 2016 QuarksLab + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef FROZEN_LETITGO_BASIC_TYPES_H +#define FROZEN_LETITGO_BASIC_TYPES_H + +#include "frozen/bits/exceptions.h" + +#include +#include +#include +#include + +namespace frozen { + +namespace bits { + +// used as a fake argument for frozen::make_set and frozen::make_map in the case of N=0 +struct ignored_arg {}; + +template +class cvector { + T data [N] = {}; // zero-initialization for scalar type T, default-initialized otherwise + std::size_t dsize = 0; + +public: + // Container typdefs + using value_type = T; + using reference = value_type &; + using const_reference = const value_type &; + using pointer = value_type *; + using const_pointer = const value_type *; + using iterator = pointer; + using const_iterator = const_pointer; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + // Constructors + constexpr cvector(void) = default; + constexpr cvector(size_type count, const T& value) : dsize(count) { + for (std::size_t i = 0; i < N; ++i) + data[i] = value; + } + + // Iterators + constexpr iterator begin() noexcept { return data; } + constexpr iterator end() noexcept { return data + dsize; } + constexpr const_iterator begin() const noexcept { return data; } + constexpr const_iterator end() const noexcept { return data + dsize; } + + // Capacity + constexpr size_type size() const { return dsize; } + + // Element access + constexpr reference operator[](std::size_t index) { return data[index]; } + constexpr const_reference operator[](std::size_t index) const { return data[index]; } + + constexpr reference back() { return data[dsize - 1]; } + constexpr const_reference back() const { return data[dsize - 1]; } + + // Modifiers + constexpr void push_back(const T & a) { data[dsize++] = a; } + constexpr void push_back(T && a) { data[dsize++] = std::move(a); } + constexpr void pop_back() { --dsize; } + + constexpr void clear() { dsize = 0; } +}; + +template +class carray { + T data_ [N] = {}; // zero-initialization for scalar type T, default-initialized otherwise + + template + constexpr carray(Iter iter, std::index_sequence) + : data_{((void)I, *iter++)...} {} + template + constexpr carray(const T& value, std::index_sequence) + : data_{((void)I, value)...} {} + +public: + // Container typdefs + using value_type = T; + using reference = value_type &; + using const_reference = const value_type &; + using pointer = value_type *; + using const_pointer = const value_type *; + using iterator = pointer; + using const_iterator = const_pointer; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + // Constructors + constexpr carray() = default; + constexpr carray(const value_type& val) + : carray(val, std::make_index_sequence()) {} + template ::value, std::size_t> M> + constexpr carray(U const (&init)[M]) + : carray(init, std::make_index_sequence()) + { + static_assert(M >= N, "Cannot initialize a carray with an smaller array"); + } + template ::value, std::size_t> M> + constexpr carray(std::array const &init) + : carray(init.begin(), std::make_index_sequence()) + { + static_assert(M >= N, "Cannot initialize a carray with an smaller array"); + } + template ::value>* = nullptr> + constexpr carray(std::initializer_list init) + : carray(init.begin(), std::make_index_sequence()) + { + // clang & gcc doesn't recognize init.size() as a constexpr + // static_assert(init.size() >= N, "Cannot initialize a carray with an smaller initializer list"); + } + template ::value>* = nullptr> + constexpr carray(const carray& rhs) + : carray(rhs.begin(), std::make_index_sequence()) + { + } + + // Iterators + constexpr iterator begin() noexcept { return data_; } + constexpr const_iterator begin() const noexcept { return data_; } + constexpr iterator end() noexcept { return data_ + N; } + constexpr const_iterator end() const noexcept { return data_ + N; } + + // Capacity + constexpr size_type size() const { return N; } + constexpr size_type max_size() const { return N; } + + // Element access + constexpr reference operator[](std::size_t index) { return data_[index]; } + constexpr const_reference operator[](std::size_t index) const { return data_[index]; } + + constexpr reference at(std::size_t index) { + if (index > N) + FROZEN_THROW_OR_ABORT(std::out_of_range("Index (" + std::to_string(index) + ") out of bound (" + std::to_string(N) + ')')); + return data_[index]; + } + constexpr const_reference at(std::size_t index) const { + if (index > N) + FROZEN_THROW_OR_ABORT(std::out_of_range("Index (" + std::to_string(index) + ") out of bound (" + std::to_string(N) + ')')); + return data_[index]; + } + + constexpr reference front() { return data_[0]; } + constexpr const_reference front() const { return data_[0]; } + + constexpr reference back() { return data_[N - 1]; } + constexpr const_reference back() const { return data_[N - 1]; } + + constexpr value_type* data() noexcept { return data_; } + constexpr const value_type* data() const noexcept { return data_; } +}; +template +class carray { + +public: + // Container typdefs + using value_type = T; + using reference = value_type &; + using const_reference = const value_type &; + using pointer = value_type *; + using const_pointer = const value_type *; + using iterator = pointer; + using const_iterator = const_pointer; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + // Constructors + constexpr carray(void) = default; + +}; + +} // namespace bits + +} // namespace frozen + +#endif diff --git a/lib/Frozen/frozen/bits/constexpr_assert.h b/lib/Frozen/frozen/bits/constexpr_assert.h new file mode 100644 index 00000000..912210dc --- /dev/null +++ b/lib/Frozen/frozen/bits/constexpr_assert.h @@ -0,0 +1,40 @@ +/* + * Frozen + * Copyright 2016 QuarksLab + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef FROZEN_LETITGO_CONSTEXPR_ASSERT_H +#define FROZEN_LETITGO_CONSTEXPR_ASSERT_H + +#include + +#ifdef _MSC_VER + +// FIXME: find a way to implement that correctly for msvc +#define constexpr_assert(cond, msg) + +#else + +#define constexpr_assert(cond, msg)\ + assert(cond && msg); +#endif + +#endif + diff --git a/lib/Frozen/frozen/bits/defines.h b/lib/Frozen/frozen/bits/defines.h new file mode 100644 index 00000000..e20f6d0c --- /dev/null +++ b/lib/Frozen/frozen/bits/defines.h @@ -0,0 +1,66 @@ +/* + * Frozen + * Copyright 2016 QuarksLab + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef FROZEN_LETITGO_DEFINES_H +#define FROZEN_LETITGO_DEFINES_H + +#if defined(_MSVC_LANG) && !(defined(__EDG__) && defined(__clang__)) // TRANSITION, VSO#273681 + #define FROZEN_LETITGO_IS_MSVC +#endif + +// Code taken from https://stackoverflow.com/questions/43639122/which-values-can-msvc-lang-have +#if defined(FROZEN_LETITGO_IS_MSVC) + #if _MSVC_LANG > 201402 + #define FROZEN_LETITGO_HAS_CXX17 1 + #else /* _MSVC_LANG > 201402 */ + #define FROZEN_LETITGO_HAS_CXX17 0 + #endif /* _MSVC_LANG > 201402 */ +#else /* _MSVC_LANG etc. */ + #if __cplusplus > 201402 + #define FROZEN_LETITGO_HAS_CXX17 1 + #else /* __cplusplus > 201402 */ + #define FROZEN_LETITGO_HAS_CXX17 0 + #endif /* __cplusplus > 201402 */ +#endif /* _MSVC_LANG etc. */ +// End if taken code + +#if FROZEN_LETITGO_HAS_CXX17 == 1 && defined(FROZEN_LETITGO_IS_MSVC) + #define FROZEN_LETITGO_HAS_STRING_VIEW // We assume Visual Studio always has string_view in C++17 +#else + #if FROZEN_LETITGO_HAS_CXX17 == 1 && __has_include() + #define FROZEN_LETITGO_HAS_STRING_VIEW + #endif +#endif + +#ifdef __cpp_char8_t + #define FROZEN_LETITGO_HAS_CHAR8T +#endif + +#if __cpp_deduction_guides >= 201703L + #define FROZEN_LETITGO_HAS_DEDUCTION_GUIDES +#endif + +#if __cpp_lib_constexpr_string >= 201907L + #define FROZEN_LETITGO_HAS_CONSTEXPR_STRING +#endif + +#endif // FROZEN_LETITGO_DEFINES_H diff --git a/lib/Frozen/frozen/bits/elsa.h b/lib/Frozen/frozen/bits/elsa.h new file mode 100644 index 00000000..6c9ecb78 --- /dev/null +++ b/lib/Frozen/frozen/bits/elsa.h @@ -0,0 +1,57 @@ +/* + * Frozen + * Copyright 2016 QuarksLab + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef FROZEN_LETITGO_ELSA_H +#define FROZEN_LETITGO_ELSA_H + +#include + +namespace frozen { + +template struct elsa { + static_assert(std::is_integral::value || std::is_enum::value, + "only supports integral types, specialize for other types"); + + constexpr std::size_t operator()(T const &value, std::size_t seed) const { + std::size_t key = seed ^ static_cast(value); + key = (~key) + (key << 21); // key = (key << 21) - key - 1; + key = key ^ (key >> 24); + key = (key + (key << 3)) + (key << 8); // key * 265 + key = key ^ (key >> 14); + key = (key + (key << 2)) + (key << 4); // key * 21 + key = key ^ (key >> 28); + key = key + (key << 31); + return key; + } +}; + +template <> struct elsa { + template + constexpr std::size_t operator()(T const &value, std::size_t seed) const { + return elsa{}(value, seed); + } +}; + +template using anna = elsa; +} // namespace frozen + +#endif diff --git a/lib/Frozen/frozen/bits/elsa_std.h b/lib/Frozen/frozen/bits/elsa_std.h new file mode 100644 index 00000000..df1a9cfc --- /dev/null +++ b/lib/Frozen/frozen/bits/elsa_std.h @@ -0,0 +1,41 @@ +#ifndef FROZEN_LETITGO_BITS_ELSA_STD_H +#define FROZEN_LETITGO_BITS_ELSA_STD_H + +#include "defines.h" +#include "elsa.h" +#include "hash_string.h" + +#ifdef FROZEN_LETITGO_HAS_STRING_VIEW +#include +#endif +#include + +namespace frozen { + +#ifdef FROZEN_LETITGO_HAS_STRING_VIEW + +template struct elsa> +{ + constexpr std::size_t operator()(const std::basic_string_view& value) const { + return hash_string(value); + } + constexpr std::size_t operator()(const std::basic_string_view& value, std::size_t seed) const { + return hash_string(value, seed); + } +}; + +#endif + +template struct elsa> +{ + constexpr std::size_t operator()(const std::basic_string& value) const { + return hash_string(value); + } + constexpr std::size_t operator()(const std::basic_string& value, std::size_t seed) const { + return hash_string(value, seed); + } +}; + +} // namespace frozen + +#endif // FROZEN_LETITGO_BITS_ELSA_STD_H diff --git a/lib/Frozen/frozen/bits/exceptions.h b/lib/Frozen/frozen/bits/exceptions.h new file mode 100644 index 00000000..b43e3e6b --- /dev/null +++ b/lib/Frozen/frozen/bits/exceptions.h @@ -0,0 +1,39 @@ +/* + * Frozen + * Copyright 2016 QuarksLab + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef FROZEN_LETITGO_EXCEPTIONS_H +#define FROZEN_LETITGO_EXCEPTIONS_H + +#if defined(FROZEN_NO_EXCEPTIONS) || (defined(_MSC_VER) && !defined(_CPPUNWIND)) || (!defined(_MSC_VER) && !defined(__cpp_exceptions)) + +#include +#define FROZEN_THROW_OR_ABORT(_) std::abort() + +#else + +#include +#define FROZEN_THROW_OR_ABORT(err) throw err + + +#endif + +#endif diff --git a/lib/Frozen/frozen/bits/hash_string.h b/lib/Frozen/frozen/bits/hash_string.h new file mode 100644 index 00000000..b2f7e90e --- /dev/null +++ b/lib/Frozen/frozen/bits/hash_string.h @@ -0,0 +1,28 @@ +#ifndef FROZEN_LETITGO_BITS_HASH_STRING_H +#define FROZEN_LETITGO_BITS_HASH_STRING_H + +#include + +namespace frozen { + +template +constexpr std::size_t hash_string(const String& value) { + std::size_t d = 5381; + for (const auto& c : value) + d = d * 33 + static_cast(c); + return d; +} + +// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function +// With the lowest bits removed, based on experimental setup. +template +constexpr std::size_t hash_string(const String& value, std::size_t seed) { + std::size_t d = (0x811c9dc5 ^ seed) * static_cast(0x01000193); + for (const auto& c : value) + d = (d ^ static_cast(c)) * static_cast(0x01000193); + return d >> 8 ; +} + +} // namespace frozen + +#endif // FROZEN_LETITGO_BITS_HASH_STRING_H \ No newline at end of file diff --git a/lib/Frozen/frozen/bits/mpl.h b/lib/Frozen/frozen/bits/mpl.h new file mode 100644 index 00000000..8f87f99c --- /dev/null +++ b/lib/Frozen/frozen/bits/mpl.h @@ -0,0 +1,56 @@ +/* + * Frozen + * Copyright 2022 Giel van Schijndel + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef FROZEN_LETITGO_BITS_MPL_H +#define FROZEN_LETITGO_BITS_MPL_H + +#include + +namespace frozen { + +namespace bits { + +// Forward declarations +template +class carray; + +template +struct remove_cv : std::remove_cv {}; + +template +struct remove_cv> { + using type = std::pair::type...>; +}; + +template +struct remove_cv> { + using type = carray::type, N>; +}; + +template +using remove_cv_t = typename remove_cv::type; + +} // namespace bits + +} // namespace frozen + +#endif diff --git a/lib/Frozen/frozen/bits/pmh.h b/lib/Frozen/frozen/bits/pmh.h new file mode 100644 index 00000000..1bb40216 --- /dev/null +++ b/lib/Frozen/frozen/bits/pmh.h @@ -0,0 +1,254 @@ +/* + * Frozen + * Copyright 2016 QuarksLab + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// inspired from http://stevehanov.ca/blog/index.php?id=119 +#ifndef FROZEN_LETITGO_PMH_H +#define FROZEN_LETITGO_PMH_H + +#include "frozen/bits/algorithms.h" +#include "frozen/bits/basic_types.h" + +#include +#include +#include +#include + +namespace frozen { + +namespace bits { + +// Function object for sorting buckets in decreasing order of size +struct bucket_size_compare { + template + bool constexpr operator()(B const &b0, + B const &b1) const { + return b0.size() > b1.size(); + } +}; + +// Step One in pmh routine is to take all items and hash them into buckets, +// with some collisions. Then process those buckets further to build a perfect +// hash function. +// pmh_buckets represents the initial placement into buckets. + +template +struct pmh_buckets { + // Step 0: Bucket max is 2 * sqrt M + // TODO: Come up with justification for this, should it not be O(log M)? + static constexpr auto bucket_max = 2 * (1u << (log(M) / 2)); + + using bucket_t = cvector; + carray buckets; + std::uint64_t seed; + + // Represents a reference to a bucket. This is used because the buckets + // have to be sorted, but buckets are big, making it slower than sorting refs + struct bucket_ref { + unsigned hash; + const bucket_t * ptr; + + // Forward some interface of bucket + using value_type = typename bucket_t::value_type; + using const_iterator = typename bucket_t::const_iterator; + + constexpr auto size() const { return ptr->size(); } + constexpr const auto & operator[](std::size_t idx) const { return (*ptr)[idx]; } + constexpr auto begin() const { return ptr->begin(); } + constexpr auto end() const { return ptr->end(); } + }; + + // Make a bucket_ref for each bucket + template + carray constexpr make_bucket_refs(std::index_sequence) const { + return {{ bucket_ref{Is, &buckets[Is]}... }}; + } + + // Makes a bucket_ref for each bucket and sorts them by size + carray constexpr get_sorted_buckets() const { + carray result{this->make_bucket_refs(std::make_index_sequence())}; + bits::quicksort(result.begin(), result.end() - 1, bucket_size_compare{}); + return result; + } +}; + +template +pmh_buckets constexpr make_pmh_buckets(const carray & items, + Hash const & hash, + Key const & key, + PRG & prg) { + using result_t = pmh_buckets; + // Continue until all items are placed without exceeding bucket_max + while (1) { + result_t result{}; + result.seed = prg(); + bool rejected = false; + for (std::size_t i = 0; i < items.size(); ++i) { + auto & bucket = result.buckets[hash(key(items[i]), static_cast(result.seed)) % M]; + if (bucket.size() >= result_t::bucket_max) { + rejected = true; + break; + } + bucket.push_back(i); + } + if (!rejected) { return result; } + } +} + +// Check if an item appears in a cvector +template +constexpr bool all_different_from(cvector & data, T & a) { + for (std::size_t i = 0; i < data.size(); ++i) + if (data[i] == a) + return false; + + return true; +} + +// Represents either an index to a data item array, or a seed to be used with +// a hasher. Seed must have high bit of 1, value has high bit of zero. +struct seed_or_index { + using value_type = std::uint64_t; + +private: + static constexpr value_type MINUS_ONE = std::numeric_limits::max(); + static constexpr value_type HIGH_BIT = ~(MINUS_ONE >> 1); + + value_type value_ = 0; + +public: + constexpr value_type value() const { return value_; } + constexpr bool is_seed() const { return value_ & HIGH_BIT; } + + constexpr seed_or_index(bool is_seed, value_type value) + : value_(is_seed ? (value | HIGH_BIT) : (value & ~HIGH_BIT)) {} + + constexpr seed_or_index() = default; + constexpr seed_or_index(const seed_or_index &) = default; + constexpr seed_or_index & operator =(const seed_or_index &) = default; +}; + +// Represents the perfect hash function created by pmh algorithm +template +struct pmh_tables : private Hasher { + std::uint64_t first_seed_; + carray first_table_; + carray second_table_; + + constexpr pmh_tables( + std::uint64_t first_seed, + carray first_table, + carray second_table, + Hasher hash) noexcept + : Hasher(hash) + , first_seed_(first_seed) + , first_table_(first_table) + , second_table_(second_table) + {} + + constexpr Hasher const& hash_function() const noexcept { + return static_cast(*this); + } + + template + constexpr std::size_t lookup(const KeyType & key) const { + return lookup(key, hash_function()); + } + + // Looks up a given key, to find its expected index in carray + // Always returns a valid index, must use KeyEqual test after to confirm. + template + constexpr std::size_t lookup(const KeyType & key, const HasherType& hasher) const { + auto const d = first_table_[hasher(key, static_cast(first_seed_)) % M]; + if (!d.is_seed()) { return static_cast(d.value()); } // this is narrowing std::uint64 -> std::size_t but should be fine + else { return second_table_[hasher(key, static_cast(d.value())) % M]; } + } +}; + +// Make pmh tables for given items, hash function, prg, etc. +template +pmh_tables constexpr make_pmh_tables(const carray & + items, + Hash const &hash, + Key const &key, + PRG prg) { + // Step 1: Place all of the keys into buckets + auto step_one = make_pmh_buckets(items, hash, key, prg); + + // Step 2: Sort the buckets to process the ones with the most items first. + auto buckets = step_one.get_sorted_buckets(); + + // Special value for unused slots. This is purposefully the index + // one-past-the-end of 'items' to function as a sentinel value. Both to avoid + // the need to apply the KeyEqual predicate and to be easily convertible to + // end(). + // Unused entries in both hash tables (G and H) have to contain this value. + const auto UNUSED = items.size(); + + // G becomes the first hash table in the resulting pmh function + carray G({false, UNUSED}); + + // H becomes the second hash table in the resulting pmh function + carray H(UNUSED); + + // Step 3: Map the items in buckets into hash tables. + for (const auto & bucket : buckets) { + auto const bsize = bucket.size(); + + if (bsize == 1) { + // Store index to the (single) item in G + // assert(bucket.hash == hash(key(items[bucket[0]]), step_one.seed) % M); + G[bucket.hash] = {false, static_cast(bucket[0])}; + } else if (bsize > 1) { + + // Repeatedly try different H of d until we find a hash function + // that places all items in the bucket into free slots + seed_or_index d{true, prg()}; + cvector bucket_slots; + + while (bucket_slots.size() < bsize) { + auto slot = hash(key(items[bucket[bucket_slots.size()]]), static_cast(d.value())) % M; + + if (H[slot] != UNUSED || !all_different_from(bucket_slots, slot)) { + bucket_slots.clear(); + d = {true, prg()}; + continue; + } + + bucket_slots.push_back(slot); + } + + // Put successful seed in G, and put indices to items in their slots + // assert(bucket.hash == hash(key(items[bucket[0]]), step_one.seed) % M); + G[bucket.hash] = d; + for (std::size_t i = 0; i < bsize; ++i) + H[bucket_slots[i]] = bucket[i]; + } + } + + return {step_one.seed, G, H, hash}; +} + +} // namespace bits + +} // namespace frozen + +#endif diff --git a/lib/Frozen/frozen/bits/version.h b/lib/Frozen/frozen/bits/version.h new file mode 100644 index 00000000..7e57d707 --- /dev/null +++ b/lib/Frozen/frozen/bits/version.h @@ -0,0 +1,30 @@ +/* + * Frozen + * Copyright 2016 QuarksLab + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef FROZEN_LETITGO_VERSION_H +#define FROZEN_LETITGO_VERSION_H + +#define FROZEN_MAJOR_VERSION 1 +#define FROZEN_MINOR_VERSION 1 +#define FROZEN_PATCH_VERSION 1 + +#endif diff --git a/lib/Frozen/frozen/map.h b/lib/Frozen/frozen/map.h new file mode 100644 index 00000000..d54128a6 --- /dev/null +++ b/lib/Frozen/frozen/map.h @@ -0,0 +1,357 @@ +/* + * Frozen + * Copyright 2016 QuarksLab + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef FROZEN_LETITGO_MAP_H +#define FROZEN_LETITGO_MAP_H + +#include "frozen/bits/algorithms.h" +#include "frozen/bits/basic_types.h" +#include "frozen/bits/constexpr_assert.h" +#include "frozen/bits/exceptions.h" +#include "frozen/bits/mpl.h" +#include "frozen/bits/version.h" + +#include +#include + +namespace frozen { + +namespace impl { + +template class CompareKey : private Comparator { +public: + constexpr Comparator const& key_comp() const noexcept { + return static_cast(*this); + } + + constexpr CompareKey(Comparator const &comparator) + : Comparator(comparator) {} + + template + constexpr int operator()(std::pair const &self, + std::pair const &other) const { + return key_comp()(std::get<0>(self), std::get<0>(other)); + } + + template + constexpr int operator()(Key1 const &self_key, + std::pair const &other) const { + return key_comp()(self_key, std::get<0>(other)); + } + + template + constexpr int operator()(std::pair const &self, + Key2 const &other_key) const { + return key_comp()(std::get<0>(self), other_key); + } + + template + constexpr int operator()(Key1 const &self_key, Key2 const &other_key) const { + return key_comp()(self_key, other_key); + } +}; + +} // namespace impl + +template > +class map : private impl::CompareKey { + using container_type = bits::carray, N>; + container_type items_; + +public: + using key_type = Key; + using mapped_type = Value; + using value_type = typename container_type::value_type; + using size_type = typename container_type::size_type; + using difference_type = typename container_type::difference_type; + using key_compare = Compare; + using value_compare = impl::CompareKey; + using reference = typename container_type::reference; + using const_reference = typename container_type::const_reference; + using pointer = typename container_type::pointer; + using const_pointer = typename container_type::const_pointer; + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + +public: + /* constructors */ + constexpr map(container_type items, Compare const &compare) + : impl::CompareKey{compare} + , items_{bits::quicksort(bits::remove_cv_t(items), value_comp())} {} + + explicit constexpr map(container_type items) + : map{items, Compare{}} {} + + constexpr map(std::initializer_list items, Compare const &compare) + : map{container_type {items}, compare} { + constexpr_assert(items.size() == N, "Inconsistent initializer_list size and type size argument"); + } + + constexpr map(std::initializer_list items) + : map{items, Compare{}} {} + + /* element access */ + constexpr Value const& at(Key const &key) const { + return at_impl(*this, key); + } + constexpr Value& at(Key const &key) { + return at_impl(*this, key); + } + + /* iterators */ + constexpr iterator begin() { return items_.begin(); } + constexpr const_iterator begin() const { return items_.begin(); } + constexpr const_iterator cbegin() const { return items_.begin(); } + constexpr iterator end() { return items_.end(); } + constexpr const_iterator end() const { return items_.end(); } + constexpr const_iterator cend() const { return items_.end(); } + + constexpr reverse_iterator rbegin() { return reverse_iterator{items_.end()}; } + constexpr const_reverse_iterator rbegin() const { return const_reverse_iterator{items_.end()}; } + constexpr const_reverse_iterator crbegin() const { return const_reverse_iterator{items_.end()}; } + constexpr reverse_iterator rend() { return reverse_iterator{items_.begin()}; } + constexpr const_reverse_iterator rend() const { return const_reverse_iterator{items_.begin()}; } + constexpr const_reverse_iterator crend() const { return const_reverse_iterator{items_.begin()}; } + + /* capacity */ + constexpr bool empty() const { return !N; } + constexpr size_type size() const { return N; } + constexpr size_type max_size() const { return N; } + + /* lookup */ + + template + constexpr std::size_t count(KeyType const &key) const { + return bits::binary_search(items_.begin(), key, value_comp()); + } + + template + constexpr const_iterator find(KeyType const &key) const { + return map::find_impl(*this, key); + } + template + constexpr iterator find(KeyType const &key) { + return map::find_impl(*this, key); + } + + template + constexpr bool contains(KeyType const &key) const { + return this->find(key) != this->end(); + } + + template + constexpr std::pair + equal_range(KeyType const &key) const { + return equal_range_impl(*this, key); + } + template + constexpr std::pair equal_range(KeyType const &key) { + return equal_range_impl(*this, key); + } + + template + constexpr const_iterator lower_bound(KeyType const &key) const { + return lower_bound_impl(*this, key); + } + template + constexpr iterator lower_bound(KeyType const &key) { + return lower_bound_impl(*this, key); + } + + template + constexpr const_iterator upper_bound(KeyType const &key) const { + return upper_bound_impl(*this, key); + } + template + constexpr iterator upper_bound(KeyType const &key) { + return upper_bound_impl(*this, key); + } + + /* observers */ + constexpr const key_compare& key_comp() const { return value_comp().key_comp(); } + constexpr const value_compare& value_comp() const { return static_cast const&>(*this); } + + private: + template + static inline constexpr auto& at_impl(This&& self, KeyType const &key) { + auto where = self.find(key); + if (where != self.end()) + return where->second; + else + FROZEN_THROW_OR_ABORT(std::out_of_range("unknown key")); + } + + template + static inline constexpr auto find_impl(This&& self, KeyType const &key) { + auto where = self.lower_bound(key); + if (where != self.end() && !self.value_comp()(key, *where)) + return where; + else + return self.end(); + } + + template + static inline constexpr auto equal_range_impl(This&& self, KeyType const &key) { + auto lower = self.lower_bound(key); + using lower_t = decltype(lower); + if (lower != self.end() && !self.value_comp()(key, *lower)) + return std::pair{lower, lower + 1}; + else + return std::pair{lower, lower}; + } + + template + static inline constexpr auto lower_bound_impl(This&& self, KeyType const &key) -> decltype(self.end()) { + return bits::lower_bound(self.items_.begin(), key, self.value_comp()); + } + + template + static inline constexpr auto upper_bound_impl(This&& self, KeyType const &key) { + auto lower = self.lower_bound(key); + if (lower != self.end() && !self.value_comp()(key, *lower)) + return lower + 1; + else + return lower; + } +}; + +template +class map : private impl::CompareKey { + using container_type = bits::carray, 0>; + +public: + using key_type = Key; + using mapped_type = Value; + using value_type = typename container_type::value_type; + using size_type = typename container_type::size_type; + using difference_type = typename container_type::difference_type; + using key_compare = Compare; + using value_compare = impl::CompareKey; + using reference = typename container_type::reference; + using const_reference = typename container_type::const_reference; + using pointer = typename container_type::pointer; + using const_pointer = typename container_type::const_pointer; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = pointer; + using const_reverse_iterator = const_pointer; + +public: + /* constructors */ + constexpr map(const map &other) = default; + constexpr map(std::initializer_list, Compare const &compare) + : impl::CompareKey{compare} {} + constexpr map(std::initializer_list items) + : map{items, Compare{}} {} + + /* element access */ + template + constexpr mapped_type at(KeyType const &) const { + FROZEN_THROW_OR_ABORT(std::out_of_range("invalid key")); + } + template + constexpr mapped_type at(KeyType const &) { + FROZEN_THROW_OR_ABORT(std::out_of_range("invalid key")); + } + + /* iterators */ + constexpr iterator begin() { return nullptr; } + constexpr const_iterator begin() const { return nullptr; } + constexpr const_iterator cbegin() const { return nullptr; } + constexpr iterator end() { return nullptr; } + constexpr const_iterator end() const { return nullptr; } + constexpr const_iterator cend() const { return nullptr; } + + constexpr reverse_iterator rbegin() { return nullptr; } + constexpr const_reverse_iterator rbegin() const { return nullptr; } + constexpr const_reverse_iterator crbegin() const { return nullptr; } + constexpr reverse_iterator rend() { return nullptr; } + constexpr const_reverse_iterator rend() const { return nullptr; } + constexpr const_reverse_iterator crend() const { return nullptr; } + + /* capacity */ + constexpr bool empty() const { return true; } + constexpr size_type size() const { return 0; } + constexpr size_type max_size() const { return 0; } + + /* lookup */ + + template + constexpr std::size_t count(KeyType const &) const { return 0; } + + template + constexpr const_iterator find(KeyType const &) const { return end(); } + template + constexpr iterator find(KeyType const &) { return end(); } + + template + constexpr std::pair + equal_range(KeyType const &) const { return {end(), end()}; } + template + constexpr std::pair + equal_range(KeyType const &) { return {end(), end()}; } + + template + constexpr const_iterator lower_bound(KeyType const &) const { return end(); } + template + constexpr iterator lower_bound(KeyType const &) { return end(); } + + template + constexpr const_iterator upper_bound(KeyType const &) const { return end(); } + template + constexpr iterator upper_bound(KeyType const &) { return end(); } + +/* observers */ + constexpr key_compare const& key_comp() const { return value_comp().key_comp(); } + constexpr value_compare const& value_comp() const { return static_cast const&>(*this); } +}; + +template > +constexpr auto make_map(bits::ignored_arg = {}/* for consistency with the initializer below for N = 0*/) { + return map{}; +} + +template +constexpr auto make_map(std::pair const (&items)[N]) { + return map{items}; +} + +template +constexpr auto make_map(std::array, N> const &items) { + return map{items}; +} + +template +constexpr auto make_map(std::pair const (&items)[N], Compare const& compare = Compare{}) { + return map{items, compare}; +} + +template +constexpr auto make_map(std::array, N> const &items, Compare const& compare = Compare{}) { + return map{items, compare}; +} + +} // namespace frozen + +#endif diff --git a/lib/Frozen/frozen/random.h b/lib/Frozen/frozen/random.h new file mode 100644 index 00000000..727133bb --- /dev/null +++ b/lib/Frozen/frozen/random.h @@ -0,0 +1,97 @@ +/* + * Frozen + * Copyright 2016 QuarksLab + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef FROZEN_LETITGO_RANDOM_H +#define FROZEN_LETITGO_RANDOM_H + +#include "frozen/bits/algorithms.h" +#include "frozen/bits/version.h" + +#include +#include + +namespace frozen { +template +class linear_congruential_engine { + + static_assert(std::is_unsigned::value, + "UIntType must be an unsigned integral type"); + + template + static constexpr UIntType modulo(T val, std::integral_constant) { + return static_cast(val); + } + + template + static constexpr UIntType modulo(T val, std::integral_constant) { + // the static cast below may end up doing a truncation + return static_cast(val % M); + } + +public: + using result_type = UIntType; + static constexpr result_type multiplier = a; + static constexpr result_type increment = c; + static constexpr result_type modulus = m; + static constexpr result_type default_seed = 1u; + + linear_congruential_engine() = default; + constexpr linear_congruential_engine(result_type s) { seed(s); } + + void seed(result_type s = default_seed) { state_ = s; } + constexpr result_type operator()() { + using uint_least_t = bits::select_uint_least_t; + uint_least_t tmp = static_cast(multiplier) * state_ + increment; + + state_ = modulo(tmp, std::integral_constant()); + return state_; + } + constexpr void discard(unsigned long long n) { + while (n--) + operator()(); + } + static constexpr result_type min() { return increment == 0u ? 1u : 0u; } + static constexpr result_type max() { return modulus - 1u; } + friend constexpr bool operator==(linear_congruential_engine const &self, + linear_congruential_engine const &other) { + return self.state_ == other.state_; + } + friend constexpr bool operator!=(linear_congruential_engine const &self, + linear_congruential_engine const &other) { + return !(self == other); + } + +private: + result_type state_ = default_seed; +}; + +using minstd_rand0 = + linear_congruential_engine; +using minstd_rand = + linear_congruential_engine; + +// This generator is used by default in unordered frozen containers +using default_prg_t = minstd_rand; + +} // namespace frozen + +#endif diff --git a/lib/Frozen/frozen/set.h b/lib/Frozen/frozen/set.h new file mode 100644 index 00000000..430d4a54 --- /dev/null +++ b/lib/Frozen/frozen/set.h @@ -0,0 +1,260 @@ +/* + * Frozen + * Copyright 2016 QuarksLab + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef FROZEN_SET_H +#define FROZEN_SET_H + +#include "frozen/bits/algorithms.h" +#include "frozen/bits/basic_types.h" +#include "frozen/bits/constexpr_assert.h" +#include "frozen/bits/version.h" +#include "frozen/bits/defines.h" + +#include +#include + +namespace frozen { + +template > class set : private Compare { + using container_type = bits::carray; + container_type keys_; + +public: + /* container typedefs*/ + using key_type = Key; + using value_type = Key; + using size_type = typename container_type::size_type; + using difference_type = typename container_type::size_type; + using key_compare = Compare; + using value_compare = Compare; + using reference = typename container_type::const_reference; + using const_reference = reference; + using pointer = typename container_type::const_pointer; + using const_pointer = pointer; + using iterator = typename container_type::const_iterator; + using reverse_iterator = std::reverse_iterator; + using const_iterator = iterator; + using const_reverse_iterator = std::reverse_iterator; + +public: + /* constructors */ + constexpr set(const set &other) = default; + + constexpr set(container_type keys, Compare const & comp) + : Compare{comp} + , keys_(bits::quicksort(keys, value_comp())) { + } + + explicit constexpr set(container_type keys) + : set{keys, Compare{}} {} + + constexpr set(std::initializer_list keys, Compare const & comp) + : set{container_type{keys}, comp} { + constexpr_assert(keys.size() == N, "Inconsistent initializer_list size and type size argument"); + } + + constexpr set(std::initializer_list keys) + : set{keys, Compare{}} {} + + constexpr set& operator=(const set &other) = default; + + /* capacity */ + constexpr bool empty() const { return !N; } + constexpr size_type size() const { return N; } + constexpr size_type max_size() const { return N; } + + /* lookup */ + template + constexpr std::size_t count(KeyType const &key) const { + return bits::binary_search(keys_.begin(), key, value_comp()); + } + + template + constexpr const_iterator find(KeyType const &key) const { + const_iterator where = lower_bound(key); + if ((where != end()) && !value_comp()(key, *where)) + return where; + else + return end(); + } + + template + constexpr bool contains(KeyType const &key) const { + return this->find(key) != keys_.end(); + } + + template + constexpr std::pair equal_range(KeyType const &key) const { + auto const lower = lower_bound(key); + if (lower == end()) + return {lower, lower}; + else + return {lower, lower + 1}; + } + + template + constexpr const_iterator lower_bound(KeyType const &key) const { + auto const where = bits::lower_bound(keys_.begin(), key, value_comp()); + if ((where != end()) && !value_comp()(key, *where)) + return where; + else + return end(); + } + + template + constexpr const_iterator upper_bound(KeyType const &key) const { + auto const where = bits::lower_bound(keys_.begin(), key, value_comp()); + if ((where != end()) && !value_comp()(key, *where)) + return where + 1; + else + return end(); + } + + /* observers */ + constexpr const key_compare& key_comp() const { return value_comp(); } + constexpr const key_compare& value_comp() const { return static_cast(*this); } + + /* iterators */ + constexpr const_iterator begin() const { return keys_.begin(); } + constexpr const_iterator cbegin() const { return keys_.begin(); } + constexpr const_iterator end() const { return keys_.end(); } + constexpr const_iterator cend() const { return keys_.end(); } + + constexpr const_reverse_iterator rbegin() const { return const_reverse_iterator{keys_.end()}; } + constexpr const_reverse_iterator crbegin() const { return const_reverse_iterator{keys_.end()}; } + constexpr const_reverse_iterator rend() const { return const_reverse_iterator{keys_.begin()}; } + constexpr const_reverse_iterator crend() const { return const_reverse_iterator{keys_.begin()}; } + + /* comparison */ + constexpr bool operator==(set const& rhs) const { return bits::equal(begin(), end(), rhs.begin()); } + constexpr bool operator!=(set const& rhs) const { return !(*this == rhs); } + constexpr bool operator<(set const& rhs) const { return bits::lexicographical_compare(begin(), end(), rhs.begin(), rhs.end()); } + constexpr bool operator<=(set const& rhs) const { return (*this < rhs) || (*this == rhs); } + constexpr bool operator>(set const& rhs) const { return bits::lexicographical_compare(rhs.begin(), rhs.end(), begin(), end()); } + constexpr bool operator>=(set const& rhs) const { return (*this > rhs) || (*this == rhs); } +}; + +template class set : private Compare { + using container_type = bits::carray; // just for the type definitions + +public: + /* container typedefs*/ + using key_type = Key; + using value_type = Key; + using size_type = typename container_type::size_type; + using difference_type = typename container_type::size_type; + using key_compare = Compare; + using value_compare = Compare; + using reference = typename container_type::const_reference; + using const_reference = reference; + using pointer = typename container_type::const_pointer; + using const_pointer = pointer; + using iterator = pointer; + using reverse_iterator = pointer; + using const_iterator = const_pointer; + using const_reverse_iterator = const_pointer; + +public: + /* constructors */ + constexpr set(const set &other) = default; + constexpr set(bits::carray, Compare const &) {} + explicit constexpr set(bits::carray) {} + + constexpr set(std::initializer_list, Compare const &comp) + : Compare{comp} {} + constexpr set(std::initializer_list keys) : set{keys, Compare{}} {} + + constexpr set& operator=(const set &other) = default; + + /* capacity */ + constexpr bool empty() const { return true; } + constexpr size_type size() const { return 0; } + constexpr size_type max_size() const { return 0; } + + /* lookup */ + template + constexpr std::size_t count(KeyType const &) const { return 0; } + + template + constexpr const_iterator find(KeyType const &) const { return end(); } + + template + constexpr std::pair + equal_range(KeyType const &) const { return {end(), end()}; } + + template + constexpr const_iterator lower_bound(KeyType const &) const { return end(); } + + template + constexpr const_iterator upper_bound(KeyType const &) const { return end(); } + + /* observers */ + constexpr const key_compare& key_comp() const { return value_comp(); } + constexpr const key_compare& value_comp() const { return static_cast(*this); } + + /* iterators */ + constexpr const_iterator begin() const { return nullptr; } + constexpr const_iterator cbegin() const { return nullptr; } + constexpr const_iterator end() const { return nullptr; } + constexpr const_iterator cend() const { return nullptr; } + + constexpr const_reverse_iterator rbegin() const { return nullptr; } + constexpr const_reverse_iterator crbegin() const { return nullptr; } + constexpr const_reverse_iterator rend() const { return nullptr; } + constexpr const_reverse_iterator crend() const { return nullptr; } +}; + +template +constexpr auto make_set(bits::ignored_arg = {}/* for consistency with the initializer below for N = 0*/) { + return set{}; +} + +template +constexpr auto make_set(const T (&args)[N]) { + return set(args); +} + +template +constexpr auto make_set(std::array const &args) { + return set(args); +} + +template +constexpr auto make_set(const T (&args)[N], Compare const& compare = Compare{}) { + return set(args, compare); +} + +template +constexpr auto make_set(std::array const &args, Compare const& compare = Compare{}) { + return set(args, compare); +} + +#ifdef FROZEN_LETITGO_HAS_DEDUCTION_GUIDES + +template +set(T, Args...) -> set; + +#endif // FROZEN_LETITGO_HAS_DEDUCTION_GUIDES + +} // namespace frozen + +#endif diff --git a/lib/Frozen/frozen/string.h b/lib/Frozen/frozen/string.h new file mode 100644 index 00000000..354ed9c1 --- /dev/null +++ b/lib/Frozen/frozen/string.h @@ -0,0 +1,152 @@ +/* + * Frozen + * Copyright 2016 QuarksLab + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef FROZEN_LETITGO_STRING_H +#define FROZEN_LETITGO_STRING_H + +#include "frozen/bits/elsa.h" +#include "frozen/bits/hash_string.h" +#include "frozen/bits/version.h" +#include "frozen/bits/defines.h" + +#include +#include + +#ifdef FROZEN_LETITGO_HAS_STRING_VIEW +#include +#endif + +namespace frozen { + +template +class basic_string { + using chr_t = _CharT; + + chr_t const *data_; + std::size_t size_; + +public: + template + constexpr basic_string(chr_t const (&data)[N]) + : data_(data), size_(N - 1) {} + constexpr basic_string(chr_t const *data, std::size_t size) + : data_(data), size_(size) {} + +#ifdef FROZEN_LETITGO_HAS_STRING_VIEW + constexpr basic_string(std::basic_string_view data) + : data_(data.data()), size_(data.size()) {} +#endif + + constexpr basic_string(const basic_string &) noexcept = default; + constexpr basic_string &operator=(const basic_string &) noexcept = default; + + constexpr std::size_t size() const { return size_; } + + constexpr chr_t operator[](std::size_t i) const { return data_[i]; } + + constexpr bool operator==(basic_string other) const { + if (size_ != other.size_) + return false; + for (std::size_t i = 0; i < size_; ++i) + if (data_[i] != other.data_[i]) + return false; + return true; + } + + constexpr bool operator<(const basic_string &other) const { + unsigned i = 0; + while (i < size() && i < other.size()) { + if ((*this)[i] < other[i]) { + return true; + } + if ((*this)[i] > other[i]) { + return false; + } + ++i; + } + return size() < other.size(); + } + + friend constexpr bool operator>(const basic_string& lhs, const basic_string& rhs) { + return rhs < lhs; + } + + constexpr const chr_t *data() const { return data_; } + constexpr const chr_t *begin() const { return data(); } + constexpr const chr_t *end() const { return data() + size(); } +}; + +template struct elsa> { + constexpr std::size_t operator()(basic_string<_CharT> value) const { + return hash_string(value); + } + constexpr std::size_t operator()(basic_string<_CharT> value, std::size_t seed) const { + return hash_string(value, seed); + } +}; + +using string = basic_string; +using wstring = basic_string; +using u16string = basic_string; +using u32string = basic_string; + +#ifdef FROZEN_LETITGO_HAS_CHAR8T +using u8string = basic_string; +#endif + +namespace string_literals { + +constexpr string operator"" _s(const char *data, std::size_t size) { + return {data, size}; +} + +constexpr wstring operator"" _s(const wchar_t *data, std::size_t size) { + return {data, size}; +} + +constexpr u16string operator"" _s(const char16_t *data, std::size_t size) { + return {data, size}; +} + +constexpr u32string operator"" _s(const char32_t *data, std::size_t size) { + return {data, size}; +} + +#ifdef FROZEN_LETITGO_HAS_CHAR8T +constexpr u8string operator"" _s(const char8_t *data, std::size_t size) { + return {data, size}; +} +#endif + +} // namespace string_literals + +} // namespace frozen + +namespace std { +template struct hash> { + std::size_t operator()(frozen::basic_string<_CharT> s) const { + return frozen::elsa>{}(s); + } +}; +} // namespace std + +#endif diff --git a/lib/Frozen/frozen/unordered_map.h b/lib/Frozen/frozen/unordered_map.h new file mode 100644 index 00000000..6f7b4a00 --- /dev/null +++ b/lib/Frozen/frozen/unordered_map.h @@ -0,0 +1,217 @@ +/* + * Frozen + * Copyright 2016 QuarksLab + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef FROZEN_LETITGO_UNORDERED_MAP_H +#define FROZEN_LETITGO_UNORDERED_MAP_H + +#include "frozen/bits/basic_types.h" +#include "frozen/bits/constexpr_assert.h" +#include "frozen/bits/elsa.h" +#include "frozen/bits/exceptions.h" +#include "frozen/bits/pmh.h" +#include "frozen/bits/version.h" +#include "frozen/random.h" + +#include +#include +#include + +namespace frozen { + +namespace bits { + +struct GetKey { + template constexpr auto const &operator()(KV const &kv) const { + return kv.first; + } +}; + +} // namespace bits + +template , + class KeyEqual = std::equal_to> +class unordered_map : private KeyEqual { + static constexpr std::size_t storage_size = + bits::next_highest_power_of_two(N) * (N < 32 ? 2 : 1); // size adjustment to prevent high collision rate for small sets + using container_type = bits::carray, N>; + using tables_type = bits::pmh_tables; + + container_type items_; + tables_type tables_; + +public: + /* typedefs */ + using Self = unordered_map; + using key_type = Key; + using mapped_type = Value; + using value_type = typename container_type::value_type; + using size_type = typename container_type::size_type; + using difference_type = typename container_type::difference_type; + using hasher = Hash; + using key_equal = KeyEqual; + using reference = typename container_type::reference; + using const_reference = typename container_type::const_reference; + using pointer = typename container_type::pointer; + using const_pointer = typename container_type::const_pointer; + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + +public: + /* constructors */ + unordered_map(unordered_map const &) = default; + constexpr unordered_map(container_type items, + Hash const &hash, KeyEqual const &equal) + : KeyEqual{equal} + , items_{items} + , tables_{ + bits::make_pmh_tables( + items_, hash, bits::GetKey{}, default_prg_t{})} {} + explicit constexpr unordered_map(container_type items) + : unordered_map{items, Hash{}, KeyEqual{}} {} + + constexpr unordered_map(std::initializer_list items, + Hash const & hash, KeyEqual const & equal) + : unordered_map{container_type{items}, hash, equal} { + constexpr_assert(items.size() == N, "Inconsistent initializer_list size and type size argument"); + } + + constexpr unordered_map(std::initializer_list items) + : unordered_map{items, Hash{}, KeyEqual{}} {} + + /* iterators */ + constexpr iterator begin() { return items_.begin(); } + constexpr iterator end() { return items_.end(); } + constexpr const_iterator begin() const { return items_.begin(); } + constexpr const_iterator end() const { return items_.end(); } + constexpr const_iterator cbegin() const { return items_.begin(); } + constexpr const_iterator cend() const { return items_.end(); } + + /* capacity */ + constexpr bool empty() const { return !N; } + constexpr size_type size() const { return N; } + constexpr size_type max_size() const { return N; } + + /* lookup */ + template + constexpr std::size_t count(KeyType const &key) const { + return find(key) != end(); + } + + template + constexpr Value const &at(KeyType const &key) const { + return at_impl(*this, key); + } + template + constexpr Value &at(KeyType const &key) { + return at_impl(*this, key); + } + + template + constexpr const_iterator find(KeyType const &key) const { + return find_impl(*this, key, hash_function(), key_eq()); + } + template + constexpr iterator find(KeyType const &key) { + return find_impl(*this, key, hash_function(), key_eq()); + } + + template + constexpr bool contains(KeyType const &key) const { + return this->find(key) != this->end(); + } + + template + constexpr std::pair equal_range(KeyType const &key) const { + return equal_range_impl(*this, key); + } + template + constexpr std::pair equal_range(KeyType const &key) { + return equal_range_impl(*this, key); + } + + /* bucket interface */ + constexpr std::size_t bucket_count() const { return storage_size; } + constexpr std::size_t max_bucket_count() const { return storage_size; } + + /* observers*/ + constexpr const hasher& hash_function() const { return tables_.hash_function(); } + constexpr const key_equal& key_eq() const { return static_cast(*this); } + +private: + template + static inline constexpr auto& at_impl(This&& self, KeyType const &key) { + auto it = self.find(key); + if (it != self.end()) + return it->second; + else + FROZEN_THROW_OR_ABORT(std::out_of_range("unknown key")); + } + + template + static inline constexpr auto find_impl(This&& self, KeyType const &key, Hasher const &hash, Equal const &equal) { + auto const pos = self.tables_.lookup(key, hash); + auto it = self.items_.begin() + pos; + if (it != self.items_.end() && equal(it->first, key)) + return it; + else + return self.items_.end(); + } + + template + static inline constexpr auto equal_range_impl(This&& self, KeyType const &key) { + auto const it = self.find(key); + if (it != self.end()) + return std::make_pair(it, it + 1); + else + return std::make_pair(self.end(), self.end()); + } +}; + +template +constexpr auto make_unordered_map(std::pair const (&items)[N]) { + return unordered_map{items}; +} + +template +constexpr auto make_unordered_map( + std::pair const (&items)[N], + Hasher const &hash = elsa{}, + Equal const &equal = std::equal_to{}) { + return unordered_map{items, hash, equal}; +} + +template +constexpr auto make_unordered_map(std::array, N> const &items) { + return unordered_map{items}; +} + +template +constexpr auto make_unordered_map( + std::array, N> const &items, + Hasher const &hash = elsa{}, + Equal const &equal = std::equal_to{}) { + return unordered_map{items, hash, equal}; +} + +} // namespace frozen + +#endif diff --git a/lib/Frozen/frozen/unordered_set.h b/lib/Frozen/frozen/unordered_set.h new file mode 100644 index 00000000..81bca6c5 --- /dev/null +++ b/lib/Frozen/frozen/unordered_set.h @@ -0,0 +1,181 @@ +/* + * Frozen + * Copyright 2016 QuarksLab + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef FROZEN_LETITGO_UNORDERED_SET_H +#define FROZEN_LETITGO_UNORDERED_SET_H + +#include "frozen/bits/basic_types.h" +#include "frozen/bits/constexpr_assert.h" +#include "frozen/bits/elsa.h" +#include "frozen/bits/pmh.h" +#include "frozen/bits/version.h" +#include "frozen/random.h" + +#include + +namespace frozen { + +namespace bits { + +struct Get { + template constexpr T const &operator()(T const &key) const { + return key; + } +}; + +} // namespace bits + +template , + class KeyEqual = std::equal_to> +class unordered_set : private KeyEqual { + static constexpr std::size_t storage_size = + bits::next_highest_power_of_two(N) * (N < 32 ? 2 : 1); // size adjustment to prevent high collision rate for small sets + using container_type = bits::carray; + using tables_type = bits::pmh_tables; + + container_type keys_; + tables_type tables_; + +public: + /* typedefs */ + using key_type = Key; + using value_type = Key; + using size_type = typename container_type::size_type; + using difference_type = typename container_type::difference_type; + using hasher = Hash; + using key_equal = KeyEqual; + using const_reference = typename container_type::const_reference; + using reference = const_reference; + using const_pointer = typename container_type::const_pointer; + using pointer = const_pointer; + using const_iterator = typename container_type::const_iterator; + using iterator = const_iterator; + +public: + /* constructors */ + unordered_set(unordered_set const &) = default; + constexpr unordered_set(container_type keys, Hash const &hash, + KeyEqual const &equal) + : KeyEqual{equal} + , keys_{keys} + , tables_{bits::make_pmh_tables( + keys_, hash, bits::Get{}, default_prg_t{})} {} + explicit constexpr unordered_set(container_type keys) + : unordered_set{keys, Hash{}, KeyEqual{}} {} + + constexpr unordered_set(std::initializer_list keys) + : unordered_set{keys, Hash{}, KeyEqual{}} {} + + constexpr unordered_set(std::initializer_list keys, Hash const & hash, KeyEqual const & equal) + : unordered_set{container_type{keys}, hash, equal} { + constexpr_assert(keys.size() == N, "Inconsistent initializer_list size and type size argument"); + } + + /* iterators */ + constexpr const_iterator begin() const { return keys_.begin(); } + constexpr const_iterator end() const { return keys_.end(); } + constexpr const_iterator cbegin() const { return keys_.begin(); } + constexpr const_iterator cend() const { return keys_.end(); } + + /* capacity */ + constexpr bool empty() const { return !N; } + constexpr size_type size() const { return N; } + constexpr size_type max_size() const { return N; } + + /* lookup */ + template + constexpr std::size_t count(KeyType const &key) const { + return find(key, hash_function(), key_eq()) != end(); + } + + template + constexpr const_iterator find(KeyType const &key, Hasher const &hash, Equal const &equal) const { + auto const pos = tables_.lookup(key, hash); + auto it = keys_.begin() + pos; + if (it != keys_.end() && equal(*it, key)) + return it; + else + return keys_.end(); + } + template + constexpr const_iterator find(KeyType const &key) const { + auto const pos = tables_.lookup(key, hash_function()); + auto it = keys_.begin() + pos; + if (it != keys_.end() && key_eq()(*it, key)) + return it; + else + return keys_.end(); + } + + template + constexpr bool contains(KeyType const &key) const { + return this->find(key) != keys_.end(); + } + + template + constexpr std::pair equal_range(KeyType const &key) const { + auto const it = find(key); + if (it != end()) + return {it, it + 1}; + else + return {keys_.end(), keys_.end()}; + } + + /* bucket interface */ + constexpr std::size_t bucket_count() const { return storage_size; } + constexpr std::size_t max_bucket_count() const { return storage_size; } + + /* observers*/ + constexpr const hasher& hash_function() const { return tables_.hash_function(); } + constexpr const key_equal& key_eq() const { return static_cast(*this); } +}; + +template +constexpr auto make_unordered_set(T const (&keys)[N]) { + return unordered_set{keys}; +} + +template +constexpr auto make_unordered_set(T const (&keys)[N], Hasher const& hash, Equal const& equal) { + return unordered_set{keys, hash, equal}; +} + +template +constexpr auto make_unordered_set(std::array const &keys) { + return unordered_set{keys}; +} + +template +constexpr auto make_unordered_set(std::array const &keys, Hasher const& hash, Equal const& equal) { + return unordered_set{keys, hash, equal}; +} + +#ifdef FROZEN_LETITGO_HAS_DEDUCTION_GUIDES + +template +unordered_set(T, Args...) -> unordered_set; + +#endif + +} // namespace frozen + +#endif diff --git a/lib/Hoymiles/src/Hoymiles.cpp b/lib/Hoymiles/src/Hoymiles.cpp index c43c968b..38791f58 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" @@ -24,12 +24,12 @@ void HoymilesClass::init() _radioCmt.reset(new HoymilesRadio_CMT()); } -void HoymilesClass::initNRF(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ) +void HoymilesClass::initNRF(SPIClass* initialisedSpiBus, const uint8_t pinCE, const uint8_t pinIRQ) { _radioNrf->init(initialisedSpiBus, pinCE, pinIRQ); } -void HoymilesClass::initCMT(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, int8_t pin_gpio2, int8_t pin_gpio3) +void HoymilesClass::initCMT(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int8_t pin_gpio2, const int8_t pin_gpio3) { _radioCmt->init(pin_sdio, pin_clk, pin_cs, pin_fcs, pin_gpio2, pin_gpio3); } @@ -40,110 +40,114 @@ 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()) { + 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 + const 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) { + const 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 + const int8_t currentWeekDay = Utils::getWeekDay(); + static int8_t lastWeekDay = -1; + if (lastWeekDay == -1) { + lastWeekDay = currentWeekDay; + } else { + if (currentWeekDay != lastWeekDay) { + + for (auto& inv : _inverters) { + // Have to reset the offets first, otherwise it will + // Substract the offset from zero which leads to a high value + inv->Statistics()->resetYieldDayCorrection(); + if (inv->getZeroYieldDayOnMidnight()) { + inv->Statistics()->zeroDailyData(); + } + } + lastWeekDay = currentWeekDay; - } else { - if (currentWeekDay != lastWeekDay) { - - for (auto& inv : _inverters) { - if (inv->getZeroYieldDayOnMidnight()) { - inv->Statistics()->zeroDailyData(); - } - } - - lastWeekDay = currentWeekDay; - } } } } } -std::shared_ptr HoymilesClass::addInverter(const char* name, uint64_t serial) +std::shared_ptr HoymilesClass::addInverter(const char* name, const uint64_t serial) { std::shared_ptr i = nullptr; if (HMT_4CH::isValidSerial(serial)) { @@ -176,7 +180,7 @@ std::shared_ptr HoymilesClass::addInverter(const char* name, u return nullptr; } -std::shared_ptr HoymilesClass::getInverterByPos(uint8_t pos) +std::shared_ptr HoymilesClass::getInverterByPos(const uint8_t pos) { if (pos >= _inverters.size()) { return nullptr; @@ -185,7 +189,7 @@ std::shared_ptr HoymilesClass::getInverterByPos(uint8_t pos) } } -std::shared_ptr HoymilesClass::getInverterBySerial(uint64_t serial) +std::shared_ptr HoymilesClass::getInverterBySerial(const uint64_t serial) { for (uint8_t i = 0; i < _inverters.size(); i++) { if (_inverters[i]->serial() == serial) { @@ -195,9 +199,9 @@ 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) { + if (fragment.len <= 4) { return nullptr; } @@ -207,10 +211,10 @@ std::shared_ptr HoymilesClass::getInverterByFragment(fragment_ serial_u p; p.u64 = inv->serial(); - if ((p.b[3] == fragment->fragment[1]) - && (p.b[2] == fragment->fragment[2]) - && (p.b[1] == fragment->fragment[3]) - && (p.b[0] == fragment->fragment[4])) { + if ((p.b[3] == fragment.fragment[1]) + && (p.b[2] == fragment.fragment[2]) + && (p.b[1] == fragment.fragment[3]) + && (p.b[0] == fragment.fragment[4])) { return inv; } @@ -218,7 +222,7 @@ std::shared_ptr HoymilesClass::getInverterByFragment(fragment_ return nullptr; } -void HoymilesClass::removeInverterBySerial(uint64_t serial) +void HoymilesClass::removeInverterBySerial(const uint64_t serial) { for (uint8_t i = 0; i < _inverters.size(); i++) { if (_inverters[i]->serial() == serial) { @@ -229,7 +233,7 @@ void HoymilesClass::removeInverterBySerial(uint64_t serial) } } -size_t HoymilesClass::getNumInverters() +size_t HoymilesClass::getNumInverters() const { return _inverters.size(); } @@ -244,17 +248,17 @@ HoymilesRadio_CMT* HoymilesClass::getRadioCmt() return _radioCmt.get(); } -bool HoymilesClass::isAllRadioIdle() +bool HoymilesClass::isAllRadioIdle() const { return _radioNrf.get()->isIdle() && _radioCmt.get()->isIdle(); } -uint32_t HoymilesClass::PollInterval() +uint32_t HoymilesClass::PollInterval() const { return _pollInterval; } -void HoymilesClass::setPollInterval(uint32_t interval) +void HoymilesClass::setPollInterval(const uint32_t interval) { _pollInterval = interval; } diff --git a/lib/Hoymiles/src/Hoymiles.h b/lib/Hoymiles/src/Hoymiles.h index 7975f1ba..86a7d6ca 100644 --- a/lib/Hoymiles/src/Hoymiles.h +++ b/lib/Hoymiles/src/Hoymiles.h @@ -16,29 +16,29 @@ class HoymilesClass { public: void init(); - void initNRF(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ); - void initCMT(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, int8_t pin_gpio2, int8_t pin_gpio3); + void initNRF(SPIClass* initialisedSpiBus, const uint8_t pinCE, const uint8_t pinIRQ); + void initCMT(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int8_t pin_gpio2, const int8_t pin_gpio3); void loop(); void setMessageOutput(Print* output); Print* getMessageOutput(); Print* getVerboseMessageOutput(); - 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); - void removeInverterBySerial(uint64_t serial); - size_t getNumInverters(); + std::shared_ptr addInverter(const char* name, const uint64_t serial); + std::shared_ptr getInverterByPos(const uint8_t pos); + std::shared_ptr getInverterBySerial(const uint64_t serial); + std::shared_ptr getInverterByFragment(const fragment_t& fragment); + void removeInverterBySerial(const uint64_t serial); + size_t getNumInverters() const; HoymilesRadio_NRF* getRadioNrf(); HoymilesRadio_CMT* getRadioCmt(); - uint32_t PollInterval(); - void setPollInterval(uint32_t interval); + uint32_t PollInterval() const; + void setPollInterval(const uint32_t interval); void setVerboseLogging(bool verboseLogging); - bool isAllRadioIdle(); + bool isAllRadioIdle() const; private: std::vector> _inverters; diff --git a/lib/Hoymiles/src/HoymilesRadio.cpp b/lib/Hoymiles/src/HoymilesRadio.cpp index 77fa609b..7534dcbe 100644 --- a/lib/Hoymiles/src/HoymilesRadio.cpp +++ b/lib/Hoymiles/src/HoymilesRadio.cpp @@ -6,17 +6,17 @@ #include "Hoymiles.h" #include "crc.h" -serial_u HoymilesRadio::DtuSerial() +serial_u HoymilesRadio::DtuSerial() const { return _dtuSerial; } -void HoymilesRadio::setDtuSerial(uint64_t serial) +void HoymilesRadio::setDtuSerial(const uint64_t serial) { _dtuSerial.u64 = serial; } -serial_u HoymilesRadio::convertSerialToRadioId(serial_u serial) +serial_u HoymilesRadio::convertSerialToRadioId(const serial_u serial) { serial_u radioId; radioId.u64 = 0; @@ -28,27 +28,27 @@ serial_u HoymilesRadio::convertSerialToRadioId(serial_u serial) return radioId; } -bool HoymilesRadio::checkFragmentCrc(fragment_t* fragment) +bool HoymilesRadio::checkFragmentCrc(const fragment_t& fragment) const { - uint8_t crc = crc8(fragment->fragment, fragment->len - 1); - return (crc == fragment->fragment[fragment->len - 1]); + const uint8_t crc = crc8(fragment.fragment, fragment.len - 1); + return (crc == fragment.fragment[fragment.len - 1]); } -void HoymilesRadio::sendRetransmitPacket(uint8_t fragment_id) +void HoymilesRadio::sendRetransmitPacket(const uint8_t fragment_id) { CommandAbstract* cmd = _commandQueue.front().get(); CommandAbstract* requestCmd = cmd->getRequestFrameCommand(fragment_id); if (requestCmd != nullptr) { - sendEsbPacket(requestCmd); + sendEsbPacket(*requestCmd); } } void HoymilesRadio::sendLastPacketAgain() { CommandAbstract* cmd = _commandQueue.front().get(); - sendEsbPacket(cmd); + sendEsbPacket(*cmd); } void HoymilesRadio::handleReceivedPackage() @@ -59,7 +59,7 @@ void HoymilesRadio::handleReceivedPackage() if (nullptr != inv) { CommandAbstract* cmd = _commandQueue.front().get(); - uint8_t verifyResult = inv->verifyAllFragments(cmd); + uint8_t verifyResult = inv->verifyAllFragments(*cmd); if (verifyResult == FRAGMENT_ALL_MISSING_RESEND) { Hoymiles.getMessageOutput()->println("Nothing received, resend whole request"); sendLastPacketAgain(); @@ -105,7 +105,7 @@ void HoymilesRadio::handleReceivedPackage() auto inv = Hoymiles.getInverterBySerial(cmd->getTargetAddress()); if (nullptr != inv) { inv->clearRxFragmentBuffer(); - sendEsbPacket(cmd); + sendEsbPacket(*cmd); } else { Hoymiles.getMessageOutput()->println("TX: Invalid inverter found"); _commandQueue.pop(); @@ -114,7 +114,7 @@ void HoymilesRadio::handleReceivedPackage() } } -void HoymilesRadio::dumpBuf(const uint8_t buf[], uint8_t len, bool appendNewline) +void HoymilesRadio::dumpBuf(const uint8_t buf[], const uint8_t len, const bool appendNewline) { for (uint8_t i = 0; i < len; i++) { Hoymiles.getVerboseMessageOutput()->printf("%02X ", buf[i]); @@ -124,17 +124,17 @@ void HoymilesRadio::dumpBuf(const uint8_t buf[], uint8_t len, bool appendNewline } } -bool HoymilesRadio::isInitialized() +bool HoymilesRadio::isInitialized() const { return _isInitialized; } -bool HoymilesRadio::isIdle() +bool HoymilesRadio::isIdle() const { return !_busyFlag; } -bool HoymilesRadio::isQueueEmpty() +bool HoymilesRadio::isQueueEmpty() const { return _commandQueue.size() == 0; } diff --git a/lib/Hoymiles/src/HoymilesRadio.h b/lib/Hoymiles/src/HoymilesRadio.h index fa2f6945..33b8c613 100644 --- a/lib/Hoymiles/src/HoymilesRadio.h +++ b/lib/Hoymiles/src/HoymilesRadio.h @@ -9,12 +9,12 @@ class HoymilesRadio { public: - serial_u DtuSerial(); - virtual void setDtuSerial(uint64_t serial); + serial_u DtuSerial() const; + virtual void setDtuSerial(const uint64_t serial); - bool isIdle(); - bool isQueueEmpty(); - bool isInitialized(); + bool isIdle() const; + bool isQueueEmpty() const; + bool isInitialized() const; void enqueCommand(std::shared_ptr cmd) { @@ -28,12 +28,12 @@ public: } protected: - static serial_u convertSerialToRadioId(serial_u serial); - void dumpBuf(const uint8_t buf[], uint8_t len, bool appendNewline = true); + static serial_u convertSerialToRadioId(const serial_u serial); + static void dumpBuf(const uint8_t buf[], const uint8_t len, const bool appendNewline = true); - bool checkFragmentCrc(fragment_t* fragment); - virtual void sendEsbPacket(CommandAbstract* cmd) = 0; - void sendRetransmitPacket(uint8_t fragment_id); + bool checkFragmentCrc(const fragment_t& fragment) const; + virtual void sendEsbPacket(CommandAbstract& cmd) = 0; + void sendRetransmitPacket(const uint8_t fragment_id); void sendLastPacketAgain(); void handleReceivedPackage(); diff --git a/lib/Hoymiles/src/HoymilesRadio_CMT.cpp b/lib/Hoymiles/src/HoymilesRadio_CMT.cpp index 9e69feec..5c61c3b6 100644 --- a/lib/Hoymiles/src/HoymilesRadio_CMT.cpp +++ b/lib/Hoymiles/src/HoymilesRadio_CMT.cpp @@ -53,7 +53,7 @@ bool HoymilesRadio_CMT::cmtSwitchDtuFreq(const uint32_t to_freq_kHz) return true; } -void HoymilesRadio_CMT::init(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, int8_t pin_gpio2, int8_t pin_gpio3) +void HoymilesRadio_CMT::init(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int8_t pin_gpio2, const int8_t pin_gpio3) { _dtuSerial.u64 = 0; @@ -122,15 +122,15 @@ void HoymilesRadio_CMT::loop() // Perform package parsing only if no packages are received if (!_rxBuffer.empty()) { fragment_t f = _rxBuffer.back(); - if (checkFragmentCrc(&f)) { + if (checkFragmentCrc(f)) { - serial_u dtuId = convertSerialToRadioId(_dtuSerial); + const serial_u dtuId = convertSerialToRadioId(_dtuSerial); // The CMT RF module does not filter foreign packages by itself. // Has to be done manually here. if (memcmp(&f.fragment[5], &dtuId.b[1], 4) == 0) { - std::shared_ptr inv = Hoymiles.getInverterByFragment(&f); + std::shared_ptr inv = Hoymiles.getInverterByFragment(f); if (nullptr != inv) { // Save packet in inverter rx buffer @@ -156,7 +156,7 @@ void HoymilesRadio_CMT::loop() handleReceivedPackage(); } -void HoymilesRadio_CMT::setPALevel(int8_t paLevel) +void HoymilesRadio_CMT::setPALevel(const int8_t paLevel) { if (!_isInitialized) { return; @@ -169,7 +169,7 @@ void HoymilesRadio_CMT::setPALevel(int8_t paLevel) } } -void HoymilesRadio_CMT::setInverterTargetFrequency(uint32_t frequency) +void HoymilesRadio_CMT::setInverterTargetFrequency(const uint32_t frequency) { _inverterTargetFrequency = frequency; if (!_isInitialized) { @@ -178,12 +178,12 @@ void HoymilesRadio_CMT::setInverterTargetFrequency(uint32_t frequency) cmtSwitchDtuFreq(_inverterTargetFrequency); } -uint32_t HoymilesRadio_CMT::getInverterTargetFrequency() +uint32_t HoymilesRadio_CMT::getInverterTargetFrequency() const { return _inverterTargetFrequency; } -bool HoymilesRadio_CMT::isConnected() +bool HoymilesRadio_CMT::isConnected() const { if (!_isInitialized) { return false; @@ -211,27 +211,27 @@ void ARDUINO_ISR_ATTR HoymilesRadio_CMT::handleInt2() _packetReceived = true; } -void HoymilesRadio_CMT::sendEsbPacket(CommandAbstract* cmd) +void HoymilesRadio_CMT::sendEsbPacket(CommandAbstract& cmd) { - cmd->incrementSendCount(); + cmd.incrementSendCount(); - cmd->setRouterAddress(DtuSerial().u64); + cmd.setRouterAddress(DtuSerial().u64); _radio->stopListening(); - if (cmd->getDataPayload()[0] == 0x56) { // @todo(tbnobody) Bad hack to identify ChannelChange Command + if (cmd.getDataPayload()[0] == 0x56) { // @todo(tbnobody) Bad hack to identify ChannelChange Command cmtSwitchDtuFreq(HOY_BOOT_FREQ / 1000); } Hoymiles.getVerboseMessageOutput()->printf("TX %s %.2f MHz --> ", - cmd->getCommandName().c_str(), getFrequencyFromChannel(_radio->getChannel())); - cmd->dumpDataPayload(Hoymiles.getVerboseMessageOutput()); + cmd.getCommandName().c_str(), getFrequencyFromChannel(_radio->getChannel())); + cmd.dumpDataPayload(Hoymiles.getVerboseMessageOutput()); - if (!_radio->write(cmd->getDataPayload(), cmd->getDataSize())) { + if (!_radio->write(cmd.getDataPayload(), cmd.getDataSize())) { Hoymiles.getMessageOutput()->println("TX SPI Timeout"); } cmtSwitchDtuFreq(_inverterTargetFrequency); _radio->startListening(); _busyFlag = true; - _rxTimeout.set(cmd->getTimeout()); + _rxTimeout.set(cmd.getTimeout()); } diff --git a/lib/Hoymiles/src/HoymilesRadio_CMT.h b/lib/Hoymiles/src/HoymilesRadio_CMT.h index 66314b3d..ee566c3e 100644 --- a/lib/Hoymiles/src/HoymilesRadio_CMT.h +++ b/lib/Hoymiles/src/HoymilesRadio_CMT.h @@ -18,13 +18,13 @@ class HoymilesRadio_CMT : public HoymilesRadio { public: - void init(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, int8_t pin_gpio2, int8_t pin_gpio3); + void init(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int8_t pin_gpio2, const int8_t pin_gpio3); void loop(); - void setPALevel(int8_t paLevel); - void setInverterTargetFrequency(uint32_t frequency); - uint32_t getInverterTargetFrequency(); + void setPALevel(const int8_t paLevel); + void setInverterTargetFrequency(const uint32_t frequency); + uint32_t getInverterTargetFrequency() const; - bool isConnected(); + bool isConnected() const; static uint32_t getMinFrequency(); static uint32_t getMaxFrequency(); @@ -36,7 +36,7 @@ private: void ARDUINO_ISR_ATTR handleInt1(); void ARDUINO_ISR_ATTR handleInt2(); - void sendEsbPacket(CommandAbstract* cmd); + void sendEsbPacket(CommandAbstract& cmd); std::unique_ptr _radio; diff --git a/lib/Hoymiles/src/HoymilesRadio_NRF.cpp b/lib/Hoymiles/src/HoymilesRadio_NRF.cpp index 8406fde4..635014d6 100644 --- a/lib/Hoymiles/src/HoymilesRadio_NRF.cpp +++ b/lib/Hoymiles/src/HoymilesRadio_NRF.cpp @@ -8,7 +8,7 @@ #include #include -void HoymilesRadio_NRF::init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ) +void HoymilesRadio_NRF::init(SPIClass* initialisedSpiBus, const uint8_t pinCE, const uint8_t pinIRQ) { _dtuSerial.u64 = 0; @@ -71,8 +71,8 @@ void HoymilesRadio_NRF::loop() // Perform package parsing only if no packages are received if (!_rxBuffer.empty()) { fragment_t f = _rxBuffer.back(); - if (checkFragmentCrc(&f)) { - std::shared_ptr inv = Hoymiles.getInverterByFragment(&f); + if (checkFragmentCrc(f)) { + std::shared_ptr inv = Hoymiles.getInverterByFragment(f); if (nullptr != inv) { // Save packet in inverter rx buffer @@ -97,7 +97,7 @@ void HoymilesRadio_NRF::loop() handleReceivedPackage(); } -void HoymilesRadio_NRF::setPALevel(rf24_pa_dbm_e paLevel) +void HoymilesRadio_NRF::setPALevel(const rf24_pa_dbm_e paLevel) { if (!_isInitialized) { return; @@ -105,7 +105,7 @@ void HoymilesRadio_NRF::setPALevel(rf24_pa_dbm_e paLevel) _radio->setPALevel(paLevel); } -void HoymilesRadio_NRF::setDtuSerial(uint64_t serial) +void HoymilesRadio_NRF::setDtuSerial(const uint64_t serial) { HoymilesRadio::setDtuSerial(serial); @@ -115,7 +115,7 @@ void HoymilesRadio_NRF::setDtuSerial(uint64_t serial) openReadingPipe(); } -bool HoymilesRadio_NRF::isConnected() +bool HoymilesRadio_NRF::isConnected() const { if (!_isInitialized) { return false; @@ -123,7 +123,7 @@ bool HoymilesRadio_NRF::isConnected() return _radio->isChipConnected(); } -bool HoymilesRadio_NRF::isPVariant() +bool HoymilesRadio_NRF::isPVariant() const { if (!_isInitialized) { return false; @@ -133,15 +133,13 @@ bool HoymilesRadio_NRF::isPVariant() void HoymilesRadio_NRF::openReadingPipe() { - serial_u s; - s = convertSerialToRadioId(_dtuSerial); + const serial_u s = convertSerialToRadioId(_dtuSerial); _radio->openReadingPipe(1, s.u64); } -void HoymilesRadio_NRF::openWritingPipe(serial_u serial) +void HoymilesRadio_NRF::openWritingPipe(const serial_u serial) { - serial_u s; - s = convertSerialToRadioId(serial); + const serial_u s = convertSerialToRadioId(serial); _radio->openWritingPipe(s.u64); } @@ -171,29 +169,29 @@ void HoymilesRadio_NRF::switchRxCh() _radio->startListening(); } -void HoymilesRadio_NRF::sendEsbPacket(CommandAbstract* cmd) +void HoymilesRadio_NRF::sendEsbPacket(CommandAbstract& cmd) { - cmd->incrementSendCount(); + cmd.incrementSendCount(); - cmd->setRouterAddress(DtuSerial().u64); + cmd.setRouterAddress(DtuSerial().u64); _radio->stopListening(); _radio->setChannel(getTxNxtChannel()); serial_u s; - s.u64 = cmd->getTargetAddress(); + s.u64 = cmd.getTargetAddress(); openWritingPipe(s); _radio->setRetries(3, 15); Hoymiles.getVerboseMessageOutput()->printf("TX %s Channel: %d --> ", - cmd->getCommandName().c_str(), _radio->getChannel()); - cmd->dumpDataPayload(Hoymiles.getVerboseMessageOutput()); - _radio->write(cmd->getDataPayload(), cmd->getDataSize()); + cmd.getCommandName().c_str(), _radio->getChannel()); + cmd.dumpDataPayload(Hoymiles.getVerboseMessageOutput()); + _radio->write(cmd.getDataPayload(), cmd.getDataSize()); _radio->setRetries(0, 0); openReadingPipe(); _radio->setChannel(getRxNxtChannel()); _radio->startListening(); _busyFlag = true; - _rxTimeout.set(cmd->getTimeout()); + _rxTimeout.set(cmd.getTimeout()); } diff --git a/lib/Hoymiles/src/HoymilesRadio_NRF.h b/lib/Hoymiles/src/HoymilesRadio_NRF.h index 8530a0e3..a6777ce5 100644 --- a/lib/Hoymiles/src/HoymilesRadio_NRF.h +++ b/lib/Hoymiles/src/HoymilesRadio_NRF.h @@ -13,14 +13,14 @@ class HoymilesRadio_NRF : public HoymilesRadio { public: - void init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ); + void init(SPIClass* initialisedSpiBus, const uint8_t pinCE, const uint8_t pinIRQ); void loop(); - void setPALevel(rf24_pa_dbm_e paLevel); + void setPALevel(const rf24_pa_dbm_e paLevel); - virtual void setDtuSerial(uint64_t serial); + virtual void setDtuSerial(const uint64_t serial); - bool isConnected(); - bool isPVariant(); + bool isConnected() const; + bool isPVariant() const; private: void ARDUINO_ISR_ATTR handleIntr(); @@ -28,9 +28,9 @@ private: uint8_t getTxNxtChannel(); void switchRxCh(); void openReadingPipe(); - void openWritingPipe(serial_u serial); + void openWritingPipe(const serial_u serial); - void sendEsbPacket(CommandAbstract* cmd); + void sendEsbPacket(CommandAbstract& cmd); std::unique_ptr _spiPtr; std::unique_ptr _radio; diff --git a/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp b/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp index 78bcd55e..95af23cf 100644 --- a/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp +++ b/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp @@ -1,13 +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 */ + +/* +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" #define CRC_SIZE 6 -ActivePowerControlCommand::ActivePowerControlCommand(uint64_t target_address, uint64_t router_address) +ActivePowerControlCommand::ActivePowerControlCommand(const uint64_t target_address, const uint64_t router_address) : DevControlCommand(target_address, router_address) { _payload[10] = 0x0b; @@ -17,21 +35,21 @@ 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; setTimeout(2000); } -String ActivePowerControlCommand::getCommandName() +String ActivePowerControlCommand::getCommandName() const { return "ActivePowerControl"; } -void ActivePowerControlCommand::setActivePowerLimit(float limit, PowerLimitControlType type) +void ActivePowerControlCommand::setActivePowerLimit(const float limit, const PowerLimitControlType type) { - uint16_t l = limit * 10; + const uint16_t l = limit * 10; // limit _payload[12] = (l >> 8) & 0xff; @@ -44,30 +62,30 @@ void ActivePowerControlCommand::setActivePowerLimit(float limit, PowerLimitContr udpateCRC(CRC_SIZE); } -bool ActivePowerControlCommand::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id) +bool ActivePowerControlCommand::handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id) { if (!DevControlCommand::handleResponse(inverter, fragment, max_fragment_id)) { return false; } if ((getType() == PowerLimitControlType::RelativNonPersistent) || (getType() == PowerLimitControlType::RelativPersistent)) { - inverter->SystemConfigPara()->setLimitPercent(getLimit()); + inverter.SystemConfigPara()->setLimitPercent(getLimit()); } else { - uint16_t max_power = inverter->DevInfo()->getMaxPower(); + const uint16_t max_power = inverter.DevInfo()->getMaxPower(); if (max_power > 0) { - inverter->SystemConfigPara()->setLimitPercent(static_cast(getLimit()) / max_power * 100); + inverter.SystemConfigPara()->setLimitPercent(static_cast(getLimit()) / max_power * 100); } else { // TODO(tbnobody): Not implemented yet because we only can publish the percentage value } } - inverter->SystemConfigPara()->setLastUpdateCommand(millis()); - inverter->SystemConfigPara()->setLastLimitCommandSuccess(CMD_OK); + inverter.SystemConfigPara()->setLastUpdateCommand(millis()); + inverter.SystemConfigPara()->setLastLimitCommandSuccess(CMD_OK); return true; } -float ActivePowerControlCommand::getLimit() +float ActivePowerControlCommand::getLimit() const { - uint16_t l = (((uint16_t)_payload[12] << 8) | _payload[13]); + const uint16_t l = (((uint16_t)_payload[12] << 8) | _payload[13]); return l / 10; } @@ -76,7 +94,7 @@ PowerLimitControlType ActivePowerControlCommand::getType() return (PowerLimitControlType)(((uint16_t)_payload[14] << 8) | _payload[15]); } -void ActivePowerControlCommand::gotTimeout(InverterAbstract* inverter) +void ActivePowerControlCommand::gotTimeout(InverterAbstract& inverter) { - inverter->SystemConfigPara()->setLastLimitCommandSuccess(CMD_NOK); + inverter.SystemConfigPara()->setLastLimitCommandSuccess(CMD_NOK); } \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/ActivePowerControlCommand.h b/lib/Hoymiles/src/commands/ActivePowerControlCommand.h index f3a359ea..b7831fb8 100644 --- a/lib/Hoymiles/src/commands/ActivePowerControlCommand.h +++ b/lib/Hoymiles/src/commands/ActivePowerControlCommand.h @@ -12,14 +12,14 @@ typedef enum { // ToDo: to be verified by field tests class ActivePowerControlCommand : public DevControlCommand { public: - explicit ActivePowerControlCommand(uint64_t target_address = 0, uint64_t router_address = 0); + explicit ActivePowerControlCommand(const uint64_t target_address = 0, const uint64_t router_address = 0); - virtual String getCommandName(); + virtual String getCommandName() const; - virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id); - virtual void gotTimeout(InverterAbstract* inverter); + virtual bool handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id); + virtual void gotTimeout(InverterAbstract& inverter); - void setActivePowerLimit(float limit, PowerLimitControlType type = RelativNonPersistent); - float getLimit(); + void setActivePowerLimit(const float limit, const PowerLimitControlType type = RelativNonPersistent); + float getLimit() const; PowerLimitControlType getType(); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/AlarmDataCommand.cpp b/lib/Hoymiles/src/commands/AlarmDataCommand.cpp index 574e0be2..143a6cd5 100644 --- a/lib/Hoymiles/src/commands/AlarmDataCommand.cpp +++ b/lib/Hoymiles/src/commands/AlarmDataCommand.cpp @@ -1,11 +1,29 @@ // 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" -AlarmDataCommand::AlarmDataCommand(uint64_t target_address, uint64_t router_address, time_t time) +AlarmDataCommand::AlarmDataCommand(const uint64_t target_address, const uint64_t router_address, const time_t time) : MultiDataCommand(target_address, router_address) { setTime(time); @@ -13,12 +31,12 @@ AlarmDataCommand::AlarmDataCommand(uint64_t target_address, uint64_t router_addr setTimeout(750); } -String AlarmDataCommand::getCommandName() +String AlarmDataCommand::getCommandName() const { return "AlarmData"; } -bool AlarmDataCommand::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id) +bool AlarmDataCommand::handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id) { // Check CRC of whole payload if (!MultiDataCommand::handleResponse(inverter, fragment, max_fragment_id)) { @@ -27,19 +45,19 @@ bool AlarmDataCommand::handleResponse(InverterAbstract* inverter, fragment_t fra // Move all fragments into target buffer uint8_t offs = 0; - inverter->EventLog()->beginAppendFragment(); - inverter->EventLog()->clearBuffer(); + inverter.EventLog()->beginAppendFragment(); + inverter.EventLog()->clearBuffer(); for (uint8_t i = 0; i < max_fragment_id; i++) { - inverter->EventLog()->appendFragment(offs, fragment[i].fragment, fragment[i].len); + inverter.EventLog()->appendFragment(offs, fragment[i].fragment, fragment[i].len); offs += (fragment[i].len); } - inverter->EventLog()->endAppendFragment(); - inverter->EventLog()->setLastAlarmRequestSuccess(CMD_OK); - inverter->EventLog()->setLastUpdate(millis()); + inverter.EventLog()->endAppendFragment(); + inverter.EventLog()->setLastAlarmRequestSuccess(CMD_OK); + inverter.EventLog()->setLastUpdate(millis()); return true; } -void AlarmDataCommand::gotTimeout(InverterAbstract* inverter) +void AlarmDataCommand::gotTimeout(InverterAbstract& inverter) { - inverter->EventLog()->setLastAlarmRequestSuccess(CMD_NOK); + inverter.EventLog()->setLastAlarmRequestSuccess(CMD_NOK); } \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/AlarmDataCommand.h b/lib/Hoymiles/src/commands/AlarmDataCommand.h index 1c34a826..abdfc5f8 100644 --- a/lib/Hoymiles/src/commands/AlarmDataCommand.h +++ b/lib/Hoymiles/src/commands/AlarmDataCommand.h @@ -5,10 +5,10 @@ class AlarmDataCommand : public MultiDataCommand { public: - explicit AlarmDataCommand(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0); + explicit AlarmDataCommand(const uint64_t target_address = 0, const uint64_t router_address = 0, const time_t time = 0); - virtual String getCommandName(); + virtual String getCommandName() const; - virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id); - virtual void gotTimeout(InverterAbstract* inverter); + virtual bool handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id); + virtual void gotTimeout(InverterAbstract& inverter); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/ChannelChangeCommand.cpp b/lib/Hoymiles/src/commands/ChannelChangeCommand.cpp index 139bbea3..1001790d 100644 --- a/lib/Hoymiles/src/commands/ChannelChangeCommand.cpp +++ b/lib/Hoymiles/src/commands/ChannelChangeCommand.cpp @@ -2,9 +2,23 @@ /* * 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) +ChannelChangeCommand::ChannelChangeCommand(const uint64_t target_address, const uint64_t router_address, const uint8_t channel) : CommandAbstract(target_address, router_address) { _payload[0] = 0x56; @@ -18,22 +32,22 @@ ChannelChangeCommand::ChannelChangeCommand(uint64_t target_address, uint64_t rou setTimeout(10); } -String ChannelChangeCommand::getCommandName() +String ChannelChangeCommand::getCommandName() const { return "ChannelChangeCommand"; } -void ChannelChangeCommand::setChannel(uint8_t channel) +void ChannelChangeCommand::setChannel(const uint8_t channel) { _payload[12] = channel; } -uint8_t ChannelChangeCommand::getChannel() +uint8_t ChannelChangeCommand::getChannel() const { return _payload[12]; } -bool ChannelChangeCommand::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id) +bool ChannelChangeCommand::handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id) { return true; } diff --git a/lib/Hoymiles/src/commands/ChannelChangeCommand.h b/lib/Hoymiles/src/commands/ChannelChangeCommand.h index b646217c..a0b38cab 100644 --- a/lib/Hoymiles/src/commands/ChannelChangeCommand.h +++ b/lib/Hoymiles/src/commands/ChannelChangeCommand.h @@ -5,14 +5,14 @@ class ChannelChangeCommand : public CommandAbstract { public: - explicit ChannelChangeCommand(uint64_t target_address = 0, uint64_t router_address = 0, uint8_t channel = 0); + explicit ChannelChangeCommand(const uint64_t target_address = 0, const uint64_t router_address = 0, const uint8_t channel = 0); - virtual String getCommandName(); + virtual String getCommandName() const; - void setChannel(uint8_t channel); - uint8_t getChannel(); + void setChannel(const uint8_t channel); + uint8_t getChannel() const; - virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id); + virtual bool handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id); virtual uint8_t getMaxResendCount(); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/CommandAbstract.cpp b/lib/Hoymiles/src/commands/CommandAbstract.cpp index 78d8d07d..dafe2b17 100644 --- a/lib/Hoymiles/src/commands/CommandAbstract.cpp +++ b/lib/Hoymiles/src/commands/CommandAbstract.cpp @@ -1,12 +1,36 @@ // 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 -CommandAbstract::CommandAbstract(uint64_t target_address, uint64_t router_address) +CommandAbstract::CommandAbstract(const uint64_t target_address, const uint64_t router_address) { memset(_payload, 0, RF_LEN); _payload_size = 0; @@ -32,48 +56,48 @@ void CommandAbstract::dumpDataPayload(Print* stream) stream->println(""); } -uint8_t CommandAbstract::getDataSize() +uint8_t CommandAbstract::getDataSize() const { return _payload_size + 1; // Original payload plus crc8 } -void CommandAbstract::setTargetAddress(uint64_t address) +void CommandAbstract::setTargetAddress(const uint64_t address) { convertSerialToPacketId(&_payload[1], address); _targetAddress = address; } -uint64_t CommandAbstract::getTargetAddress() +uint64_t CommandAbstract::getTargetAddress() const { return _targetAddress; } -void CommandAbstract::setRouterAddress(uint64_t address) +void CommandAbstract::setRouterAddress(const uint64_t address) { convertSerialToPacketId(&_payload[5], address); _routerAddress = address; } -uint64_t CommandAbstract::getRouterAddress() +uint64_t CommandAbstract::getRouterAddress() const { return _routerAddress; } -void CommandAbstract::setTimeout(uint32_t timeout) +void CommandAbstract::setTimeout(const uint32_t timeout) { _timeout = timeout; } -uint32_t CommandAbstract::getTimeout() +uint32_t CommandAbstract::getTimeout() const { return _timeout; } -void CommandAbstract::setSendCount(uint8_t count) +void CommandAbstract::setSendCount(const uint8_t count) { _sendCount = count; } -uint8_t CommandAbstract::getSendCount() +uint8_t CommandAbstract::getSendCount() const { return _sendCount; } @@ -83,12 +107,12 @@ uint8_t CommandAbstract::incrementSendCount() return _sendCount++; } -CommandAbstract* CommandAbstract::getRequestFrameCommand(uint8_t frame_no) +CommandAbstract* CommandAbstract::getRequestFrameCommand(const uint8_t frame_no) { return nullptr; } -void CommandAbstract::convertSerialToPacketId(uint8_t buffer[], uint64_t serial) +void CommandAbstract::convertSerialToPacketId(uint8_t buffer[], const uint64_t serial) { serial_u s; s.u64 = serial; @@ -98,16 +122,16 @@ void CommandAbstract::convertSerialToPacketId(uint8_t buffer[], uint64_t serial) buffer[0] = s.b[3]; } -void CommandAbstract::gotTimeout(InverterAbstract* inverter) +void CommandAbstract::gotTimeout(InverterAbstract& inverter) { } -uint8_t CommandAbstract::getMaxResendCount() +uint8_t CommandAbstract::getMaxResendCount() const { return MAX_RESEND_COUNT; } -uint8_t CommandAbstract::getMaxRetransmitCount() +uint8_t CommandAbstract::getMaxRetransmitCount() const { return MAX_RETRANSMIT_COUNT; } diff --git a/lib/Hoymiles/src/commands/CommandAbstract.h b/lib/Hoymiles/src/commands/CommandAbstract.h index e6abc686..677fc0d1 100644 --- a/lib/Hoymiles/src/commands/CommandAbstract.h +++ b/lib/Hoymiles/src/commands/CommandAbstract.h @@ -13,39 +13,39 @@ class InverterAbstract; class CommandAbstract { public: - explicit CommandAbstract(uint64_t target_address = 0, uint64_t router_address = 0); + explicit CommandAbstract(const uint64_t target_address = 0, const uint64_t router_address = 0); virtual ~CommandAbstract() {}; const uint8_t* getDataPayload(); void dumpDataPayload(Print* stream); - uint8_t getDataSize(); + uint8_t getDataSize() const; - void setTargetAddress(uint64_t address); - uint64_t getTargetAddress(); + void setTargetAddress(const uint64_t address); + uint64_t getTargetAddress() const; - void setRouterAddress(uint64_t address); - uint64_t getRouterAddress(); + void setRouterAddress(const uint64_t address); + uint64_t getRouterAddress() const; - void setTimeout(uint32_t timeout); - uint32_t getTimeout(); + void setTimeout(const uint32_t timeout); + uint32_t getTimeout() const; - virtual String getCommandName() = 0; + virtual String getCommandName() const = 0; - void setSendCount(uint8_t count); - uint8_t getSendCount(); + void setSendCount(const uint8_t count); + uint8_t getSendCount() const; uint8_t incrementSendCount(); - virtual CommandAbstract* getRequestFrameCommand(uint8_t frame_no); + virtual CommandAbstract* getRequestFrameCommand(const uint8_t frame_no); - virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id) = 0; - virtual void gotTimeout(InverterAbstract* inverter); + virtual bool handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id) = 0; + virtual void gotTimeout(InverterAbstract& inverter); // Sets the amount how often the specific command is resent if all fragments where missing - virtual uint8_t getMaxResendCount(); + virtual uint8_t getMaxResendCount() const; // Sets the amount how often a missing fragment is re-requested if it was not available - virtual uint8_t getMaxRetransmitCount(); + virtual uint8_t getMaxRetransmitCount() const; protected: uint8_t _payload[RF_LEN]; @@ -57,5 +57,5 @@ protected: uint64_t _routerAddress; private: - static void convertSerialToPacketId(uint8_t buffer[], uint64_t serial); + static void convertSerialToPacketId(uint8_t buffer[], const uint64_t serial); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/DevControlCommand.cpp b/lib/Hoymiles/src/commands/DevControlCommand.cpp index fce935b8..a5e7d2b6 100644 --- a/lib/Hoymiles/src/commands/DevControlCommand.cpp +++ b/lib/Hoymiles/src/commands/DevControlCommand.cpp @@ -1,12 +1,29 @@ // 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" -DevControlCommand::DevControlCommand(uint64_t target_address, uint64_t router_address) +DevControlCommand::DevControlCommand(const uint64_t target_address, const uint64_t router_address) : CommandAbstract(target_address, router_address) { _payload[0] = 0x51; @@ -15,14 +32,14 @@ DevControlCommand::DevControlCommand(uint64_t target_address, uint64_t router_ad setTimeout(1000); } -void DevControlCommand::udpateCRC(uint8_t len) +void DevControlCommand::udpateCRC(const uint8_t len) { - uint16_t crc = crc16(&_payload[10], len); + const uint16_t crc = crc16(&_payload[10], len); _payload[10 + len] = (uint8_t)(crc >> 8); _payload[10 + len + 1] = (uint8_t)(crc); } -bool DevControlCommand::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id) +bool DevControlCommand::handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id) { for (uint8_t i = 0; i < max_fragment_id; i++) { if (fragment[i].mainCmd != (_payload[0] | 0x80)) { diff --git a/lib/Hoymiles/src/commands/DevControlCommand.h b/lib/Hoymiles/src/commands/DevControlCommand.h index f4d0a049..c24bc60b 100644 --- a/lib/Hoymiles/src/commands/DevControlCommand.h +++ b/lib/Hoymiles/src/commands/DevControlCommand.h @@ -5,10 +5,10 @@ class DevControlCommand : public CommandAbstract { public: - explicit DevControlCommand(uint64_t target_address = 0, uint64_t router_address = 0); + explicit DevControlCommand(const uint64_t target_address = 0, const uint64_t router_address = 0); - virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id); + virtual bool handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id); protected: - void udpateCRC(uint8_t len); + void udpateCRC(const uint8_t len); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/DevInfoAllCommand.cpp b/lib/Hoymiles/src/commands/DevInfoAllCommand.cpp index b175822e..c7bd8027 100644 --- a/lib/Hoymiles/src/commands/DevInfoAllCommand.cpp +++ b/lib/Hoymiles/src/commands/DevInfoAllCommand.cpp @@ -1,11 +1,27 @@ // 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" -DevInfoAllCommand::DevInfoAllCommand(uint64_t target_address, uint64_t router_address, time_t time) +DevInfoAllCommand::DevInfoAllCommand(const uint64_t target_address, const uint64_t router_address, const time_t time) : MultiDataCommand(target_address, router_address) { setTime(time); @@ -13,12 +29,12 @@ DevInfoAllCommand::DevInfoAllCommand(uint64_t target_address, uint64_t router_ad setTimeout(200); } -String DevInfoAllCommand::getCommandName() +String DevInfoAllCommand::getCommandName() const { return "DevInfoAll"; } -bool DevInfoAllCommand::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id) +bool DevInfoAllCommand::handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id) { // Check CRC of whole payload if (!MultiDataCommand::handleResponse(inverter, fragment, max_fragment_id)) { @@ -27,13 +43,13 @@ bool DevInfoAllCommand::handleResponse(InverterAbstract* inverter, fragment_t fr // Move all fragments into target buffer uint8_t offs = 0; - inverter->DevInfo()->beginAppendFragment(); - inverter->DevInfo()->clearBufferAll(); + inverter.DevInfo()->beginAppendFragment(); + inverter.DevInfo()->clearBufferAll(); for (uint8_t i = 0; i < max_fragment_id; i++) { - inverter->DevInfo()->appendFragmentAll(offs, fragment[i].fragment, fragment[i].len); + inverter.DevInfo()->appendFragmentAll(offs, fragment[i].fragment, fragment[i].len); offs += (fragment[i].len); } - inverter->DevInfo()->endAppendFragment(); - inverter->DevInfo()->setLastUpdateAll(millis()); + inverter.DevInfo()->endAppendFragment(); + inverter.DevInfo()->setLastUpdateAll(millis()); return true; } \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/DevInfoAllCommand.h b/lib/Hoymiles/src/commands/DevInfoAllCommand.h index 16556384..3facffa7 100644 --- a/lib/Hoymiles/src/commands/DevInfoAllCommand.h +++ b/lib/Hoymiles/src/commands/DevInfoAllCommand.h @@ -5,9 +5,9 @@ class DevInfoAllCommand : public MultiDataCommand { public: - explicit DevInfoAllCommand(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0); + explicit DevInfoAllCommand(const uint64_t target_address = 0, const uint64_t router_address = 0, const time_t time = 0); - virtual String getCommandName(); + virtual String getCommandName() const; - virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id); + virtual bool handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/DevInfoSimpleCommand.cpp b/lib/Hoymiles/src/commands/DevInfoSimpleCommand.cpp index 09d5a467..2afaae4b 100644 --- a/lib/Hoymiles/src/commands/DevInfoSimpleCommand.cpp +++ b/lib/Hoymiles/src/commands/DevInfoSimpleCommand.cpp @@ -1,11 +1,27 @@ // 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" -DevInfoSimpleCommand::DevInfoSimpleCommand(uint64_t target_address, uint64_t router_address, time_t time) +DevInfoSimpleCommand::DevInfoSimpleCommand(const uint64_t target_address, const uint64_t router_address, const time_t time) : MultiDataCommand(target_address, router_address) { setTime(time); @@ -13,12 +29,12 @@ DevInfoSimpleCommand::DevInfoSimpleCommand(uint64_t target_address, uint64_t rou setTimeout(200); } -String DevInfoSimpleCommand::getCommandName() +String DevInfoSimpleCommand::getCommandName() const { return "DevInfoSimple"; } -bool DevInfoSimpleCommand::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id) +bool DevInfoSimpleCommand::handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id) { // Check CRC of whole payload if (!MultiDataCommand::handleResponse(inverter, fragment, max_fragment_id)) { @@ -27,13 +43,13 @@ bool DevInfoSimpleCommand::handleResponse(InverterAbstract* inverter, fragment_t // Move all fragments into target buffer uint8_t offs = 0; - inverter->DevInfo()->beginAppendFragment(); - inverter->DevInfo()->clearBufferSimple(); + inverter.DevInfo()->beginAppendFragment(); + inverter.DevInfo()->clearBufferSimple(); for (uint8_t i = 0; i < max_fragment_id; i++) { - inverter->DevInfo()->appendFragmentSimple(offs, fragment[i].fragment, fragment[i].len); + inverter.DevInfo()->appendFragmentSimple(offs, fragment[i].fragment, fragment[i].len); offs += (fragment[i].len); } - inverter->DevInfo()->endAppendFragment(); - inverter->DevInfo()->setLastUpdateSimple(millis()); + inverter.DevInfo()->endAppendFragment(); + inverter.DevInfo()->setLastUpdateSimple(millis()); return true; } \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/DevInfoSimpleCommand.h b/lib/Hoymiles/src/commands/DevInfoSimpleCommand.h index 99b7f503..66a7301a 100644 --- a/lib/Hoymiles/src/commands/DevInfoSimpleCommand.h +++ b/lib/Hoymiles/src/commands/DevInfoSimpleCommand.h @@ -5,9 +5,9 @@ class DevInfoSimpleCommand : public MultiDataCommand { public: - explicit DevInfoSimpleCommand(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0); + explicit DevInfoSimpleCommand(const uint64_t target_address = 0, const uint64_t router_address = 0, const time_t time = 0); - virtual String getCommandName(); + virtual String getCommandName() const; - virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id); + virtual bool handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/GridOnProFilePara.cpp b/lib/Hoymiles/src/commands/GridOnProFilePara.cpp index e9171672..c98c7e5a 100644 --- a/lib/Hoymiles/src/commands/GridOnProFilePara.cpp +++ b/lib/Hoymiles/src/commands/GridOnProFilePara.cpp @@ -1,12 +1,28 @@ // 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" -GridOnProFilePara::GridOnProFilePara(uint64_t target_address, uint64_t router_address, time_t time) +GridOnProFilePara::GridOnProFilePara(const uint64_t target_address, const uint64_t router_address, const time_t time) : MultiDataCommand(target_address, router_address) { setTime(time); @@ -14,12 +30,12 @@ GridOnProFilePara::GridOnProFilePara(uint64_t target_address, uint64_t router_ad setTimeout(500); } -String GridOnProFilePara::getCommandName() +String GridOnProFilePara::getCommandName() const { return "GridOnProFilePara"; } -bool GridOnProFilePara::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id) +bool GridOnProFilePara::handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id) { // Check CRC of whole payload if (!MultiDataCommand::handleResponse(inverter, fragment, max_fragment_id)) { @@ -28,13 +44,13 @@ bool GridOnProFilePara::handleResponse(InverterAbstract* inverter, fragment_t fr // Move all fragments into target buffer uint8_t offs = 0; - inverter->GridProfile()->beginAppendFragment(); - inverter->GridProfile()->clearBuffer(); + inverter.GridProfile()->beginAppendFragment(); + inverter.GridProfile()->clearBuffer(); for (uint8_t i = 0; i < max_fragment_id; i++) { - inverter->GridProfile()->appendFragment(offs, fragment[i].fragment, fragment[i].len); + inverter.GridProfile()->appendFragment(offs, fragment[i].fragment, fragment[i].len); offs += (fragment[i].len); } - inverter->GridProfile()->endAppendFragment(); - inverter->GridProfile()->setLastUpdate(millis()); + inverter.GridProfile()->endAppendFragment(); + inverter.GridProfile()->setLastUpdate(millis()); return true; } \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/GridOnProFilePara.h b/lib/Hoymiles/src/commands/GridOnProFilePara.h index 41ee57ec..382ebcbb 100644 --- a/lib/Hoymiles/src/commands/GridOnProFilePara.h +++ b/lib/Hoymiles/src/commands/GridOnProFilePara.h @@ -5,9 +5,9 @@ class GridOnProFilePara : public MultiDataCommand { public: - explicit GridOnProFilePara(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0); + explicit GridOnProFilePara(const uint64_t target_address = 0, const uint64_t router_address = 0, const time_t time = 0); - virtual String getCommandName(); + virtual String getCommandName() const; - virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id); + virtual bool handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/MultiDataCommand.cpp b/lib/Hoymiles/src/commands/MultiDataCommand.cpp index 39a0d4c6..bbd32091 100644 --- a/lib/Hoymiles/src/commands/MultiDataCommand.cpp +++ b/lib/Hoymiles/src/commands/MultiDataCommand.cpp @@ -1,11 +1,34 @@ // 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 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" -MultiDataCommand::MultiDataCommand(uint64_t target_address, uint64_t router_address, uint8_t data_type, time_t time) +MultiDataCommand::MultiDataCommand(const uint64_t target_address, const uint64_t router_address, const uint8_t data_type, const time_t time) : CommandAbstract(target_address, router_address) { _payload[0] = 0x15; @@ -27,17 +50,17 @@ MultiDataCommand::MultiDataCommand(uint64_t target_address, uint64_t router_addr _payload_size = 26; } -void MultiDataCommand::setDataType(uint8_t data_type) +void MultiDataCommand::setDataType(const uint8_t data_type) { _payload[10] = data_type; udpateCRC(); } -uint8_t MultiDataCommand::getDataType() +uint8_t MultiDataCommand::getDataType() const { return _payload[10]; } -void MultiDataCommand::setTime(time_t time) +void MultiDataCommand::setTime(const time_t time) { _payload[12] = (uint8_t)(time >> 24); _payload[13] = (uint8_t)(time >> 16); @@ -46,7 +69,7 @@ void MultiDataCommand::setTime(time_t time) udpateCRC(); } -time_t MultiDataCommand::getTime() +time_t MultiDataCommand::getTime() const { return (time_t)(_payload[12] << 24) | (time_t)(_payload[13] << 16) @@ -54,7 +77,7 @@ time_t MultiDataCommand::getTime() | (time_t)(_payload[15]); } -CommandAbstract* MultiDataCommand::getRequestFrameCommand(uint8_t frame_no) +CommandAbstract* MultiDataCommand::getRequestFrameCommand(const uint8_t frame_no) { _cmdRequestFrame.setTargetAddress(getTargetAddress()); _cmdRequestFrame.setFrameNo(frame_no); @@ -62,7 +85,7 @@ CommandAbstract* MultiDataCommand::getRequestFrameCommand(uint8_t frame_no) return &_cmdRequestFrame; } -bool MultiDataCommand::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id) +bool MultiDataCommand::handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id) { // All fragments are available --> Check CRC uint16_t crc = 0xffff, crcRcv = 0; @@ -88,12 +111,12 @@ 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); } -uint8_t MultiDataCommand::getTotalFragmentSize(fragment_t fragment[], uint8_t max_fragment_id) +uint8_t MultiDataCommand::getTotalFragmentSize(const fragment_t fragment[], const uint8_t max_fragment_id) { uint8_t fragmentSize = 0; for (uint8_t i = 0; i < max_fragment_id; i++) { diff --git a/lib/Hoymiles/src/commands/MultiDataCommand.h b/lib/Hoymiles/src/commands/MultiDataCommand.h index 4d2adfde..82107474 100644 --- a/lib/Hoymiles/src/commands/MultiDataCommand.h +++ b/lib/Hoymiles/src/commands/MultiDataCommand.h @@ -7,20 +7,20 @@ class MultiDataCommand : public CommandAbstract { public: - explicit MultiDataCommand(uint64_t target_address = 0, uint64_t router_address = 0, uint8_t data_type = 0, time_t time = 0); + explicit MultiDataCommand(const uint64_t target_address = 0, const uint64_t router_address = 0, const uint8_t data_type = 0, const time_t time = 0); - void setTime(time_t time); - time_t getTime(); + void setTime(const time_t time); + time_t getTime() const; - CommandAbstract* getRequestFrameCommand(uint8_t frame_no); + CommandAbstract* getRequestFrameCommand(const uint8_t frame_no); - virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id); + virtual bool handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id); protected: - void setDataType(uint8_t data_type); - uint8_t getDataType(); + void setDataType(const uint8_t data_type); + uint8_t getDataType() const; void udpateCRC(); - static uint8_t getTotalFragmentSize(fragment_t fragment[], uint8_t max_fragment_id); + static uint8_t getTotalFragmentSize(const fragment_t fragment[], const uint8_t max_fragment_id); RequestFrameCommand _cmdRequestFrame; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/ParaSetCommand.cpp b/lib/Hoymiles/src/commands/ParaSetCommand.cpp index 4a48cbea..a3374945 100644 --- a/lib/Hoymiles/src/commands/ParaSetCommand.cpp +++ b/lib/Hoymiles/src/commands/ParaSetCommand.cpp @@ -4,7 +4,7 @@ */ #include "ParaSetCommand.h" -ParaSetCommand::ParaSetCommand(uint64_t target_address, uint64_t router_address) +ParaSetCommand::ParaSetCommand(const uint64_t target_address, const uint64_t router_address) : CommandAbstract(target_address, router_address) { _payload[0] = 0x52; diff --git a/lib/Hoymiles/src/commands/ParaSetCommand.h b/lib/Hoymiles/src/commands/ParaSetCommand.h index 9ca4e8a9..424d0e37 100644 --- a/lib/Hoymiles/src/commands/ParaSetCommand.h +++ b/lib/Hoymiles/src/commands/ParaSetCommand.h @@ -5,5 +5,5 @@ class ParaSetCommand : public CommandAbstract { public: - explicit ParaSetCommand(uint64_t target_address = 0, uint64_t router_address = 0); + explicit ParaSetCommand(const uint64_t target_address = 0, const uint64_t router_address = 0); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/PowerControlCommand.cpp b/lib/Hoymiles/src/commands/PowerControlCommand.cpp index 522ad5f2..fbf12db8 100644 --- a/lib/Hoymiles/src/commands/PowerControlCommand.cpp +++ b/lib/Hoymiles/src/commands/PowerControlCommand.cpp @@ -1,13 +1,32 @@ // 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" #define CRC_SIZE 2 -PowerControlCommand::PowerControlCommand(uint64_t target_address, uint64_t router_address) +PowerControlCommand::PowerControlCommand(const uint64_t target_address, const uint64_t router_address) : DevControlCommand(target_address, router_address) { _payload[10] = 0x00; // TurnOn @@ -20,28 +39,28 @@ PowerControlCommand::PowerControlCommand(uint64_t target_address, uint64_t route setTimeout(2000); } -String PowerControlCommand::getCommandName() +String PowerControlCommand::getCommandName() const { return "PowerControl"; } -bool PowerControlCommand::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id) +bool PowerControlCommand::handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id) { if (!DevControlCommand::handleResponse(inverter, fragment, max_fragment_id)) { return false; } - inverter->PowerCommand()->setLastUpdateCommand(millis()); - inverter->PowerCommand()->setLastPowerCommandSuccess(CMD_OK); + inverter.PowerCommand()->setLastUpdateCommand(millis()); + inverter.PowerCommand()->setLastPowerCommandSuccess(CMD_OK); return true; } -void PowerControlCommand::gotTimeout(InverterAbstract* inverter) +void PowerControlCommand::gotTimeout(InverterAbstract& inverter) { - inverter->PowerCommand()->setLastPowerCommandSuccess(CMD_NOK); + inverter.PowerCommand()->setLastPowerCommandSuccess(CMD_NOK); } -void PowerControlCommand::setPowerOn(bool state) +void PowerControlCommand::setPowerOn(const bool state) { if (state) { _payload[10] = 0x00; // TurnOn diff --git a/lib/Hoymiles/src/commands/PowerControlCommand.h b/lib/Hoymiles/src/commands/PowerControlCommand.h index 376d201e..8b9f11ac 100644 --- a/lib/Hoymiles/src/commands/PowerControlCommand.h +++ b/lib/Hoymiles/src/commands/PowerControlCommand.h @@ -5,13 +5,13 @@ class PowerControlCommand : public DevControlCommand { public: - explicit PowerControlCommand(uint64_t target_address = 0, uint64_t router_address = 0); + explicit PowerControlCommand(const uint64_t target_address = 0, const uint64_t router_address = 0); - virtual String getCommandName(); + virtual String getCommandName() const; - virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id); - virtual void gotTimeout(InverterAbstract* inverter); + virtual bool handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id); + virtual void gotTimeout(InverterAbstract& inverter); - void setPowerOn(bool state); + void setPowerOn(const bool state); void setRestart(); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp index 3f0aed36..5f04c948 100644 --- a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp +++ b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp @@ -1,12 +1,28 @@ // 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 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" -RealTimeRunDataCommand::RealTimeRunDataCommand(uint64_t target_address, uint64_t router_address, time_t time) +RealTimeRunDataCommand::RealTimeRunDataCommand(const uint64_t target_address, const uint64_t router_address, const time_t time) : MultiDataCommand(target_address, router_address) { setTime(time); @@ -14,12 +30,12 @@ RealTimeRunDataCommand::RealTimeRunDataCommand(uint64_t target_address, uint64_t setTimeout(500); } -String RealTimeRunDataCommand::getCommandName() +String RealTimeRunDataCommand::getCommandName() const { return "RealTimeRunData"; } -bool RealTimeRunDataCommand::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id) +bool RealTimeRunDataCommand::handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id) { // Check CRC of whole payload if (!MultiDataCommand::handleResponse(inverter, fragment, max_fragment_id)) { @@ -29,8 +45,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); @@ -40,19 +56,19 @@ bool RealTimeRunDataCommand::handleResponse(InverterAbstract* inverter, fragment // Move all fragments into target buffer uint8_t offs = 0; - inverter->Statistics()->beginAppendFragment(); - inverter->Statistics()->clearBuffer(); + inverter.Statistics()->beginAppendFragment(); + inverter.Statistics()->clearBuffer(); for (uint8_t i = 0; i < max_fragment_id; i++) { - inverter->Statistics()->appendFragment(offs, fragment[i].fragment, fragment[i].len); + inverter.Statistics()->appendFragment(offs, fragment[i].fragment, fragment[i].len); offs += (fragment[i].len); } - inverter->Statistics()->endAppendFragment(); - inverter->Statistics()->resetRxFailureCount(); - inverter->Statistics()->setLastUpdate(millis()); + inverter.Statistics()->endAppendFragment(); + inverter.Statistics()->resetRxFailureCount(); + inverter.Statistics()->setLastUpdate(millis()); return true; } -void RealTimeRunDataCommand::gotTimeout(InverterAbstract* inverter) +void RealTimeRunDataCommand::gotTimeout(InverterAbstract& inverter) { - inverter->Statistics()->incrementRxFailureCount(); + inverter.Statistics()->incrementRxFailureCount(); } \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.h b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.h index 8cb5be39..7a0eeec1 100644 --- a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.h +++ b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.h @@ -5,10 +5,10 @@ class RealTimeRunDataCommand : public MultiDataCommand { public: - explicit RealTimeRunDataCommand(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0); + explicit RealTimeRunDataCommand(const uint64_t target_address = 0, const uint64_t router_address = 0, const time_t time = 0); - virtual String getCommandName(); + virtual String getCommandName() const; - virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id); - virtual void gotTimeout(InverterAbstract* inverter); + virtual bool handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id); + virtual void gotTimeout(InverterAbstract& inverter); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/RequestFrameCommand.cpp b/lib/Hoymiles/src/commands/RequestFrameCommand.cpp index e2bfb766..68c4977f 100644 --- a/lib/Hoymiles/src/commands/RequestFrameCommand.cpp +++ b/lib/Hoymiles/src/commands/RequestFrameCommand.cpp @@ -1,10 +1,28 @@ // 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) +RequestFrameCommand::RequestFrameCommand(const uint64_t target_address, const uint64_t router_address, uint8_t frame_no) : SingleDataCommand(target_address, router_address) { if (frame_no > 127) { @@ -14,22 +32,22 @@ RequestFrameCommand::RequestFrameCommand(uint64_t target_address, uint64_t route _payload_size = 10; } -String RequestFrameCommand::getCommandName() +String RequestFrameCommand::getCommandName() const { return "RequestFrame"; } -void RequestFrameCommand::setFrameNo(uint8_t frame_no) +void RequestFrameCommand::setFrameNo(const uint8_t frame_no) { _payload[9] = frame_no | 0x80; } -uint8_t RequestFrameCommand::getFrameNo() +uint8_t RequestFrameCommand::getFrameNo() const { return _payload[9] & (~0x80); } -bool RequestFrameCommand::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id) +bool RequestFrameCommand::handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id) { return true; } \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/RequestFrameCommand.h b/lib/Hoymiles/src/commands/RequestFrameCommand.h index 5d5e9da1..92663b70 100644 --- a/lib/Hoymiles/src/commands/RequestFrameCommand.h +++ b/lib/Hoymiles/src/commands/RequestFrameCommand.h @@ -5,12 +5,12 @@ class RequestFrameCommand : public SingleDataCommand { public: - explicit RequestFrameCommand(uint64_t target_address = 0, uint64_t router_address = 0, uint8_t frame_no = 0); + explicit RequestFrameCommand(const uint64_t target_address = 0, const uint64_t router_address = 0, uint8_t frame_no = 0); - virtual String getCommandName(); + virtual String getCommandName() const; - void setFrameNo(uint8_t frame_no); - uint8_t getFrameNo(); + void setFrameNo(const uint8_t frame_no); + uint8_t getFrameNo() const; - virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id); + virtual bool handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/SingleDataCommand.cpp b/lib/Hoymiles/src/commands/SingleDataCommand.cpp index 636ee87a..4f775146 100644 --- a/lib/Hoymiles/src/commands/SingleDataCommand.cpp +++ b/lib/Hoymiles/src/commands/SingleDataCommand.cpp @@ -1,10 +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 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) +SingleDataCommand::SingleDataCommand(const uint64_t target_address, const uint64_t router_address) : CommandAbstract(target_address, router_address) { _payload[0] = 0x15; diff --git a/lib/Hoymiles/src/commands/SingleDataCommand.h b/lib/Hoymiles/src/commands/SingleDataCommand.h index c891bda9..d0515169 100644 --- a/lib/Hoymiles/src/commands/SingleDataCommand.h +++ b/lib/Hoymiles/src/commands/SingleDataCommand.h @@ -5,5 +5,5 @@ class SingleDataCommand : public CommandAbstract { public: - explicit SingleDataCommand(uint64_t target_address = 0, uint64_t router_address = 0); + explicit SingleDataCommand(const uint64_t target_address = 0, const uint64_t router_address = 0); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp b/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp index 5e238a59..0c8e7ded 100644 --- a/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp +++ b/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp @@ -1,12 +1,28 @@ // 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 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" -SystemConfigParaCommand::SystemConfigParaCommand(uint64_t target_address, uint64_t router_address, time_t time) +SystemConfigParaCommand::SystemConfigParaCommand(const uint64_t target_address, const uint64_t router_address, const time_t time) : MultiDataCommand(target_address, router_address) { setTime(time); @@ -14,12 +30,12 @@ SystemConfigParaCommand::SystemConfigParaCommand(uint64_t target_address, uint64 setTimeout(200); } -String SystemConfigParaCommand::getCommandName() +String SystemConfigParaCommand::getCommandName() const { return "SystemConfigPara"; } -bool SystemConfigParaCommand::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id) +bool SystemConfigParaCommand::handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id) { // Check CRC of whole payload if (!MultiDataCommand::handleResponse(inverter, fragment, max_fragment_id)) { @@ -29,8 +45,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); @@ -40,19 +56,19 @@ bool SystemConfigParaCommand::handleResponse(InverterAbstract* inverter, fragmen // Move all fragments into target buffer uint8_t offs = 0; - inverter->SystemConfigPara()->beginAppendFragment(); - inverter->SystemConfigPara()->clearBuffer(); + inverter.SystemConfigPara()->beginAppendFragment(); + inverter.SystemConfigPara()->clearBuffer(); for (uint8_t i = 0; i < max_fragment_id; i++) { - inverter->SystemConfigPara()->appendFragment(offs, fragment[i].fragment, fragment[i].len); + inverter.SystemConfigPara()->appendFragment(offs, fragment[i].fragment, fragment[i].len); offs += (fragment[i].len); } - inverter->SystemConfigPara()->endAppendFragment(); - inverter->SystemConfigPara()->setLastUpdateRequest(millis()); - inverter->SystemConfigPara()->setLastLimitRequestSuccess(CMD_OK); + inverter.SystemConfigPara()->endAppendFragment(); + inverter.SystemConfigPara()->setLastUpdateRequest(millis()); + inverter.SystemConfigPara()->setLastLimitRequestSuccess(CMD_OK); return true; } -void SystemConfigParaCommand::gotTimeout(InverterAbstract* inverter) +void SystemConfigParaCommand::gotTimeout(InverterAbstract& inverter) { - inverter->SystemConfigPara()->setLastLimitRequestSuccess(CMD_NOK); + inverter.SystemConfigPara()->setLastLimitRequestSuccess(CMD_NOK); } \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/SystemConfigParaCommand.h b/lib/Hoymiles/src/commands/SystemConfigParaCommand.h index ef266fff..e2480a97 100644 --- a/lib/Hoymiles/src/commands/SystemConfigParaCommand.h +++ b/lib/Hoymiles/src/commands/SystemConfigParaCommand.h @@ -5,10 +5,10 @@ class SystemConfigParaCommand : public MultiDataCommand { public: - explicit SystemConfigParaCommand(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0); + explicit SystemConfigParaCommand(const uint64_t target_address = 0, const uint64_t router_address = 0, const time_t time = 0); - virtual String getCommandName(); + virtual String getCommandName() const; - virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id); - virtual void gotTimeout(InverterAbstract* inverter); + virtual bool handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id); + virtual void gotTimeout(InverterAbstract& inverter); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/crc.cpp b/lib/Hoymiles/src/crc.cpp index 53197132..582f4ed6 100644 --- a/lib/Hoymiles/src/crc.cpp +++ b/lib/Hoymiles/src/crc.cpp @@ -4,7 +4,7 @@ */ #include "crc.h" -uint8_t crc8(const uint8_t buf[], uint8_t len) +uint8_t crc8(const uint8_t buf[], const uint8_t len) { uint8_t crc = CRC8_INIT; for (uint8_t i = 0; i < len; i++) { @@ -16,7 +16,7 @@ uint8_t crc8(const uint8_t buf[], uint8_t len) return crc; } -uint16_t crc16(const uint8_t buf[], uint8_t len, uint16_t start) +uint16_t crc16(const uint8_t buf[], const uint8_t len, const uint16_t start) { uint16_t crc = start; uint8_t shift = 0; @@ -33,7 +33,7 @@ uint16_t crc16(const uint8_t buf[], uint8_t len, uint16_t start) return crc; } -uint16_t crc16nrf24(const uint8_t buf[], uint16_t lenBits, uint16_t startBit, uint16_t crcIn) +uint16_t crc16nrf24(const uint8_t buf[], const uint16_t lenBits, const uint16_t startBit, const uint16_t crcIn) { uint16_t crc = crcIn; uint8_t idx, val = buf[(startBit >> 3)]; diff --git a/lib/Hoymiles/src/crc.h b/lib/Hoymiles/src/crc.h index a1b01feb..e0fad889 100644 --- a/lib/Hoymiles/src/crc.h +++ b/lib/Hoymiles/src/crc.h @@ -9,6 +9,6 @@ #define CRC16_MODBUS_POLYNOM 0xA001 #define CRC16_NRF24_POLYNOM 0x1021 -uint8_t crc8(const uint8_t buf[], uint8_t len); -uint16_t crc16(const uint8_t buf[], uint8_t len, uint16_t start = 0xffff); -uint16_t crc16nrf24(const uint8_t buf[], uint16_t lenBits, uint16_t startBit = 0, uint16_t crcIn = 0xffff); +uint8_t crc8(const uint8_t buf[], const uint8_t len); +uint16_t crc16(const uint8_t buf[], const uint8_t len, const uint16_t start = 0xffff); +uint16_t crc16nrf24(const uint8_t buf[], const uint16_t lenBits, const uint16_t startBit = 0, const uint16_t crcIn = 0xffff); diff --git a/lib/Hoymiles/src/inverters/HMS_1CH.cpp b/lib/Hoymiles/src/inverters/HMS_1CH.cpp index c659794c..312cf630 100644 --- a/lib/Hoymiles/src/inverters/HMS_1CH.cpp +++ b/lib/Hoymiles/src/inverters/HMS_1CH.cpp @@ -28,27 +28,27 @@ static const byteAssign_t byteAssignment[] = { { TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 } }; -HMS_1CH::HMS_1CH(HoymilesRadio* radio, uint64_t serial) +HMS_1CH::HMS_1CH(HoymilesRadio* radio, const uint64_t serial) : HMS_Abstract(radio, serial) {}; -bool HMS_1CH::isValidSerial(uint64_t serial) +bool HMS_1CH::isValidSerial(const uint64_t serial) { // serial >= 0x112400000000 && serial <= 0x112499999999 uint16_t preSerial = (serial >> 32) & 0xffff; return preSerial == 0x1124; } -String HMS_1CH::typeName() +String HMS_1CH::typeName() const { return "HMS-300/350/400/450/500-1T"; } -const byteAssign_t* HMS_1CH::getByteAssignment() +const byteAssign_t* HMS_1CH::getByteAssignment() const { return byteAssignment; } -uint8_t HMS_1CH::getByteAssignmentSize() +uint8_t HMS_1CH::getByteAssignmentSize() const { return sizeof(byteAssignment) / sizeof(byteAssignment[0]); } \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMS_1CH.h b/lib/Hoymiles/src/inverters/HMS_1CH.h index 437f3d33..a5a64c17 100644 --- a/lib/Hoymiles/src/inverters/HMS_1CH.h +++ b/lib/Hoymiles/src/inverters/HMS_1CH.h @@ -6,9 +6,9 @@ class HMS_1CH : public HMS_Abstract { public: - explicit HMS_1CH(HoymilesRadio* radio, uint64_t serial); - static bool isValidSerial(uint64_t serial); - String typeName(); - const byteAssign_t* getByteAssignment(); - uint8_t getByteAssignmentSize(); + explicit HMS_1CH(HoymilesRadio* radio, const uint64_t serial); + static bool isValidSerial(const uint64_t serial); + String typeName() const; + const byteAssign_t* getByteAssignment() const; + uint8_t getByteAssignmentSize() const; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMS_1CHv2.cpp b/lib/Hoymiles/src/inverters/HMS_1CHv2.cpp index 08de0a35..b6a9d93e 100644 --- a/lib/Hoymiles/src/inverters/HMS_1CHv2.cpp +++ b/lib/Hoymiles/src/inverters/HMS_1CHv2.cpp @@ -28,27 +28,27 @@ static const byteAssign_t byteAssignment[] = { { TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 } }; -HMS_1CHv2::HMS_1CHv2(HoymilesRadio* radio, uint64_t serial) +HMS_1CHv2::HMS_1CHv2(HoymilesRadio* radio, const uint64_t serial) : HMS_Abstract(radio, serial) {}; -bool HMS_1CHv2::isValidSerial(uint64_t serial) +bool HMS_1CHv2::isValidSerial(const uint64_t serial) { // serial >= 0x112500000000 && serial <= 0x112599999999 uint16_t preSerial = (serial >> 32) & 0xffff; return preSerial == 0x1125; } -String HMS_1CHv2::typeName() +String HMS_1CHv2::typeName() const { return "HMS-500-1T v2"; } -const byteAssign_t* HMS_1CHv2::getByteAssignment() +const byteAssign_t* HMS_1CHv2::getByteAssignment() const { return byteAssignment; } -uint8_t HMS_1CHv2::getByteAssignmentSize() +uint8_t HMS_1CHv2::getByteAssignmentSize() const { return sizeof(byteAssignment) / sizeof(byteAssignment[0]); } \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMS_1CHv2.h b/lib/Hoymiles/src/inverters/HMS_1CHv2.h index 5f498118..c831d120 100644 --- a/lib/Hoymiles/src/inverters/HMS_1CHv2.h +++ b/lib/Hoymiles/src/inverters/HMS_1CHv2.h @@ -6,9 +6,9 @@ class HMS_1CHv2 : public HMS_Abstract { public: - explicit HMS_1CHv2(HoymilesRadio* radio, uint64_t serial); - static bool isValidSerial(uint64_t serial); - String typeName(); - const byteAssign_t* getByteAssignment(); - uint8_t getByteAssignmentSize(); + explicit HMS_1CHv2(HoymilesRadio* radio, const uint64_t serial); + static bool isValidSerial(const uint64_t serial); + String typeName() const; + const byteAssign_t* getByteAssignment() const; + uint8_t getByteAssignmentSize() const; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMS_2CH.cpp b/lib/Hoymiles/src/inverters/HMS_2CH.cpp index e33de994..d038e772 100644 --- a/lib/Hoymiles/src/inverters/HMS_2CH.cpp +++ b/lib/Hoymiles/src/inverters/HMS_2CH.cpp @@ -35,27 +35,27 @@ static const byteAssign_t byteAssignment[] = { { TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 } }; -HMS_2CH::HMS_2CH(HoymilesRadio* radio, uint64_t serial) +HMS_2CH::HMS_2CH(HoymilesRadio* radio, const uint64_t serial) : HMS_Abstract(radio, serial) {}; -bool HMS_2CH::isValidSerial(uint64_t serial) +bool HMS_2CH::isValidSerial(const uint64_t serial) { // serial >= 0x114400000000 && serial <= 0x114499999999 uint16_t preSerial = (serial >> 32) & 0xffff; return preSerial == 0x1144; } -String HMS_2CH::typeName() +String HMS_2CH::typeName() const { return "HMS-600/700/800/900/1000-2T"; } -const byteAssign_t* HMS_2CH::getByteAssignment() +const byteAssign_t* HMS_2CH::getByteAssignment() const { return byteAssignment; } -uint8_t HMS_2CH::getByteAssignmentSize() +uint8_t HMS_2CH::getByteAssignmentSize() const { return sizeof(byteAssignment) / sizeof(byteAssignment[0]); } \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMS_2CH.h b/lib/Hoymiles/src/inverters/HMS_2CH.h index dff704ec..9f1ed91f 100644 --- a/lib/Hoymiles/src/inverters/HMS_2CH.h +++ b/lib/Hoymiles/src/inverters/HMS_2CH.h @@ -6,9 +6,9 @@ class HMS_2CH : public HMS_Abstract { public: - explicit HMS_2CH(HoymilesRadio* radio, uint64_t serial); - static bool isValidSerial(uint64_t serial); - String typeName(); - const byteAssign_t* getByteAssignment(); - uint8_t getByteAssignmentSize(); + explicit HMS_2CH(HoymilesRadio* radio, const uint64_t serial); + static bool isValidSerial(const uint64_t serial); + String typeName() const; + const byteAssign_t* getByteAssignment() const; + uint8_t getByteAssignmentSize() const; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMS_4CH.cpp b/lib/Hoymiles/src/inverters/HMS_4CH.cpp index ffdc2055..eff44abc 100644 --- a/lib/Hoymiles/src/inverters/HMS_4CH.cpp +++ b/lib/Hoymiles/src/inverters/HMS_4CH.cpp @@ -49,27 +49,27 @@ static const byteAssign_t byteAssignment[] = { { TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 } }; -HMS_4CH::HMS_4CH(HoymilesRadio* radio, uint64_t serial) +HMS_4CH::HMS_4CH(HoymilesRadio* radio, const uint64_t serial) : HMS_Abstract(radio, serial) {}; -bool HMS_4CH::isValidSerial(uint64_t serial) +bool HMS_4CH::isValidSerial(const uint64_t serial) { // serial >= 0x116400000000 && serial <= 0x116499999999 uint16_t preSerial = (serial >> 32) & 0xffff; return preSerial == 0x1164; } -String HMS_4CH::typeName() +String HMS_4CH::typeName() const { - return "HMS-1600/1800/2000"; + return "HMS-1600/1800/2000-4T"; } -const byteAssign_t* HMS_4CH::getByteAssignment() +const byteAssign_t* HMS_4CH::getByteAssignment() const { return byteAssignment; } -uint8_t HMS_4CH::getByteAssignmentSize() +uint8_t HMS_4CH::getByteAssignmentSize() const { return sizeof(byteAssignment) / sizeof(byteAssignment[0]); } \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMS_4CH.h b/lib/Hoymiles/src/inverters/HMS_4CH.h index 6a2e2b14..9d49de07 100644 --- a/lib/Hoymiles/src/inverters/HMS_4CH.h +++ b/lib/Hoymiles/src/inverters/HMS_4CH.h @@ -5,9 +5,9 @@ class HMS_4CH : public HMS_Abstract { public: - explicit HMS_4CH(HoymilesRadio* radio, uint64_t serial); - static bool isValidSerial(uint64_t serial); - String typeName(); - const byteAssign_t* getByteAssignment(); - uint8_t getByteAssignmentSize(); + explicit HMS_4CH(HoymilesRadio* radio, const uint64_t serial); + static bool isValidSerial(const uint64_t serial); + String typeName() const; + const byteAssign_t* getByteAssignment() const; + uint8_t getByteAssignmentSize() const; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMS_Abstract.cpp b/lib/Hoymiles/src/inverters/HMS_Abstract.cpp index f67ff11b..235a9ba7 100644 --- a/lib/Hoymiles/src/inverters/HMS_Abstract.cpp +++ b/lib/Hoymiles/src/inverters/HMS_Abstract.cpp @@ -7,7 +7,7 @@ #include "HoymilesRadio_CMT.h" #include "commands/ChannelChangeCommand.h" -HMS_Abstract::HMS_Abstract(HoymilesRadio* radio, uint64_t serial) +HMS_Abstract::HMS_Abstract(HoymilesRadio* radio, const uint64_t serial) : HM_Abstract(radio, serial) { } diff --git a/lib/Hoymiles/src/inverters/HMS_Abstract.h b/lib/Hoymiles/src/inverters/HMS_Abstract.h index 6d363f6e..c4026a53 100644 --- a/lib/Hoymiles/src/inverters/HMS_Abstract.h +++ b/lib/Hoymiles/src/inverters/HMS_Abstract.h @@ -5,7 +5,7 @@ class HMS_Abstract : public HM_Abstract { public: - explicit HMS_Abstract(HoymilesRadio* radio, uint64_t serial); + explicit HMS_Abstract(HoymilesRadio* radio, const uint64_t serial); virtual bool sendChangeChannelRequest(); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMT_4CH.cpp b/lib/Hoymiles/src/inverters/HMT_4CH.cpp index d30a404b..717099b7 100644 --- a/lib/Hoymiles/src/inverters/HMT_4CH.cpp +++ b/lib/Hoymiles/src/inverters/HMT_4CH.cpp @@ -58,27 +58,27 @@ static const byteAssign_t byteAssignment[] = { { TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 } }; -HMT_4CH::HMT_4CH(HoymilesRadio* radio, uint64_t serial) +HMT_4CH::HMT_4CH(HoymilesRadio* radio, const uint64_t serial) : HMT_Abstract(radio, serial) {}; -bool HMT_4CH::isValidSerial(uint64_t serial) +bool HMT_4CH::isValidSerial(const uint64_t serial) { // serial >= 0x136100000000 && serial <= 0x136199999999 uint16_t preSerial = (serial >> 32) & 0xffff; return preSerial == 0x1361; } -String HMT_4CH::typeName() +String HMT_4CH::typeName() const { return F("HMT-1600/1800/2000-4T"); } -const byteAssign_t* HMT_4CH::getByteAssignment() +const byteAssign_t* HMT_4CH::getByteAssignment() const { return byteAssignment; } -uint8_t HMT_4CH::getByteAssignmentSize() +uint8_t HMT_4CH::getByteAssignmentSize() const { return sizeof(byteAssignment) / sizeof(byteAssignment[0]); } diff --git a/lib/Hoymiles/src/inverters/HMT_4CH.h b/lib/Hoymiles/src/inverters/HMT_4CH.h index 7358dd45..01d32893 100644 --- a/lib/Hoymiles/src/inverters/HMT_4CH.h +++ b/lib/Hoymiles/src/inverters/HMT_4CH.h @@ -5,9 +5,9 @@ class HMT_4CH : public HMT_Abstract { public: - explicit HMT_4CH(HoymilesRadio* radio, uint64_t serial); - static bool isValidSerial(uint64_t serial); - String typeName(); - const byteAssign_t* getByteAssignment(); - uint8_t getByteAssignmentSize(); + explicit HMT_4CH(HoymilesRadio* radio, const uint64_t serial); + static bool isValidSerial(const uint64_t serial); + String typeName() const; + const byteAssign_t* getByteAssignment() const; + uint8_t getByteAssignmentSize() const; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMT_6CH.cpp b/lib/Hoymiles/src/inverters/HMT_6CH.cpp index 69b3a60b..6cbd2097 100644 --- a/lib/Hoymiles/src/inverters/HMT_6CH.cpp +++ b/lib/Hoymiles/src/inverters/HMT_6CH.cpp @@ -72,27 +72,27 @@ static const byteAssign_t byteAssignment[] = { { TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 } }; -HMT_6CH::HMT_6CH(HoymilesRadio* radio, uint64_t serial) +HMT_6CH::HMT_6CH(HoymilesRadio* radio, const uint64_t serial) : HMT_Abstract(radio, serial) {}; -bool HMT_6CH::isValidSerial(uint64_t serial) +bool HMT_6CH::isValidSerial(const uint64_t serial) { // serial >= 0x138200000000 && serial <= 0x138299999999 uint16_t preSerial = (serial >> 32) & 0xffff; return preSerial == 0x1382; } -String HMT_6CH::typeName() +String HMT_6CH::typeName() const { return F("HMT-1800/2250-6T"); } -const byteAssign_t* HMT_6CH::getByteAssignment() +const byteAssign_t* HMT_6CH::getByteAssignment() const { return byteAssignment; } -uint8_t HMT_6CH::getByteAssignmentSize() +uint8_t HMT_6CH::getByteAssignmentSize() const { return sizeof(byteAssignment) / sizeof(byteAssignment[0]); } diff --git a/lib/Hoymiles/src/inverters/HMT_6CH.h b/lib/Hoymiles/src/inverters/HMT_6CH.h index ea4be715..6b728006 100644 --- a/lib/Hoymiles/src/inverters/HMT_6CH.h +++ b/lib/Hoymiles/src/inverters/HMT_6CH.h @@ -5,9 +5,9 @@ class HMT_6CH : public HMT_Abstract { public: - explicit HMT_6CH(HoymilesRadio* radio, uint64_t serial); - static bool isValidSerial(uint64_t serial); - String typeName(); - const byteAssign_t* getByteAssignment(); - uint8_t getByteAssignmentSize(); + explicit HMT_6CH(HoymilesRadio* radio, const uint64_t serial); + static bool isValidSerial(const uint64_t serial); + String typeName() const; + const byteAssign_t* getByteAssignment() const; + uint8_t getByteAssignmentSize() const; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMT_Abstract.cpp b/lib/Hoymiles/src/inverters/HMT_Abstract.cpp index c345be97..578233ee 100644 --- a/lib/Hoymiles/src/inverters/HMT_Abstract.cpp +++ b/lib/Hoymiles/src/inverters/HMT_Abstract.cpp @@ -8,7 +8,7 @@ #include "commands/ChannelChangeCommand.h" #include "parser/AlarmLogParser.h" -HMT_Abstract::HMT_Abstract(HoymilesRadio* radio, uint64_t serial) +HMT_Abstract::HMT_Abstract(HoymilesRadio* radio, const uint64_t serial) : HM_Abstract(radio, serial) { EventLog()->setMessageType(AlarmMessageType_t::HMT); diff --git a/lib/Hoymiles/src/inverters/HMT_Abstract.h b/lib/Hoymiles/src/inverters/HMT_Abstract.h index 9e10a2c3..c913683c 100644 --- a/lib/Hoymiles/src/inverters/HMT_Abstract.h +++ b/lib/Hoymiles/src/inverters/HMT_Abstract.h @@ -5,7 +5,7 @@ class HMT_Abstract : public HM_Abstract { public: - explicit HMT_Abstract(HoymilesRadio* radio, uint64_t serial); + explicit HMT_Abstract(HoymilesRadio* radio, const uint64_t serial); virtual bool sendChangeChannelRequest(); }; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HM_1CH.cpp b/lib/Hoymiles/src/inverters/HM_1CH.cpp index a7c39f4c..7b23207d 100644 --- a/lib/Hoymiles/src/inverters/HM_1CH.cpp +++ b/lib/Hoymiles/src/inverters/HM_1CH.cpp @@ -28,10 +28,10 @@ static const byteAssign_t byteAssignment[] = { { TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 } }; -HM_1CH::HM_1CH(HoymilesRadio* radio, uint64_t serial) +HM_1CH::HM_1CH(HoymilesRadio* radio, const uint64_t serial) : HM_Abstract(radio, serial) {}; -bool HM_1CH::isValidSerial(uint64_t serial) +bool HM_1CH::isValidSerial(const uint64_t serial) { // serial >= 0x112100000000 && serial <= 0x112199999999 @@ -51,17 +51,17 @@ bool HM_1CH::isValidSerial(uint64_t serial) return false; } -String HM_1CH::typeName() +String HM_1CH::typeName() const { return "HM-300/350/400-1T"; } -const byteAssign_t* HM_1CH::getByteAssignment() +const byteAssign_t* HM_1CH::getByteAssignment() const { return byteAssignment; } -uint8_t HM_1CH::getByteAssignmentSize() +uint8_t HM_1CH::getByteAssignmentSize() const { return sizeof(byteAssignment) / sizeof(byteAssignment[0]); } \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HM_1CH.h b/lib/Hoymiles/src/inverters/HM_1CH.h index cb18dcf5..a35b4e56 100644 --- a/lib/Hoymiles/src/inverters/HM_1CH.h +++ b/lib/Hoymiles/src/inverters/HM_1CH.h @@ -6,9 +6,9 @@ class HM_1CH : public HM_Abstract { public: - explicit HM_1CH(HoymilesRadio* radio, uint64_t serial); - static bool isValidSerial(uint64_t serial); - String typeName(); - const byteAssign_t* getByteAssignment(); - uint8_t getByteAssignmentSize(); + explicit HM_1CH(HoymilesRadio* radio, const uint64_t serial); + static bool isValidSerial(const uint64_t serial); + String typeName() const; + const byteAssign_t* getByteAssignment() const; + uint8_t getByteAssignmentSize() const; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HM_2CH.cpp b/lib/Hoymiles/src/inverters/HM_2CH.cpp index 2dc674b2..2f56ec3e 100644 --- a/lib/Hoymiles/src/inverters/HM_2CH.cpp +++ b/lib/Hoymiles/src/inverters/HM_2CH.cpp @@ -36,10 +36,10 @@ static const byteAssign_t byteAssignment[] = { { TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 } }; -HM_2CH::HM_2CH(HoymilesRadio* radio, uint64_t serial) +HM_2CH::HM_2CH(HoymilesRadio* radio, const uint64_t serial) : HM_Abstract(radio, serial) {}; -bool HM_2CH::isValidSerial(uint64_t serial) +bool HM_2CH::isValidSerial(const uint64_t serial) { // serial >= 0x114100000000 && serial <= 0x114199999999 @@ -59,17 +59,17 @@ bool HM_2CH::isValidSerial(uint64_t serial) return false; } -String HM_2CH::typeName() +String HM_2CH::typeName() const { return "HM-600/700/800-2T"; } -const byteAssign_t* HM_2CH::getByteAssignment() +const byteAssign_t* HM_2CH::getByteAssignment() const { return byteAssignment; } -uint8_t HM_2CH::getByteAssignmentSize() +uint8_t HM_2CH::getByteAssignmentSize() const { return sizeof(byteAssignment) / sizeof(byteAssignment[0]); } \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HM_2CH.h b/lib/Hoymiles/src/inverters/HM_2CH.h index 06ac509d..1fd54496 100644 --- a/lib/Hoymiles/src/inverters/HM_2CH.h +++ b/lib/Hoymiles/src/inverters/HM_2CH.h @@ -5,9 +5,9 @@ class HM_2CH : public HM_Abstract { public: - explicit HM_2CH(HoymilesRadio* radio, uint64_t serial); - static bool isValidSerial(uint64_t serial); - String typeName(); - const byteAssign_t* getByteAssignment(); - uint8_t getByteAssignmentSize(); + explicit HM_2CH(HoymilesRadio* radio, const uint64_t serial); + static bool isValidSerial(const uint64_t serial); + String typeName() const; + const byteAssign_t* getByteAssignment() const; + uint8_t getByteAssignmentSize() const; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HM_4CH.cpp b/lib/Hoymiles/src/inverters/HM_4CH.cpp index f5920491..bcad2536 100644 --- a/lib/Hoymiles/src/inverters/HM_4CH.cpp +++ b/lib/Hoymiles/src/inverters/HM_4CH.cpp @@ -49,10 +49,10 @@ static const byteAssign_t byteAssignment[] = { { TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 } }; -HM_4CH::HM_4CH(HoymilesRadio* radio, uint64_t serial) +HM_4CH::HM_4CH(HoymilesRadio* radio, const uint64_t serial) : HM_Abstract(radio, serial) {}; -bool HM_4CH::isValidSerial(uint64_t serial) +bool HM_4CH::isValidSerial(const uint64_t serial) { // serial >= 0x116100000000 && serial <= 0x116199999999 @@ -72,17 +72,17 @@ bool HM_4CH::isValidSerial(uint64_t serial) return false; } -String HM_4CH::typeName() +String HM_4CH::typeName() const { return "HM-1000/1200/1500-4T"; } -const byteAssign_t* HM_4CH::getByteAssignment() +const byteAssign_t* HM_4CH::getByteAssignment() const { return byteAssignment; } -uint8_t HM_4CH::getByteAssignmentSize() +uint8_t HM_4CH::getByteAssignmentSize() const { return sizeof(byteAssignment) / sizeof(byteAssignment[0]); } \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HM_4CH.h b/lib/Hoymiles/src/inverters/HM_4CH.h index 44d341ae..e54f3323 100644 --- a/lib/Hoymiles/src/inverters/HM_4CH.h +++ b/lib/Hoymiles/src/inverters/HM_4CH.h @@ -5,9 +5,9 @@ class HM_4CH : public HM_Abstract { public: - explicit HM_4CH(HoymilesRadio* radio, uint64_t serial); - static bool isValidSerial(uint64_t serial); - String typeName(); - const byteAssign_t* getByteAssignment(); - uint8_t getByteAssignmentSize(); + explicit HM_4CH(HoymilesRadio* radio, const uint64_t serial); + static bool isValidSerial(const uint64_t serial); + String typeName() const; + const byteAssign_t* getByteAssignment() const; + uint8_t getByteAssignmentSize() const; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HM_Abstract.cpp b/lib/Hoymiles/src/inverters/HM_Abstract.cpp index 10d93679..38515ab8 100644 --- a/lib/Hoymiles/src/inverters/HM_Abstract.cpp +++ b/lib/Hoymiles/src/inverters/HM_Abstract.cpp @@ -13,7 +13,7 @@ #include "commands/RealTimeRunDataCommand.h" #include "commands/SystemConfigParaCommand.h" -HM_Abstract::HM_Abstract(HoymilesRadio* radio, uint64_t serial) +HM_Abstract::HM_Abstract(HoymilesRadio* radio, const uint64_t serial) : InverterAbstract(radio, serial) {}; bool HM_Abstract::sendStatsRequest() @@ -38,7 +38,7 @@ bool HM_Abstract::sendStatsRequest() return true; } -bool HM_Abstract::sendAlarmLogRequest(bool force) +bool HM_Abstract::sendAlarmLogRequest(const bool force) { if (!getEnablePolling()) { return false; @@ -121,7 +121,7 @@ bool HM_Abstract::sendSystemConfigParaRequest() return true; } -bool HM_Abstract::sendActivePowerControlRequest(float limit, PowerLimitControlType type) +bool HM_Abstract::sendActivePowerControlRequest(float limit, const PowerLimitControlType type) { if (!getEnableCommands()) { return false; @@ -152,7 +152,7 @@ bool HM_Abstract::resendActivePowerControlRequest() return sendActivePowerControlRequest(_activePowerControlLimit, _activePowerControlType); } -bool HM_Abstract::sendPowerControlRequest(bool turnOn) +bool HM_Abstract::sendPowerControlRequest(const bool turnOn) { if (!getEnableCommands()) { return false; diff --git a/lib/Hoymiles/src/inverters/HM_Abstract.h b/lib/Hoymiles/src/inverters/HM_Abstract.h index 3a5cc637..491149dc 100644 --- a/lib/Hoymiles/src/inverters/HM_Abstract.h +++ b/lib/Hoymiles/src/inverters/HM_Abstract.h @@ -5,14 +5,14 @@ class HM_Abstract : public InverterAbstract { public: - explicit HM_Abstract(HoymilesRadio* radio, uint64_t serial); + explicit HM_Abstract(HoymilesRadio* radio, const uint64_t serial); bool sendStatsRequest(); - bool sendAlarmLogRequest(bool force = false); + bool sendAlarmLogRequest(const bool force = false); bool sendDevInfoRequest(); bool sendSystemConfigParaRequest(); - bool sendActivePowerControlRequest(float limit, PowerLimitControlType type); + bool sendActivePowerControlRequest(float limit, const PowerLimitControlType type); bool resendActivePowerControlRequest(); - bool sendPowerControlRequest(bool turnOn); + bool sendPowerControlRequest(const bool turnOn); bool sendRestartControlRequest(); bool resendPowerControlRequest(); bool sendGridOnProFileParaRequest(); diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.cpp b/lib/Hoymiles/src/inverters/InverterAbstract.cpp index 4c5aa422..d80d0e53 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.cpp +++ b/lib/Hoymiles/src/inverters/InverterAbstract.cpp @@ -7,7 +7,7 @@ #include "crc.h" #include -InverterAbstract::InverterAbstract(HoymilesRadio* radio, uint64_t serial) +InverterAbstract::InverterAbstract(HoymilesRadio* radio, const uint64_t serial) { _serial.u64 = serial; _radio = radio; @@ -35,12 +35,12 @@ void InverterAbstract::init() _statisticsParser.get()->setByteAssignment(getByteAssignment(), getByteAssignmentSize()); } -uint64_t InverterAbstract::serial() +uint64_t InverterAbstract::serial() const { return _serial.u64; } -const String& InverterAbstract::serialString() +const String& InverterAbstract::serialString() const { return _serialString; } @@ -55,7 +55,7 @@ void InverterAbstract::setName(const char* name) _name[len] = '\0'; } -const char* InverterAbstract::name() +const char* InverterAbstract::name() const { return _name; } @@ -77,52 +77,52 @@ bool InverterAbstract::isReachable() return _enablePolling && Statistics()->getRxFailureCount() <= _reachableThreshold; } -void InverterAbstract::setEnablePolling(bool enabled) +void InverterAbstract::setEnablePolling(const bool enabled) { _enablePolling = enabled; } -bool InverterAbstract::getEnablePolling() +bool InverterAbstract::getEnablePolling() const { return _enablePolling; } -void InverterAbstract::setEnableCommands(bool enabled) +void InverterAbstract::setEnableCommands(const bool enabled) { _enableCommands = enabled; } -bool InverterAbstract::getEnableCommands() +bool InverterAbstract::getEnableCommands() const { return _enableCommands; } -void InverterAbstract::setReachableThreshold(uint8_t threshold) +void InverterAbstract::setReachableThreshold(const uint8_t threshold) { _reachableThreshold = threshold; } -uint8_t InverterAbstract::getReachableThreshold() +uint8_t InverterAbstract::getReachableThreshold() const { return _reachableThreshold; } -void InverterAbstract::setZeroValuesIfUnreachable(bool enabled) +void InverterAbstract::setZeroValuesIfUnreachable(const bool enabled) { _zeroValuesIfUnreachable = enabled; } -bool InverterAbstract::getZeroValuesIfUnreachable() +bool InverterAbstract::getZeroValuesIfUnreachable() const { return _zeroValuesIfUnreachable; } -void InverterAbstract::setZeroYieldDayOnMidnight(bool enabled) +void InverterAbstract::setZeroYieldDayOnMidnight(const bool enabled) { _zeroYieldDayOnMidnight = enabled; } -bool InverterAbstract::getZeroYieldDayOnMidnight() +bool InverterAbstract::getZeroYieldDayOnMidnight() const { return _zeroYieldDayOnMidnight; } @@ -175,7 +175,7 @@ void InverterAbstract::clearRxFragmentBuffer() _rxFragmentRetransmitCnt = 0; } -void InverterAbstract::addRxFragment(uint8_t fragment[], uint8_t len) +void InverterAbstract::addRxFragment(const uint8_t fragment[], const uint8_t len) { if (len < 11) { Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) fragment too short\r\n", __FILE__, __LINE__); @@ -187,10 +187,10 @@ void InverterAbstract::addRxFragment(uint8_t fragment[], uint8_t len) return; } - uint8_t fragmentCount = fragment[9]; + const uint8_t fragmentCount = fragment[9]; // Packets with 0x81 will be seen as 1 - uint8_t fragmentId = fragmentCount & 0b01111111; // fragmentId is 1 based + const uint8_t fragmentId = fragmentCount & 0b01111111; // fragmentId is 1 based if (fragmentId == 0) { Hoymiles.getMessageOutput()->println("ERROR: fragment id zero received and ignored"); @@ -218,15 +218,15 @@ void InverterAbstract::addRxFragment(uint8_t fragment[], uint8_t len) } // Returns Zero on Success or the Fragment ID for retransmit or error code -uint8_t InverterAbstract::verifyAllFragments(CommandAbstract* cmd) +uint8_t InverterAbstract::verifyAllFragments(CommandAbstract& cmd) { // All missing if (_rxFragmentLastPacketId == 0) { Hoymiles.getMessageOutput()->println("All missing"); - if (cmd->getSendCount() <= cmd->getMaxResendCount()) { + if (cmd.getSendCount() <= cmd.getMaxResendCount()) { return FRAGMENT_ALL_MISSING_RESEND; } else { - cmd->gotTimeout(this); + cmd.gotTimeout(*this); return FRAGMENT_ALL_MISSING_TIMEOUT; } } @@ -234,10 +234,10 @@ uint8_t InverterAbstract::verifyAllFragments(CommandAbstract* cmd) // Last fragment is missing (the one with 0x80) if (_rxFragmentMaxPacketId == 0) { Hoymiles.getMessageOutput()->println("Last missing"); - if (_rxFragmentRetransmitCnt++ < cmd->getMaxRetransmitCount()) { + if (_rxFragmentRetransmitCnt++ < cmd.getMaxRetransmitCount()) { return _rxFragmentLastPacketId + 1; } else { - cmd->gotTimeout(this); + cmd.gotTimeout(*this); return FRAGMENT_RETRANSMIT_TIMEOUT; } } @@ -246,17 +246,17 @@ uint8_t InverterAbstract::verifyAllFragments(CommandAbstract* cmd) for (uint8_t i = 0; i < _rxFragmentMaxPacketId - 1; i++) { if (!_rxFragmentBuffer[i].wasReceived) { Hoymiles.getMessageOutput()->println("Middle missing"); - if (_rxFragmentRetransmitCnt++ < cmd->getMaxRetransmitCount()) { + if (_rxFragmentRetransmitCnt++ < cmd.getMaxRetransmitCount()) { return i + 1; } else { - cmd->gotTimeout(this); + cmd.gotTimeout(*this); return FRAGMENT_RETRANSMIT_TIMEOUT; } } } - if (!cmd->handleResponse(this, _rxFragmentBuffer, _rxFragmentMaxPacketId)) { - cmd->gotTimeout(this); + if (!cmd.handleResponse(*this, _rxFragmentBuffer, _rxFragmentMaxPacketId)) { + cmd.gotTimeout(*this); return FRAGMENT_HANDLE_ERROR; } diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.h b/lib/Hoymiles/src/inverters/InverterAbstract.h index e6f70f07..3d9929d7 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.h +++ b/lib/Hoymiles/src/inverters/InverterAbstract.h @@ -30,45 +30,45 @@ class CommandAbstract; class InverterAbstract { public: - explicit InverterAbstract(HoymilesRadio* radio, uint64_t serial); + explicit InverterAbstract(HoymilesRadio* radio, const uint64_t serial); void init(); - uint64_t serial(); - const String& serialString(); + uint64_t serial() const; + const String& serialString() const; void setName(const char* name); - const char* name(); - virtual String typeName() = 0; - virtual const byteAssign_t* getByteAssignment() = 0; - virtual uint8_t getByteAssignmentSize() = 0; + const char* name() const; + virtual String typeName() const = 0; + virtual const byteAssign_t* getByteAssignment() const = 0; + virtual uint8_t getByteAssignmentSize() const = 0; bool isProducing(); bool isReachable(); - void setEnablePolling(bool enabled); - bool getEnablePolling(); + void setEnablePolling(const bool enabled); + bool getEnablePolling() const; - void setEnableCommands(bool enabled); - bool getEnableCommands(); + void setEnableCommands(const bool enabled); + bool getEnableCommands() const; - void setReachableThreshold(uint8_t threshold); - uint8_t getReachableThreshold(); + void setReachableThreshold(const uint8_t threshold); + uint8_t getReachableThreshold() const; - void setZeroValuesIfUnreachable(bool enabled); - bool getZeroValuesIfUnreachable(); + void setZeroValuesIfUnreachable(const bool enabled); + bool getZeroValuesIfUnreachable() const; - void setZeroYieldDayOnMidnight(bool enabled); - bool getZeroYieldDayOnMidnight(); + void setZeroYieldDayOnMidnight(const bool enabled); + bool getZeroYieldDayOnMidnight() const; void clearRxFragmentBuffer(); - void addRxFragment(uint8_t fragment[], uint8_t len); - uint8_t verifyAllFragments(CommandAbstract* cmd); + void addRxFragment(const uint8_t fragment[], const uint8_t len); + uint8_t verifyAllFragments(CommandAbstract& cmd); virtual bool sendStatsRequest() = 0; - virtual bool sendAlarmLogRequest(bool force = false) = 0; + virtual bool sendAlarmLogRequest(const bool force = false) = 0; virtual bool sendDevInfoRequest() = 0; virtual bool sendSystemConfigParaRequest() = 0; - virtual bool sendActivePowerControlRequest(float limit, PowerLimitControlType type) = 0; + virtual bool sendActivePowerControlRequest(float limit, const PowerLimitControlType type) = 0; virtual bool resendActivePowerControlRequest() = 0; - virtual bool sendPowerControlRequest(bool turnOn) = 0; + virtual bool sendPowerControlRequest(const bool turnOn) = 0; virtual bool sendRestartControlRequest() = 0; virtual bool resendPowerControlRequest() = 0; virtual bool sendChangeChannelRequest(); diff --git a/lib/Hoymiles/src/parser/AlarmLogParser.cpp b/lib/Hoymiles/src/parser/AlarmLogParser.cpp index fe2d2bab..4086f8e3 100644 --- a/lib/Hoymiles/src/parser/AlarmLogParser.cpp +++ b/lib/Hoymiles/src/parser/AlarmLogParser.cpp @@ -181,7 +181,7 @@ void AlarmLogParser::clearBuffer() _alarmLogLength = 0; } -void AlarmLogParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len) +void AlarmLogParser::appendFragment(const uint8_t offset, const uint8_t* payload, const uint8_t len) { if (offset + len > ALARM_LOG_PAYLOAD_SIZE) { Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) stats packet too large for buffer (%d > %d)\r\n", __FILE__, __LINE__, offset + len, ALARM_LOG_PAYLOAD_SIZE); @@ -191,7 +191,7 @@ void AlarmLogParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t le _alarmLogLength += len; } -uint8_t AlarmLogParser::getEntryCount() +uint8_t AlarmLogParser::getEntryCount() const { if (_alarmLogLength < 2) { return 0; @@ -199,30 +199,30 @@ uint8_t AlarmLogParser::getEntryCount() return (_alarmLogLength - 2) / ALARM_LOG_ENTRY_SIZE; } -void AlarmLogParser::setLastAlarmRequestSuccess(LastCommandSuccess status) +void AlarmLogParser::setLastAlarmRequestSuccess(const LastCommandSuccess status) { _lastAlarmRequestSuccess = status; } -LastCommandSuccess AlarmLogParser::getLastAlarmRequestSuccess() +LastCommandSuccess AlarmLogParser::getLastAlarmRequestSuccess() const { return _lastAlarmRequestSuccess; } -void AlarmLogParser::setMessageType(AlarmMessageType_t type) +void AlarmLogParser::setMessageType(const AlarmMessageType_t type) { _messageType = type; } -void AlarmLogParser::getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry, AlarmMessageLocale_t locale) +void AlarmLogParser::getLogEntry(const uint8_t entryId, AlarmLogEntry_t& entry, const 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; @@ -233,40 +233,40 @@ void AlarmLogParser::getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry, AlarmM endTimeOffset = 12 * 60 * 60; } - entry->MessageId = _payloadAlarmLog[entryStartOffset + 1]; - entry->StartTime = (((uint16_t)_payloadAlarmLog[entryStartOffset + 4] << 8) | ((uint16_t)_payloadAlarmLog[entryStartOffset + 5])) + startTimeOffset + timezoneOffset; - entry->EndTime = ((uint16_t)_payloadAlarmLog[entryStartOffset + 6] << 8) | ((uint16_t)_payloadAlarmLog[entryStartOffset + 7]); + entry.MessageId = _payloadAlarmLog[entryStartOffset + 1]; + entry.StartTime = (((uint16_t)_payloadAlarmLog[entryStartOffset + 4] << 8) | ((uint16_t)_payloadAlarmLog[entryStartOffset + 5])) + startTimeOffset + timezoneOffset; + entry.EndTime = ((uint16_t)_payloadAlarmLog[entryStartOffset + 6] << 8) | ((uint16_t)_payloadAlarmLog[entryStartOffset + 7]); HOY_SEMAPHORE_GIVE(); - if (entry->EndTime > 0) { - entry->EndTime += (endTimeOffset + timezoneOffset); + if (entry.EndTime > 0) { + entry.EndTime += (endTimeOffset + timezoneOffset); } switch (locale) { case AlarmMessageLocale_t::DE: - entry->Message = "Unbekannt"; + entry.Message = "Unbekannt"; break; case AlarmMessageLocale_t::FR: - entry->Message = "Inconnu"; + entry.Message = "Inconnu"; break; default: - entry->Message = "Unknown"; + entry.Message = "Unknown"; } for (auto& msg : _alarmMessages) { - if (msg.MessageId == entry->MessageId) { + if (msg.MessageId == entry.MessageId) { if (msg.InverterType == _messageType) { - entry->Message = getLocaleMessage(&msg, locale); + entry.Message = getLocaleMessage(&msg, locale); break; } else if (msg.InverterType == AlarmMessageType_t::ALL) { - entry->Message = getLocaleMessage(&msg, locale); + entry.Message = getLocaleMessage(&msg, locale); } } } } -String AlarmLogParser::getLocaleMessage(const AlarmMessage_t* msg, AlarmMessageLocale_t locale) +String AlarmLogParser::getLocaleMessage(const AlarmMessage_t* msg, const AlarmMessageLocale_t locale) const { if (locale == AlarmMessageLocale_t::DE) { return msg->Message_de[0] != '\0' ? msg->Message_de : msg->Message_en; diff --git a/lib/Hoymiles/src/parser/AlarmLogParser.h b/lib/Hoymiles/src/parser/AlarmLogParser.h index 9189d175..a6f0c10c 100644 --- a/lib/Hoymiles/src/parser/AlarmLogParser.h +++ b/lib/Hoymiles/src/parser/AlarmLogParser.h @@ -31,28 +31,28 @@ enum class AlarmMessageLocale_t { typedef struct { AlarmMessageType_t InverterType; uint16_t MessageId; - char Message_en[62]; - char Message_de[63]; - char Message_fr[64]; + const char* Message_en; + const char* Message_de; + const char* Message_fr; } AlarmMessage_t; class AlarmLogParser : public Parser { public: AlarmLogParser(); void clearBuffer(); - void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); + void appendFragment(const uint8_t offset, const uint8_t* payload, const uint8_t len); - uint8_t getEntryCount(); - void getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry, AlarmMessageLocale_t locale = AlarmMessageLocale_t::EN); + uint8_t getEntryCount() const; + void getLogEntry(const uint8_t entryId, AlarmLogEntry_t& entry, const AlarmMessageLocale_t locale = AlarmMessageLocale_t::EN); - void setLastAlarmRequestSuccess(LastCommandSuccess status); - LastCommandSuccess getLastAlarmRequestSuccess(); + void setLastAlarmRequestSuccess(const LastCommandSuccess status); + LastCommandSuccess getLastAlarmRequestSuccess() const; - void setMessageType(AlarmMessageType_t type); + void setMessageType(const AlarmMessageType_t type); private: static int getTimezoneOffset(); - String getLocaleMessage(const AlarmMessage_t *msg, AlarmMessageLocale_t locale); + String getLocaleMessage(const AlarmMessage_t* msg, const AlarmMessageLocale_t locale) const; uint8_t _payloadAlarmLog[ALARM_LOG_PAYLOAD_SIZE]; uint8_t _alarmLogLength = 0; diff --git a/lib/Hoymiles/src/parser/DevInfoParser.cpp b/lib/Hoymiles/src/parser/DevInfoParser.cpp index bc28ce39..d4f59900 100644 --- a/lib/Hoymiles/src/parser/DevInfoParser.cpp +++ b/lib/Hoymiles/src/parser/DevInfoParser.cpp @@ -15,38 +15,41 @@ 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, 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() @@ -62,7 +65,7 @@ void DevInfoParser::clearBufferAll() _devInfoAllLength = 0; } -void DevInfoParser::appendFragmentAll(uint8_t offset, uint8_t* payload, uint8_t len) +void DevInfoParser::appendFragmentAll(const uint8_t offset, const uint8_t* payload, const uint8_t len) { if (offset + len > DEV_INFO_SIZE) { Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) dev info all packet too large for buffer\r\n", __FILE__, __LINE__); @@ -78,7 +81,7 @@ void DevInfoParser::clearBufferSimple() _devInfoSimpleLength = 0; } -void DevInfoParser::appendFragmentSimple(uint8_t offset, uint8_t* payload, uint8_t len) +void DevInfoParser::appendFragmentSimple(const uint8_t offset, const uint8_t* payload, const uint8_t len) { if (offset + len > DEV_INFO_SIZE) { Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) dev info Simple packet too large for buffer\r\n", __FILE__, __LINE__); @@ -88,37 +91,37 @@ void DevInfoParser::appendFragmentSimple(uint8_t offset, uint8_t* payload, uint8 _devInfoSimpleLength += len; } -uint32_t DevInfoParser::getLastUpdateAll() +uint32_t DevInfoParser::getLastUpdateAll() const { return _lastUpdateAll; } -void DevInfoParser::setLastUpdateAll(uint32_t lastUpdate) +void DevInfoParser::setLastUpdateAll(const uint32_t lastUpdate) { _lastUpdateAll = lastUpdate; setLastUpdate(lastUpdate); } -uint32_t DevInfoParser::getLastUpdateSimple() +uint32_t DevInfoParser::getLastUpdateSimple() const { return _lastUpdateSimple; } -void DevInfoParser::setLastUpdateSimple(uint32_t lastUpdate) +void DevInfoParser::setLastUpdateSimple(const uint32_t lastUpdate) { _lastUpdateSimple = lastUpdate; setLastUpdate(lastUpdate); } -uint16_t DevInfoParser::getFwBuildVersion() +uint16_t DevInfoParser::getFwBuildVersion() const { 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; } -time_t DevInfoParser::getFwBuildDateTime() +time_t DevInfoParser::getFwBuildDateTime() const { struct tm timeinfo = {}; HOY_SEMAPHORE_TAKE(); @@ -134,28 +137,25 @@ time_t DevInfoParser::getFwBuildDateTime() return timegm(&timeinfo); } -uint16_t DevInfoParser::getFwBootloaderVersion() +uint16_t DevInfoParser::getFwBootloaderVersion() const { 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() +uint32_t DevInfoParser::getHwPartNumber() const { - 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); } -String DevInfoParser::getHwVersion() +String DevInfoParser::getHwVersion() const { char buf[8]; HOY_SEMAPHORE_TAKE(); @@ -164,27 +164,27 @@ String DevInfoParser::getHwVersion() return buf; } -uint16_t DevInfoParser::getMaxPower() +uint16_t DevInfoParser::getMaxPower() const { - uint8_t idx = getDevIdx(); + const uint8_t idx = getDevIdx(); if (idx == 0xff) { return 0; } return devInfo[idx].maxPower; } -String DevInfoParser::getHwModelName() +String DevInfoParser::getHwModelName() const { - uint8_t idx = getDevIdx(); + const uint8_t idx = getDevIdx(); if (idx == 0xff) { return ""; } return devInfo[idx].modelName; } -bool DevInfoParser::containsValidData() +bool DevInfoParser::containsValidData() const { - time_t t = getFwBuildDateTime(); + const time_t t = getFwBuildDateTime(); struct tm info; localtime_r(&t, &info); @@ -192,7 +192,7 @@ bool DevInfoParser::containsValidData() return info.tm_year > (2016 - 1900); } -uint8_t DevInfoParser::getDevIdx() +uint8_t DevInfoParser::getDevIdx() const { uint8_t ret = 0xff; uint8_t pos; @@ -228,7 +228,7 @@ uint8_t DevInfoParser::getDevIdx() } /* struct tm to seconds since Unix epoch */ -time_t DevInfoParser::timegm(struct tm* t) +time_t DevInfoParser::timegm(const struct tm* t) { uint32_t year; time_t result; diff --git a/lib/Hoymiles/src/parser/DevInfoParser.h b/lib/Hoymiles/src/parser/DevInfoParser.h index 838ba110..89c40f86 100644 --- a/lib/Hoymiles/src/parser/DevInfoParser.h +++ b/lib/Hoymiles/src/parser/DevInfoParser.h @@ -8,32 +8,32 @@ class DevInfoParser : public Parser { public: DevInfoParser(); void clearBufferAll(); - void appendFragmentAll(uint8_t offset, uint8_t* payload, uint8_t len); + void appendFragmentAll(const uint8_t offset, const uint8_t* payload, const uint8_t len); void clearBufferSimple(); - void appendFragmentSimple(uint8_t offset, uint8_t* payload, uint8_t len); + void appendFragmentSimple(const uint8_t offset, const uint8_t* payload, const uint8_t len); - uint32_t getLastUpdateAll(); - void setLastUpdateAll(uint32_t lastUpdate); + uint32_t getLastUpdateAll() const; + void setLastUpdateAll(const uint32_t lastUpdate); - uint32_t getLastUpdateSimple(); - void setLastUpdateSimple(uint32_t lastUpdate); + uint32_t getLastUpdateSimple() const; + void setLastUpdateSimple(const uint32_t lastUpdate); - uint16_t getFwBuildVersion(); - time_t getFwBuildDateTime(); - uint16_t getFwBootloaderVersion(); + uint16_t getFwBuildVersion() const; + time_t getFwBuildDateTime() const; + uint16_t getFwBootloaderVersion() const; - uint32_t getHwPartNumber(); - String getHwVersion(); + uint32_t getHwPartNumber() const; + String getHwVersion() const; - uint16_t getMaxPower(); - String getHwModelName(); + uint16_t getMaxPower() const; + String getHwModelName() const; - bool containsValidData(); + bool containsValidData() const; private: - time_t timegm(struct tm* tm); - uint8_t getDevIdx(); + static time_t timegm(const struct tm* tm); + uint8_t getDevIdx() const; uint32_t _lastUpdateAll = 0; uint32_t _lastUpdateSimple = 0; diff --git a/lib/Hoymiles/src/parser/GridProfileParser.cpp b/lib/Hoymiles/src/parser/GridProfileParser.cpp index 35f7689d..da1c8060 100644 --- a/lib/Hoymiles/src/parser/GridProfileParser.cpp +++ b/lib/Hoymiles/src/parser/GridProfileParser.cpp @@ -5,6 +5,315 @@ #include "GridProfileParser.h" #include "../Hoymiles.h" #include +#include +#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 - EU_EN50438" }, + { 0x37, 0x00, "Swiss - CH_NA EEA-NE7-CH2020" }, +} }; + +constexpr frozen::map profileSection = { + { 0x00, "Voltage (H/LVRT)" }, + { 0x10, "Frequency (H/LFRT)" }, + { 0x20, "Island Detection (ID)" }, + { 0x30, "Reconnection (RT)" }, + { 0x40, "Ramp Rates (RR)" }, + { 0x50, "Frequency Watt (FW)" }, + { 0x60, "Volt Watt (VW)" }, + { 0x70, "Active Power Control (APC)" }, + { 0x80, "Volt Var (VV)" }, + { 0x90, "Specified Power Factor (SPF)" }, + { 0xA0, "Reactive Power Control (RPC)" }, + { 0xB0, "Watt Power Factor (WPF)" }, +}; + +struct GridProfileItemDefinition_t { + frozen::string Name; + frozen::string Unit; + uint8_t Divider; +}; + +constexpr GridProfileItemDefinition_t make_value(frozen::string Name, frozen::string Unit, uint8_t divisor) +{ + GridProfileItemDefinition_t v = { Name, Unit, divisor }; + return v; +} + +constexpr frozen::map itemDefinitions = { + { 0x01, make_value("Nominale Voltage (NV)", "V", 10) }, + { 0x02, make_value("Low Voltage 1 (LV1)", "V", 10) }, + { 0x03, make_value("LV1 Maximum Trip Time (MTT)", "s", 10) }, + { 0x04, make_value("High Voltage 1 (HV1)", "V", 10) }, + { 0x05, make_value("HV1 Maximum Trip Time (MTT)", "s", 10) }, + { 0x06, make_value("Low Voltage 2 (LV2)", "V", 10) }, + { 0x07, make_value("LV2 Maximum Trip Time (MTT)", "s", 10) }, + { 0x08, make_value("High Voltage 2 (HV2)", "V", 10) }, + { 0x09, make_value("HV2 Maximum Trip Time (MTT)", "s", 10) }, + { 0x0a, make_value("10mins Average High Voltage (AHV)", "V", 10) }, + { 0x0b, make_value("High Voltage 3 (HV3)", "V", 10) }, + { 0x0c, make_value("HV3 Maximum Trip Time (MTT)", "s", 10) }, + { 0x0d, make_value("Nominal Frequency", "Hz", 100) }, + { 0x0e, make_value("Low Frequency 1 (LF1)", "Hz", 100) }, + { 0x0f, make_value("LF1 Maximum Trip Time (MTT)", "s", 10) }, + { 0x10, make_value("High Frequency 1 (HF1)", "Hz", 100) }, + { 0x11, make_value("HF1 Maximum Trip time (MTT)", "s", 10) }, + { 0x12, make_value("Low Frequency 2 (LF2)", "Hz", 100) }, + { 0x13, make_value("LF2 Maximum Trip Time (MTT)", "s", 10) }, + { 0x14, make_value("High Frequency 2 (HF2)", "Hz", 100) }, + { 0x15, make_value("HF2 Maximum Trip time (MTT)", "s", 10) }, + { 0x16, make_value("ID Function Activated", "bool", 1) }, + { 0x17, make_value("Reconnect Time (RT)", "s", 10) }, + { 0x18, make_value("Reconnect High Voltage (RHV)", "V", 10) }, + { 0x19, make_value("Reconnect Low Voltage (RLV)", "V", 10) }, + { 0x1a, make_value("Reconnect High Frequency (RHF)", "Hz", 100) }, + { 0x1b, make_value("Reconnect Low Frequency (RLF)", "Hz", 100) }, + { 0x1c, make_value("Normal Ramp up Rate(RUR_NM)", "Rated%/s", 100) }, + { 0x1d, make_value("Soft Start Ramp up Rate (RUR_SS)", "Rated%/s", 100) }, + { 0x1e, make_value("FW Function Activated", "bool", 1) }, + { 0x1f, make_value("Start of Frequency Watt Droop (Fstart)", "Hz", 100) }, + { 0x20, make_value("FW Droop Slope (Kpower_Freq)", "Pn%/Hz", 10) }, + { 0x21, make_value("Recovery Ramp Rate (RRR)", "Pn%/s", 100) }, + { 0x22, make_value("Recovery High Frequency (RVHF)", "Hz", 100) }, + { 0x23, make_value("Recovery Low Frequency (RVLF)", "Hz", 100) }, + { 0x24, make_value("VW Function Activated", "bool", 1) }, + { 0x25, make_value("Start of Voltage Watt Droop (Vstart)", "V", 10) }, + { 0x26, make_value("End of Voltage Watt Droop (Vend)", "V", 10) }, + { 0x27, make_value("Droop Slope (Kpower_Volt)", "Pn%/V", 100) }, + { 0x28, make_value("APC Function Activated", "bool", 1) }, + { 0x29, make_value("Power Ramp Rate (PRR)", "Pn%/s", 100) }, + { 0x2a, make_value("VV Function Activated", "bool", 1) }, + { 0x2b, make_value("Voltage Set Point V1", "V", 10) }, + { 0x2c, make_value("Reactive Set Point Q1", "%Pn", 10) }, + { 0x2d, make_value("Voltage Set Point V2", "V", 10) }, + { 0x2e, make_value("Voltage Set Point V3", "V", 10) }, + { 0x2f, make_value("Voltage Set Point V4", "V", 10) }, + { 0x30, make_value("Reactive Set Point Q4", "%Pn", 10) }, + { 0x31, make_value("Setting Time (Tr)", "s", 10) }, + { 0x32, make_value("SPF Function Activated", "bool", 1) }, + { 0x33, make_value("Power Factor (PF)", "", 100) }, + { 0x34, make_value("RPC Function Activated", "bool", 1) }, + { 0x35, make_value("Reactive Power (VAR)", "%Sn", 1) }, + { 0x36, make_value("WPF Function Activated", "bool", 1) }, + { 0x37, make_value("Start of Power of WPF (Pstart)", "%Pn", 10) }, + { 0x38, make_value("Power Factor ar Rated Power (PFRP)", "", 100) }, + { 0xff, make_value("Unkown Value", "", 1) }, +}; + +const std::array GridProfileParser::_profileValues = { { + // Voltage (H/LVRT) + // Version 0x00 + { 0x00, 0x00, 0x01 }, + { 0x00, 0x00, 0x02 }, + { 0x00, 0x00, 0x03 }, + { 0x00, 0x00, 0x04 }, + { 0x00, 0x00, 0x05 }, + + // Version 0x03 + { 0x00, 0x03, 0x01 }, + { 0x00, 0x03, 0x02 }, + { 0x00, 0x03, 0x03 }, + { 0x00, 0x03, 0x05 }, + { 0x00, 0x03, 0x06 }, + { 0x00, 0x03, 0x07 }, + { 0x00, 0x03, 0x08 }, + { 0x00, 0x03, 0x09 }, + + // Version 0x08 + { 0x00, 0x08, 0x01 }, + { 0x00, 0x08, 0x02 }, + { 0x00, 0x08, 0x03 }, + { 0x00, 0x08, 0x04 }, + { 0x00, 0x08, 0x05 }, + { 0x00, 0x08, 0xff }, + + // Version 0x0a + { 0x00, 0x0a, 0x01 }, + { 0x00, 0x0a, 0x02 }, + { 0x00, 0x0a, 0x03 }, + { 0x00, 0x0a, 0x04 }, + { 0x00, 0x0a, 0x05 }, + { 0x00, 0x0a, 0x06 }, + { 0x00, 0x0a, 0x07 }, + { 0x00, 0x0a, 0x0a }, + + // Version 0x0b + { 0x00, 0x0b, 0x01 }, + { 0x00, 0x0b, 0x02 }, + { 0x00, 0x0b, 0x03 }, + { 0x00, 0x0b, 0x04 }, + { 0x00, 0x0b, 0x05 }, + { 0x00, 0x0b, 0x06 }, + { 0x00, 0x0b, 0x07 }, + { 0x00, 0x0b, 0x08 }, + { 0x00, 0x0b, 0x09 }, + { 0x00, 0x0b, 0x0a }, + + // Version 0x0c + { 0x00, 0x0c, 0x01 }, + { 0x00, 0x0c, 0x02 }, + { 0x00, 0x0c, 0x03 }, + { 0x00, 0x0c, 0x04 }, + { 0x00, 0x0c, 0x05 }, + { 0x00, 0x0c, 0x06 }, + { 0x00, 0x0c, 0x07 }, + { 0x00, 0x0c, 0x08 }, + { 0x00, 0x0c, 0x09 }, + { 0x00, 0x0c, 0x0b }, + { 0x00, 0x0c, 0x0c }, + { 0x00, 0x0c, 0x0a }, + + // Version 0x35 + { 0x00, 0x35, 0x01 }, + { 0x00, 0x35, 0x02 }, + { 0x00, 0x35, 0x03 }, + { 0x00, 0x35, 0x04 }, + { 0x00, 0x35, 0x05 }, + { 0x00, 0x35, 0x06 }, + { 0x00, 0x35, 0x07 }, + { 0x00, 0x35, 0x08 }, + { 0x00, 0x35, 0x09 }, + { 0x00, 0x35, 0xff }, + { 0x00, 0x35, 0xff }, + { 0x00, 0x35, 0xff }, + { 0x00, 0x35, 0xff }, + + // Frequency (H/LFRT) + // Version 0x00 + { 0x10, 0x00, 0x0d }, + { 0x10, 0x00, 0x0e }, + { 0x10, 0x00, 0x0f }, + { 0x10, 0x00, 0x10 }, + { 0x10, 0x00, 0x11 }, + + // Version 0x03 + { 0x10, 0x03, 0x0d }, + { 0x10, 0x03, 0x0e }, + { 0x10, 0x03, 0x0f }, + { 0x10, 0x03, 0x10 }, + { 0x10, 0x03, 0x11 }, + { 0x10, 0x03, 0x12 }, + { 0x10, 0x03, 0x13 }, + { 0x10, 0x03, 0x14 }, + { 0x10, 0x03, 0x15 }, + + // Island Detection (ID) + // Version 0x00 + { 0x20, 0x00, 0x16 }, + + // Reconnection (RT) + // Version 0x03 + { 0x30, 0x03, 0x17 }, + { 0x30, 0x03, 0x18 }, + { 0x30, 0x03, 0x19 }, + { 0x30, 0x03, 0x1a }, + { 0x30, 0x03, 0x1b }, + + // Version 0x07 + { 0x30, 0x07, 0x17 }, + { 0x30, 0x07, 0x18 }, + { 0x30, 0x07, 0x19 }, + { 0x30, 0x07, 0x1a }, + { 0x30, 0x07, 0x1b }, + { 0x30, 0x07, 0xff }, + { 0x30, 0x07, 0xff }, + + // Ramp Rates (RR) + // Version 0x00 + { 0x40, 0x00, 0x1c }, + { 0x40, 0x00, 0x1d }, + + // Frequency Watt (FW) + // Version 0x00 + { 0x50, 0x00, 0x1e }, + { 0x50, 0x00, 0x1f }, + { 0x50, 0x00, 0x20 }, + { 0x50, 0x00, 0x21 }, + + // Version 0x01 + { 0x50, 0x01, 0x1e }, + { 0x50, 0x01, 0x1f }, + { 0x50, 0x01, 0x20 }, + { 0x50, 0x01, 0x21 }, + { 0x50, 0x01, 0x22 }, + + // Version 0x08 + { 0x50, 0x08, 0x1e }, + { 0x50, 0x08, 0x1f }, + { 0x50, 0x08, 0x20 }, + { 0x50, 0x08, 0x21 }, + { 0x50, 0x08, 0x22 }, + { 0x50, 0x08, 0x23 }, + + // Version 0x11 + { 0x50, 0x11, 0x1e }, + { 0x50, 0x11, 0x1f }, + { 0x50, 0x11, 0x20 }, + { 0x50, 0x11, 0x21 }, + { 0x50, 0x11, 0x22 }, + + // Volt Watt (VW) + // Version 0x00 + { 0x60, 0x00, 0x24 }, + { 0x60, 0x00, 0x25 }, + { 0x60, 0x00, 0x26 }, + { 0x60, 0x00, 0x27 }, + + // Version 0x04 + { 0x60, 0x04, 0x24 }, + { 0x60, 0x04, 0x25 }, + { 0x60, 0x04, 0x26 }, + { 0x60, 0x04, 0x27 }, + + // Active Power Control (APC) + // Version 0x00 + { 0x70, 0x00, 0x28 }, + + // Version 0x02 + { 0x70, 0x02, 0x28 }, + { 0x70, 0x02, 0x29 }, + + // Volt Var (VV) + // Version 0x00 + { 0x80, 0x00, 0x2a }, + { 0x80, 0x00, 0x2b }, + { 0x80, 0x00, 0x2c }, + { 0x80, 0x00, 0x2d }, + { 0x80, 0x00, 0x2e }, + { 0x80, 0x00, 0x2f }, + { 0x80, 0x00, 0x30 }, + + // Version 0x01 + { 0x80, 0x01, 0x2a }, + { 0x80, 0x01, 0x2b }, + { 0x80, 0x01, 0x2c }, + { 0x80, 0x01, 0x2d }, + { 0x80, 0x01, 0x2e }, + { 0x80, 0x01, 0x2f }, + { 0x80, 0x01, 0x30 }, + { 0x80, 0x01, 0x31 }, + + // Specified Power Factor (SPF) + // Version 0x00 + { 0x90, 0x00, 0x32 }, + { 0x90, 0x00, 0x33 }, + + // Reactive Power Control (RPC) + // Version 0x02 + { 0xa0, 0x02, 0x34 }, + { 0xa0, 0x02, 0x35 }, + + // Watt Power Factor (WPF) + // Version 0x00 + { 0xb0, 0x00, 0x36 }, + { 0xb0, 0x00, 0x37 }, + { 0xb0, 0x00, 0x38 }, +} }; GridProfileParser::GridProfileParser() : Parser() @@ -18,7 +327,7 @@ void GridProfileParser::clearBuffer() _gridProfileLength = 0; } -void GridProfileParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len) +void GridProfileParser::appendFragment(const uint8_t offset, const uint8_t* payload, const uint8_t len) { if (offset + len > GRID_PROFILE_SIZE) { Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) grid profile packet too large for buffer\r\n", __FILE__, __LINE__); @@ -28,7 +337,26 @@ void GridProfileParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t _gridProfileLength += len; } -std::vector GridProfileParser::getRawData() +String GridProfileParser::getProfileName() const +{ + for (auto& ptype : _profileTypes) { + if (ptype.lIdx == _payloadGridProfile[0] && ptype.hIdx == _payloadGridProfile[1]) { + return ptype.Name; + } + } + return "Unknown"; +} + +String GridProfileParser::getProfileVersion() const +{ + char buffer[10]; + HOY_SEMAPHORE_TAKE(); + snprintf(buffer, sizeof(buffer), "%d.%d.%d", (_payloadGridProfile[2] >> 4) & 0x0f, _payloadGridProfile[2] & 0x0f, _payloadGridProfile[3]); + HOY_SEMAPHORE_GIVE(); + return buffer; +} + +std::vector GridProfileParser::getRawData() const { std::vector ret; HOY_SEMAPHORE_TAKE(); @@ -38,3 +366,75 @@ std::vector GridProfileParser::getRawData() HOY_SEMAPHORE_GIVE(); return ret; } + +std::list GridProfileParser::getProfile() const +{ + std::list l; + + if (_gridProfileLength > 4) { + uint16_t pos = 4; + do { + const uint8_t section_id = _payloadGridProfile[pos]; + const uint8_t section_version = _payloadGridProfile[pos + 1]; + const int16_t section_start = getSectionStart(section_id, section_version); + const uint8_t section_size = getSectionSize(section_id, section_version); + pos += 2; + + GridProfileSection_t section; + try { + section.SectionName = profileSection.at(section_id).data(); + } catch (const std::out_of_range&) { + section.SectionName = "Unknown"; + break; + } + + if (section_start == -1) { + section.SectionName = "Unknown"; + break; + } + + for (uint8_t val_id = 0; val_id < section_size; val_id++) { + auto itemDefinition = itemDefinitions.at(_profileValues[section_start + val_id].ItemDefinition); + + float value = (int16_t)((_payloadGridProfile[pos] << 8) | _payloadGridProfile[pos + 1]); + value /= itemDefinition.Divider; + + GridProfileItem_t v; + v.Name = itemDefinition.Name.data(); + v.Unit = itemDefinition.Unit.data(); + v.Value = value; + section.items.push_back(v); + + pos += 2; + } + + l.push_back(section); + + } while (pos < _gridProfileLength); + } + + return l; +} + +uint8_t GridProfileParser::getSectionSize(const uint8_t section_id, const uint8_t section_version) +{ + uint8_t count = 0; + for (auto& values : _profileValues) { + if (values.Section == section_id && values.Version == section_version) { + count++; + } + } + return count; +} + +int16_t GridProfileParser::getSectionStart(const uint8_t section_id, const uint8_t section_version) +{ + int16_t count = -1; + for (auto& values : _profileValues) { + count++; + if (values.Section == section_id && values.Version == section_version) { + break; + } + } + return count; +} diff --git a/lib/Hoymiles/src/parser/GridProfileParser.h b/lib/Hoymiles/src/parser/GridProfileParser.h index c2af52f8..031891f3 100644 --- a/lib/Hoymiles/src/parser/GridProfileParser.h +++ b/lib/Hoymiles/src/parser/GridProfileParser.h @@ -1,18 +1,55 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "Parser.h" +#include #define GRID_PROFILE_SIZE 141 +#define PROFILE_TYPE_COUNT 7 +#define SECTION_VALUE_COUNT 144 + +typedef struct { + uint8_t lIdx; + uint8_t hIdx; + const char* Name; +} ProfileType_t; + +struct GridProfileValue_t { + uint8_t Section; + uint8_t Version; + uint8_t ItemDefinition; +}; + +struct GridProfileItem_t { + String Name; + String Unit; + float Value; +}; + +struct GridProfileSection_t { + String SectionName; + std::list items; +}; class GridProfileParser : public Parser { public: GridProfileParser(); void clearBuffer(); - void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); + void appendFragment(const uint8_t offset, const uint8_t* payload, const uint8_t len); - std::vector getRawData(); + String getProfileName() const; + String getProfileVersion() const; + + std::vector getRawData() const; + + std::list getProfile() const; private: + static uint8_t getSectionSize(const uint8_t section_id, const uint8_t section_version); + static int16_t getSectionStart(const uint8_t section_id, const uint8_t section_version); + uint8_t _payloadGridProfile[GRID_PROFILE_SIZE] = {}; uint8_t _gridProfileLength = 0; + + static const std::array _profileTypes; + static const std::array _profileValues; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/Parser.cpp b/lib/Hoymiles/src/parser/Parser.cpp index b8e5e0e5..96681ce2 100644 --- a/lib/Hoymiles/src/parser/Parser.cpp +++ b/lib/Hoymiles/src/parser/Parser.cpp @@ -10,12 +10,12 @@ Parser::Parser() HOY_SEMAPHORE_GIVE(); // release before first use } -uint32_t Parser::getLastUpdate() +uint32_t Parser::getLastUpdate() const { return _lastUpdate; } -void Parser::setLastUpdate(uint32_t lastUpdate) +void Parser::setLastUpdate(const uint32_t lastUpdate) { _lastUpdate = lastUpdate; } diff --git a/lib/Hoymiles/src/parser/Parser.h b/lib/Hoymiles/src/parser/Parser.h index 5d6df75d..dda0ef8a 100644 --- a/lib/Hoymiles/src/parser/Parser.h +++ b/lib/Hoymiles/src/parser/Parser.h @@ -17,8 +17,8 @@ typedef enum { class Parser { public: Parser(); - uint32_t getLastUpdate(); - void setLastUpdate(uint32_t lastUpdate); + uint32_t getLastUpdate() const; + void setLastUpdate(const uint32_t lastUpdate); void beginAppendFragment(); void endAppendFragment(); diff --git a/lib/Hoymiles/src/parser/PowerCommandParser.cpp b/lib/Hoymiles/src/parser/PowerCommandParser.cpp index d698dad8..dc8dc797 100644 --- a/lib/Hoymiles/src/parser/PowerCommandParser.cpp +++ b/lib/Hoymiles/src/parser/PowerCommandParser.cpp @@ -4,22 +4,22 @@ */ #include "PowerCommandParser.h" -void PowerCommandParser::setLastPowerCommandSuccess(LastCommandSuccess status) +void PowerCommandParser::setLastPowerCommandSuccess(const LastCommandSuccess status) { _lastLimitCommandSuccess = status; } -LastCommandSuccess PowerCommandParser::getLastPowerCommandSuccess() +LastCommandSuccess PowerCommandParser::getLastPowerCommandSuccess() const { return _lastLimitCommandSuccess; } -uint32_t PowerCommandParser::getLastUpdateCommand() +uint32_t PowerCommandParser::getLastUpdateCommand() const { return _lastUpdateCommand; } -void PowerCommandParser::setLastUpdateCommand(uint32_t lastUpdate) +void PowerCommandParser::setLastUpdateCommand(const uint32_t lastUpdate) { _lastUpdateCommand = lastUpdate; setLastUpdate(lastUpdate); diff --git a/lib/Hoymiles/src/parser/PowerCommandParser.h b/lib/Hoymiles/src/parser/PowerCommandParser.h index e005812e..b448692e 100644 --- a/lib/Hoymiles/src/parser/PowerCommandParser.h +++ b/lib/Hoymiles/src/parser/PowerCommandParser.h @@ -4,10 +4,10 @@ class PowerCommandParser : public Parser { public: - void setLastPowerCommandSuccess(LastCommandSuccess status); - LastCommandSuccess getLastPowerCommandSuccess(); - uint32_t getLastUpdateCommand(); - void setLastUpdateCommand(uint32_t lastUpdate); + void setLastPowerCommandSuccess(const LastCommandSuccess status); + LastCommandSuccess getLastPowerCommandSuccess() const; + uint32_t getLastUpdateCommand() const; + void setLastUpdateCommand(const uint32_t lastUpdate); private: LastCommandSuccess _lastLimitCommandSuccess = CMD_OK; // Set to OK because we have to assume nothing is done at startup diff --git a/lib/Hoymiles/src/parser/StatisticsParser.cpp b/lib/Hoymiles/src/parser/StatisticsParser.cpp index 71c1ebbd..831c1ad1 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.cpp +++ b/lib/Hoymiles/src/parser/StatisticsParser.cpp @@ -60,7 +60,7 @@ StatisticsParser::StatisticsParser() clearBuffer(); } -void StatisticsParser::setByteAssignment(const byteAssign_t* byteAssignment, uint8_t size) +void StatisticsParser::setByteAssignment(const byteAssign_t* byteAssignment, const uint8_t size) { _byteAssignment = byteAssignment; _byteAssignmentSize = size; @@ -84,7 +84,7 @@ void StatisticsParser::clearBuffer() _statisticLength = 0; } -void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len) +void StatisticsParser::appendFragment(const uint8_t offset, const uint8_t* payload, const uint8_t len) { if (offset + len > STATISTIC_PACKET_SIZE) { Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) stats packet too large for buffer\r\n", __FILE__, __LINE__); @@ -94,38 +94,60 @@ void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t _statisticLength += len; } -const byteAssign_t* StatisticsParser::getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) +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, _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(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) const { for (uint8_t i = 0; i < _byteAssignmentSize; i++) { if (_byteAssignment[i].type == type && _byteAssignment[i].ch == channel && _byteAssignment[i].fieldId == fieldId) { return &_byteAssignment[i]; } } - return NULL; + return nullptr; } -fieldSettings_t* StatisticsParser::getSettingByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) +fieldSettings_t* StatisticsParser::getSettingByChannelField(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) { for (auto& i : _fieldSettings) { if (i.type == type && i.ch == channel && i.fieldId == fieldId) { return &i; } } - return NULL; + return nullptr; } -float StatisticsParser::getChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) +float StatisticsParser::getChannelFieldValue(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) { const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId); - fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId); - - if (pos == NULL) { + if (pos == nullptr) { 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 @@ -147,7 +169,9 @@ float StatisticsParser::getChannelFieldValue(ChannelType_t type, ChannelNum_t ch } result /= static_cast(div); - if (setting != NULL && _statisticLength > 0) { + + const fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId); + if (setting != nullptr && _statisticLength > 0) { result += setting->offset; } return result; @@ -159,24 +183,23 @@ float StatisticsParser::getChannelFieldValue(ChannelType_t type, ChannelNum_t ch return 0; } -bool StatisticsParser::setChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float value) +bool StatisticsParser::setChannelFieldValue(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, float value) { const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId); - fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId); - - if (pos == NULL) { + if (pos == nullptr) { 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; } - if (setting != NULL) { + const fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId); + if (setting != nullptr) { value -= setting->offset; } value *= static_cast(div); @@ -200,57 +223,57 @@ bool StatisticsParser::setChannelFieldValue(ChannelType_t type, ChannelNum_t cha return true; } -String StatisticsParser::getChannelFieldValueString(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) +String StatisticsParser::getChannelFieldValueString(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) { return String( getChannelFieldValue(type, channel, fieldId), static_cast(getChannelFieldDigits(type, channel, fieldId))); } -bool StatisticsParser::hasChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) +bool StatisticsParser::hasChannelFieldValue(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) const { const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId); - return pos != NULL; + return pos != nullptr; } -const char* StatisticsParser::getChannelFieldUnit(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) +const char* StatisticsParser::getChannelFieldUnit(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) const { const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId); return units[pos->unitId]; } -const char* StatisticsParser::getChannelFieldName(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) +const char* StatisticsParser::getChannelFieldName(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) const { const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId); return fields[pos->fieldId]; } -uint8_t StatisticsParser::getChannelFieldDigits(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) +uint8_t StatisticsParser::getChannelFieldDigits(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) const { const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId); return pos->digits; } -float StatisticsParser::getChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) +float StatisticsParser::getChannelFieldOffset(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) { - fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId); - if (setting != NULL) { + const fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId); + if (setting != nullptr) { return setting->offset; } return 0; } -void StatisticsParser::setChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float offset) +void StatisticsParser::setChannelFieldOffset(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, const float offset) { fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId); - if (setting != NULL) { + if (setting != nullptr) { setting->offset = offset; } else { _fieldSettings.push_back({ type, channel, fieldId, offset }); } } -std::list StatisticsParser::getChannelTypes() +std::list StatisticsParser::getChannelTypes() const { return { TYPE_AC, @@ -259,12 +282,12 @@ std::list StatisticsParser::getChannelTypes() }; } -const char* StatisticsParser::getChannelTypeName(ChannelType_t type) +const char* StatisticsParser::getChannelTypeName(const ChannelType_t type) const { return channelsTypes[type]; } -std::list StatisticsParser::getChannelsByType(ChannelType_t type) +std::list StatisticsParser::getChannelsByType(const ChannelType_t type) const { std::list l; for (uint8_t i = 0; i < _byteAssignmentSize; i++) { @@ -276,12 +299,12 @@ std::list StatisticsParser::getChannelsByType(ChannelType_t type) return l; } -uint16_t StatisticsParser::getStringMaxPower(uint8_t channel) +uint16_t StatisticsParser::getStringMaxPower(const uint8_t channel) const { return _stringMaxPower[channel]; } -void StatisticsParser::setStringMaxPower(uint8_t channel, uint16_t power) +void StatisticsParser::setStringMaxPower(const uint8_t channel, const uint16_t power) { if (channel < sizeof(_stringMaxPower) / sizeof(_stringMaxPower[0])) { _stringMaxPower[channel] = power; @@ -298,7 +321,7 @@ void StatisticsParser::incrementRxFailureCount() _rxFailureCount++; } -uint32_t StatisticsParser::getRxFailureCount() +uint32_t StatisticsParser::getRxFailureCount() const { return _rxFailureCount; } @@ -313,22 +336,32 @@ void StatisticsParser::zeroDailyData() zeroFields(dailyProductionFields); } -void StatisticsParser::setLastUpdate(uint32_t lastUpdate) +void StatisticsParser::setLastUpdate(const uint32_t lastUpdate) { Parser::setLastUpdate(lastUpdate); setLastUpdateFromInternal(lastUpdate); } -uint32_t StatisticsParser::getLastUpdateFromInternal() +uint32_t StatisticsParser::getLastUpdateFromInternal() const { return _lastUpdateFromInternal; } -void StatisticsParser::setLastUpdateFromInternal(uint32_t lastUpdate) +void StatisticsParser::setLastUpdateFromInternal(const uint32_t lastUpdate) { _lastUpdateFromInternal = lastUpdate; } +bool StatisticsParser::getYieldDayCorrection() const +{ + return _enableYieldDayCorrection; +} + +void StatisticsParser::setYieldDayCorrection(const bool enabled) +{ + _enableYieldDayCorrection = enabled; +} + void StatisticsParser::zeroFields(const FieldId_t* fields) { // Loop all channels @@ -344,6 +377,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; @@ -399,7 +441,7 @@ static float calcEffiencyCh0(StatisticsParser* iv, uint8_t arg0) // arg0 = channel static float calcIrradiation(StatisticsParser* iv, uint8_t arg0) { - if (NULL != iv) { + if (nullptr != iv) { if (iv->getStringMaxPower(arg0) > 0) return iv->getChannelFieldValue(TYPE_DC, static_cast(arg0), FLD_PDC) / iv->getStringMaxPower(arg0) * 100.0f; } diff --git a/lib/Hoymiles/src/parser/StatisticsParser.h b/lib/Hoymiles/src/parser/StatisticsParser.h index da291004..10f06e04 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.h +++ b/lib/Hoymiles/src/parser/StatisticsParser.h @@ -105,49 +105,53 @@ class StatisticsParser : public Parser { public: StatisticsParser(); void clearBuffer(); - void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); + void appendFragment(const uint8_t offset, const uint8_t* payload, const uint8_t len); + void endAppendFragment(); - void setByteAssignment(const byteAssign_t* byteAssignment, uint8_t size); + void setByteAssignment(const byteAssign_t* byteAssignment, const uint8_t size); // Returns 1 based amount of expected bytes of statistic data uint8_t getExpectedByteCount(); - const byteAssign_t* getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); - fieldSettings_t* getSettingByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); + const byteAssign_t* getAssignmentByChannelField(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) const; + fieldSettings_t* getSettingByChannelField(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId); - float getChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); - String getChannelFieldValueString(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); - bool hasChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); - const char* getChannelFieldUnit(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); - const char* getChannelFieldName(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); - uint8_t getChannelFieldDigits(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); + float getChannelFieldValue(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId); + String getChannelFieldValueString(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId); + bool hasChannelFieldValue(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) const; + const char* getChannelFieldUnit(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) const; + const char* getChannelFieldName(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) const; + uint8_t getChannelFieldDigits(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) const; - bool setChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float value); + bool setChannelFieldValue(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, float value); - float getChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); - void setChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float offset); + float getChannelFieldOffset(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId); + void setChannelFieldOffset(const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, const float offset); - std::list getChannelTypes(); - const char* getChannelTypeName(ChannelType_t type); - std::list getChannelsByType(ChannelType_t type); + std::list getChannelTypes() const; + const char* getChannelTypeName(const ChannelType_t type) const; + std::list getChannelsByType(const ChannelType_t type) const; - uint16_t getStringMaxPower(uint8_t channel); - void setStringMaxPower(uint8_t channel, uint16_t power); + uint16_t getStringMaxPower(const uint8_t channel) const; + void setStringMaxPower(const uint8_t channel, const uint16_t power); void resetRxFailureCount(); void incrementRxFailureCount(); - uint32_t getRxFailureCount(); + uint32_t getRxFailureCount() const; void zeroRuntimeData(); void zeroDailyData(); + void resetYieldDayCorrection(); // Update time when new data from the inverter is received - void setLastUpdate(uint32_t lastUpdate); + void setLastUpdate(const uint32_t lastUpdate); // Update time when internal data structure changes (from inverter and by internal manipulation) - uint32_t getLastUpdateFromInternal(); - void setLastUpdateFromInternal(uint32_t lastUpdate); + uint32_t getLastUpdateFromInternal() const; + void setLastUpdateFromInternal(const uint32_t lastUpdate); + bool getYieldDayCorrection() const; + void setYieldDayCorrection(const 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/lib/Hoymiles/src/parser/SystemConfigParaParser.cpp b/lib/Hoymiles/src/parser/SystemConfigParaParser.cpp index d1ed30b6..e866e874 100644 --- a/lib/Hoymiles/src/parser/SystemConfigParaParser.cpp +++ b/lib/Hoymiles/src/parser/SystemConfigParaParser.cpp @@ -18,7 +18,7 @@ void SystemConfigParaParser::clearBuffer() _payloadLength = 0; } -void SystemConfigParaParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len) +void SystemConfigParaParser::appendFragment(const uint8_t offset, const uint8_t* payload, const uint8_t len) { if (offset + len > (SYSTEM_CONFIG_PARA_SIZE)) { Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) stats packet too large for buffer\r\n", __FILE__, __LINE__); @@ -28,15 +28,15 @@ void SystemConfigParaParser::appendFragment(uint8_t offset, uint8_t* payload, ui _payloadLength += len; } -float SystemConfigParaParser::getLimitPercent() +float SystemConfigParaParser::getLimitPercent() const { HOY_SEMAPHORE_TAKE(); - float ret = ((((uint16_t)_payload[2]) << 8) | _payload[3]) / 10.0; + const float ret = ((((uint16_t)_payload[2]) << 8) | _payload[3]) / 10.0; HOY_SEMAPHORE_GIVE(); return ret; } -void SystemConfigParaParser::setLimitPercent(float value) +void SystemConfigParaParser::setLimitPercent(const float value) { HOY_SEMAPHORE_TAKE(); _payload[2] = ((uint16_t)(value * 10)) >> 8; @@ -44,49 +44,49 @@ void SystemConfigParaParser::setLimitPercent(float value) HOY_SEMAPHORE_GIVE(); } -void SystemConfigParaParser::setLastLimitCommandSuccess(LastCommandSuccess status) +void SystemConfigParaParser::setLastLimitCommandSuccess(const LastCommandSuccess status) { _lastLimitCommandSuccess = status; } -LastCommandSuccess SystemConfigParaParser::getLastLimitCommandSuccess() +LastCommandSuccess SystemConfigParaParser::getLastLimitCommandSuccess() const { return _lastLimitCommandSuccess; } -uint32_t SystemConfigParaParser::getLastUpdateCommand() +uint32_t SystemConfigParaParser::getLastUpdateCommand() const { return _lastUpdateCommand; } -void SystemConfigParaParser::setLastUpdateCommand(uint32_t lastUpdate) +void SystemConfigParaParser::setLastUpdateCommand(const uint32_t lastUpdate) { _lastUpdateCommand = lastUpdate; setLastUpdate(lastUpdate); } -void SystemConfigParaParser::setLastLimitRequestSuccess(LastCommandSuccess status) +void SystemConfigParaParser::setLastLimitRequestSuccess(const LastCommandSuccess status) { _lastLimitRequestSuccess = status; } -LastCommandSuccess SystemConfigParaParser::getLastLimitRequestSuccess() +LastCommandSuccess SystemConfigParaParser::getLastLimitRequestSuccess() const { return _lastLimitRequestSuccess; } -uint32_t SystemConfigParaParser::getLastUpdateRequest() +uint32_t SystemConfigParaParser::getLastUpdateRequest() const { return _lastUpdateRequest; } -void SystemConfigParaParser::setLastUpdateRequest(uint32_t lastUpdate) +void SystemConfigParaParser::setLastUpdateRequest(const uint32_t lastUpdate) { _lastUpdateRequest = lastUpdate; setLastUpdate(lastUpdate); } -uint8_t SystemConfigParaParser::getExpectedByteCount() +uint8_t SystemConfigParaParser::getExpectedByteCount() const { return SYSTEM_CONFIG_PARA_SIZE; } diff --git a/lib/Hoymiles/src/parser/SystemConfigParaParser.h b/lib/Hoymiles/src/parser/SystemConfigParaParser.h index 300a8182..847a5d1d 100644 --- a/lib/Hoymiles/src/parser/SystemConfigParaParser.h +++ b/lib/Hoymiles/src/parser/SystemConfigParaParser.h @@ -8,23 +8,23 @@ class SystemConfigParaParser : public Parser { public: SystemConfigParaParser(); void clearBuffer(); - void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); + void appendFragment(const uint8_t offset, const uint8_t* payload, const uint8_t len); - float getLimitPercent(); - void setLimitPercent(float value); + float getLimitPercent() const; + void setLimitPercent(const float value); - void setLastLimitCommandSuccess(LastCommandSuccess status); - LastCommandSuccess getLastLimitCommandSuccess(); - uint32_t getLastUpdateCommand(); - void setLastUpdateCommand(uint32_t lastUpdate); + void setLastLimitCommandSuccess(const LastCommandSuccess status); + LastCommandSuccess getLastLimitCommandSuccess() const; + uint32_t getLastUpdateCommand() const; + void setLastUpdateCommand(const uint32_t lastUpdate); - void setLastLimitRequestSuccess(LastCommandSuccess status); - LastCommandSuccess getLastLimitRequestSuccess(); - uint32_t getLastUpdateRequest(); - void setLastUpdateRequest(uint32_t lastUpdate); + void setLastLimitRequestSuccess(const LastCommandSuccess status); + LastCommandSuccess getLastLimitRequestSuccess() const; + uint32_t getLastUpdateRequest() const; + void setLastUpdateRequest(const uint32_t lastUpdate); // Returns 1 based amount of expected bytes of data - uint8_t getExpectedByteCount(); + uint8_t getExpectedByteCount() const; private: uint8_t _payload[SYSTEM_CONFIG_PARA_SIZE]; diff --git a/lib/ResetReason/src/ResetReason.cpp b/lib/ResetReason/src/ResetReason.cpp index b00dab79..52367d4a 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(const 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(const 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..0238cab2 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(const uint8_t cpu_id); + static String get_reset_reason_short(const uint8_t cpu_id); +}; \ No newline at end of file diff --git a/lib/TimeoutHelper/TimeoutHelper.cpp b/lib/TimeoutHelper/TimeoutHelper.cpp index 975a9bba..3f00c2bc 100644 --- a/lib/TimeoutHelper/TimeoutHelper.cpp +++ b/lib/TimeoutHelper/TimeoutHelper.cpp @@ -11,13 +11,13 @@ TimeoutHelper::TimeoutHelper() startMillis = 0; } -void TimeoutHelper::set(uint32_t ms) +void TimeoutHelper::set(const uint32_t ms) { timeout = ms; startMillis = millis(); } -void TimeoutHelper::extend(uint32_t ms) +void TimeoutHelper::extend(const uint32_t ms) { timeout += ms; } @@ -27,7 +27,7 @@ void TimeoutHelper::reset() startMillis = millis(); } -bool TimeoutHelper::occured() +bool TimeoutHelper::occured() const { return millis() > (startMillis + timeout); } \ No newline at end of file diff --git a/lib/TimeoutHelper/TimeoutHelper.h b/lib/TimeoutHelper/TimeoutHelper.h index 369749e6..058de09d 100644 --- a/lib/TimeoutHelper/TimeoutHelper.h +++ b/lib/TimeoutHelper/TimeoutHelper.h @@ -6,10 +6,10 @@ class TimeoutHelper { public: TimeoutHelper(); - void set(uint32_t ms); - void extend(uint32_t ms); + void set(const uint32_t ms); + void extend(const uint32_t ms); void reset(); - bool occured(); + bool occured() const; private: uint32_t startMillis; diff --git a/platformio.ini b/platformio.ini index 2b48ede1..4a5478d9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,9 +22,9 @@ 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 + -D_TASK_STD_FUNCTION=1 + -Wall -Wextra -Werror -Wunused -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference -std=c++17 -std=gnu++17 build_unflags = @@ -32,11 +32,12 @@ 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 + bblanchon/ArduinoJson @ ^6.21.4 + 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 https://github.com/coryjfowler/MCP_CAN_lib plerup/EspSoftwareSerial@^8.0.1 mobizt/FirebaseJson @ ^3.0.6 @@ -49,6 +50,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 @@ -161,25 +170,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 diff --git a/src/Battery.cpp b/src/Battery.cpp index e3af69c5..d68db08e 100644 --- a/src/Battery.cpp +++ b/src/Battery.cpp @@ -20,21 +20,30 @@ std::shared_ptr BatteryClass::getStats() const return _upProvider->getStats(); } -void BatteryClass::init() +void BatteryClass::init(Scheduler& scheduler) { + scheduler.addTask(_loopTask); + _loopTask.setCallback(std::bind(&BatteryClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); std::lock_guard lock(_mutex); + this->updateSettings(); +} + +void BatteryClass::updateSettings() +{ if (_upProvider) { _upProvider->deinit(); _upProvider = nullptr; } CONFIG_T& config = Configuration.get(); - if (!config.Battery_Enabled) { return; } + if (!config.Battery.Enabled) { return; } - bool verboseLogging = config.Battery_VerboseLogging; + bool verboseLogging = config.Battery.VerboseLogging; - switch (config.Battery_Provider) { + switch (config.Battery.Provider) { case 0: _upProvider = std::make_unique(); if (!_upProvider->init(verboseLogging)) { _upProvider = nullptr; } @@ -48,11 +57,12 @@ void BatteryClass::init() if (!_upProvider->init(verboseLogging)) { _upProvider = nullptr; } break; default: - MessageOutput.printf("Unknown battery provider: %d\r\n", config.Battery_Provider); + MessageOutput.printf("Unknown battery provider: %d\r\n", config.Battery.Provider); break; } } + void BatteryClass::loop() { std::lock_guard lock(_mutex); @@ -64,7 +74,7 @@ void BatteryClass::loop() CONFIG_T& config = Configuration.get(); if (!MqttSettings.getConnected() - || (millis() - _lastMqttPublish) < (config.Mqtt_PublishInterval * 1000)) { + || (millis() - _lastMqttPublish) < (config.Mqtt.PublishInterval * 1000)) { return; } diff --git a/src/BatteryStats.cpp b/src/BatteryStats.cpp index 4024bf2c..34e2589b 100644 --- a/src/BatteryStats.cpp +++ b/src/BatteryStats.cpp @@ -232,7 +232,7 @@ void JkBmsBatteryStats::mqttPublish() const // publish all topics every minute, unless the retain flag is enabled bool fullPublish = _lastFullMqttPublish + 60 * 1000 < millis(); - fullPublish &= !config.Mqtt_Retain; + fullPublish &= !config.Mqtt.Retain; for (auto iter = _dataPoints.cbegin(); iter != _dataPoints.cend(); ++iter) { // skip data points that did not change since last published diff --git a/src/Configuration.cpp b/src/Configuration.cpp index da07d97c..485d0351 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -21,89 +21,97 @@ 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["verbose_logging"] = config.Mqtt_VerboseLogging; - 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["verbose_logging"] = config.Mqtt.VerboseLogging; + 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; + mqtt_lwt["qos"] = config.Mqtt.Lwt.Qos; 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["verbose_logging"] = config.Dtu_VerboseLogging; - 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["verbose_logging"] = config.Dtu.VerboseLogging; + 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; + display["diagram_duration"] = config.Display.DiagramDuration; + + 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++) { @@ -118,6 +126,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++) { @@ -129,76 +138,76 @@ bool ConfigurationClass::write() } JsonObject vedirect = doc.createNestedObject("vedirect"); - vedirect["enabled"] = config.Vedirect_Enabled; - vedirect["verbose_logging"] = config.Vedirect_VerboseLogging; - vedirect["updates_only"] = config.Vedirect_UpdatesOnly; + vedirect["enabled"] = config.Vedirect.Enabled; + vedirect["verbose_logging"] = config.Vedirect.VerboseLogging; + vedirect["updates_only"] = config.Vedirect.UpdatesOnly; JsonObject powermeter = doc.createNestedObject("powermeter"); - powermeter["enabled"] = config.PowerMeter_Enabled; - powermeter["verbose_logging"] = config.PowerMeter_VerboseLogging; - powermeter["interval"] = config.PowerMeter_Interval; - powermeter["source"] = config.PowerMeter_Source; - powermeter["mqtt_topic_powermeter_1"] = config.PowerMeter_MqttTopicPowerMeter1; - powermeter["mqtt_topic_powermeter_2"] = config.PowerMeter_MqttTopicPowerMeter2; - powermeter["mqtt_topic_powermeter_3"] = config.PowerMeter_MqttTopicPowerMeter3; - powermeter["sdmbaudrate"] = config.PowerMeter_SdmBaudrate; - powermeter["sdmaddress"] = config.PowerMeter_SdmAddress; - powermeter["http_individual_requests"] = config.PowerMeter_HttpIndividualRequests; + powermeter["enabled"] = config.PowerMeter.Enabled; + powermeter["verbose_logging"] = config.PowerMeter.VerboseLogging; + powermeter["interval"] = config.PowerMeter.Interval; + powermeter["source"] = config.PowerMeter.Source; + powermeter["mqtt_topic_powermeter_1"] = config.PowerMeter.MqttTopicPowerMeter1; + powermeter["mqtt_topic_powermeter_2"] = config.PowerMeter.MqttTopicPowerMeter2; + powermeter["mqtt_topic_powermeter_3"] = config.PowerMeter.MqttTopicPowerMeter3; + powermeter["sdmbaudrate"] = config.PowerMeter.SdmBaudrate; + powermeter["sdmaddress"] = config.PowerMeter.SdmAddress; + powermeter["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests; JsonArray powermeter_http_phases = powermeter.createNestedArray("http_phases"); for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) { JsonObject powermeter_phase = powermeter_http_phases.createNestedObject(); - powermeter_phase["enabled"] = config.Powermeter_Http_Phase[i].Enabled; - powermeter_phase["url"] = config.Powermeter_Http_Phase[i].Url; - powermeter_phase["auth_type"] = config.Powermeter_Http_Phase[i].AuthType; - powermeter_phase["username"] = config.Powermeter_Http_Phase[i].Username; - powermeter_phase["password"] = config.Powermeter_Http_Phase[i].Password; - powermeter_phase["header_key"] = config.Powermeter_Http_Phase[i].HeaderKey; - powermeter_phase["header_value"] = config.Powermeter_Http_Phase[i].HeaderValue; - powermeter_phase["timeout"] = config.Powermeter_Http_Phase[i].Timeout; - powermeter_phase["json_path"] = config.Powermeter_Http_Phase[i].JsonPath; + powermeter_phase["enabled"] = config.PowerMeter.Http_Phase[i].Enabled; + powermeter_phase["url"] = config.PowerMeter.Http_Phase[i].Url; + powermeter_phase["auth_type"] = config.PowerMeter.Http_Phase[i].AuthType; + powermeter_phase["username"] = config.PowerMeter.Http_Phase[i].Username; + powermeter_phase["password"] = config.PowerMeter.Http_Phase[i].Password; + powermeter_phase["header_key"] = config.PowerMeter.Http_Phase[i].HeaderKey; + powermeter_phase["header_value"] = config.PowerMeter.Http_Phase[i].HeaderValue; + powermeter_phase["timeout"] = config.PowerMeter.Http_Phase[i].Timeout; + powermeter_phase["json_path"] = config.PowerMeter.Http_Phase[i].JsonPath; } JsonObject powerlimiter = doc.createNestedObject("powerlimiter"); - powerlimiter["enabled"] = config.PowerLimiter_Enabled; - powerlimiter["verbose_logging"] = config.PowerLimiter_VerboseLogging; - powerlimiter["solar_passtrough_enabled"] = config.PowerLimiter_SolarPassThroughEnabled; - powerlimiter["solar_passtrough_losses"] = config.PowerLimiter_SolarPassThroughLosses; - powerlimiter["battery_drain_strategy"] = config.PowerLimiter_BatteryDrainStategy; - powerlimiter["interval"] = config.PowerLimiter_Interval; - powerlimiter["is_inverter_behind_powermeter"] = config.PowerLimiter_IsInverterBehindPowerMeter; - powerlimiter["inverter_id"] = config.PowerLimiter_InverterId; - powerlimiter["inverter_channel_id"] = config.PowerLimiter_InverterChannelId; - powerlimiter["target_power_consumption"] = config.PowerLimiter_TargetPowerConsumption; - powerlimiter["target_power_consumption_hysteresis"] = config.PowerLimiter_TargetPowerConsumptionHysteresis; - powerlimiter["lower_power_limit"] = config.PowerLimiter_LowerPowerLimit; - powerlimiter["upper_power_limit"] = config.PowerLimiter_UpperPowerLimit; - powerlimiter["battery_soc_start_threshold"] = config.PowerLimiter_BatterySocStartThreshold; - powerlimiter["battery_soc_stop_threshold"] = config.PowerLimiter_BatterySocStopThreshold; - powerlimiter["voltage_start_threshold"] = config.PowerLimiter_VoltageStartThreshold; - powerlimiter["voltage_stop_threshold"] = config.PowerLimiter_VoltageStopThreshold; - powerlimiter["voltage_load_correction_factor"] = config.PowerLimiter_VoltageLoadCorrectionFactor; - powerlimiter["inverter_restart_hour"] = config.PowerLimiter_RestartHour; - powerlimiter["full_solar_passthrough_soc"] = config.PowerLimiter_FullSolarPassThroughSoc; - powerlimiter["full_solar_passthrough_start_voltage"] = config.PowerLimiter_FullSolarPassThroughStartVoltage; - powerlimiter["full_solar_passthrough_stop_voltage"] = config.PowerLimiter_FullSolarPassThroughStopVoltage; + powerlimiter["enabled"] = config.PowerLimiter.Enabled; + powerlimiter["verbose_logging"] = config.PowerLimiter.VerboseLogging; + powerlimiter["solar_passtrough_enabled"] = config.PowerLimiter.SolarPassThroughEnabled; + powerlimiter["solar_passtrough_losses"] = config.PowerLimiter.SolarPassThroughLosses; + powerlimiter["battery_drain_strategy"] = config.PowerLimiter.BatteryDrainStategy; + powerlimiter["interval"] = config.PowerLimiter.Interval; + powerlimiter["is_inverter_behind_powermeter"] = config.PowerLimiter.IsInverterBehindPowerMeter; + powerlimiter["inverter_id"] = config.PowerLimiter.InverterId; + powerlimiter["inverter_channel_id"] = config.PowerLimiter.InverterChannelId; + powerlimiter["target_power_consumption"] = config.PowerLimiter.TargetPowerConsumption; + powerlimiter["target_power_consumption_hysteresis"] = config.PowerLimiter.TargetPowerConsumptionHysteresis; + powerlimiter["lower_power_limit"] = config.PowerLimiter.LowerPowerLimit; + powerlimiter["upper_power_limit"] = config.PowerLimiter.UpperPowerLimit; + powerlimiter["battery_soc_start_threshold"] = config.PowerLimiter.BatterySocStartThreshold; + powerlimiter["battery_soc_stop_threshold"] = config.PowerLimiter.BatterySocStopThreshold; + powerlimiter["voltage_start_threshold"] = config.PowerLimiter.VoltageStartThreshold; + powerlimiter["voltage_stop_threshold"] = config.PowerLimiter.VoltageStopThreshold; + powerlimiter["voltage_load_correction_factor"] = config.PowerLimiter.VoltageLoadCorrectionFactor; + powerlimiter["inverter_restart_hour"] = config.PowerLimiter.RestartHour; + powerlimiter["full_solar_passthrough_soc"] = config.PowerLimiter.FullSolarPassThroughSoc; + powerlimiter["full_solar_passthrough_start_voltage"] = config.PowerLimiter.FullSolarPassThroughStartVoltage; + powerlimiter["full_solar_passthrough_stop_voltage"] = config.PowerLimiter.FullSolarPassThroughStopVoltage; JsonObject battery = doc.createNestedObject("battery"); - battery["enabled"] = config.Battery_Enabled; - battery["verbose_logging"] = config.Battery_VerboseLogging; - battery["provider"] = config.Battery_Provider; - battery["jkbms_interface"] = config.Battery_JkBmsInterface; - battery["jkbms_polling_interval"] = config.Battery_JkBmsPollingInterval; + battery["enabled"] = config.Battery.Enabled; + battery["verbose_logging"] = config.Battery.VerboseLogging; + battery["provider"] = config.Battery.Provider; + battery["jkbms_interface"] = config.Battery.JkBmsInterface; + battery["jkbms_polling_interval"] = config.Battery.JkBmsPollingInterval; JsonObject huawei = doc.createNestedObject("huawei"); - huawei["enabled"] = config.Huawei_Enabled; - huawei["can_controller_frequency"] = config.Huawei_CAN_Controller_Frequency; - huawei["auto_power_enabled"] = config.Huawei_Auto_Power_Enabled; - huawei["voltage_limit"] = config.Huawei_Auto_Power_Voltage_Limit; - huawei["enable_voltage_limit"] = config.Huawei_Auto_Power_Enable_Voltage_Limit; - huawei["lower_power_limit"] = config.Huawei_Auto_Power_Lower_Power_Limit; - huawei["upper_power_limit"] = config.Huawei_Auto_Power_Upper_Power_Limit; + huawei["enabled"] = config.Huawei.Enabled; + huawei["can_controller_frequency"] = config.Huawei.CAN_Controller_Frequency; + huawei["auto_power_enabled"] = config.Huawei.Auto_Power_Enabled; + huawei["voltage_limit"] = config.Huawei.Auto_Power_Voltage_Limit; + huawei["enable_voltage_limit"] = config.Huawei.Auto_Power_Enable_Voltage_Limit; + huawei["lower_power_limit"] = config.Huawei.Auto_Power_Lower_Power_Limit; + huawei["upper_power_limit"] = config.Huawei.Auto_Power_Upper_Power_Limit; // Serialize JSON to file if (serializeJson(doc, f) == 0) { @@ -216,121 +225,129 @@ bool ConfigurationClass::read() DynamicJsonDocument doc(JSON_BUFFER_SIZE); // Deserialize the JSON document - DeserializationError error = deserializeJson(doc, f); + const DeserializationError error = deserializeJson(doc, f); if (error) { MessageOutput.println("Failed to read file, using default configuration"); } 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; - config.Mqtt_VerboseLogging = mqtt["verbose_logging"] | VERBOSE_LOGGING; - 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; + config.Mqtt.VerboseLogging = mqtt["verbose_logging"] | VERBOSE_LOGGING; + 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)); + config.Mqtt.Lwt.Qos = mqtt_lwt["qos"] | MQTT_LWT_QOS; 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_VerboseLogging = dtu["verbose_logging"] | VERBOSE_LOGGING; - 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.VerboseLogging = dtu["verbose_logging"] | VERBOSE_LOGGING; + 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; + config.Display.DiagramDuration = display["diagram_duration"] | DISPLAY_DIAGRAM_DURATION; + + 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++) { @@ -346,6 +363,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++) { @@ -356,76 +374,76 @@ bool ConfigurationClass::read() } JsonObject vedirect = doc["vedirect"]; - config.Vedirect_Enabled = vedirect["enabled"] | VEDIRECT_ENABLED; - config.Vedirect_VerboseLogging = vedirect["verbose_logging"] | VEDIRECT_VERBOSE_LOGGING; - config.Vedirect_UpdatesOnly = vedirect["updates_only"] | VEDIRECT_UPDATESONLY; + config.Vedirect.Enabled = vedirect["enabled"] | VEDIRECT_ENABLED; + config.Vedirect.VerboseLogging = vedirect["verbose_logging"] | VEDIRECT_VERBOSE_LOGGING; + config.Vedirect.UpdatesOnly = vedirect["updates_only"] | VEDIRECT_UPDATESONLY; JsonObject powermeter = doc["powermeter"]; - config.PowerMeter_Enabled = powermeter["enabled"] | POWERMETER_ENABLED; - config.PowerMeter_VerboseLogging = powermeter["verbose_logging"] | VERBOSE_LOGGING; - config.PowerMeter_Interval = powermeter["interval"] | POWERMETER_INTERVAL; - config.PowerMeter_Source = powermeter["source"] | POWERMETER_SOURCE; - strlcpy(config.PowerMeter_MqttTopicPowerMeter1, powermeter["mqtt_topic_powermeter_1"] | "", sizeof(config.PowerMeter_MqttTopicPowerMeter1)); - strlcpy(config.PowerMeter_MqttTopicPowerMeter2, powermeter["mqtt_topic_powermeter_2"] | "", sizeof(config.PowerMeter_MqttTopicPowerMeter2)); - strlcpy(config.PowerMeter_MqttTopicPowerMeter3, powermeter["mqtt_topic_powermeter_3"] | "", sizeof(config.PowerMeter_MqttTopicPowerMeter3)); - config.PowerMeter_SdmBaudrate = powermeter["sdmbaudrate"] | POWERMETER_SDMBAUDRATE; - config.PowerMeter_SdmAddress = powermeter["sdmaddress"] | POWERMETER_SDMADDRESS; - config.PowerMeter_HttpIndividualRequests = powermeter["http_individual_requests"] | false; + config.PowerMeter.Enabled = powermeter["enabled"] | POWERMETER_ENABLED; + config.PowerMeter.VerboseLogging = powermeter["verbose_logging"] | VERBOSE_LOGGING; + config.PowerMeter.Interval = powermeter["interval"] | POWERMETER_INTERVAL; + config.PowerMeter.Source = powermeter["source"] | POWERMETER_SOURCE; + strlcpy(config.PowerMeter.MqttTopicPowerMeter1, powermeter["mqtt_topic_powermeter_1"] | "", sizeof(config.PowerMeter.MqttTopicPowerMeter1)); + strlcpy(config.PowerMeter.MqttTopicPowerMeter2, powermeter["mqtt_topic_powermeter_2"] | "", sizeof(config.PowerMeter.MqttTopicPowerMeter2)); + strlcpy(config.PowerMeter.MqttTopicPowerMeter3, powermeter["mqtt_topic_powermeter_3"] | "", sizeof(config.PowerMeter.MqttTopicPowerMeter3)); + config.PowerMeter.SdmBaudrate = powermeter["sdmbaudrate"] | POWERMETER_SDMBAUDRATE; + config.PowerMeter.SdmAddress = powermeter["sdmaddress"] | POWERMETER_SDMADDRESS; + config.PowerMeter.HttpIndividualRequests = powermeter["http_individual_requests"] | false; JsonArray powermeter_http_phases = powermeter["http_phases"]; for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) { JsonObject powermeter_phase = powermeter_http_phases[i].as(); - config.Powermeter_Http_Phase[i].Enabled = powermeter_phase["enabled"] | (i == 0); - strlcpy(config.Powermeter_Http_Phase[i].Url, powermeter_phase["url"] | "", sizeof(config.Powermeter_Http_Phase[i].Url)); - config.Powermeter_Http_Phase[i].AuthType = powermeter_phase["auth_type"] | Auth::none; - strlcpy(config.Powermeter_Http_Phase[i].Username, powermeter_phase["username"] | "", sizeof(config.Powermeter_Http_Phase[i].Username)); - strlcpy(config.Powermeter_Http_Phase[i].Password, powermeter_phase["password"] | "", sizeof(config.Powermeter_Http_Phase[i].Password)); - strlcpy(config.Powermeter_Http_Phase[i].HeaderKey, powermeter_phase["header_key"] | "", sizeof(config.Powermeter_Http_Phase[i].HeaderKey)); - strlcpy(config.Powermeter_Http_Phase[i].HeaderValue, powermeter_phase["header_value"] | "", sizeof(config.Powermeter_Http_Phase[i].HeaderValue)); - config.Powermeter_Http_Phase[i].Timeout = powermeter_phase["timeout"] | POWERMETER_HTTP_TIMEOUT; - strlcpy(config.Powermeter_Http_Phase[i].JsonPath, powermeter_phase["json_path"] | "", sizeof(config.Powermeter_Http_Phase[i].JsonPath)); + config.PowerMeter.Http_Phase[i].Enabled = powermeter_phase["enabled"] | (i == 0); + strlcpy(config.PowerMeter.Http_Phase[i].Url, powermeter_phase["url"] | "", sizeof(config.PowerMeter.Http_Phase[i].Url)); + config.PowerMeter.Http_Phase[i].AuthType = powermeter_phase["auth_type"] | Auth::none; + strlcpy(config.PowerMeter.Http_Phase[i].Username, powermeter_phase["username"] | "", sizeof(config.PowerMeter.Http_Phase[i].Username)); + strlcpy(config.PowerMeter.Http_Phase[i].Password, powermeter_phase["password"] | "", sizeof(config.PowerMeter.Http_Phase[i].Password)); + strlcpy(config.PowerMeter.Http_Phase[i].HeaderKey, powermeter_phase["header_key"] | "", sizeof(config.PowerMeter.Http_Phase[i].HeaderKey)); + strlcpy(config.PowerMeter.Http_Phase[i].HeaderValue, powermeter_phase["header_value"] | "", sizeof(config.PowerMeter.Http_Phase[i].HeaderValue)); + config.PowerMeter.Http_Phase[i].Timeout = powermeter_phase["timeout"] | POWERMETER_HTTP_TIMEOUT; + strlcpy(config.PowerMeter.Http_Phase[i].JsonPath, powermeter_phase["json_path"] | "", sizeof(config.PowerMeter.Http_Phase[i].JsonPath)); } JsonObject powerlimiter = doc["powerlimiter"]; - config.PowerLimiter_Enabled = powerlimiter["enabled"] | POWERLIMITER_ENABLED; - config.PowerLimiter_VerboseLogging = powerlimiter["verbose_logging"] | VERBOSE_LOGGING; - config.PowerLimiter_SolarPassThroughEnabled = powerlimiter["solar_passtrough_enabled"] | POWERLIMITER_SOLAR_PASSTHROUGH_ENABLED; - config.PowerLimiter_SolarPassThroughLosses = powerlimiter["solar_passthrough_losses"] | POWERLIMITER_SOLAR_PASSTHROUGH_LOSSES; - config.PowerLimiter_BatteryDrainStategy = powerlimiter["battery_drain_strategy"] | POWERLIMITER_BATTERY_DRAIN_STRATEGY; - config.PowerLimiter_Interval = powerlimiter["interval"] | POWERLIMITER_INTERVAL; - config.PowerLimiter_IsInverterBehindPowerMeter = powerlimiter["is_inverter_behind_powermeter"] | POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER; - config.PowerLimiter_InverterId = powerlimiter["inverter_id"] | POWERLIMITER_INVERTER_ID; - config.PowerLimiter_InverterChannelId = powerlimiter["inverter_channel_id"] | POWERLIMITER_INVERTER_CHANNEL_ID; - config.PowerLimiter_TargetPowerConsumption = powerlimiter["target_power_consumption"] | POWERLIMITER_TARGET_POWER_CONSUMPTION; - config.PowerLimiter_TargetPowerConsumptionHysteresis = powerlimiter["target_power_consumption_hysteresis"] | POWERLIMITER_TARGET_POWER_CONSUMPTION_HYSTERESIS; - config.PowerLimiter_LowerPowerLimit = powerlimiter["lower_power_limit"] | POWERLIMITER_LOWER_POWER_LIMIT; - config.PowerLimiter_UpperPowerLimit = powerlimiter["upper_power_limit"] | POWERLIMITER_UPPER_POWER_LIMIT; - config.PowerLimiter_BatterySocStartThreshold = powerlimiter["battery_soc_start_threshold"] | POWERLIMITER_BATTERY_SOC_START_THRESHOLD; - config.PowerLimiter_BatterySocStopThreshold = powerlimiter["battery_soc_stop_threshold"] | POWERLIMITER_BATTERY_SOC_STOP_THRESHOLD; - config.PowerLimiter_VoltageStartThreshold = powerlimiter["voltage_start_threshold"] | POWERLIMITER_VOLTAGE_START_THRESHOLD; - config.PowerLimiter_VoltageStopThreshold = powerlimiter["voltage_stop_threshold"] | POWERLIMITER_VOLTAGE_STOP_THRESHOLD; - config.PowerLimiter_VoltageLoadCorrectionFactor = powerlimiter["voltage_load_correction_factor"] | POWERLIMITER_VOLTAGE_LOAD_CORRECTION_FACTOR; - config.PowerLimiter_RestartHour = powerlimiter["inverter_restart_hour"] | POWERLIMITER_RESTART_HOUR; - config.PowerLimiter_FullSolarPassThroughSoc = powerlimiter["full_solar_passthrough_soc"] | POWERLIMITER_FULL_SOLAR_PASSTHROUGH_SOC; - config.PowerLimiter_FullSolarPassThroughStartVoltage = powerlimiter["full_solar_passthrough_start_voltage"] | POWERLIMITER_FULL_SOLAR_PASSTHROUGH_START_VOLTAGE; - config.PowerLimiter_FullSolarPassThroughStopVoltage = powerlimiter["full_solar_passthrough_stop_voltage"] | POWERLIMITER_FULL_SOLAR_PASSTHROUGH_STOP_VOLTAGE; + config.PowerLimiter.Enabled = powerlimiter["enabled"] | POWERLIMITER_ENABLED; + config.PowerLimiter.VerboseLogging = powerlimiter["verbose_logging"] | VERBOSE_LOGGING; + config.PowerLimiter.SolarPassThroughEnabled = powerlimiter["solar_passtrough_enabled"] | POWERLIMITER_SOLAR_PASSTHROUGH_ENABLED; + config.PowerLimiter.SolarPassThroughLosses = powerlimiter["solar_passthrough_losses"] | POWERLIMITER_SOLAR_PASSTHROUGH_LOSSES; + config.PowerLimiter.BatteryDrainStategy = powerlimiter["battery_drain_strategy"] | POWERLIMITER_BATTERY_DRAIN_STRATEGY; + config.PowerLimiter.Interval = powerlimiter["interval"] | POWERLIMITER_INTERVAL; + config.PowerLimiter.IsInverterBehindPowerMeter = powerlimiter["is_inverter_behind_powermeter"] | POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER; + config.PowerLimiter.InverterId = powerlimiter["inverter_id"] | POWERLIMITER_INVERTER_ID; + config.PowerLimiter.InverterChannelId = powerlimiter["inverter_channel_id"] | POWERLIMITER_INVERTER_CHANNEL_ID; + config.PowerLimiter.TargetPowerConsumption = powerlimiter["target_power_consumption"] | POWERLIMITER_TARGET_POWER_CONSUMPTION; + config.PowerLimiter.TargetPowerConsumptionHysteresis = powerlimiter["target_power_consumption_hysteresis"] | POWERLIMITER_TARGET_POWER_CONSUMPTION_HYSTERESIS; + config.PowerLimiter.LowerPowerLimit = powerlimiter["lower_power_limit"] | POWERLIMITER_LOWER_POWER_LIMIT; + config.PowerLimiter.UpperPowerLimit = powerlimiter["upper_power_limit"] | POWERLIMITER_UPPER_POWER_LIMIT; + config.PowerLimiter.BatterySocStartThreshold = powerlimiter["battery_soc_start_threshold"] | POWERLIMITER_BATTERY_SOC_START_THRESHOLD; + config.PowerLimiter.BatterySocStopThreshold = powerlimiter["battery_soc_stop_threshold"] | POWERLIMITER_BATTERY_SOC_STOP_THRESHOLD; + config.PowerLimiter.VoltageStartThreshold = powerlimiter["voltage_start_threshold"] | POWERLIMITER_VOLTAGE_START_THRESHOLD; + config.PowerLimiter.VoltageStopThreshold = powerlimiter["voltage_stop_threshold"] | POWERLIMITER_VOLTAGE_STOP_THRESHOLD; + config.PowerLimiter.VoltageLoadCorrectionFactor = powerlimiter["voltage_load_correction_factor"] | POWERLIMITER_VOLTAGE_LOAD_CORRECTION_FACTOR; + config.PowerLimiter.RestartHour = powerlimiter["inverter_restart_hour"] | POWERLIMITER_RESTART_HOUR; + config.PowerLimiter.FullSolarPassThroughSoc = powerlimiter["full_solar_passthrough_soc"] | POWERLIMITER_FULL_SOLAR_PASSTHROUGH_SOC; + config.PowerLimiter.FullSolarPassThroughStartVoltage = powerlimiter["full_solar_passthrough_start_voltage"] | POWERLIMITER_FULL_SOLAR_PASSTHROUGH_START_VOLTAGE; + config.PowerLimiter.FullSolarPassThroughStopVoltage = powerlimiter["full_solar_passthrough_stop_voltage"] | POWERLIMITER_FULL_SOLAR_PASSTHROUGH_STOP_VOLTAGE; JsonObject battery = doc["battery"]; - config.Battery_Enabled = battery["enabled"] | BATTERY_ENABLED; - config.Battery_VerboseLogging = battery["verbose_logging"] | VERBOSE_LOGGING; - config.Battery_Provider = battery["provider"] | BATTERY_PROVIDER; - config.Battery_JkBmsInterface = battery["jkbms_interface"] | BATTERY_JKBMS_INTERFACE; - config.Battery_JkBmsPollingInterval = battery["jkbms_polling_interval"] | BATTERY_JKBMS_POLLING_INTERVAL; + config.Battery.Enabled = battery["enabled"] | BATTERY_ENABLED; + config.Battery.VerboseLogging = battery["verbose_logging"] | VERBOSE_LOGGING; + config.Battery.Provider = battery["provider"] | BATTERY_PROVIDER; + config.Battery.JkBmsInterface = battery["jkbms_interface"] | BATTERY_JKBMS_INTERFACE; + config.Battery.JkBmsPollingInterval = battery["jkbms_polling_interval"] | BATTERY_JKBMS_POLLING_INTERVAL; JsonObject huawei = doc["huawei"]; - config.Huawei_Enabled = huawei["enabled"] | HUAWEI_ENABLED; - config.Huawei_CAN_Controller_Frequency = huawei["can_controller_frequency"] | HUAWEI_CAN_CONTROLLER_FREQUENCY; - config.Huawei_Auto_Power_Enabled = huawei["auto_power_enabled"] | false; - config.Huawei_Auto_Power_Voltage_Limit = huawei["voltage_limit"] | HUAWEI_AUTO_POWER_VOLTAGE_LIMIT; - config.Huawei_Auto_Power_Enable_Voltage_Limit = huawei["enable_voltage_limit"] | HUAWEI_AUTO_POWER_ENABLE_VOLTAGE_LIMIT; - config.Huawei_Auto_Power_Lower_Power_Limit = huawei["lower_power_limit"] | HUAWEI_AUTO_POWER_LOWER_POWER_LIMIT; - config.Huawei_Auto_Power_Upper_Power_Limit = huawei["upper_power_limit"] | HUAWEI_AUTO_POWER_UPPER_POWER_LIMIT; + config.Huawei.Enabled = huawei["enabled"] | HUAWEI_ENABLED; + config.Huawei.CAN_Controller_Frequency = huawei["can_controller_frequency"] | HUAWEI_CAN_CONTROLLER_FREQUENCY; + config.Huawei.Auto_Power_Enabled = huawei["auto_power_enabled"] | false; + config.Huawei.Auto_Power_Voltage_Limit = huawei["voltage_limit"] | HUAWEI_AUTO_POWER_VOLTAGE_LIMIT; + config.Huawei.Auto_Power_Enable_Voltage_Limit = huawei["enable_voltage_limit"] | HUAWEI_AUTO_POWER_ENABLE_VOLTAGE_LIMIT; + config.Huawei.Auto_Power_Lower_Power_Limit = huawei["lower_power_limit"] | HUAWEI_AUTO_POWER_LOWER_POWER_LIMIT; + config.Huawei.Auto_Power_Upper_Power_Limit = huawei["upper_power_limit"] | HUAWEI_AUTO_POWER_UPPER_POWER_LIMIT; f.close(); return true; @@ -441,13 +459,13 @@ void ConfigurationClass::migrate() DynamicJsonDocument doc(JSON_BUFFER_SIZE); // Deserialize the JSON document - DeserializationError error = deserializeJson(doc, f); + const DeserializationError error = deserializeJson(doc, f); if (error) { MessageOutput.printf("Failed to read file, cancel migration: %s\r\n", error.c_str()); 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(); @@ -459,19 +477,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(); } @@ -489,10 +507,10 @@ INVERTER_CONFIG_T* ConfigurationClass::getFreeInverterSlot() } } - return NULL; + return nullptr; } -INVERTER_CONFIG_T* ConfigurationClass::getInverterConfig(uint64_t serial) +INVERTER_CONFIG_T* ConfigurationClass::getInverterConfig(const uint64_t serial) { for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { if (config.Inverter[i].Serial == serial) { @@ -500,7 +518,7 @@ INVERTER_CONFIG_T* ConfigurationClass::getInverterConfig(uint64_t serial) } } - return NULL; + return nullptr; } ConfigurationClass Configuration; diff --git a/src/Datastore.cpp b/src/Datastore.cpp index 4ff67b80..1d4c2eb0 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/Display_Graphic.cpp b/src/Display_Graphic.cpp index 26991cb5..5a06452f 100644 --- a/src/Display_Graphic.cpp +++ b/src/Display_Graphic.cpp @@ -1,4 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Thomas Basler and others + */ #include "Display_Graphic.h" #include "Datastore.h" #include @@ -39,7 +42,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, const DisplayType_t type, const uint8_t data, const uint8_t clk, const uint8_t cs, const uint8_t reset) { _display_type = type; if (_display_type > DisplayType_t::None) { @@ -48,7 +51,14 @@ void DisplayGraphicClass::init(DisplayType_t type, uint8_t data, uint8_t clk, ui _display->begin(); setContrast(DISPLAY_CONTRAST); setStatus(true); + _diagram.init(scheduler, _display); } + + scheduler.addTask(_loopTask); + _loopTask.setCallback(std::bind(&DisplayGraphicClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.setInterval(_period); + _loopTask.enable(); } void DisplayGraphicClass::calcLineHeights() @@ -61,7 +71,7 @@ void DisplayGraphicClass::calcLineHeights() } } -void DisplayGraphicClass::setFont(uint8_t line) +void DisplayGraphicClass::setFont(const uint8_t line) { switch (line) { case 0: @@ -76,13 +86,13 @@ void DisplayGraphicClass::setFont(uint8_t line) } } -void DisplayGraphicClass::printText(const char* text, uint8_t line) +void DisplayGraphicClass::printText(const char* text, const uint8_t line) { uint8_t dispX; if (!_isLarge) { dispX = (line == 0) ? 5 : 0; } else { - dispX = (line == 0) ? 20 : 5; + dispX = (line == 0) ? 10 : 5; } setFont(line); @@ -90,7 +100,7 @@ void DisplayGraphicClass::printText(const char* text, uint8_t line) _display->drawStr(dispX, _lineOffsets[line], text); } -void DisplayGraphicClass::setOrientation(uint8_t rotation) +void DisplayGraphicClass::setOrientation(const uint8_t rotation) { if (_display_type == DisplayType_t::None) { return; @@ -115,7 +125,7 @@ void DisplayGraphicClass::setOrientation(uint8_t rotation) calcLineHeights(); } -void DisplayGraphicClass::setLanguage(uint8_t language) +void DisplayGraphicClass::setLanguage(const uint8_t language) { _display_language = language < sizeof(languages) / sizeof(languages[0]) ? language : DISPLAY_LANGUAGE; } @@ -131,71 +141,77 @@ void DisplayGraphicClass::setStartupDisplay() _display->sendBuffer(); } +DisplayGraphicDiagramClass& DisplayGraphicClass::Diagram() +{ + return _diagram; +} + void DisplayGraphicClass::loop() { if (_display_type == DisplayType_t::None) { 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(); + //=====> Actual Production ========== + if (Datastore.getIsAtLeastOneReachable()) { + displayPowerSave = false; + if (_isLarge) { + _diagram.redraw(); } - //<======================= - - //=====> 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); + 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) +void DisplayGraphicClass::setContrast(const uint8_t contrast) { if (_display_type == DisplayType_t::None) { return; @@ -203,7 +219,7 @@ void DisplayGraphicClass::setContrast(uint8_t contrast) _display->setContrast(contrast * 2.55f); } -void DisplayGraphicClass::setStatus(bool turnOn) +void DisplayGraphicClass::setStatus(const bool turnOn) { _displayTurnedOn = turnOn; } diff --git a/src/Display_Graphic_Diagram.cpp b/src/Display_Graphic_Diagram.cpp new file mode 100644 index 00000000..4a98c169 --- /dev/null +++ b/src/Display_Graphic_Diagram.cpp @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Thomas Basler and others + */ +#include "Display_Graphic_Diagram.h" +#include "Configuration.h" +#include "Datastore.h" +#include + +DisplayGraphicDiagramClass::DisplayGraphicDiagramClass() +{ +} + +void DisplayGraphicDiagramClass::init(Scheduler& scheduler, U8G2* display) +{ + _display = display; + + scheduler.addTask(_averageTask); + _averageTask.setCallback(std::bind(&DisplayGraphicDiagramClass::averageLoop, this)); + _averageTask.setIterations(TASK_FOREVER); + _averageTask.setInterval(1 * TASK_SECOND); + _averageTask.enable(); + + scheduler.addTask(_dataPointTask); + _dataPointTask.setCallback(std::bind(&DisplayGraphicDiagramClass::dataPointLoop, this)); + _dataPointTask.setIterations(TASK_FOREVER); + updatePeriod(); + _dataPointTask.enable(); +} + +void DisplayGraphicDiagramClass::averageLoop() +{ + const float currentWatts = Datastore.getTotalAcPowerEnabled(); // get the current AC production + _iRunningAverage += currentWatts; + _iRunningAverageCnt++; +} + +void DisplayGraphicDiagramClass::dataPointLoop() +{ + if (_graphValuesCount >= CHART_WIDTH) { + for (uint8_t i = 0; i < CHART_WIDTH - 1; i++) { + _graphValues[i] = _graphValues[i + 1]; + } + _graphValuesCount = CHART_WIDTH - 1; + } + if (_iRunningAverageCnt != 0) { + _graphValues[_graphValuesCount++] = _iRunningAverage / _iRunningAverageCnt; + _iRunningAverage = 0; + _iRunningAverageCnt = 0; + } + + if (Configuration.get().Display.ScreenSaver) { + _graphPosX = DIAG_POSX - (_graphValuesCount % 2); + } +} + +uint32_t DisplayGraphicDiagramClass::getSecondsPerDot() +{ + return Configuration.get().Display.DiagramDuration / CHART_WIDTH; +} + +void DisplayGraphicDiagramClass::updatePeriod() +{ + _dataPointTask.setInterval(getSecondsPerDot() * TASK_SECOND); +} + +void DisplayGraphicDiagramClass::redraw() +{ + uint8_t graphPosY = DIAG_POSY; + + // draw diagram axis + _display->drawVLine(_graphPosX, graphPosY, CHART_HEIGHT); + _display->drawHLine(_graphPosX, graphPosY + CHART_HEIGHT - 1, CHART_WIDTH); + + _display->drawLine(_graphPosX + 1, graphPosY + 1, _graphPosX + 2, graphPosY + 2); // UP-arrow + _display->drawLine(_graphPosX + CHART_WIDTH - 3, graphPosY + CHART_HEIGHT - 3, _graphPosX + CHART_WIDTH - 2, graphPosY + CHART_HEIGHT - 2); // LEFT-arrow + _display->drawLine(_graphPosX + CHART_WIDTH - 3, graphPosY + CHART_HEIGHT + 1, _graphPosX + CHART_WIDTH - 2, graphPosY + CHART_HEIGHT); // LEFT-arrow + + // draw AC value + _display->setFont(u8g2_font_tom_thumb_4x6_mr); + char fmtText[7]; + const float maxWatts = *std::max_element(_graphValues.begin(), _graphValues.end()); + snprintf(fmtText, sizeof(fmtText), "%dW", static_cast(maxWatts)); + const uint8_t textLength = strlen(fmtText); + _display->drawStr(_graphPosX - (textLength * 4), graphPosY + 5, fmtText); + + // draw chart + const float scaleFactor = maxWatts / CHART_HEIGHT; + uint8_t axisTick = 1; + for (int i = 0; i < _graphValuesCount; i++) { + if (scaleFactor > 0) { + if (i == 0) { + _display->drawPixel(_graphPosX + 1 + i, graphPosY + CHART_HEIGHT - ((_graphValues[i] / scaleFactor) + 0.5)); // + 0.5 to round mathematical + } else { + _display->drawLine(_graphPosX + i, graphPosY + CHART_HEIGHT - ((_graphValues[i - 1] / scaleFactor) + 0.5), _graphPosX + 1 + i, graphPosY + CHART_HEIGHT - ((_graphValues[i] / scaleFactor) + 0.5)); + } + } + + // draw one tick per hour to the x-axis + if (i * getSecondsPerDot() > (3600u * axisTick)) { + _display->drawPixel(_graphPosX + 1 + i, graphPosY + CHART_HEIGHT); + axisTick++; + } + } +} diff --git a/src/HttpPowerMeter.cpp b/src/HttpPowerMeter.cpp index 3c0e358d..09da47a5 100644 --- a/src/HttpPowerMeter.cpp +++ b/src/HttpPowerMeter.cpp @@ -26,14 +26,14 @@ bool HttpPowerMeterClass::updateValues() errorMessage[256]; for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) { - POWERMETER_HTTP_PHASE_CONFIG_T phaseConfig = config.Powermeter_Http_Phase[i]; + POWERMETER_HTTP_PHASE_CONFIG_T phaseConfig = config.PowerMeter.Http_Phase[i]; if (!phaseConfig.Enabled) { power[i] = 0.0; continue; } - if (i == 0 || config.PowerMeter_HttpIndividualRequests) { + if (i == 0 || config.PowerMeter.HttpIndividualRequests) { if (httpRequest(phaseConfig.Url, phaseConfig.AuthType, phaseConfig.Username, phaseConfig.Password, phaseConfig.HeaderKey, phaseConfig.HeaderValue, phaseConfig.Timeout, response, sizeof(response), errorMessage, sizeof(errorMessage))) { if (!getFloatValueByJsonPath(response, phaseConfig.JsonPath, power[i])) { diff --git a/src/Huawei_can.cpp b/src/Huawei_can.cpp index f42c7b01..87b141aa 100644 --- a/src/Huawei_can.cpp +++ b/src/Huawei_can.cpp @@ -193,7 +193,17 @@ void HuaweiCanCommClass::sendRequest() // Huawei CAN Controller // ******************************************************* -void HuaweiCanClass::init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs, uint8_t huawei_power) +void HuaweiCanClass::init(Scheduler& scheduler, uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs, uint8_t huawei_power) +{ + scheduler.addTask(_loopTask); + _loopTask.setCallback(std::bind(&HuaweiCanClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); + + this->updateSettings(huawei_miso, huawei_mosi, huawei_clk, huawei_irq, huawei_cs, huawei_power); +} + +void HuaweiCanClass::updateSettings(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs, uint8_t huawei_power) { if (_initialized) { return; @@ -201,11 +211,11 @@ void HuaweiCanClass::init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huaw const CONFIG_T& config = Configuration.get(); - if (!config.Huawei_Enabled) { + if (!config.Huawei.Enabled) { return; } - if (!HuaweiCanComm.init(huawei_miso, huawei_mosi, huawei_clk, huawei_irq, huawei_cs, config.Huawei_CAN_Controller_Frequency)) { + if (!HuaweiCanComm.init(huawei_miso, huawei_mosi, huawei_clk, huawei_irq, huawei_cs, config.Huawei.CAN_Controller_Frequency)) { MessageOutput.println("[HuaweiCanClass::init] Error Initializing Huawei CAN communication..."); return; }; @@ -214,7 +224,7 @@ void HuaweiCanClass::init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huaw digitalWrite(huawei_power, HIGH); _huaweiPower = huawei_power; - if (config.Huawei_Auto_Power_Enabled) { + if (config.Huawei.Auto_Power_Enabled) { _mode = HUAWEI_MODE_AUTO_INT; } @@ -258,17 +268,17 @@ void HuaweiCanClass::loop() { const CONFIG_T& config = Configuration.get(); - if (!config.Huawei_Enabled || !_initialized) { + if (!config.Huawei.Enabled || !_initialized) { return; } processReceivedParameters(); uint8_t com_error = HuaweiCanComm.getErrorCode(true); - if (com_error && HUAWEI_ERROR_CODE_RX) { + if (com_error & HUAWEI_ERROR_CODE_RX) { MessageOutput.println("[HuaweiCanClass::loop] Data request error"); } - if (com_error && HUAWEI_ERROR_CODE_TX) { + if (com_error & HUAWEI_ERROR_CODE_TX) { MessageOutput.println("[HuaweiCanClass::loop] Data set error"); } @@ -296,8 +306,8 @@ void HuaweiCanClass::loop() // Set voltage limit in periodic intervals if ( _nextAutoModePeriodicIntMillis < millis()) { - MessageOutput.printf("[HuaweiCanClass::loop] Periodically setting voltage limit: %f \r\n", config.Huawei_Auto_Power_Voltage_Limit); - _setValue(config.Huawei_Auto_Power_Voltage_Limit, HUAWEI_ONLINE_VOLTAGE); + MessageOutput.printf("[HuaweiCanClass::loop] Periodically setting voltage limit: %f \r\n", config.Huawei.Auto_Power_Voltage_Limit); + _setValue(config.Huawei.Auto_Power_Voltage_Limit, HUAWEI_ONLINE_VOLTAGE); _nextAutoModePeriodicIntMillis = millis() + 60000; } @@ -308,14 +318,14 @@ void HuaweiCanClass::loop() } // Re-enable automatic power control if the output voltage has dropped below threshold - if(_rp.output_voltage < config.Huawei_Auto_Power_Enable_Voltage_Limit ) { + if(_rp.output_voltage < config.Huawei.Auto_Power_Enable_Voltage_Limit ) { _autoPowerEnabledCounter = 10; } // Check if inverter used by the power limiter is active std::shared_ptr inverter = - Hoymiles.getInverterByPos(config.PowerLimiter_InverterId); + Hoymiles.getInverterByPos(config.PowerLimiter.InverterId); if (inverter != nullptr) { if(inverter->isProducing()) { @@ -339,12 +349,12 @@ void HuaweiCanClass::loop() newPowerLimit += _rp.output_power; MessageOutput.printf("[HuaweiCanClass::loop] PL: %f, OP: %f \r\n", newPowerLimit, _rp.output_power); - if (newPowerLimit > config.Huawei_Auto_Power_Lower_Power_Limit) { + if (newPowerLimit > config.Huawei.Auto_Power_Lower_Power_Limit) { // Check if the output power has dropped below the lower limit (i.e. the battery is full) // and if the PSU should be turned off. Also we use a simple counter mechanism here to be able // to ramp up from zero output power when starting up - if (_rp.output_power < config.Huawei_Auto_Power_Lower_Power_Limit) { + if (_rp.output_power < config.Huawei.Auto_Power_Lower_Power_Limit) { MessageOutput.printf("[HuaweiCanClass::loop] Power and voltage limit reached. Disabling automatic power control .... \r\n"); _autoPowerEnabledCounter--; if (_autoPowerEnabledCounter == 0) { @@ -357,8 +367,8 @@ void HuaweiCanClass::loop() } // Limit power to maximum - if (newPowerLimit > config.Huawei_Auto_Power_Upper_Power_Limit) { - newPowerLimit = config.Huawei_Auto_Power_Upper_Power_Limit; + if (newPowerLimit > config.Huawei.Auto_Power_Upper_Power_Limit) { + newPowerLimit = config.Huawei.Auto_Power_Upper_Power_Limit; } // Set the actual output limit @@ -391,7 +401,7 @@ void HuaweiCanClass::_setValue(float in, uint8_t parameterType) const CONFIG_T& config = Configuration.get(); - if (!config.Huawei_Enabled) { + if (!config.Huawei.Enabled) { return; } @@ -422,7 +432,7 @@ void HuaweiCanClass::_setValue(float in, uint8_t parameterType) void HuaweiCanClass::setMode(uint8_t mode) { const CONFIG_T& config = Configuration.get(); - if (!config.Huawei_Enabled) { + if (!config.Huawei.Enabled) { return; } @@ -435,7 +445,7 @@ void HuaweiCanClass::setMode(uint8_t mode) { _mode = HUAWEI_MODE_ON; } - if (mode == HUAWEI_MODE_AUTO_INT && !config.Huawei_Auto_Power_Enabled ) { + if (mode == HUAWEI_MODE_AUTO_INT && !config.Huawei.Auto_Power_Enabled ) { MessageOutput.println("[HuaweiCanClass::setMode] WARNING: Trying to setmode to internal automatic power control without being enabled in the UI. Ignoring command"); return; } diff --git a/src/InverterSettings.cpp b/src/InverterSettings.cpp index 0188cff4..a81527bf 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(); @@ -46,22 +46,22 @@ 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); MessageOutput.println(" Setting verbosity... "); - Hoymiles.setVerboseLogging(config.Dtu_VerboseLogging); + Hoymiles.setVerboseLogging(config.Dtu.VerboseLogging); for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { if (config.Inverter[i].Serial > 0) { @@ -77,6 +77,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); @@ -89,27 +90,39 @@ void InverterSettingsClass::init() } else { MessageOutput.println("Invalid pin config"); } + + 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(); } diff --git a/src/JkBmsController.cpp b/src/JkBmsController.cpp index 7e5ea0ac..2422d6d2 100644 --- a/src/JkBmsController.cpp +++ b/src/JkBmsController.cpp @@ -250,8 +250,8 @@ void Controller::deinit() Controller::Interface Controller::getInterface() const { CONFIG_T& config = Configuration.get(); - if (0x00 == config.Battery_JkBmsInterface) { return Interface::Uart; } - if (0x01 == config.Battery_JkBmsInterface) { return Interface::Transceiver; } + if (0x00 == config.Battery.JkBmsInterface) { return Interface::Uart; } + if (0x01 == config.Battery.JkBmsInterface) { return Interface::Transceiver; } return Interface::Invalid; } @@ -323,7 +323,7 @@ void Controller::sendRequest(uint8_t pollInterval) void Controller::loop() { CONFIG_T& config = Configuration.get(); - uint8_t pollInterval = config.Battery_JkBmsPollingInterval; + uint8_t pollInterval = config.Battery.JkBmsPollingInterval; while (HwSerial.available()) { rxData(HwSerial.read()); diff --git a/src/Led_Single.cpp b/src/Led_Single.cpp index 09658c85..744c7b78 100644 --- a/src/Led_Single.cpp +++ b/src/Led_Single.cpp @@ -12,85 +12,116 @@ 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() { } -void LedSingleClass::init() +void LedSingleClass::init(Scheduler& scheduler) { + bool ledActive = false; + _blinkTimeout.set(500); - _updateTimeout.set(LEDSINGLE_UPDATE_INTERVAL); turnAllOn(); - auto& pin = PinMapping.get(); + const auto& pin = PinMapping.get(); for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { if (pin.led[i] >= 0) { pinMode(pin.led[i], OUTPUT); - digitalWrite(pin.led[i], LOW); - _ledActive++; + setLed(i, false); + ledActive = true; } - _ledState[i] = LedState_t::Off; + _ledMode[i] = LedState_t::Off; + } + + 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 (_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; + if (getLocalTime(&timeinfo, 5) && (!config.Mqtt.Enabled || (config.Mqtt.Enabled && MqttSettings.getConnected()))) { + _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; } } - _updateTimeout.reset(); - } else if (_updateTimeout.occured() && _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; } +} - auto& pin = PinMapping.get(); +void LedSingleClass::outputLoop() +{ 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; @@ -98,12 +129,32 @@ void LedSingleClass::loop() } } +void LedSingleClass::setLed(const uint8_t ledNo, const 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/MessageOutput.cpp b/src/MessageOutput.cpp index 79e53a4e..f602bee1 100644 --- a/src/MessageOutput.cpp +++ b/src/MessageOutput.cpp @@ -1,105 +1,63 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022-2023 Thomas Basler and others */ -#include #include "MessageOutput.h" +#include + MessageOutputClass MessageOutput; -void MessageOutputClass::register_ws_output(AsyncWebSocket* output) +void MessageOutputClass::init(Scheduler& scheduler) { - std::lock_guard lock(_msgLock); - - _ws = output; + scheduler.addTask(_loopTask); + _loopTask.setCallback(std::bind(&MessageOutputClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); } -void MessageOutputClass::serialWrite(MessageOutputClass::message_t const& m) +void MessageOutputClass::register_ws_output(AsyncWebSocket* output) { - // on ESP32-S3, Serial.flush() blocks until a serial console is attached. - // operator bool() of HWCDC returns false if the device is not attached to - // a USB host. in general it makes sense to skip writing entirely if the - // default serial port is not ready. - if (!Serial) { return; } - - size_t written = 0; - while (written < m.size()) { - written += Serial.write(m.data() + written, m.size() - written); - } - Serial.flush(); + _ws = output; } size_t MessageOutputClass::write(uint8_t c) { - std::lock_guard lock(_msgLock); - - auto res = _task_messages.emplace(xTaskGetCurrentTaskHandle(), message_t()); - auto iter = res.first; - auto& message = iter->second; - - message.push_back(c); - - if (c == '\n') { - serialWrite(message); - _lines.emplace(std::move(message)); - _task_messages.erase(iter); + if (_buff_pos < BUFFER_SIZE) { + std::lock_guard lock(_msgLock); + _buffer[_buff_pos] = c; + _buff_pos++; + } else { + _forceSend = true; } - return 1; + return Serial.write(c); } -size_t MessageOutputClass::write(const uint8_t *buffer, size_t size) +size_t MessageOutputClass::write(const uint8_t* buffer, size_t size) { std::lock_guard lock(_msgLock); - - auto res = _task_messages.emplace(xTaskGetCurrentTaskHandle(), message_t()); - auto iter = res.first; - auto& message = iter->second; - - message.reserve(message.size() + size); - - for (size_t idx = 0; idx < size; ++idx) { - uint8_t c = buffer[idx]; - - message.push_back(c); - - if (c == '\n') { - serialWrite(message); - _lines.emplace(std::move(message)); - message.clear(); - message.reserve(size - idx - 1); - } + if (_buff_pos + size < BUFFER_SIZE) { + memcpy(&_buffer[_buff_pos], buffer, size); + _buff_pos += size; } + _forceSend = true; - if (message.empty()) { _task_messages.erase(iter); } - - return size; + return Serial.write(buffer, size); } void MessageOutputClass::loop() { - std::lock_guard lock(_msgLock); - - // clean up (possibly filled) buffers of deleted tasks - auto map_iter = _task_messages.begin(); - while (map_iter != _task_messages.end()) { - if (eTaskGetState(map_iter->first) == eDeleted) { - map_iter = _task_messages.erase(map_iter); - continue; + // Send data via websocket if either time is over or buffer is full + if (_forceSend || (millis() - _lastSend > 1000)) { + std::lock_guard lock(_msgLock); + if (_ws && _buff_pos > 0) { + _ws->textAll(_buffer, _buff_pos); + _buff_pos = 0; } - - ++map_iter; - } - - if (!_ws) { - while (!_lines.empty()) { - _lines.pop(); // do not hog memory + if (_forceSend) { + _buff_pos = 0; } - return; - } - - while (!_lines.empty() && _ws->availableForWriteAll()) { - _ws->textAll(std::make_shared(std::move(_lines.front()))); - _lines.pop(); + _forceSend = false; } } \ No newline at end of file diff --git a/src/MqttHandlVedirectHass.cpp b/src/MqttHandlVedirectHass.cpp index e406f875..22b0566f 100644 --- a/src/MqttHandlVedirectHass.cpp +++ b/src/MqttHandlVedirectHass.cpp @@ -11,13 +11,17 @@ MqttHandleVedirectHassClass MqttHandleVedirectHass; -void MqttHandleVedirectHassClass::init() +void MqttHandleVedirectHassClass::init(Scheduler& scheduler) { + scheduler.addTask(_loopTask); + _loopTask.setCallback(std::bind(&MqttHandleVedirectHassClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); } void MqttHandleVedirectHassClass::loop() { - if (!Configuration.get().Vedirect_Enabled) { + if (!Configuration.get().Vedirect.Enabled) { return; } if (_updateForced) { @@ -42,8 +46,8 @@ void MqttHandleVedirectHassClass::forceUpdate() void MqttHandleVedirectHassClass::publishConfig() { - if ((!Configuration.get().Mqtt_Hass_Enabled) || - (!Configuration.get().Vedirect_Enabled)) { + if ((!Configuration.get().Mqtt.Hass.Enabled) || + (!Configuration.get().Vedirect.Enabled)) { return; } @@ -120,8 +124,8 @@ void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char* JsonObject deviceObj = root.createNestedObject("dev"); createDeviceInfo(deviceObj); - if (Configuration.get().Mqtt_Hass_Expire) { - root[F("exp_aft")] = Configuration.get().Mqtt_PublishInterval * 3; + if (Configuration.get().Mqtt.Hass.Expire) { + root[F("exp_aft")] = Configuration.get().Mqtt.PublishInterval * 3; } if (deviceClass != NULL) { root[F("dev_cla")] = deviceClass; @@ -188,7 +192,7 @@ void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject& object) void MqttHandleVedirectHassClass::publish(const String& subtopic, const String& payload) { - String topic = Configuration.get().Mqtt_Hass_Topic; + String topic = Configuration.get().Mqtt.Hass.Topic; topic += subtopic; - MqttSettings.publishGeneric(topic.c_str(), payload.c_str(), Configuration.get().Mqtt_Hass_Retain); + MqttSettings.publishGeneric(topic.c_str(), payload.c_str(), Configuration.get().Mqtt.Hass.Retain); } diff --git a/src/MqttHandleDtu.cpp b/src/MqttHandleDtu.cpp index ee5ad417..c20ddb1e 100644 --- a/src/MqttHandleDtu.cpp +++ b/src/MqttHandleDtu.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 "MqttHandleDtu.h" #include "Configuration.h" @@ -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", WiFi.BSSIDstr()); } } \ No newline at end of file diff --git a/src/MqttHandleHass.cpp b/src/MqttHandleHass.cpp index dd2f5608..88553e15 100644 --- a/src/MqttHandleHass.cpp +++ b/src/MqttHandleHass.cpp @@ -1,16 +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 */ #include "MqttHandleHass.h" #include "MqttHandleInverter.h" #include "MqttSettings.h" #include "NetworkSettings.h" +#include "Utils.h" +#include "defaults.h" 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() @@ -37,7 +43,7 @@ void MqttHandleHassClass::forceUpdate() void MqttHandleHassClass::publishConfig() { - if (!Configuration.get().Mqtt_Hass_Enabled) { + if (!Configuration.get().Mqtt.Hass.Enabled) { return; } @@ -47,6 +53,14 @@ void MqttHandleHassClass::publishConfig() const CONFIG_T& config = Configuration.get(); + // publish DTU sensors + publishDtuSensor("IP", "", "diagnostic", "mdi:network-outline", "", ""); + publishDtuSensor("WiFi Signal", "signal_strength", "diagnostic", "", "dBm", "rssi"); + publishDtuSensor("Uptime", "duration", "diagnostic", "", "s", ""); + publishDtuBinarySensor("Status", "connectivity", "diagnostic", config.Mqtt.Lwt.Value_Online, config.Mqtt.Lwt.Value_Offline, config.Mqtt.Lwt.Topic); + + yield(); + // Loop all inverters for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); @@ -55,11 +69,11 @@ void MqttHandleHassClass::publishConfig() publishInverterButton(inv, "Turn Inverter On", "mdi:power-plug", "config", "", "cmd/power", "1"); publishInverterButton(inv, "Restart Inverter", "", "config", "restart", "cmd/restart", "1"); - publishInverterNumber(inv, "Limit NonPersistent Relative", "mdi:speedometer", "config", "cmd/limit_nonpersistent_relative", "status/limit_relative", "%"); - publishInverterNumber(inv, "Limit Persistent Relative", "mdi:speedometer", "config", "cmd/limit_persistent_relative", "status/limit_relative", "%"); + publishInverterNumber(inv, "Limit NonPersistent Relative", "mdi:speedometer", "config", "cmd/limit_nonpersistent_relative", "status/limit_relative", "%", 0, 100); + publishInverterNumber(inv, "Limit Persistent Relative", "mdi:speedometer", "config", "cmd/limit_persistent_relative", "status/limit_relative", "%", 0, 100); - publishInverterNumber(inv, "Limit NonPersistent Absolute", "mdi:speedometer", "config", "cmd/limit_nonpersistent_absolute", "status/limit_absolute", "W", 10, 2250); - publishInverterNumber(inv, "Limit Persistent Absolute", "mdi:speedometer", "config", "cmd/limit_persistent_absolute", "status/limit_absolute", "W", 10, 2250); + publishInverterNumber(inv, "Limit NonPersistent Absolute", "mdi:speedometer", "config", "cmd/limit_nonpersistent_absolute", "status/limit_absolute", "W", 0, MAX_INVERTER_LIMIT); + publishInverterNumber(inv, "Limit Persistent Absolute", "mdi:speedometer", "config", "cmd/limit_persistent_absolute", "status/limit_absolute", "W", 0, MAX_INVERTER_LIMIT); publishInverterBinarySensor(inv, "Reachable", "status/reachable", "1", "0"); publishInverterBinarySensor(inv, "Producing", "status/producing", "1", "0"); @@ -69,10 +83,10 @@ 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); + publishInverterField(inv, t, c, deviceFieldAssignment[f], clear); } } } @@ -81,13 +95,13 @@ void MqttHandleHassClass::publishConfig() } } -void MqttHandleHassClass::publishField(std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel, byteAssign_fieldDeviceClass_t fieldType, bool clear) +void MqttHandleHassClass::publishInverterField(std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear) { if (!inv->Statistics()->hasChannelFieldValue(type, channel, fieldType.fieldId)) { return; } - String serial = inv->serialString(); + const String serial = inv->serialString(); String fieldName; if (type == TYPE_AC && fieldType.fieldId == FLD_PDC) { @@ -104,12 +118,12 @@ void MqttHandleHassClass::publishField(std::shared_ptr inv, Ch chanNum = channel; } - String configTopic = "sensor/dtu_" + serial + const String configTopic = "sensor/dtu_" + serial + "/" + "ch" + chanNum + "_" + fieldName + "/config"; if (!clear) { - String stateTopic = MqttSettings.getPrefix() + MqttHandleInverter.getTopic(inv, type, channel, fieldType.fieldId); + const String stateTopic = MqttSettings.getPrefix() + MqttHandleInverter.getTopic(inv, type, channel, fieldType.fieldId); const char* devCls = deviceClasses[fieldType.deviceClsId]; const char* stateCls = stateClasses[fieldType.stateClsId]; @@ -130,11 +144,10 @@ void MqttHandleHassClass::publishField(std::shared_ptr inv, Ch root["unit_of_meas"] = unit_of_measure; } - JsonObject deviceObj = root.createNestedObject("dev"); - createDeviceInfo(deviceObj, inv); + createInverterInfo(root, 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; @@ -153,17 +166,17 @@ void MqttHandleHassClass::publishField(std::shared_ptr inv, Ch void MqttHandleHassClass::publishInverterButton(std::shared_ptr inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload) { - String serial = inv->serialString(); + const String serial = inv->serialString(); String buttonId = caption; buttonId.replace(" ", "_"); buttonId.toLowerCase(); - String configTopic = "button/dtu_" + serial + const String configTopic = "button/dtu_" + serial + "/" + buttonId + "/config"; - String cmdTopic = MqttSettings.getPrefix() + serial + "/" + subTopic; + const String cmdTopic = MqttSettings.getPrefix() + serial + "/" + subTopic; DynamicJsonDocument root(1024); root["name"] = caption; @@ -178,8 +191,7 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptr inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, - int16_t min, int16_t max) + const int16_t min, const int16_t max) { - String serial = inv->serialString(); + const String serial = inv->serialString(); String buttonId = caption; buttonId.replace(" ", "_"); buttonId.toLowerCase(); - String configTopic = "number/dtu_" + serial + const String configTopic = "number/dtu_" + serial + "/" + buttonId + "/config"; - String cmdTopic = MqttSettings.getPrefix() + serial + "/" + commandTopic; - String statTopic = MqttSettings.getPrefix() + serial + "/" + stateTopic; + const String cmdTopic = MqttSettings.getPrefix() + serial + "/" + commandTopic; + const String statTopic = MqttSettings.getPrefix() + serial + "/" + stateTopic; DynamicJsonDocument root(1024); root["name"] = caption; @@ -217,8 +229,7 @@ void MqttHandleHassClass::publishInverterNumber( root["min"] = min; root["max"] = max; - JsonObject deviceObj = root.createNestedObject("dev"); - createDeviceInfo(deviceObj, inv); + createInverterInfo(root, inv); String buffer; serializeJson(root, buffer); @@ -227,17 +238,17 @@ void MqttHandleHassClass::publishInverterNumber( void MqttHandleHassClass::publishInverterBinarySensor(std::shared_ptr inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off) { - String serial = inv->serialString(); + const String serial = inv->serialString(); String sensorId = caption; sensorId.replace(" ", "_"); sensorId.toLowerCase(); - String configTopic = "binary_sensor/dtu_" + serial + const String configTopic = "binary_sensor/dtu_" + serial + "/" + sensorId + "/config"; - String statTopic = MqttSettings.getPrefix() + serial + "/" + subTopic; + const String statTopic = MqttSettings.getPrefix() + serial + "/" + subTopic; DynamicJsonDocument root(1024); root["name"] = caption; @@ -246,27 +257,145 @@ void MqttHandleHassClass::publishInverterBinarySensor(std::shared_ptr inv) +void MqttHandleHassClass::publishDtuSensor(const char* name, const char* device_class, const char* category, const char* icon, const char* unit_of_measure, const char* subTopic) { - object["name"] = inv->name(); - object["ids"] = inv->serialString(); - object["cu"] = String("http://") + NetworkSettings.localIP().toString(); - object["mf"] = "OpenDTU"; - object["mdl"] = inv->typeName(); - object["sw"] = AUTO_GIT_HASH; + String id = name; + id.toLowerCase(); + id.replace(" ", "_"); + String topic = subTopic; + if (topic == "") { + topic = id; + } + + DynamicJsonDocument root(1024); + root["name"] = name; + root["uniq_id"] = getDtuUniqueId() + "_" + id; + if (strcmp(device_class, "")) { + root["dev_cla"] = device_class; + } + if (strcmp(category, "")) { + root["ent_cat"] = category; + } + if (strcmp(icon, "")) { + root["ic"] = icon; + } + if (strcmp(unit_of_measure, "")) { + root["unit_of_meas"] = unit_of_measure; + } + root["stat_t"] = MqttSettings.getPrefix() + "dtu" + "/" + topic; + + root["avty_t"] = MqttSettings.getPrefix() + Configuration.get().Mqtt.Lwt.Topic; + + const CONFIG_T& config = Configuration.get(); + root["pl_avail"] = config.Mqtt.Lwt.Value_Online; + root["pl_not_avail"] = config.Mqtt.Lwt.Value_Offline; + + createDtuInfo(root); + + String buffer; + const String configTopic = "sensor/" + getDtuUniqueId() + "/" + id + "/config"; + serializeJson(root, buffer); + publish(configTopic, buffer); +} + +void MqttHandleHassClass::publishDtuBinarySensor(const char* name, const char* device_class, const char* category, const char* payload_on, const char* payload_off, const char* subTopic) +{ + String id = name; + id.toLowerCase(); + id.replace(" ", "_"); + + String topic = subTopic; + if (!strcmp(subTopic, "")) { + topic = String("dtu/") + "/" + id; + } + + DynamicJsonDocument root(1024); + root["name"] = name; + root["uniq_id"] = getDtuUniqueId() + "_" + id; + root["stat_t"] = MqttSettings.getPrefix() + topic; + root["pl_on"] = payload_on; + root["pl_off"] = payload_off; + + if (strcmp(device_class, "")) { + root["dev_cla"] = device_class; + } + if (strcmp(category, "")) { + root["ent_cat"] = category; + } + + createDtuInfo(root); + + String buffer; + const String configTopic = "binary_sensor/" + getDtuUniqueId() + "/" + id + "/config"; + serializeJson(root, buffer); + publish(configTopic, buffer); +} + +void MqttHandleHassClass::createInverterInfo(DynamicJsonDocument& root, std::shared_ptr inv) +{ + createDeviceInfo( + root, + inv->name(), + inv->serialString(), + getDtuUrl(), + "OpenDTU", + inv->typeName(), + AUTO_GIT_HASH, + getDtuUniqueId()); +} + +void MqttHandleHassClass::createDtuInfo(DynamicJsonDocument& root) +{ + createDeviceInfo( + root, + NetworkSettings.getHostname(), + getDtuUniqueId(), + getDtuUrl(), + "OpenDTU", + "OpenDTU", + AUTO_GIT_HASH); +} + +void MqttHandleHassClass::createDeviceInfo( + DynamicJsonDocument& root, + const String& name, const String& identifiers, const String& configuration_url, + const String& manufacturer, const String& model, const String& sw_version, + const String& via_device) +{ + auto object = root.createNestedObject("dev"); + + object["name"] = name; + object["ids"] = identifiers; + object["cu"] = configuration_url; + object["mf"] = manufacturer; + object["mdl"] = model; + object["sw"] = sw_version; + + if (via_device != "") { + object["via_device"] = via_device; + } +} + +String MqttHandleHassClass::getDtuUniqueId() +{ + return NetworkSettings.getHostname() + "_" + Utils::getChipId(); +} + +String MqttHandleHassClass::getDtuUrl() +{ + return String("http://") + NetworkSettings.localIP().toString(); } void MqttHandleHassClass::publish(const String& subtopic, const String& payload) { - String topic = Configuration.get().Mqtt_Hass_Topic; + String topic = Configuration.get().Mqtt.Hass.Topic; topic += subtopic; - MqttSettings.publishGeneric(topic.c_str(), payload.c_str(), Configuration.get().Mqtt_Hass_Retain); + MqttSettings.publishGeneric(topic.c_str(), payload.c_str(), Configuration.get().Mqtt.Hass.Retain); } \ No newline at end of file diff --git a/src/MqttHandleHuawei.cpp b/src/MqttHandleHuawei.cpp index 5df671f1..06e5d22a 100644 --- a/src/MqttHandleHuawei.cpp +++ b/src/MqttHandleHuawei.cpp @@ -18,8 +18,13 @@ MqttHandleHuaweiClass MqttHandleHuawei; -void MqttHandleHuaweiClass::init() +void MqttHandleHuaweiClass::init(Scheduler& scheduler) { + scheduler.addTask(_loopTask); + _loopTask.setCallback(std::bind(&MqttHandleHuaweiClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); + using std::placeholders::_1; using std::placeholders::_2; using std::placeholders::_3; @@ -47,13 +52,13 @@ void MqttHandleHuaweiClass::loop() const CONFIG_T& config = Configuration.get(); - if (!config.Huawei_Enabled) { + if (!config.Huawei.Enabled) { return; } const RectifierParameters_t *rp = HuaweiCan.get(); - if ((millis() - _lastPublish) > (config.Mqtt_PublishInterval * 1000) ) { + if ((millis() - _lastPublish) > (config.Mqtt.PublishInterval * 1000) ) { MqttSettings.publish("huawei/data_age", String((millis() - HuaweiCan.getLastUpdate()) / 1000)); MqttSettings.publish("huawei/input_voltage", String(rp->input_voltage)); MqttSettings.publish("huawei/input_current", String(rp->input_current)); @@ -78,7 +83,7 @@ void MqttHandleHuaweiClass::onMqttMessage(const espMqttClientTypes::MessagePrope const CONFIG_T& config = Configuration.get(); // ignore messages if Huawei is disabled - if (!config.Huawei_Enabled) { + if (!config.Huawei.Enabled) { return; } @@ -86,7 +91,7 @@ void MqttHandleHuaweiClass::onMqttMessage(const espMqttClientTypes::MessagePrope strncpy(token_topic, topic, MQTT_MAX_TOPIC_STRLEN + 40); // convert const char* to char* char* setting; - char* rest = &token_topic[strlen(config.Mqtt_Topic)]; + char* rest = &token_topic[strlen(config.Mqtt.Topic)]; strtok_r(rest, "/", &rest); // Remove "huawei" strtok_r(rest, "/", &rest); // Remove "cmd" diff --git a/src/MqttHandleInverter.cpp b/src/MqttHandleInverter.cpp index 9048e06f..88121096 100644 --- a/src/MqttHandleInverter.cpp +++ b/src/MqttHandleInverter.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 "MqttHandleInverter.h" #include "MessageOutput.h" @@ -18,7 +18,7 @@ MqttHandleInverterClass MqttHandleInverter; -void MqttHandleInverterClass::init() +void MqttHandleInverterClass::init(Scheduler& scheduler) { using std::placeholders::_1; using std::placeholders::_2; @@ -27,103 +27,106 @@ void MqttHandleInverterClass::init() using std::placeholders::_5; using std::placeholders::_6; - String topic = MqttSettings.getPrefix(); + const String topic = MqttSettings.getPrefix(); MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); 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); + const 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())); + const 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(); } } -void MqttHandleInverterClass::publishField(std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) +void MqttHandleInverterClass::publishField(std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) { - String topic = getTopic(inv, type, channel, fieldId); + const String topic = getTopic(inv, type, channel, fieldId); if (topic == "") { return; } @@ -131,10 +134,10 @@ void MqttHandleInverterClass::publishField(std::shared_ptr inv MqttSettings.publish(topic, inv->Statistics()->getChannelFieldValueString(type, channel, fieldId)); } -String MqttHandleInverterClass::getTopic(std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) +String MqttHandleInverterClass::getTopic(std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId) { if (!inv->Statistics()->hasChannelFieldValue(type, channel, fieldId)) { - return String(""); + return ""; } String chanName; @@ -156,7 +159,7 @@ String MqttHandleInverterClass::getTopic(std::shared_ptr inv, return inv->serialString() + "/" + chanNum + "/" + chanName; } -void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) +void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, const size_t len, const size_t index, const size_t total) { const CONFIG_T& config = Configuration.get(); @@ -166,7 +169,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); @@ -176,8 +179,7 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro return; } - uint64_t serial; - serial = strtoull(serial_str, 0, 16); + const uint64_t serial = strtoull(serial_str, 0, 16); auto inv = Hoymiles.getInverterBySerial(serial); @@ -194,7 +196,7 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro char* strlimit = new char[len + 1]; memcpy(strlimit, payload, len); strlimit[len] = '\0'; - int32_t payload_val = strtol(strlimit, NULL, 10); + const int32_t payload_val = strtol(strlimit, NULL, 10); delete[] strlimit; if (payload_val < 0) { diff --git a/src/MqttHandleInverterTotal.cpp b/src/MqttHandleInverterTotal.cpp index ac8e6a4e..db584b26 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/MqttHandlePowerLimiter.cpp b/src/MqttHandlePowerLimiter.cpp index d5d6987e..9a4a7f77 100644 --- a/src/MqttHandlePowerLimiter.cpp +++ b/src/MqttHandlePowerLimiter.cpp @@ -11,8 +11,13 @@ MqttHandlePowerLimiterClass MqttHandlePowerLimiter; -void MqttHandlePowerLimiterClass::init() +void MqttHandlePowerLimiterClass::init(Scheduler& scheduler) { + scheduler.addTask(_loopTask); + _loopTask.setCallback(std::bind(&MqttHandlePowerLimiterClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); + using std::placeholders::_1; using std::placeholders::_2; using std::placeholders::_3; @@ -35,7 +40,7 @@ void MqttHandlePowerLimiterClass::loop() const CONFIG_T& config = Configuration.get(); - if ((millis() - _lastPublish) > (config.Mqtt_PublishInterval * 1000) ) { + if ((millis() - _lastPublish) > (config.Mqtt.PublishInterval * 1000) ) { auto val = static_cast(PowerLimiter.getMode()); MqttSettings.publish("powerlimiter/status/mode", String(val)); @@ -51,7 +56,7 @@ void MqttHandlePowerLimiterClass::onCmdMode(const espMqttClientTypes::MessagePro const CONFIG_T& config = Configuration.get(); // ignore messages if PowerLimiter is disabled - if (!config.PowerLimiter_Enabled) { + if (!config.PowerLimiter.Enabled) { return; } diff --git a/src/MqttHandlePylontechHass.cpp b/src/MqttHandlePylontechHass.cpp index 7259cf91..c2353953 100644 --- a/src/MqttHandlePylontechHass.cpp +++ b/src/MqttHandlePylontechHass.cpp @@ -9,14 +9,18 @@ MqttHandlePylontechHassClass MqttHandlePylontechHass; -void MqttHandlePylontechHassClass::init() +void MqttHandlePylontechHassClass::init(Scheduler& scheduler) { + scheduler.addTask(_loopTask); + _loopTask.setCallback(std::bind(&MqttHandlePylontechHassClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); } void MqttHandlePylontechHassClass::loop() { CONFIG_T& config = Configuration.get(); - if (!config.Battery_Enabled) { + if (!config.Battery.Enabled) { return; } if (_updateForced) { @@ -42,7 +46,7 @@ void MqttHandlePylontechHassClass::forceUpdate() void MqttHandlePylontechHassClass::publishConfig() { CONFIG_T& config = Configuration.get(); - if ((!config.Mqtt_Hass_Enabled) || (!config.Battery_Enabled)) { + if ((!config.Mqtt.Hass.Enabled) || (!config.Battery.Enabled)) { return; } @@ -111,29 +115,29 @@ void MqttHandlePylontechHassClass::publishSensor(const char* caption, const char statTopic.concat(subTopic); DynamicJsonDocument root(1024); - root[F("name")] = caption; - root[F("stat_t")] = statTopic; - root[F("uniq_id")] = serial + "_" + sensorId; + root["name"] = caption; + root["stat_t"] = statTopic; + root["uniq_id"] = serial + "_" + sensorId; if (icon != NULL) { - root[F("icon")] = icon; + root["icon"] = icon; } if (unitOfMeasurement != NULL) { - root[F("unit_of_meas")] = unitOfMeasurement; + root["unit_of_meas"] = unitOfMeasurement; } JsonObject deviceObj = root.createNestedObject("dev"); createDeviceInfo(deviceObj); - if (Configuration.get().Mqtt_Hass_Expire) { - root[F("exp_aft")] = Configuration.get().Mqtt_PublishInterval * 3; + if (Configuration.get().Mqtt.Hass.Expire) { + root["exp_aft"] = Configuration.get().Mqtt.PublishInterval * 3; } if (deviceClass != NULL) { - root[F("dev_cla")] = deviceClass; + root["dev_cla"] = deviceClass; } if (stateClass != NULL) { - root[F("stat_cla")] = stateClass; + root["stat_cla"] = stateClass; } char buffer[512]; @@ -162,14 +166,14 @@ void MqttHandlePylontechHassClass::publishBinarySensor(const char* caption, cons statTopic.concat(subTopic); DynamicJsonDocument root(1024); - root[F("name")] = caption; - root[F("uniq_id")] = serial + "_" + sensorId; - root[F("stat_t")] = statTopic; - root[F("pl_on")] = payload_on; - root[F("pl_off")] = payload_off; + root["name"] = caption; + root["uniq_id"] = serial + "_" + sensorId; + root["stat_t"] = statTopic; + root["pl_on"] = payload_on; + root["pl_off"] = payload_off; if (icon != NULL) { - root[F("icon")] = icon; + root["icon"] = icon; } JsonObject deviceObj = root.createNestedObject("dev"); @@ -182,17 +186,17 @@ void MqttHandlePylontechHassClass::publishBinarySensor(const char* caption, cons void MqttHandlePylontechHassClass::createDeviceInfo(JsonObject& object) { - object[F("name")] = "Battery(" + serial + ")"; - object[F("ids")] = serial; - object[F("cu")] = String(F("http://")) + NetworkSettings.localIP().toString(); - object[F("mf")] = F("OpenDTU"); - object[F("mdl")] = Battery.getStats()->getManufacturer(); - object[F("sw")] = AUTO_GIT_HASH; + object["name"] = "Battery(" + serial + ")"; + object["ids"] = serial; + object["cu"] = String("http://") + NetworkSettings.localIP().toString(); + object["mf"] = "OpenDTU"; + object["mdl"] = Battery.getStats()->getManufacturer(); + object["sw"] = AUTO_GIT_HASH; } void MqttHandlePylontechHassClass::publish(const String& subtopic, const String& payload) { - String topic = Configuration.get().Mqtt_Hass_Topic; + String topic = Configuration.get().Mqtt.Hass.Topic; topic += subtopic; - MqttSettings.publishGeneric(topic.c_str(), payload.c_str(), Configuration.get().Mqtt_Hass_Retain); + MqttSettings.publishGeneric(topic.c_str(), payload.c_str(), Configuration.get().Mqtt.Hass.Retain); } diff --git a/src/MqttHandleVedirect.cpp b/src/MqttHandleVedirect.cpp index 7466fc1a..e5997dc9 100644 --- a/src/MqttHandleVedirect.cpp +++ b/src/MqttHandleVedirect.cpp @@ -14,18 +14,30 @@ MqttHandleVedirectClass MqttHandleVedirect; // #define MQTTHANDLEVEDIRECT_DEBUG -void MqttHandleVedirectClass::init() +void MqttHandleVedirectClass::init(Scheduler& scheduler) +{ + scheduler.addTask(_loopTask); + _loopTask.setCallback(std::bind(&MqttHandleVedirectClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); + + // initially force a full publish + this->forceUpdate(); +} + +void MqttHandleVedirectClass::forceUpdate() { // initially force a full publish _nextPublishUpdatesOnly = 0; _nextPublishFull = 1; } + void MqttHandleVedirectClass::loop() { CONFIG_T& config = Configuration.get(); - if (!MqttSettings.getConnected() || !config.Vedirect_Enabled) { + if (!MqttSettings.getConnected() || !config.Vedirect.Enabled) { return; } @@ -38,7 +50,7 @@ void MqttHandleVedirectClass::loop() if (_nextPublishFull <= _nextPublishUpdatesOnly) { _PublishFull = true; } else { - _PublishFull = !config.Vedirect_UpdatesOnly; + _PublishFull = !config.Vedirect.UpdatesOnly; } #ifdef MQTTHANDLEVEDIRECT_DEBUG @@ -129,14 +141,14 @@ void MqttHandleVedirectClass::loop() } // now calculate next points of time to publish - _nextPublishUpdatesOnly = millis() + (config.Mqtt_PublishInterval * 1000); + _nextPublishUpdatesOnly = millis() + (config.Mqtt.PublishInterval * 1000); if (_PublishFull) { // when Home Assistant MQTT-Auto-Discovery is active, // and "enable expiration" is active, all values must be published at // least once before the announced expiry interval is reached - if ((config.Vedirect_UpdatesOnly) && (config.Mqtt_Hass_Enabled) && (config.Mqtt_Hass_Expire)) { - _nextPublishFull = millis() + (((config.Mqtt_PublishInterval * 3) - 1) * 1000); + if ((config.Vedirect.UpdatesOnly) && (config.Mqtt.Hass.Enabled) && (config.Mqtt.Hass.Expire)) { + _nextPublishFull = millis() + (((config.Mqtt.PublishInterval * 3) - 1) * 1000); #ifdef MQTTHANDLEVEDIRECT_DEBUG uint32_t _tmpNextFullSeconds = (config.Mqtt_PublishInterval * 3) - 1; diff --git a/src/MqttSettings.cpp b/src/MqttSettings.cpp index 2232d53b..a0b23686 100644 --- a/src/MqttSettings.cpp +++ b/src/MqttSettings.cpp @@ -19,33 +19,33 @@ void MqttSettingsClass::NetworkEvent(network_event event) break; case network_event::NETWORK_DISCONNECTED: MessageOutput.println("Network lost connection"); - mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi + _mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi break; default: break; } } -void MqttSettingsClass::onMqttConnect(bool sessionPresent) +void MqttSettingsClass::onMqttConnect(const 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) { + if (_mqttClient != nullptr) { for (const auto& cb : _mqttSubscribeParser.get_callbacks()) { - mqttClient->subscribe(cb.topic.c_str(), cb.qos); + _mqttClient->subscribe(cb.topic.c_str(), cb.qos); } } } -void MqttSettingsClass::subscribe(const String& topic, uint8_t qos, const espMqttClientTypes::OnMessageCallback& cb) +void MqttSettingsClass::subscribe(const String& topic, const uint8_t qos, const espMqttClientTypes::OnMessageCallback& cb) { _mqttSubscribeParser.register_callback(topic.c_str(), qos, cb); std::lock_guard lock(_clientLock); - if (mqttClient != nullptr) { - mqttClient->subscribe(topic.c_str(), qos); + if (_mqttClient != nullptr) { + _mqttClient->subscribe(topic.c_str(), qos); } } @@ -53,8 +53,8 @@ void MqttSettingsClass::unsubscribe(const String& topic) { _mqttSubscribeParser.unregister_callback(topic.c_str()); std::lock_guard lock(_clientLock); - if (mqttClient != nullptr) { - mqttClient->unsubscribe(topic.c_str()); + if (_mqttClient != nullptr) { + _mqttClient->unsubscribe(topic.c_str()); } } @@ -85,11 +85,11 @@ void MqttSettingsClass::onMqttDisconnect(espMqttClientTypes::DisconnectReason re default: MessageOutput.println("Unknown"); } - mqttReconnectTimer.once( + _mqttReconnectTimer.once( 2, +[](MqttSettingsClass* instance) { instance->performConnect(); }, this); } -void MqttSettingsClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) +void MqttSettingsClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, const size_t len, const size_t index, const size_t total) { if (_verboseLogging) { MessageOutput.print("Received MQTT message on topic: "); @@ -101,7 +101,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; @@ -110,53 +110,53 @@ void MqttSettingsClass::performConnect() using std::placeholders::_6; std::lock_guard lock(_clientLock); - if (mqttClient == nullptr) { + if (_mqttClient == nullptr) { return; } MessageOutput.println("Connecting to MQTT..."); const CONFIG_T& config = Configuration.get(); - _verboseLogging = config.Mqtt_VerboseLogging; - willTopic = getPrefix() + config.Mqtt_LwtTopic; - 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); + _verboseLogging = config.Mqtt.VerboseLogging; + const String willTopic = getPrefix() + config.Mqtt.Lwt.Topic; + const String clientId = NetworkSettings.getApName(); + 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)->setClientId(clientId.c_str()); - 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)); + 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)->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)->setClientId(clientId.c_str()); - 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)); + 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(), 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)); + 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)); } - mqttClient->connect(); + _mqttClient->connect(); } } 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) { + if (_mqttClient == nullptr) { return; } - mqttClient->disconnect(); + _mqttClient->disconnect(); } void MqttSettingsClass::performReconnect() @@ -165,22 +165,22 @@ void MqttSettingsClass::performReconnect() createMqttClientObject(); - mqttReconnectTimer.once( + _mqttReconnectTimer.once( 2, +[](MqttSettingsClass* instance) { instance->performConnect(); }, this); } bool MqttSettingsClass::getConnected() { std::lock_guard lock(_clientLock); - if (mqttClient == nullptr) { + if (_mqttClient == nullptr) { return false; } - return mqttClient->connected(); + return _mqttClient->connected(); } -String MqttSettingsClass::getPrefix() +String MqttSettingsClass::getPrefix() const { - return Configuration.get().Mqtt_Topic; + return Configuration.get().Mqtt.Topic; } void MqttSettingsClass::publish(const String& subtopic, const String& payload) @@ -191,16 +191,16 @@ 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) +void MqttSettingsClass::publishGeneric(const String& topic, const String& payload, const bool retain, const uint8_t qos) { std::lock_guard lock(_clientLock); - if (mqttClient == nullptr) { + if (_mqttClient == nullptr) { return; } - mqttClient->publish(topic.c_str(), qos, retain, payload.c_str()); + _mqttClient->publish(topic.c_str(), qos, retain, payload.c_str()); } void MqttSettingsClass::init() @@ -211,24 +211,18 @@ void MqttSettingsClass::init() createMqttClientObject(); } -void MqttSettingsClass::loop() -{ - if (nullptr == mqttClient) { return; } - mqttClient->loop(); -} - void MqttSettingsClass::createMqttClientObject() { std::lock_guard lock(_clientLock); - if (mqttClient != nullptr) { - delete mqttClient; - mqttClient = nullptr; + if (_mqttClient != nullptr) { + delete _mqttClient; + _mqttClient = nullptr; } const CONFIG_T& config = Configuration.get(); - if (config.Mqtt_Tls) { - mqttClient = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO); + if (config.Mqtt.Tls.Enabled) { + _mqttClient = static_cast(new espMqttClientSecure); } else { - mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO); + _mqttClient = static_cast(new espMqttClient); } } diff --git a/src/NetworkSettings.cpp b/src/NetworkSettings.cpp index 7e725ff1..92a3156d 100644 --- a/src/NetworkSettings.cpp +++ b/src/NetworkSettings.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 "NetworkSettings.h" #include "Configuration.h" @@ -12,13 +12,13 @@ #include NetworkSettingsClass::NetworkSettingsClass() - : apIp(192, 168, 4, 1) - , apNetmask(255, 255, 255, 0) + : _apIp(192, 168, 4, 1) + , _apNetmask(255, 255, 255, 0) { - dnsServer.reset(new DNSServer()); + _dnsServer.reset(new DNSServer()); } -void NetworkSettingsClass::init() +void NetworkSettingsClass::init(Scheduler& scheduler) { using std::placeholders::_1; @@ -27,9 +27,14 @@ 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) +void NetworkSettingsClass::NetworkEvent(const WiFiEvent_t event) { switch (event) { case ARDUINO_EVENT_ETH_START: @@ -87,7 +92,7 @@ void NetworkSettingsClass::NetworkEvent(WiFiEvent_t event) } } -bool NetworkSettingsClass::onEvent(NetworkEventCb cbEvent, network_event event) +bool NetworkSettingsClass::onEvent(NetworkEventCb cbEvent, const network_event event) { if (!cbEvent) { return pdFALSE; @@ -99,10 +104,10 @@ bool NetworkSettingsClass::onEvent(NetworkEventCb cbEvent, network_event event) return true; } -void NetworkSettingsClass::raiseEvent(network_event event) +void NetworkSettingsClass::raiseEvent(const network_event event) { for (uint32_t i = 0; i < _cbEventList.size(); i++) { - NetworkEventCbList_t entry = _cbEventList[i]; + const NetworkEventCbList_t entry = _cbEventList[i]; if (entry.cb) { if (entry.event == event || entry.event == network_event::NETWORK_EVENT_MAX) { entry.cb(event); @@ -113,13 +118,13 @@ void NetworkSettingsClass::raiseEvent(network_event event) void NetworkSettingsClass::handleMDNS() { - bool mdnsEnabled = Configuration.get().Mdns_Enabled; + const bool mdnsEnabled = Configuration.get().Mdns.Enabled; - if (lastMdnsEnabled == mdnsEnabled) { + if (_lastMdnsEnabled == mdnsEnabled) { return; } - lastMdnsEnabled = mdnsEnabled; + _lastMdnsEnabled = mdnsEnabled; MDNS.end(); @@ -142,17 +147,17 @@ void NetworkSettingsClass::handleMDNS() void NetworkSettingsClass::setupMode() { - if (adminEnabled) { + if (_adminEnabled) { WiFi.mode(WIFI_AP_STA); String ssidString = getApName(); - WiFi.softAPConfig(apIp, apIp, apNetmask); - WiFi.softAP((const char*)ssidString.c_str(), Configuration.get().Security_Password); - dnsServer->setErrorReplyCode(DNSReplyCode::NoError); - dnsServer->start(DNS_PORT, "*", WiFi.softAPIP()); - dnsServerStatus = true; + WiFi.softAPConfig(_apIp, _apIp, _apNetmask); + WiFi.softAP(ssidString.c_str(), Configuration.get().Security.Password); + _dnsServer->setErrorReplyCode(DNSReplyCode::NoError); + _dnsServer->start(DNS_PORT, "*", WiFi.softAPIP()); + _dnsServerStatus = true; } else { - dnsServerStatus = false; - dnsServer->stop(); + _dnsServerStatus = false; + _dnsServer->stop(); if (_networkMode == network_mode::WiFi) { WiFi.mode(WIFI_STA); } else { @@ -168,13 +173,13 @@ void NetworkSettingsClass::setupMode() void NetworkSettingsClass::enableAdminMode() { - adminEnabled = true; - adminTimeoutCounter = 0; - adminTimeoutCounterMax = Configuration.get().WiFi_ApTimeout * 60; + _adminEnabled = true; + _adminTimeoutCounter = 0; + _adminTimeoutCounterMax = Configuration.get().WiFi.ApTimeout * 60; setupMode(); } -String NetworkSettingsClass::getApName() +String NetworkSettingsClass::getApName() const { return String(ACCESS_POINT_NAME + String(Utils::getChipId())); } @@ -198,26 +203,26 @@ void NetworkSettingsClass::loop() applyConfig(); } - if (millis() - lastTimerCall > 1000) { - if (adminEnabled && adminTimeoutCounterMax > 0) { - adminTimeoutCounter++; - if (adminTimeoutCounter % 10 == 0) { - MessageOutput.printf("Admin AP remaining seconds: %d / %d\r\n", adminTimeoutCounter, adminTimeoutCounterMax); + if (millis() - _lastTimerCall > 1000) { + if (_adminEnabled && _adminTimeoutCounterMax > 0) { + _adminTimeoutCounter++; + if (_adminTimeoutCounter % 10 == 0) { + MessageOutput.printf("Admin AP remaining seconds: %d / %d\r\n", _adminTimeoutCounter, _adminTimeoutCounterMax); } } - connectTimeoutTimer++; - connectRedoTimer++; - lastTimerCall = millis(); + _connectTimeoutTimer++; + _connectRedoTimer++; + _lastTimerCall = millis(); } - if (adminEnabled) { + if (_adminEnabled) { // Don't disable the admin mode when network is not available if (!isConnected()) { - adminTimeoutCounter = 0; + _adminTimeoutCounter = 0; } // If WiFi is connected to AP for more than adminTimeoutCounterMax // seconds, disable the internal Access Point - if (adminTimeoutCounter > adminTimeoutCounterMax) { - adminEnabled = false; + if (_adminTimeoutCounter > _adminTimeoutCounterMax) { + _adminEnabled = false; MessageOutput.println("Admin mode disabled"); setupMode(); } @@ -225,28 +230,28 @@ void NetworkSettingsClass::loop() // WiFi is searching for an AP. So disable searching afer // WIFI_RECONNECT_TIMEOUT and repeat after WIFI_RECONNECT_REDO_TIMEOUT if (isConnected()) { - connectTimeoutTimer = 0; - connectRedoTimer = 0; + _connectTimeoutTimer = 0; + _connectRedoTimer = 0; } else { - if (connectTimeoutTimer > WIFI_RECONNECT_TIMEOUT && !forceDisconnection) { + if (_connectTimeoutTimer > WIFI_RECONNECT_TIMEOUT && !_forceDisconnection) { MessageOutput.print("Disable search for AP... "); WiFi.mode(WIFI_AP); MessageOutput.println("done"); - connectRedoTimer = 0; - forceDisconnection = true; + _connectRedoTimer = 0; + _forceDisconnection = true; } - if (connectRedoTimer > WIFI_RECONNECT_REDO_TIMEOUT && forceDisconnection) { + if (_connectRedoTimer > WIFI_RECONNECT_REDO_TIMEOUT && _forceDisconnection) { MessageOutput.print("Enable search for AP... "); WiFi.mode(WIFI_AP_STA); MessageOutput.println("done"); applyConfig(); - connectTimeoutTimer = 0; - forceDisconnection = false; + _connectTimeoutTimer = 0; + _forceDisconnection = false; } } } - if (dnsServerStatus) { - dnsServer->processNextRequest(); + if (_dnsServerStatus) { + _dnsServer->processNextRequest(); } handleMDNS(); @@ -255,15 +260,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,39 +303,39 @@ 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.Dhcp) { 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"); } } } -IPAddress NetworkSettingsClass::localIP() +IPAddress NetworkSettingsClass::localIP() const { switch (_networkMode) { case network_mode::Ethernet: @@ -344,7 +349,7 @@ IPAddress NetworkSettingsClass::localIP() } } -IPAddress NetworkSettingsClass::subnetMask() +IPAddress NetworkSettingsClass::subnetMask() const { switch (_networkMode) { case network_mode::Ethernet: @@ -358,7 +363,7 @@ IPAddress NetworkSettingsClass::subnetMask() } } -IPAddress NetworkSettingsClass::gatewayIP() +IPAddress NetworkSettingsClass::gatewayIP() const { switch (_networkMode) { case network_mode::Ethernet: @@ -372,7 +377,7 @@ IPAddress NetworkSettingsClass::gatewayIP() } } -IPAddress NetworkSettingsClass::dnsIP(uint8_t dns_no) +IPAddress NetworkSettingsClass::dnsIP(const uint8_t dns_no) const { switch (_networkMode) { case network_mode::Ethernet: @@ -386,7 +391,7 @@ IPAddress NetworkSettingsClass::dnsIP(uint8_t dns_no) } } -String NetworkSettingsClass::macAddress() +String NetworkSettingsClass::macAddress() const { switch (_networkMode) { case network_mode::Ethernet: @@ -407,8 +412,8 @@ String NetworkSettingsClass::getHostname() char resultHostname[WIFI_MAX_HOSTNAME_STRLEN + 1]; uint8_t pos = 0; - uint32_t chipId = Utils::getChipId(); - snprintf(preparedHostname, WIFI_MAX_HOSTNAME_STRLEN + 1, config.WiFi_Hostname, chipId); + const uint32_t chipId = Utils::getChipId(); + 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 @@ -439,12 +444,12 @@ String NetworkSettingsClass::getHostname() return resultHostname; } -bool NetworkSettingsClass::isConnected() +bool NetworkSettingsClass::isConnected() const { return WiFi.localIP()[0] != 0 || ETH.localIP()[0] != 0; } -network_mode NetworkSettingsClass::NetworkMode() +network_mode NetworkSettingsClass::NetworkMode() const { return _networkMode; } 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/PinMapping.cpp b/src/PinMapping.cpp index 2cc48906..776cde8c 100644 --- a/src/PinMapping.cpp +++ b/src/PinMapping.cpp @@ -282,7 +282,7 @@ bool PinMappingClass::init(const String& deviceMapping) return false; } -bool PinMappingClass::isValidNrf24Config() +bool PinMappingClass::isValidNrf24Config() const { return _pinMapping.nrf24_clk >= 0 && _pinMapping.nrf24_cs >= 0 @@ -292,7 +292,7 @@ bool PinMappingClass::isValidNrf24Config() && _pinMapping.nrf24_mosi >= 0; } -bool PinMappingClass::isValidCmt2300Config() +bool PinMappingClass::isValidCmt2300Config() const { return _pinMapping.cmt_clk >= 0 && _pinMapping.cmt_cs >= 0 @@ -300,7 +300,7 @@ bool PinMappingClass::isValidCmt2300Config() && _pinMapping.cmt_sdio >= 0; } -bool PinMappingClass::isValidEthConfig() +bool PinMappingClass::isValidEthConfig() const { return _pinMapping.eth_enabled; } diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index feb41dd8..86cb2751 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -18,7 +18,13 @@ PowerLimiterClass PowerLimiter; -void PowerLimiterClass::init() { } +void PowerLimiterClass::init(Scheduler& scheduler) +{ + scheduler.addTask(_loopTask); + _loopTask.setCallback(std::bind(&PowerLimiterClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); +} std::string const& PowerLimiterClass::getStatusText(PowerLimiterClass::Status status) { @@ -100,7 +106,7 @@ bool PowerLimiterClass::shutdown(PowerLimiterClass::Status status) if (CMD_PENDING == lastPowerCommandState) { return true; } CONFIG_T& config = Configuration.get(); - commitPowerLimit(_inverter, config.PowerLimiter_LowerPowerLimit, false); + commitPowerLimit(_inverter, config.PowerLimiter.LowerPowerLimit, false); return true; } @@ -108,7 +114,7 @@ bool PowerLimiterClass::shutdown(PowerLimiterClass::Status status) void PowerLimiterClass::loop() { CONFIG_T const& config = Configuration.get(); - _verboseLogging = config.PowerLimiter_VerboseLogging; + _verboseLogging = config.PowerLimiter.VerboseLogging; // we know that the Hoymiles library refuses to send any message to any // inverter until the system has valid time information. until then we can @@ -126,7 +132,7 @@ void PowerLimiterClass::loop() return; } - if (!config.PowerLimiter_Enabled) { + if (!config.PowerLimiter.Enabled) { shutdown(Status::DisabledByConfig); return; } @@ -137,7 +143,7 @@ void PowerLimiterClass::loop() } std::shared_ptr currentInverter = - Hoymiles.getInverterByPos(config.PowerLimiter_InverterId); + Hoymiles.getInverterByPos(config.PowerLimiter.InverterId); // in case of (newly) broken configuration, shut down // the last inverter we worked with (if any) @@ -192,7 +198,7 @@ void PowerLimiterClass::loop() // the normal mode of operation requires a valid // power meter reading to calculate a power limit - if (!config.PowerMeter_Enabled) { + if (!config.PowerMeter.Enabled) { shutdown(Status::PowerMeterDisabled); return; } @@ -239,7 +245,7 @@ void PowerLimiterClass::loop() } // Check if NTP time is set and next inverter restart not calculated yet - if ((config.PowerLimiter_RestartHour >= 0) && (_nextInverterRestart == 0) ) { + if ((config.PowerLimiter.RestartHour >= 0) && (_nextInverterRestart == 0) ) { // check every 5 seconds if (_nextCalculateCheck < millis()) { struct tm timeinfo; @@ -260,12 +266,12 @@ void PowerLimiterClass::loop() } else { // UI: Solar Passthrough Enabled -> false // Battery discharge can be enabled when start threshold is reached - if (!config.PowerLimiter_SolarPassThroughEnabled && isStartThresholdReached()) { + if (!config.PowerLimiter.SolarPassThroughEnabled && isStartThresholdReached()) { _batteryDischargeEnabled = true; } // UI: Solar Passthrough Enabled -> true && EMPTY_AT_NIGHT - if (config.PowerLimiter_SolarPassThroughEnabled && config.PowerLimiter_BatteryDrainStategy == EMPTY_AT_NIGHT) { + if (config.PowerLimiter.SolarPassThroughEnabled && config.PowerLimiter.BatteryDrainStategy == EMPTY_AT_NIGHT) { if(isStartThresholdReached()) { // In this case we should only discharge the battery as long it is above startThreshold _batteryDischargeEnabled = true; @@ -278,24 +284,24 @@ void PowerLimiterClass::loop() // UI: Solar Passthrough Enabled -> true && EMPTY_WHEN_FULL // Battery discharge can be enabled when start threshold is reached - if (config.PowerLimiter_SolarPassThroughEnabled && isStartThresholdReached() && config.PowerLimiter_BatteryDrainStategy == EMPTY_WHEN_FULL) { + if (config.PowerLimiter.SolarPassThroughEnabled && isStartThresholdReached() && config.PowerLimiter.BatteryDrainStategy == EMPTY_WHEN_FULL) { _batteryDischargeEnabled = true; } } if (_verboseLogging) { MessageOutput.printf("[DPL::loop] battery interface %s, SoC: %d %%, StartTH: %d %%, StopTH: %d %%, SoC age: %d s\r\n", - (config.Battery_Enabled?"enabled":"disabled"), + (config.Battery.Enabled?"enabled":"disabled"), Battery.getStats()->getSoC(), - config.PowerLimiter_BatterySocStartThreshold, - config.PowerLimiter_BatterySocStopThreshold, + config.PowerLimiter.BatterySocStartThreshold, + config.PowerLimiter.BatterySocStopThreshold, Battery.getStats()->getSoCAgeSeconds()); - float dcVoltage = _inverter->Statistics()->getChannelFieldValue(TYPE_DC, (ChannelNum_t)config.PowerLimiter_InverterChannelId, FLD_UDC); + float dcVoltage = _inverter->Statistics()->getChannelFieldValue(TYPE_DC, (ChannelNum_t)config.PowerLimiter.InverterChannelId, FLD_UDC); MessageOutput.printf("[DPL::loop] dcVoltage: %.2f V, loadCorrectedVoltage: %.2f V, StartTH: %.2f V, StopTH: %.2f V\r\n", dcVoltage, getLoadCorrectedVoltage(), - config.PowerLimiter_VoltageStartThreshold, - config.PowerLimiter_VoltageStopThreshold); + config.PowerLimiter.VoltageStartThreshold, + config.PowerLimiter.VoltageStopThreshold); MessageOutput.printf("[DPL::loop] StartTH reached: %s, StopTH reached: %s, inverter %s producing\r\n", (isStartThresholdReached()?"yes":"no"), @@ -303,13 +309,13 @@ void PowerLimiterClass::loop() (_inverter->isProducing()?"is":"is NOT")); MessageOutput.printf("[DPL::loop] SolarPT %s, Drain Strategy: %i, canUseDirectSolarPower: %s\r\n", - (config.PowerLimiter_SolarPassThroughEnabled?"enabled":"disabled"), - config.PowerLimiter_BatteryDrainStategy, (canUseDirectSolarPower()?"yes":"no")); + (config.PowerLimiter.SolarPassThroughEnabled?"enabled":"disabled"), + config.PowerLimiter.BatteryDrainStategy, (canUseDirectSolarPower()?"yes":"no")); MessageOutput.printf("[DPL::loop] battery discharging %s, PowerMeter: %d W, target consumption: %d W\r\n", (_batteryDischargeEnabled?"allowed":"prevented"), static_cast(round(PowerMeter.getPowerTotal())), - config.PowerLimiter_TargetPowerConsumption); + config.PowerLimiter.TargetPowerConsumption); } // Calculate and set Power Limit (NOTE: might reset _inverter to nullptr!) @@ -350,7 +356,7 @@ int32_t PowerLimiterClass::inverterPowerDcToAc(std::shared_ptr float inverterEfficiencyFactor = (inverterEfficiencyPercent > 0) ? inverterEfficiencyPercent/100 : 0.967; // account for losses between solar charger and inverter (cables, junctions...) - float lossesFactor = 1.00 - static_cast(config.PowerLimiter_SolarPassThroughLosses)/100; + float lossesFactor = 1.00 - static_cast(config.PowerLimiter.SolarPassThroughLosses)/100; return dcPower * inverterEfficiencyFactor * lossesFactor; } @@ -402,7 +408,7 @@ bool PowerLimiterClass::canUseDirectSolarPower() { CONFIG_T& config = Configuration.get(); - if (!config.PowerLimiter_SolarPassThroughEnabled + if (!config.PowerLimiter.SolarPassThroughEnabled || isBelowStopThreshold() || !VictronMppt.isDataValid()) { return false; @@ -432,7 +438,7 @@ int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr inve return 0; } - if (config.PowerLimiter_IsInverterBehindPowerMeter) { + if (config.PowerLimiter.IsInverterBehindPowerMeter) { // If the inverter the behind the power meter (part of measurement), // the produced power of this inverter has also to be taken into account. // We don't use FLD_PAC from the statistics, because that @@ -444,7 +450,7 @@ int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr inve // We're not trying to hit 0 exactly but take an offset into account // This means we never fully compensate the used power with the inverter // Case 3 - newPowerLimit -= config.PowerLimiter_TargetPowerConsumption; + newPowerLimit -= config.PowerLimiter.TargetPowerConsumption; // At this point we've calculated the required energy to compensate for household consumption. // If the battery is enabled this can always be supplied since we assume that the battery can supply unlimited power @@ -514,14 +520,14 @@ bool PowerLimiterClass::setNewPowerLimit(std::shared_ptr inver CONFIG_T& config = Configuration.get(); // Stop the inverter if limit is below threshold. - if (newPowerLimit < config.PowerLimiter_LowerPowerLimit) { + if (newPowerLimit < config.PowerLimiter.LowerPowerLimit) { // the status must not change outside of loop(). this condition is // communicated through log messages already. return shutdown(); } // enforce configured upper power limit - int32_t effPowerLimit = std::min(newPowerLimit, config.PowerLimiter_UpperPowerLimit); + int32_t effPowerLimit = std::min(newPowerLimit, config.PowerLimiter.UpperPowerLimit); // scale the power limit by the amount of all inverter channels devided by // the amount of producing inverter channels. the inverters limit each of @@ -544,7 +550,7 @@ bool PowerLimiterClass::setNewPowerLimit(std::shared_ptr inver // Check if the new value is within the limits of the hysteresis auto diff = std::abs(effPowerLimit - _lastRequestedPowerLimit); - auto hysteresis = config.PowerLimiter_TargetPowerConsumptionHysteresis; + auto hysteresis = config.PowerLimiter.TargetPowerConsumptionHysteresis; // (re-)send power limit in case the last was sent a long time ago. avoids // staleness in case a power limit update was not received by the inverter. @@ -586,7 +592,7 @@ float PowerLimiterClass::getLoadCorrectedVoltage() CONFIG_T& config = Configuration.get(); - auto channel = static_cast(config.PowerLimiter_InverterChannelId); + auto channel = static_cast(config.PowerLimiter.InverterChannelId); float acPower = _inverter->Statistics()->getChannelFieldValue(TYPE_AC, CH0, FLD_PAC); float dcVoltage = _inverter->Statistics()->getChannelFieldValue(TYPE_DC, channel, FLD_UDC); @@ -594,7 +600,7 @@ float PowerLimiterClass::getLoadCorrectedVoltage() return 0.0; } - return dcVoltage + (acPower * config.PowerLimiter_VoltageLoadCorrectionFactor); + return dcVoltage + (acPower * config.PowerLimiter.VoltageLoadCorrectionFactor); } bool PowerLimiterClass::testThreshold(float socThreshold, float voltThreshold, @@ -603,7 +609,7 @@ bool PowerLimiterClass::testThreshold(float socThreshold, float voltThreshold, CONFIG_T& config = Configuration.get(); // prefer SoC provided through battery interface - if (config.Battery_Enabled && socThreshold > 0.0 + if (config.Battery.Enabled && socThreshold > 0.0 && Battery.getStats()->isValid() && Battery.getStats()->getSoCAgeSeconds() < 60) { return compare(Battery.getStats()->getSoC(), socThreshold); @@ -620,8 +626,8 @@ bool PowerLimiterClass::isStartThresholdReached() CONFIG_T& config = Configuration.get(); return testThreshold( - config.PowerLimiter_BatterySocStartThreshold, - config.PowerLimiter_VoltageStartThreshold, + config.PowerLimiter.BatterySocStartThreshold, + config.PowerLimiter.VoltageStartThreshold, [](float a, float b) -> bool { return a >= b; } ); } @@ -631,8 +637,8 @@ bool PowerLimiterClass::isStopThresholdReached() CONFIG_T& config = Configuration.get(); return testThreshold( - config.PowerLimiter_BatterySocStopThreshold, - config.PowerLimiter_VoltageStopThreshold, + config.PowerLimiter.BatterySocStopThreshold, + config.PowerLimiter.VoltageStopThreshold, [](float a, float b) -> bool { return a <= b; } ); } @@ -642,8 +648,8 @@ bool PowerLimiterClass::isBelowStopThreshold() CONFIG_T& config = Configuration.get(); return testThreshold( - config.PowerLimiter_BatterySocStopThreshold, - config.PowerLimiter_VoltageStopThreshold, + config.PowerLimiter.BatterySocStopThreshold, + config.PowerLimiter.VoltageStopThreshold, [](float a, float b) -> bool { return a < b; } ); } @@ -654,7 +660,7 @@ void PowerLimiterClass::calcNextInverterRestart() CONFIG_T& config = Configuration.get(); // first check if restart is configured at all - if (config.PowerLimiter_RestartHour < 0) { + if (config.PowerLimiter.RestartHour < 0) { _nextInverterRestart = 1; MessageOutput.println("[DPL::calcNextInverterRestart] _nextInverterRestart disabled"); return; @@ -665,8 +671,8 @@ void PowerLimiterClass::calcNextInverterRestart() if (getLocalTime(&timeinfo, 5)) { // calculation first step is offset to next restart in minutes uint16_t dayMinutes = timeinfo.tm_hour * 60 + timeinfo.tm_min; - uint16_t targetMinutes = config.PowerLimiter_RestartHour * 60; - if (config.PowerLimiter_RestartHour > timeinfo.tm_hour) { + uint16_t targetMinutes = config.PowerLimiter.RestartHour * 60; + if (config.PowerLimiter.RestartHour > timeinfo.tm_hour) { // next restart is on the same day _nextInverterRestart = targetMinutes - dayMinutes; } else { @@ -674,7 +680,7 @@ void PowerLimiterClass::calcNextInverterRestart() _nextInverterRestart = 1440 - dayMinutes + targetMinutes; } if (_verboseLogging) { - MessageOutput.printf("[DPL::calcNextInverterRestart] Localtime read %d %d / configured RestartHour %d\r\n", timeinfo.tm_hour, timeinfo.tm_min, config.PowerLimiter_RestartHour); + MessageOutput.printf("[DPL::calcNextInverterRestart] Localtime read %d %d / configured RestartHour %d\r\n", timeinfo.tm_hour, timeinfo.tm_min, config.PowerLimiter.RestartHour); MessageOutput.printf("[DPL::calcNextInverterRestart] dayMinutes %d / targetMinutes %d\r\n", dayMinutes, targetMinutes); MessageOutput.printf("[DPL::calcNextInverterRestart] next inverter restart in %d minutes\r\n", _nextInverterRestart); } @@ -693,18 +699,18 @@ bool PowerLimiterClass::useFullSolarPassthrough() CONFIG_T& config = Configuration.get(); // We only do full solar PT if general solar PT is enabled - if(!config.PowerLimiter_SolarPassThroughEnabled) { + if(!config.PowerLimiter.SolarPassThroughEnabled) { return false; } - if (testThreshold(config.PowerLimiter_FullSolarPassThroughSoc, - config.PowerLimiter_FullSolarPassThroughStartVoltage, + if (testThreshold(config.PowerLimiter.FullSolarPassThroughSoc, + config.PowerLimiter.FullSolarPassThroughStartVoltage, [](float a, float b) -> bool { return a >= b; })) { _fullSolarPassThroughEnabled = true; } - if (testThreshold(config.PowerLimiter_FullSolarPassThroughSoc, - config.PowerLimiter_FullSolarPassThroughStopVoltage, + if (testThreshold(config.PowerLimiter.FullSolarPassThroughSoc, + config.PowerLimiter.FullSolarPassThroughStopVoltage, [](float a, float b) -> bool { return a < b; })) { _fullSolarPassThroughEnabled = false; } diff --git a/src/PowerMeter.cpp b/src/PowerMeter.cpp index a7b1fee8..e45b5837 100644 --- a/src/PowerMeter.cpp +++ b/src/PowerMeter.cpp @@ -18,8 +18,13 @@ SDM sdm(Serial2, 9600, NOT_A_PIN, SERIAL_8N1, SDM_RX_PIN, SDM_TX_PIN); SoftwareSerial inputSerial; -void PowerMeterClass::init() +void PowerMeterClass::init(Scheduler& scheduler) { + scheduler.addTask(_loopTask); + _loopTask.setCallback(std::bind(&PowerMeterClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); + _lastPowerMeterCheck = 0; _lastPowerMeterUpdate = 0; @@ -28,11 +33,11 @@ void PowerMeterClass::init() CONFIG_T& config = Configuration.get(); - if (!config.PowerMeter_Enabled) { + if (!config.PowerMeter.Enabled) { return; } - switch(config.PowerMeter_Source) { + switch(config.PowerMeter.Source) { case SOURCE_MQTT: { auto subscribe = [this](char const* topic, float* target) { if (strlen(topic) == 0) { return; } @@ -45,9 +50,9 @@ void PowerMeterClass::init() _mqttSubscriptions.try_emplace(topic, target); }; - subscribe(config.PowerMeter_MqttTopicPowerMeter1, &_powerMeter1Power); - subscribe(config.PowerMeter_MqttTopicPowerMeter2, &_powerMeter2Power); - subscribe(config.PowerMeter_MqttTopicPowerMeter3, &_powerMeter3Power); + subscribe(config.PowerMeter.MqttTopicPowerMeter1, &_powerMeter1Power); + subscribe(config.PowerMeter.MqttTopicPowerMeter2, &_powerMeter2Power); + subscribe(config.PowerMeter.MqttTopicPowerMeter3, &_powerMeter3Power); break; } @@ -98,7 +103,7 @@ float PowerMeterClass::getPowerTotal(bool forceUpdate) { if (forceUpdate) { CONFIG_T& config = Configuration.get(); - if (config.PowerMeter_Enabled + if (config.PowerMeter.Enabled && (millis() - _lastPowerMeterUpdate) > (1000)) { readPowerMeter(); } @@ -132,11 +137,11 @@ void PowerMeterClass::mqtt() void PowerMeterClass::loop() { CONFIG_T const& config = Configuration.get(); - _verboseLogging = config.PowerMeter_VerboseLogging; + _verboseLogging = config.PowerMeter.VerboseLogging; - if (!config.PowerMeter_Enabled) { return; } + if (!config.PowerMeter.Enabled) { return; } - if (config.PowerMeter_Source == SOURCE_SML) { + if (config.PowerMeter.Source == SOURCE_SML) { if (!smlReadLoop()) { return; } else { @@ -144,7 +149,7 @@ void PowerMeterClass::loop() } } - if ((millis() - _lastPowerMeterCheck) < (config.PowerMeter_Interval * 1000)) { + if ((millis() - _lastPowerMeterCheck) < (config.PowerMeter.Interval * 1000)) { return; } @@ -161,9 +166,9 @@ void PowerMeterClass::readPowerMeter() { CONFIG_T& config = Configuration.get(); - uint8_t _address = config.PowerMeter_SdmAddress; + uint8_t _address = config.PowerMeter.SdmAddress; - if (config.PowerMeter_Source == SOURCE_SDM1PH) { + if (config.PowerMeter.Source == SOURCE_SDM1PH) { _powerMeter1Power = static_cast(sdm.readVal(SDM_PHASE_1_POWER, _address)); _powerMeter2Power = 0.0; _powerMeter3Power = 0.0; @@ -174,7 +179,7 @@ void PowerMeterClass::readPowerMeter() _powerMeterExport = static_cast(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address)); _lastPowerMeterUpdate = millis(); } - else if (config.PowerMeter_Source == SOURCE_SDM3PH) { + else if (config.PowerMeter.Source == SOURCE_SDM3PH) { _powerMeter1Power = static_cast(sdm.readVal(SDM_PHASE_1_POWER, _address)); _powerMeter2Power = static_cast(sdm.readVal(SDM_PHASE_2_POWER, _address)); _powerMeter3Power = static_cast(sdm.readVal(SDM_PHASE_3_POWER, _address)); @@ -185,7 +190,7 @@ void PowerMeterClass::readPowerMeter() _powerMeterExport = static_cast(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address)); _lastPowerMeterUpdate = millis(); } - else if (config.PowerMeter_Source == SOURCE_HTTP) { + else if (config.PowerMeter.Source == SOURCE_HTTP) { if (HttpPowerMeter.updateValues()) { _powerMeter1Power = HttpPowerMeter.getPower(1); _powerMeter2Power = HttpPowerMeter.getPower(2); 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/SunPosition.cpp b/src/SunPosition.cpp index 0d4d419b..a3a9f47f 100644 --- a/src/SunPosition.cpp +++ b/src/SunPosition.cpp @@ -13,18 +13,23 @@ 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.setInterval(5 * TASK_SECOND); + _loopTask.enable(); } void SunPositionClass::loop() { - if (getDoRecalc() || checkRecalcDayChanged()) { + if (_doRecalc || checkRecalcDayChanged()) { updateSunData(); } } -bool SunPositionClass::isDayPeriod() +bool SunPositionClass::isDayPeriod() const { if (!_isValidInfo) { return true; @@ -32,28 +37,21 @@ bool SunPositionClass::isDayPeriod() struct tm timeinfo; getLocalTime(&timeinfo, 5); - uint32_t minutesPastMidnight = timeinfo.tm_hour * 60 + timeinfo.tm_min; + const uint32_t minutesPastMidnight = timeinfo.tm_hour * 60 + timeinfo.tm_min; return (minutesPastMidnight >= _sunriseMinutes) && (minutesPastMidnight < _sunsetMinutes); } -bool SunPositionClass::isSunsetAvailable() +bool SunPositionClass::isSunsetAvailable() const { return _isSunsetAvailable; } -void SunPositionClass::setDoRecalc(bool doRecalc) +void SunPositionClass::setDoRecalc(const bool doRecalc) { - std::lock_guard lock(_recalcLock); _doRecalc = doRecalc; } -bool SunPositionClass::getDoRecalc() -{ - std::lock_guard lock(_recalcLock); - return _doRecalc; -} - -bool SunPositionClass::checkRecalcDayChanged() +bool SunPositionClass::checkRecalcDayChanged() const { time_t now; struct tm timeinfo; @@ -61,39 +59,31 @@ bool SunPositionClass::checkRecalcDayChanged() time(&now); localtime_r(&now, &timeinfo); // don't use getLocalTime() as there could be a delay of 10ms - uint32_t ymd; - ymd = (timeinfo.tm_year << 9) | (timeinfo.tm_mon << 5) | timeinfo.tm_mday; + const uint32_t 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() { struct tm timeinfo; - bool gotLocalTime; + const bool gotLocalTime = getLocalTime(&timeinfo, 5); - gotLocalTime = getLocalTime(&timeinfo, 5); _lastSunPositionCalculatedYMD = (timeinfo.tm_year << 9) | (timeinfo.tm_mon << 5) | timeinfo.tm_mday; setDoRecalc(false); 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) { + switch (config.Ntp.SunsetType) { case 0: sunset_type = SunSet::SUNSET_OFFICIAL; break; @@ -108,15 +98,21 @@ void SunPositionClass::updateSunData() break; } - double sunriseRaw = _sun.calcCustomSunrise(sunset_type); - double sunsetRaw = _sun.calcCustomSunset(sunset_type); + const 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); + + const double sunriseRaw = sun.calcCustomSunrise(sunset_type); + const 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 +124,7 @@ void SunPositionClass::updateSunData() _isValidInfo = true; } -bool SunPositionClass::sunsetTime(struct tm* info) +bool SunPositionClass::getSunTime(struct tm* info, const uint32_t offset) const { // Get today's date time_t aTime = time(NULL); @@ -137,29 +133,21 @@ 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); + const time_t midnight = mktime(&tm); localtime_r(&midnight, info); return _isValidInfo; } -bool SunPositionClass::sunriseTime(struct tm* info) +bool SunPositionClass::sunsetTime(struct tm* info) const { - // 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, _sunsetMinutes); +} + +bool SunPositionClass::sunriseTime(struct tm* info) const +{ + return getSunTime(info, _sunriseMinutes); } diff --git a/src/VictronMppt.cpp b/src/VictronMppt.cpp index 609f8bf0..0e5fe082 100644 --- a/src/VictronMppt.cpp +++ b/src/VictronMppt.cpp @@ -6,14 +6,24 @@ VictronMpptClass VictronMppt; -void VictronMpptClass::init() +void VictronMpptClass::init(Scheduler& scheduler) +{ + scheduler.addTask(_loopTask); + _loopTask.setCallback(std::bind(&VictronMpptClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); + + this->updateSettings(); +} + +void VictronMpptClass::updateSettings() { std::lock_guard lock(_mutex); _controllers.clear(); CONFIG_T& config = Configuration.get(); - if (!config.Vedirect_Enabled) { return; } + if (!config.Vedirect.Enabled) { return; } const PinMapping_t& pin = PinMapping.get(); int8_t rx = pin.victron_rx; @@ -27,7 +37,7 @@ void VictronMpptClass::init() } auto upController = std::make_unique(); - upController->init(rx, tx, &MessageOutput, config.Vedirect_VerboseLogging); + upController->init(rx, tx, &MessageOutput, config.Vedirect.VerboseLogging); _controllers.push_back(std::move(upController)); } diff --git a/src/WebApi.cpp b/src/WebApi.cpp index 82cd140a..df4c0aa0 100644 --- a/src/WebApi.cpp +++ b/src/WebApi.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.h" #include "Configuration.h" @@ -9,44 +9,46 @@ WebApiClass::WebApiClass() : _server(HTTP_PORT) - , _events("/events") { } -void WebApiClass::init() +void WebApiClass::init(Scheduler& scheduler) { - _server.addHandler(&_events); - - _webApiBattery.init(&_server); - _webApiConfig.init(&_server); - _webApiDevice.init(&_server); - _webApiDevInfo.init(&_server); - _webApiDtu.init(&_server); - _webApiEventlog.init(&_server); - _webApiFirmware.init(&_server); - _webApiGridprofile.init(&_server); - _webApiInverter.init(&_server); - _webApiLimit.init(&_server); - _webApiMaintenance.init(&_server); - _webApiMqtt.init(&_server); - _webApiNetwork.init(&_server); - _webApiNtp.init(&_server); - _webApiPower.init(&_server); - _webApiPowerMeter.init(&_server); - _webApiPowerLimiter.init(&_server); - _webApiPrometheus.init(&_server); - _webApiSecurity.init(&_server); - _webApiSysstatus.init(&_server); - _webApiWebapp.init(&_server); - _webApiWsConsole.init(&_server); - _webApiWsLive.init(&_server); - _webApiWsVedirectLive.init(&_server); - _webApiVedirect.init(&_server); - _webApiWsHuaweiLive.init(&_server); - _webApiHuaweiClass.init(&_server); - _webApiWsBatteryLive.init(&_server); + _webApiConfig.init(_server); + _webApiDevice.init(_server); + _webApiDevInfo.init(_server); + _webApiDtu.init(_server); + _webApiEventlog.init(_server); + _webApiFirmware.init(_server); + _webApiGridprofile.init(_server); + _webApiInverter.init(_server); + _webApiLimit.init(_server); + _webApiMaintenance.init(_server); + _webApiMqtt.init(_server); + _webApiNetwork.init(_server); + _webApiNtp.init(_server); + _webApiPower.init(_server); + _webApiPrometheus.init(_server); + _webApiSecurity.init(_server); + _webApiSysstatus.init(_server); + _webApiWebapp.init(_server); + _webApiWsConsole.init(_server); + _webApiWsLive.init(_server); + _webApiBattery.init(_server); + _webApiPowerMeter.init(_server); + _webApiPowerLimiter.init(_server); + _webApiWsVedirectLive.init(_server); + _webApiVedirect.init(_server); + _webApiWsHuaweiLive.init(_server); + _webApiHuaweiClass.init(_server); + _webApiWsBatteryLive.init(_server); _server.begin(); + + scheduler.addTask(_loopTask); + _loopTask.setCallback(std::bind(&WebApiClass::loop, this)); + _loopTask.setIterations(TASK_FOREVER); + _loopTask.enable(); } void WebApiClass::loop() @@ -83,7 +85,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; } @@ -101,7 +103,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_Huawei.cpp b/src/WebApi_Huawei.cpp index 691c6504..586e2ab9 100644 --- a/src/WebApi_Huawei.cpp +++ b/src/WebApi_Huawei.cpp @@ -12,11 +12,11 @@ #include #include -void WebApiHuaweiClass::init(AsyncWebServer* server) +void WebApiHuaweiClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/huawei/status", HTTP_GET, std::bind(&WebApiHuaweiClass::onStatus, this, _1)); _server->on("/api/huawei/config", HTTP_GET, std::bind(&WebApiHuaweiClass::onAdminGet, this, _1)); @@ -32,26 +32,26 @@ void WebApiHuaweiClass::getJsonData(JsonObject& root) { const RectifierParameters_t * rp = HuaweiCan.get(); root["data_age"] = (millis() - HuaweiCan.getLastUpdate()) / 1000; - root[F("input_voltage")]["v"] = rp->input_voltage; - root[F("input_voltage")]["u"] = "V"; - root[F("input_current")]["v"] = rp->input_current; - root[F("input_current")]["u"] = "A"; - root[F("input_power")]["v"] = rp->input_power; - root[F("input_power")]["u"] = "W"; - root[F("output_voltage")]["v"] = rp->output_voltage; - root[F("output_voltage")]["u"] = "V"; - root[F("output_current")]["v"] = rp->output_current; - root[F("output_current")]["u"] = "A"; - root[F("max_output_current")]["v"] = rp->max_output_current; - root[F("max_output_current")]["u"] = "A"; - root[F("output_power")]["v"] = rp->output_power; - root[F("output_power")]["u"] = "W"; - root[F("input_temp")]["v"] = rp->input_temp; - root[F("input_temp")]["u"] = "°C"; - root[F("output_temp")]["v"] = rp->output_temp; - root[F("output_temp")]["u"] = "°C"; - root[F("efficiency")]["v"] = rp->efficiency * 100; - root[F("efficiency")]["u"] = "%"; + root["input_voltage"]["v"] = rp->input_voltage; + root["input_voltage"]["u"] = "V"; + root["input_current"]["v"] = rp->input_current; + root["input_current"]["u"] = "A"; + root["input_power"]["v"] = rp->input_power; + root["input_power"]["u"] = "W"; + root["output_voltage"]["v"] = rp->output_voltage; + root["output_voltage"]["u"] = "V"; + root["output_current"]["v"] = rp->output_current; + root["output_current"]["u"] = "A"; + root["max_output_current"]["v"] = rp->max_output_current; + root["max_output_current"]["u"] = "A"; + root["output_power"]["v"] = rp->output_power; + root["output_power"]["u"] = "W"; + root["input_temp"]["v"] = rp->input_temp; + root["input_temp"]["u"] = "°C"; + root["output_temp"]["v"] = rp->output_temp; + root["output_temp"]["u"] = "°C"; + root["efficiency"]["v"] = rp->efficiency * 100; + root["efficiency"]["u"] = "%"; } @@ -77,11 +77,11 @@ void WebApiHuaweiClass::onPost(AsyncWebServerRequest* request) AsyncJsonResponse* response = new AsyncJsonResponse(); JsonObject retMsg = response->getRoot(); - retMsg[F("type")] = F("warning"); + retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { - retMsg[F("message")] = F("No values found!"); - retMsg[F("code")] = WebApiError::GenericNoValueFound; + retMsg["message"] = "No values found!"; + retMsg["code"] = WebApiError::GenericNoValueFound; response->setLength(); request->send(response); return; @@ -90,8 +90,8 @@ void WebApiHuaweiClass::onPost(AsyncWebServerRequest* request) String json = request->getParam("data", true)->value(); if (json.length() > 1024) { - retMsg[F("message")] = F("Data too large!"); - retMsg[F("code")] = WebApiError::GenericDataTooLarge; + retMsg["message"] = "Data too large!"; + retMsg["code"] = WebApiError::GenericDataTooLarge; response->setLength(); request->send(response); return; @@ -104,40 +104,40 @@ void WebApiHuaweiClass::onPost(AsyncWebServerRequest* request) float minimal_voltage; if (error) { - retMsg[F("message")] = F("Failed to parse data!"); - retMsg[F("code")] = WebApiError::GenericParseError; + retMsg["message"] = "Failed to parse data!"; + retMsg["code"] = WebApiError::GenericParseError; response->setLength(); request->send(response); return; } if (root.containsKey("online")) { - online = root[F("online")].as(); + online = root["online"].as(); if (online) { minimal_voltage = HUAWEI_MINIMAL_ONLINE_VOLTAGE; } else { minimal_voltage = HUAWEI_MINIMAL_OFFLINE_VOLTAGE; } } else { - retMsg[F("message")] = F("Could not read info if data should be set for online/offline operation!"); - retMsg[F("code")] = WebApiError::LimitInvalidType; + retMsg["message"] = "Could not read info if data should be set for online/offline operation!"; + retMsg["code"] = WebApiError::LimitInvalidType; response->setLength(); request->send(response); return; } if (root.containsKey("voltage_valid")) { - if (root[F("voltage_valid")].as()) { - if (root[F("voltage")].as() < minimal_voltage || root[F("voltage")].as() > 58) { - retMsg[F("message")] = F("voltage not in range between 42 (online)/48 (offline and 58V !"); - retMsg[F("code")] = WebApiError::LimitInvalidLimit; - retMsg[F("param")][F("max")] = 58; - retMsg[F("param")][F("min")] = minimal_voltage; + if (root["voltage_valid"].as()) { + if (root["voltage"].as() < minimal_voltage || root["voltage"].as() > 58) { + retMsg["message"] = "voltage not in range between 42 (online)/48 (offline and 58V !"; + retMsg["code"] = WebApiError::LimitInvalidLimit; + retMsg["param"]["max"] = 58; + retMsg["param"]["min"] = minimal_voltage; response->setLength(); request->send(response); return; } else { - value = root[F("voltage")].as(); + value = root["voltage"].as(); if (online) { HuaweiCan.setValue(value, HUAWEI_ONLINE_VOLTAGE); } else { @@ -148,17 +148,17 @@ void WebApiHuaweiClass::onPost(AsyncWebServerRequest* request) } if (root.containsKey("current_valid")) { - if (root[F("current_valid")].as()) { - if (root[F("current")].as() < 0 || root[F("current")].as() > 60) { - retMsg[F("message")] = F("current must be in range between 0 and 60!"); - retMsg[F("code")] = WebApiError::LimitInvalidLimit; - retMsg[F("param")][F("max")] = 60; - retMsg[F("param")][F("min")] = 0; + if (root["current_valid"].as()) { + if (root["current"].as() < 0 || root["current"].as() > 60) { + retMsg["message"] = "current must be in range between 0 and 60!"; + retMsg["code"] = WebApiError::LimitInvalidLimit; + retMsg["param"]["max"] = 60; + retMsg["param"]["min"] = 0; response->setLength(); request->send(response); return; } else { - value = root[F("current")].as(); + value = root["current"].as(); if (online) { HuaweiCan.setValue(value, HUAWEI_ONLINE_CURRENT); } else { @@ -168,9 +168,9 @@ void WebApiHuaweiClass::onPost(AsyncWebServerRequest* request) } } - retMsg[F("type")] = F("success"); - retMsg[F("message")] = F("Settings saved!"); - retMsg[F("code")] = WebApiError::GenericSuccess; + retMsg["type"] = "success"; + retMsg["message"] = "Settings saved!"; + retMsg["code"] = WebApiError::GenericSuccess; response->setLength(); request->send(response); @@ -189,13 +189,13 @@ void WebApiHuaweiClass::onAdminGet(AsyncWebServerRequest* request) JsonObject root = response->getRoot(); const CONFIG_T& config = Configuration.get(); - root[F("enabled")] = config.Huawei_Enabled; - root[F("can_controller_frequency")] = config.Huawei_CAN_Controller_Frequency; - root[F("auto_power_enabled")] = config.Huawei_Auto_Power_Enabled; - root[F("voltage_limit")] = static_cast(config.Huawei_Auto_Power_Voltage_Limit * 100) / 100.0; - root[F("enable_voltage_limit")] = static_cast(config.Huawei_Auto_Power_Enable_Voltage_Limit * 100) / 100.0; - root[F("lower_power_limit")] = config.Huawei_Auto_Power_Lower_Power_Limit; - root[F("upper_power_limit")] = config.Huawei_Auto_Power_Upper_Power_Limit; + root["enabled"] = config.Huawei.Enabled; + root["can_controller_frequency"] = config.Huawei.CAN_Controller_Frequency; + root["auto_power_enabled"] = config.Huawei.Auto_Power_Enabled; + root["voltage_limit"] = static_cast(config.Huawei.Auto_Power_Voltage_Limit * 100) / 100.0; + root["enable_voltage_limit"] = static_cast(config.Huawei.Auto_Power_Enable_Voltage_Limit * 100) / 100.0; + root["lower_power_limit"] = config.Huawei.Auto_Power_Lower_Power_Limit; + root["upper_power_limit"] = config.Huawei.Auto_Power_Upper_Power_Limit; response->setLength(); request->send(response); @@ -209,11 +209,11 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request) AsyncJsonResponse* response = new AsyncJsonResponse(); JsonObject retMsg = response->getRoot(); - retMsg[F("type")] = F("warning"); + retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { - retMsg[F("message")] = F("No values found!"); - retMsg[F("code")] = WebApiError::GenericNoValueFound; + retMsg["message"] = "No values found!"; + retMsg["code"] = WebApiError::GenericNoValueFound; response->setLength(); request->send(response); return; @@ -222,8 +222,8 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request) String json = request->getParam("data", true)->value(); if (json.length() > 1024) { - retMsg[F("message")] = F("Data too large!"); - retMsg[F("code")] = WebApiError::GenericDataTooLarge; + retMsg["message"] = "Data too large!"; + retMsg["code"] = WebApiError::GenericDataTooLarge; response->setLength(); request->send(response); return; @@ -233,8 +233,8 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request) DeserializationError error = deserializeJson(root, json); if (error) { - retMsg[F("message")] = F("Failed to parse data!"); - retMsg[F("code")] = WebApiError::GenericParseError; + retMsg["message"] = "Failed to parse data!"; + retMsg["code"] = WebApiError::GenericParseError; response->setLength(); request->send(response); return; @@ -246,26 +246,26 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request) !(root.containsKey("voltage_limit")) || !(root.containsKey("lower_power_limit")) || !(root.containsKey("upper_power_limit"))) { - retMsg[F("message")] = F("Values are missing!"); - retMsg[F("code")] = WebApiError::GenericValueMissing; + retMsg["message"] = "Values are missing!"; + retMsg["code"] = WebApiError::GenericValueMissing; response->setLength(); request->send(response); return; } CONFIG_T& config = Configuration.get(); - config.Huawei_Enabled = root[F("enabled")].as(); - config.Huawei_CAN_Controller_Frequency = root[F("can_controller_frequency")].as(); - config.Huawei_Auto_Power_Enabled = root[F("auto_power_enabled")].as(); - config.Huawei_Auto_Power_Voltage_Limit = root[F("voltage_limit")].as(); - config.Huawei_Auto_Power_Enable_Voltage_Limit = root[F("enable_voltage_limit")].as(); - config.Huawei_Auto_Power_Lower_Power_Limit = root[F("lower_power_limit")].as(); - config.Huawei_Auto_Power_Upper_Power_Limit = root[F("upper_power_limit")].as(); + config.Huawei.Enabled = root["enabled"].as(); + config.Huawei.CAN_Controller_Frequency = root["can_controller_frequency"].as(); + config.Huawei.Auto_Power_Enabled = root["auto_power_enabled"].as(); + config.Huawei.Auto_Power_Voltage_Limit = root["voltage_limit"].as(); + config.Huawei.Auto_Power_Enable_Voltage_Limit = root["enable_voltage_limit"].as(); + config.Huawei.Auto_Power_Lower_Power_Limit = root["lower_power_limit"].as(); + config.Huawei.Auto_Power_Upper_Power_Limit = root["upper_power_limit"].as(); Configuration.write(); - retMsg[F("type")] = F("success"); - retMsg[F("message")] = F("Settings saved!"); - retMsg[F("code")] = WebApiError::GenericSuccess; + retMsg["type"] = "success"; + retMsg["message"] = "Settings saved!"; + retMsg["code"] = WebApiError::GenericSuccess; response->setLength(); request->send(response); @@ -280,26 +280,26 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request) const PinMapping_t& pin = PinMapping.get(); // Properly turn this on - if (config.Huawei_Enabled) { - MessageOutput.println(F("Initialize Huawei AC charger interface... ")); + if (config.Huawei.Enabled) { + MessageOutput.println("Initialize Huawei AC charger interface... "); if (PinMapping.isValidHuaweiConfig()) { MessageOutput.printf("Huawei AC-charger miso = %d, mosi = %d, clk = %d, irq = %d, cs = %d, power_pin = %d\r\n", pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power); - HuaweiCan.init(pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power); - MessageOutput.println(F("done")); + HuaweiCan.updateSettings(pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power); + MessageOutput.println("done"); } else { - MessageOutput.println(F("Invalid pin config")); + MessageOutput.println("Invalid pin config"); } } // Properly turn this off - if (!config.Huawei_Enabled) { + if (!config.Huawei.Enabled) { HuaweiCan.setValue(0, HUAWEI_ONLINE_CURRENT); delay(500); HuaweiCan.setMode(HUAWEI_MODE_OFF); return; } - if (config.Huawei_Auto_Power_Enabled) { + if (config.Huawei.Auto_Power_Enabled) { HuaweiCan.setMode(HUAWEI_MODE_AUTO_INT); return; } diff --git a/src/WebApi_battery.cpp b/src/WebApi_battery.cpp index a21f09a4..05897840 100644 --- a/src/WebApi_battery.cpp +++ b/src/WebApi_battery.cpp @@ -13,11 +13,11 @@ #include "WebApi_errors.h" #include "helper.h" -void WebApiBatteryClass::init(AsyncWebServer* server) +void WebApiBatteryClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/battery/status", HTTP_GET, std::bind(&WebApiBatteryClass::onStatus, this, _1)); _server->on("/api/battery/config", HTTP_GET, std::bind(&WebApiBatteryClass::onAdminGet, this, _1)); @@ -38,11 +38,11 @@ void WebApiBatteryClass::onStatus(AsyncWebServerRequest* request) JsonObject root = response->getRoot(); const CONFIG_T& config = Configuration.get(); - root[F("enabled")] = config.Battery_Enabled; - root[F("verbose_logging")] = config.Battery_VerboseLogging; - root[F("provider")] = config.Battery_Provider; - root[F("jkbms_interface")] = config.Battery_JkBmsInterface; - root[F("jkbms_polling_interval")] = config.Battery_JkBmsPollingInterval; + root[F("enabled")] = config.Battery.Enabled; + root[F("verbose_logging")] = config.Battery.VerboseLogging; + root[F("provider")] = config.Battery.Provider; + root[F("jkbms_interface")] = config.Battery.JkBmsInterface; + root[F("jkbms_polling_interval")] = config.Battery.JkBmsPollingInterval; response->setLength(); request->send(response); @@ -101,11 +101,11 @@ void WebApiBatteryClass::onAdminPost(AsyncWebServerRequest* request) } CONFIG_T& config = Configuration.get(); - config.Battery_Enabled = root[F("enabled")].as(); - config.Battery_VerboseLogging = root[F("verbose_logging")].as(); - config.Battery_Provider = root[F("provider")].as(); - config.Battery_JkBmsInterface = root[F("jkbms_interface")].as(); - config.Battery_JkBmsPollingInterval = root[F("jkbms_polling_interval")].as(); + config.Battery.Enabled = root[F("enabled")].as(); + config.Battery.VerboseLogging = root[F("verbose_logging")].as(); + config.Battery.Provider = root[F("provider")].as(); + config.Battery.JkBmsInterface = root[F("jkbms_interface")].as(); + config.Battery.JkBmsPollingInterval = root[F("jkbms_polling_interval")].as(); Configuration.write(); retMsg[F("type")] = F("success"); @@ -115,5 +115,5 @@ void WebApiBatteryClass::onAdminPost(AsyncWebServerRequest* request) response->setLength(); request->send(response); - Battery.init(); + Battery.updateSettings(); } diff --git a/src/WebApi_config.cpp b/src/WebApi_config.cpp index 08b86d65..e466c79c 100644 --- a/src/WebApi_config.cpp +++ b/src/WebApi_config.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_config.h" #include "Configuration.h" @@ -10,7 +10,7 @@ #include #include -void WebApiConfigClass::init(AsyncWebServer* server) +void WebApiConfigClass::init(AsyncWebServer& server) { using std::placeholders::_1; using std::placeholders::_2; @@ -19,7 +19,7 @@ void WebApiConfigClass::init(AsyncWebServer* server) using std::placeholders::_5; using std::placeholders::_6; - _server = server; + _server = &server; _server->on("/api/config/get", HTTP_GET, std::bind(&WebApiConfigClass::onConfigGet, this, _1)); _server->on("/api/config/delete", HTTP_POST, std::bind(&WebApiConfigClass::onConfigDelete, this, _1)); @@ -70,7 +70,7 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request) return; } - String json = request->getParam("data", true)->value(); + const String json = request->getParam("data", true)->value(); if (json.length() > 1024) { retMsg["message"] = "Data too large!"; @@ -81,7 +81,7 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request) } DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); + const DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; @@ -173,7 +173,7 @@ void WebApiConfigClass::onConfigUpload(AsyncWebServerRequest* request, String fi request->send(500); return; } - String name = "/" + request->getParam("file")->value(); + const String name = "/" + request->getParam("file")->value(); request->_tempFile = LittleFS.open(name, "w"); } diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index f3d3b342..29508cc9 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.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_device.h" #include "Configuration.h" @@ -12,11 +12,11 @@ #include "helper.h" #include -void WebApiDeviceClass::init(AsyncWebServer* server) +void WebApiDeviceClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/device/config", HTTP_GET, std::bind(&WebApiDeviceClass::onDeviceAdminGet, this, _1)); _server->on("/api/device/config", HTTP_POST, std::bind(&WebApiDeviceClass::onDeviceAdminPost, this, _1)); @@ -73,15 +73,23 @@ 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; - 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; + display["diagramduration"] = config.Display.DiagramDuration; + + 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; + } JsonObject victronPinObj = curPin.createNestedObject("victron"); victronPinObj[F("rx")] = pin.victron_rx; @@ -123,7 +131,7 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) return; } - String json = request->getParam("data", true)->value(); + const String json = request->getParam("data", true)->value(); if (json.length() > MQTT_JSON_DOC_SIZE) { retMsg["message"] = "Data too large!"; @@ -134,7 +142,7 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) } DynamicJsonDocument root(MQTT_JSON_DOC_SIZE); - DeserializationError error = deserializeJson(root, json); + const DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; @@ -166,17 +174,24 @@ 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(); + config.Display.DiagramDuration = root["display"]["diagramduration"].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); + for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { + 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; + Display.setContrast(config.Display.Contrast); + Display.setLanguage(config.Display.Language); + Display.Diagram().updatePeriod(); Configuration.write(); diff --git a/src/WebApi_devinfo.cpp b/src/WebApi_devinfo.cpp index 31d1d0ca..04b8a581 100644 --- a/src/WebApi_devinfo.cpp +++ b/src/WebApi_devinfo.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_devinfo.h" #include "WebApi.h" @@ -8,11 +8,11 @@ #include #include -void WebApiDevInfoClass::init(AsyncWebServer* server) +void WebApiDevInfoClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/devinfo/status", HTTP_GET, std::bind(&WebApiDevInfoClass::onDevInfoStatus, this, _1)); } diff --git a/src/WebApi_dtu.cpp b/src/WebApi_dtu.cpp index 844b4000..45bfc2de 100644 --- a/src/WebApi_dtu.cpp +++ b/src/WebApi_dtu.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_dtu.h" #include "Configuration.h" @@ -9,11 +9,11 @@ #include #include -void WebApiDtuClass::init(AsyncWebServer* server) +void WebApiDtuClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/dtu/config", HTTP_GET, std::bind(&WebApiDtuClass::onDtuAdminGet, this, _1)); _server->on("/api/dtu/config", HTTP_POST, std::bind(&WebApiDtuClass::onDtuAdminPost, this, _1)); @@ -36,16 +36,16 @@ 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["verbose_logging"] = config.Dtu_VerboseLogging; + root["pollinterval"] = config.Dtu.PollInterval; + root["verbose_logging"] = config.Dtu.VerboseLogging; 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); @@ -69,7 +69,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) return; } - String json = request->getParam("data", true)->value(); + const String json = request->getParam("data", true)->value(); if (json.length() > 1024) { retMsg["message"] = "Data too large!"; @@ -80,7 +80,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) } DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); + const DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; @@ -151,12 +151,12 @@ 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_VerboseLogging = root["verbose_logging"].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.VerboseLogging = root["verbose_logging"].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"; @@ -166,11 +166,11 @@ 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.setVerboseLogging(config.Dtu_VerboseLogging); + 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); + Hoymiles.setVerboseLogging(config.Dtu.VerboseLogging); } diff --git a/src/WebApi_eventlog.cpp b/src/WebApi_eventlog.cpp index 2b267272..e0c8b316 100644 --- a/src/WebApi_eventlog.cpp +++ b/src/WebApi_eventlog.cpp @@ -1,17 +1,17 @@ // 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_eventlog.h" #include "WebApi.h" #include #include -void WebApiEventlogClass::init(AsyncWebServer* server) +void WebApiEventlogClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/eventlog/status", HTTP_GET, std::bind(&WebApiEventlogClass::onEventlogStatus, this, _1)); } @@ -59,7 +59,7 @@ void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request) JsonObject eventsObject = eventsArray.createNestedObject(); AlarmLogEntry_t entry; - inv->EventLog()->getLogEntry(logEntry, &entry, locale); + inv->EventLog()->getLogEntry(logEntry, entry, locale); eventsObject["message_id"] = entry.MessageId; eventsObject["message"] = entry.Message; diff --git a/src/WebApi_firmware.cpp b/src/WebApi_firmware.cpp index 62cf5615..cbc4d770 100644 --- a/src/WebApi_firmware.cpp +++ b/src/WebApi_firmware.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_firmware.h" #include "Configuration.h" @@ -10,7 +10,7 @@ #include "helper.h" #include -void WebApiFirmwareClass::init(AsyncWebServer* server) +void WebApiFirmwareClass::init(AsyncWebServer& server) { using std::placeholders::_1; using std::placeholders::_2; @@ -19,7 +19,7 @@ void WebApiFirmwareClass::init(AsyncWebServer* server) using std::placeholders::_5; using std::placeholders::_6; - _server = server; + _server = &server; _server->on("/api/firmware/update", HTTP_POST, std::bind(&WebApiFirmwareClass::onFirmwareUpdateFinish, this, _1), diff --git a/src/WebApi_gridprofile.cpp b/src/WebApi_gridprofile.cpp index c9d2adb8..ee945bc4 100644 --- a/src/WebApi_gridprofile.cpp +++ b/src/WebApi_gridprofile.cpp @@ -1,19 +1,20 @@ // 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_gridprofile.h" #include "WebApi.h" #include #include -void WebApiGridProfileClass::init(AsyncWebServer* server) +void WebApiGridProfileClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/gridprofile/status", HTTP_GET, std::bind(&WebApiGridProfileClass::onGridProfileStatus, this, _1)); + _server->on("/api/gridprofile/rawdata", HTTP_GET, std::bind(&WebApiGridProfileClass::onGridProfileRawdata, this, _1)); } void WebApiGridProfileClass::loop() @@ -26,6 +27,50 @@ void WebApiGridProfileClass::onGridProfileStatus(AsyncWebServerRequest* request) return; } + AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192); + JsonObject root = response->getRoot(); + + uint64_t serial = 0; + if (request->hasParam("inv")) { + String s = request->getParam("inv")->value(); + serial = strtoll(s.c_str(), NULL, 16); + } + + auto inv = Hoymiles.getInverterBySerial(serial); + + if (inv != nullptr) { + root["name"] = inv->GridProfile()->getProfileName(); + root["version"] = inv->GridProfile()->getProfileVersion(); + + auto jsonSections = root.createNestedArray("sections"); + auto profSections = inv->GridProfile()->getProfile(); + + for (auto &profSection : profSections) { + auto jsonSection = jsonSections.createNestedObject(); + jsonSection["name"] = profSection.SectionName; + + auto jsonItems = jsonSection.createNestedArray("items"); + + for (auto &profItem : profSection.items) { + auto jsonItem = jsonItems.createNestedObject(); + + jsonItem["n"] = profItem.Name; + jsonItem["u"] = profItem.Unit; + jsonItem["v"] = profItem.Value; + } + } + } + + response->setLength(); + request->send(response); +} + +void WebApiGridProfileClass::onGridProfileRawdata(AsyncWebServerRequest* request) +{ + if (!WebApi.checkCredentialsReadonly(request)) { + return; + } + AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096); JsonObject root = response->getRoot(); diff --git a/src/WebApi_inverter.cpp b/src/WebApi_inverter.cpp index d5ea9b45..88ddd37e 100644 --- a/src/WebApi_inverter.cpp +++ b/src/WebApi_inverter.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_inverter.h" #include "Configuration.h" @@ -12,11 +12,11 @@ #include #include -void WebApiInverterClass::init(AsyncWebServer* server) +void WebApiInverterClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/inverter/list", HTTP_GET, std::bind(&WebApiInverterClass::onInverterList, this, _1)); _server->on("/api/inverter/add", HTTP_POST, std::bind(&WebApiInverterClass::onInverterAdd, this, _1)); @@ -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; @@ -104,7 +105,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request) return; } - String json = request->getParam("data", true)->value(); + const String json = request->getParam("data", true)->value(); if (json.length() > 1024) { retMsg["message"] = "Data too large!"; @@ -115,7 +116,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request) } DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); + const DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; @@ -204,7 +205,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) return; } - String json = request->getParam("data", true)->value(); + const String json = request->getParam("data", true)->value(); if (json.length() > 1024) { retMsg["message"] = "Data too large!"; @@ -215,7 +216,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) } DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); + const DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; @@ -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); @@ -348,7 +351,7 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request) return; } - String json = request->getParam("data", true)->value(); + const String json = request->getParam("data", true)->value(); if (json.length() > 1024) { retMsg["message"] = "Data too large!"; @@ -359,7 +362,7 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request) } DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); + const DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; @@ -422,7 +425,7 @@ void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request) return; } - String json = request->getParam("data", true)->value(); + const String json = request->getParam("data", true)->value(); if (json.length() > 1024) { retMsg["message"] = "Data too large!"; @@ -433,7 +436,7 @@ void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request) } DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); + const DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; diff --git a/src/WebApi_limit.cpp b/src/WebApi_limit.cpp index 9470e4ca..1e5b3f21 100644 --- a/src/WebApi_limit.cpp +++ b/src/WebApi_limit.cpp @@ -1,18 +1,20 @@ // 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_limit.h" #include "WebApi.h" #include "WebApi_errors.h" +#include "defaults.h" +#include "helper.h" #include #include -void WebApiLimitClass::init(AsyncWebServer* server) +void WebApiLimitClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/limit/status", HTTP_GET, std::bind(&WebApiLimitClass::onLimitStatus, this, _1)); _server->on("/api/limit/config", HTTP_POST, std::bind(&WebApiLimitClass::onLimitPost, this, _1)); @@ -73,7 +75,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request) return; } - String json = request->getParam("data", true)->value(); + const String json = request->getParam("data", true)->value(); if (json.length() > 1024) { retMsg["message"] = "Data too large!"; @@ -84,7 +86,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request) } DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); + const DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; @@ -112,10 +114,10 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request) return; } - if (root["limit_value"].as() == 0 || root["limit_value"].as() > 2250) { - retMsg["message"] = "Limit must between 1 and 2250!"; + if (root["limit_value"].as() > MAX_INVERTER_LIMIT) { + retMsg["message"] = "Limit must between 0 and " STR(MAX_INVERTER_LIMIT) "!"; retMsg["code"] = WebApiError::LimitInvalidLimit; - retMsg["param"]["max"] = 2250; + retMsg["param"]["max"] = MAX_INVERTER_LIMIT; response->setLength(); request->send(response); return; diff --git a/src/WebApi_maintenance.cpp b/src/WebApi_maintenance.cpp index ed2d6867..8b65c935 100644 --- a/src/WebApi_maintenance.cpp +++ b/src/WebApi_maintenance.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_maintenance.h" @@ -9,11 +9,11 @@ #include "WebApi_errors.h" #include -void WebApiMaintenanceClass::init(AsyncWebServer* server) +void WebApiMaintenanceClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/maintenance/reboot", HTTP_POST, std::bind(&WebApiMaintenanceClass::onRebootPost, this, _1)); } @@ -40,7 +40,7 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request) return; } - String json = request->getParam("data", true)->value(); + const String json = request->getParam("data", true)->value(); if (json.length() > MQTT_JSON_DOC_SIZE) { retMsg["message"] = "Data too large!"; @@ -51,7 +51,7 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request) } DynamicJsonDocument root(MQTT_JSON_DOC_SIZE); - DeserializationError error = deserializeJson(root, json); + const DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; diff --git a/src/WebApi_mqtt.cpp b/src/WebApi_mqtt.cpp index 99de0b1b..c92d787d 100644 --- a/src/WebApi_mqtt.cpp +++ b/src/WebApi_mqtt.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_mqtt.h" #include "Configuration.h" @@ -15,11 +15,11 @@ #include "PowerMeter.h" #include -void WebApiMqttClass::init(AsyncWebServer* server) +void WebApiMqttClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/mqtt/status", HTTP_GET, std::bind(&WebApiMqttClass::onMqttStatus, this, _1)); _server->on("/api/mqtt/config", HTTP_GET, std::bind(&WebApiMqttClass::onMqttAdminGet, this, _1)); @@ -40,26 +40,26 @@ void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request) JsonObject root = response->getRoot(); const CONFIG_T& config = Configuration.get(); - root["mqtt_enabled"] = config.Mqtt_Enabled; - root["mqtt_verbose_logging"] = config.Mqtt_VerboseLogging; - 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_verbose_logging"] = config.Mqtt.VerboseLogging; + 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); @@ -75,29 +75,30 @@ void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request) JsonObject root = response->getRoot(); const CONFIG_T& config = Configuration.get(); - root["mqtt_enabled"] = config.Mqtt_Enabled; - root["mqtt_verbose_logging"] = config.Mqtt_VerboseLogging; - 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_verbose_logging"] = config.Mqtt.VerboseLogging; + 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_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; + 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); @@ -121,7 +122,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) return; } - String json = request->getParam("data", true)->value(); + const String json = request->getParam("data", true)->value(); if (json.length() > MQTT_JSON_DOC_SIZE) { retMsg["message"] = "Data too large!"; @@ -132,7 +133,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) } DynamicJsonDocument root(MQTT_JSON_DOC_SIZE); - DeserializationError error = deserializeJson(root, json); + const DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; @@ -157,6 +158,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") @@ -276,6 +278,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; @@ -307,29 +318,30 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) } CONFIG_T& config = Configuration.get(); - config.Mqtt_Enabled = root["mqtt_enabled"].as(); - config.Mqtt_VerboseLogging = root["mqtt_verbose_logging"].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.VerboseLogging = root["mqtt_verbose_logging"].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.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(); + 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"; @@ -342,9 +354,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) MqttSettings.performReconnect(); MqttHandleHass.forceUpdate(); MqttHandleVedirectHass.forceUpdate(); - MqttHandleVedirect.init(); - PowerMeter.init(); - PowerLimiter.init(); + MqttHandleVedirect.forceUpdate(); } String WebApiMqttClass::getTlsCertInfo(const char* cert) diff --git a/src/WebApi_network.cpp b/src/WebApi_network.cpp index 849c5f8a..a6e91f3d 100644 --- a/src/WebApi_network.cpp +++ b/src/WebApi_network.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_network.h" #include "Configuration.h" @@ -10,11 +10,11 @@ #include "helper.h" #include -void WebApiNetworkClass::init(AsyncWebServer* server) +void WebApiNetworkClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/network/status", HTTP_GET, std::bind(&WebApiNetworkClass::onNetworkStatus, this, _1)); _server->on("/api/network/config", HTTP_GET, std::bind(&WebApiNetworkClass::onNetworkAdminGet, this, _1)); @@ -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); @@ -100,7 +100,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) return; } - String json = request->getParam("data", true)->value(); + const String json = request->getParam("data", true)->value(); if (json.length() > 1024) { retMsg["message"] = "Data too large!"; @@ -111,7 +111,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) } DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); + const DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; @@ -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..ed841ba9 100644 --- a/src/WebApi_ntp.cpp +++ b/src/WebApi_ntp.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_ntp.h" #include "Configuration.h" @@ -11,11 +11,11 @@ #include "helper.h" #include -void WebApiNtpClass::init(AsyncWebServer* server) +void WebApiNtpClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/ntp/status", HTTP_GET, std::bind(&WebApiNtpClass::onNtpStatus, this, _1)); _server->on("/api/ntp/config", HTTP_GET, std::bind(&WebApiNtpClass::onNtpAdminGet, this, _1)); @@ -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); @@ -112,7 +112,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request) return; } - String json = request->getParam("data", true)->value(); + const String json = request->getParam("data", true)->value(); if (json.length() > 1024) { retMsg["message"] = "Data too large!"; @@ -123,7 +123,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request) } DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); + const DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; @@ -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"; @@ -239,7 +239,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request) return; } - String json = request->getParam("data", true)->value(); + const String json = request->getParam("data", true)->value(); if (json.length() > 1024) { retMsg["message"] = "Data too large!"; @@ -250,7 +250,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request) } DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); + const DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; diff --git a/src/WebApi_power.cpp b/src/WebApi_power.cpp index ca792364..3fa47984 100644 --- a/src/WebApi_power.cpp +++ b/src/WebApi_power.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_power.h" #include "WebApi.h" @@ -8,11 +8,11 @@ #include #include -void WebApiPowerClass::init(AsyncWebServer* server) +void WebApiPowerClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/power/status", HTTP_GET, std::bind(&WebApiPowerClass::onPowerStatus, this, _1)); _server->on("/api/power/config", HTTP_POST, std::bind(&WebApiPowerClass::onPowerPost, this, _1)); @@ -68,7 +68,7 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request) return; } - String json = request->getParam("data", true)->value(); + const String json = request->getParam("data", true)->value(); if (json.length() > 1024) { retMsg["message"] = "Data too large!"; @@ -79,7 +79,7 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request) } DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); + const DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; diff --git a/src/WebApi_powerlimiter.cpp b/src/WebApi_powerlimiter.cpp index 7aa6f4cc..361ff563 100644 --- a/src/WebApi_powerlimiter.cpp +++ b/src/WebApi_powerlimiter.cpp @@ -16,11 +16,11 @@ #include "helper.h" #include "WebApi_errors.h" -void WebApiPowerLimiterClass::init(AsyncWebServer* server) +void WebApiPowerLimiterClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/powerlimiter/status", HTTP_GET, std::bind(&WebApiPowerLimiterClass::onStatus, this, _1)); _server->on("/api/powerlimiter/config", HTTP_GET, std::bind(&WebApiPowerLimiterClass::onAdminGet, this, _1)); @@ -37,27 +37,27 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request) JsonObject root = response->getRoot(); const CONFIG_T& config = Configuration.get(); - root[F("enabled")] = config.PowerLimiter_Enabled; - root[F("verbose_logging")] = config.PowerLimiter_VerboseLogging; - root[F("solar_passthrough_enabled")] = config.PowerLimiter_SolarPassThroughEnabled; - root[F("solar_passthrough_losses")] = config.PowerLimiter_SolarPassThroughLosses; - root[F("battery_drain_strategy")] = config.PowerLimiter_BatteryDrainStategy; - root[F("is_inverter_behind_powermeter")] = config.PowerLimiter_IsInverterBehindPowerMeter; - root[F("inverter_id")] = config.PowerLimiter_InverterId; - root[F("inverter_channel_id")] = config.PowerLimiter_InverterChannelId; - root[F("target_power_consumption")] = config.PowerLimiter_TargetPowerConsumption; - root[F("target_power_consumption_hysteresis")] = config.PowerLimiter_TargetPowerConsumptionHysteresis; - root[F("lower_power_limit")] = config.PowerLimiter_LowerPowerLimit; - root[F("upper_power_limit")] = config.PowerLimiter_UpperPowerLimit; - root[F("battery_soc_start_threshold")] = config.PowerLimiter_BatterySocStartThreshold; - root[F("battery_soc_stop_threshold")] = config.PowerLimiter_BatterySocStopThreshold; - root[F("voltage_start_threshold")] = static_cast(config.PowerLimiter_VoltageStartThreshold * 100 +0.5) / 100.0; - root[F("voltage_stop_threshold")] = static_cast(config.PowerLimiter_VoltageStopThreshold * 100 +0.5) / 100.0;; - root[F("voltage_load_correction_factor")] = config.PowerLimiter_VoltageLoadCorrectionFactor; - root[F("inverter_restart_hour")] = config.PowerLimiter_RestartHour; - root[F("full_solar_passthrough_soc")] = config.PowerLimiter_FullSolarPassThroughSoc; - root[F("full_solar_passthrough_start_voltage")] = static_cast(config.PowerLimiter_FullSolarPassThroughStartVoltage * 100 + 0.5) / 100.0; - root[F("full_solar_passthrough_stop_voltage")] = static_cast(config.PowerLimiter_FullSolarPassThroughStopVoltage * 100 + 0.5) / 100.0; + root[F("enabled")] = config.PowerLimiter.Enabled; + root[F("verbose_logging")] = config.PowerLimiter.VerboseLogging; + root[F("solar_passthrough_enabled")] = config.PowerLimiter.SolarPassThroughEnabled; + root[F("solar_passthrough_losses")] = config.PowerLimiter.SolarPassThroughLosses; + root[F("battery_drain_strategy")] = config.PowerLimiter.BatteryDrainStategy; + root[F("is_inverter_behind_powermeter")] = config.PowerLimiter.IsInverterBehindPowerMeter; + root[F("inverter_id")] = config.PowerLimiter.InverterId; + root[F("inverter_channel_id")] = config.PowerLimiter.InverterChannelId; + root[F("target_power_consumption")] = config.PowerLimiter.TargetPowerConsumption; + root[F("target_power_consumption_hysteresis")] = config.PowerLimiter.TargetPowerConsumptionHysteresis; + root[F("lower_power_limit")] = config.PowerLimiter.LowerPowerLimit; + root[F("upper_power_limit")] = config.PowerLimiter.UpperPowerLimit; + root[F("battery_soc_start_threshold")] = config.PowerLimiter.BatterySocStartThreshold; + root[F("battery_soc_stop_threshold")] = config.PowerLimiter.BatterySocStopThreshold; + root[F("voltage_start_threshold")] = static_cast(config.PowerLimiter.VoltageStartThreshold * 100 +0.5) / 100.0; + root[F("voltage_stop_threshold")] = static_cast(config.PowerLimiter.VoltageStopThreshold * 100 +0.5) / 100.0;; + root[F("voltage_load_correction_factor")] = config.PowerLimiter.VoltageLoadCorrectionFactor; + root[F("inverter_restart_hour")] = config.PowerLimiter.RestartHour; + root[F("full_solar_passthrough_soc")] = config.PowerLimiter.FullSolarPassThroughSoc; + root[F("full_solar_passthrough_start_voltage")] = static_cast(config.PowerLimiter.FullSolarPassThroughStartVoltage * 100 + 0.5) / 100.0; + root[F("full_solar_passthrough_stop_voltage")] = static_cast(config.PowerLimiter.FullSolarPassThroughStopVoltage * 100 + 0.5) / 100.0; response->setLength(); request->send(response); @@ -124,30 +124,30 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request) CONFIG_T& config = Configuration.get(); - config.PowerLimiter_Enabled = root[F("enabled")].as(); + config.PowerLimiter.Enabled = root[F("enabled")].as(); PowerLimiter.setMode(PowerLimiterClass::Mode::Normal); // User input sets PL to normal operation - config.PowerLimiter_VerboseLogging = root[F("verbose_logging")].as(); - config.PowerLimiter_SolarPassThroughEnabled = root[F("solar_passthrough_enabled")].as(); - config.PowerLimiter_SolarPassThroughLosses = root[F("solar_passthrough_losses")].as(); - config.PowerLimiter_BatteryDrainStategy= root[F("battery_drain_strategy")].as(); - config.PowerLimiter_IsInverterBehindPowerMeter = root[F("is_inverter_behind_powermeter")].as(); - config.PowerLimiter_InverterId = root[F("inverter_id")].as(); - config.PowerLimiter_InverterChannelId = root[F("inverter_channel_id")].as(); - config.PowerLimiter_TargetPowerConsumption = root[F("target_power_consumption")].as(); - config.PowerLimiter_TargetPowerConsumptionHysteresis = root[F("target_power_consumption_hysteresis")].as(); - config.PowerLimiter_LowerPowerLimit = root[F("lower_power_limit")].as(); - config.PowerLimiter_UpperPowerLimit = root[F("upper_power_limit")].as(); - config.PowerLimiter_BatterySocStartThreshold = root[F("battery_soc_start_threshold")].as(); - config.PowerLimiter_BatterySocStopThreshold = root[F("battery_soc_stop_threshold")].as(); - config.PowerLimiter_VoltageStartThreshold = root[F("voltage_start_threshold")].as(); - config.PowerLimiter_VoltageStartThreshold = static_cast(config.PowerLimiter_VoltageStartThreshold * 100) / 100.0; - config.PowerLimiter_VoltageStopThreshold = root[F("voltage_stop_threshold")].as(); - config.PowerLimiter_VoltageStopThreshold = static_cast(config.PowerLimiter_VoltageStopThreshold * 100) / 100.0; - config.PowerLimiter_VoltageLoadCorrectionFactor = root[F("voltage_load_correction_factor")].as(); - config.PowerLimiter_RestartHour = root[F("inverter_restart_hour")].as(); - config.PowerLimiter_FullSolarPassThroughSoc = root[F("full_solar_passthrough_soc")].as(); - config.PowerLimiter_FullSolarPassThroughStartVoltage = static_cast(root[F("full_solar_passthrough_start_voltage")].as() * 100) / 100.0; - config.PowerLimiter_FullSolarPassThroughStopVoltage = static_cast(root[F("full_solar_passthrough_stop_voltage")].as() * 100) / 100.0; + config.PowerLimiter.VerboseLogging = root[F("verbose_logging")].as(); + config.PowerLimiter.SolarPassThroughEnabled = root[F("solar_passthrough_enabled")].as(); + config.PowerLimiter.SolarPassThroughLosses = root[F("solar_passthrough_losses")].as(); + config.PowerLimiter.BatteryDrainStategy= root[F("battery_drain_strategy")].as(); + config.PowerLimiter.IsInverterBehindPowerMeter = root[F("is_inverter_behind_powermeter")].as(); + config.PowerLimiter.InverterId = root[F("inverter_id")].as(); + config.PowerLimiter.InverterChannelId = root[F("inverter_channel_id")].as(); + config.PowerLimiter.TargetPowerConsumption = root[F("target_power_consumption")].as(); + config.PowerLimiter.TargetPowerConsumptionHysteresis = root[F("target_power_consumption_hysteresis")].as(); + config.PowerLimiter.LowerPowerLimit = root[F("lower_power_limit")].as(); + config.PowerLimiter.UpperPowerLimit = root[F("upper_power_limit")].as(); + config.PowerLimiter.BatterySocStartThreshold = root[F("battery_soc_start_threshold")].as(); + config.PowerLimiter.BatterySocStopThreshold = root[F("battery_soc_stop_threshold")].as(); + config.PowerLimiter.VoltageStartThreshold = root[F("voltage_start_threshold")].as(); + config.PowerLimiter.VoltageStartThreshold = static_cast(config.PowerLimiter.VoltageStartThreshold * 100) / 100.0; + config.PowerLimiter.VoltageStopThreshold = root[F("voltage_stop_threshold")].as(); + config.PowerLimiter.VoltageStopThreshold = static_cast(config.PowerLimiter.VoltageStopThreshold * 100) / 100.0; + config.PowerLimiter.VoltageLoadCorrectionFactor = root[F("voltage_load_correction_factor")].as(); + config.PowerLimiter.RestartHour = root[F("inverter_restart_hour")].as(); + config.PowerLimiter.FullSolarPassThroughSoc = root[F("full_solar_passthrough_soc")].as(); + config.PowerLimiter.FullSolarPassThroughStartVoltage = static_cast(root[F("full_solar_passthrough_start_voltage")].as() * 100) / 100.0; + config.PowerLimiter.FullSolarPassThroughStopVoltage = static_cast(root[F("full_solar_passthrough_stop_voltage")].as() * 100) / 100.0; diff --git a/src/WebApi_powermeter.cpp b/src/WebApi_powermeter.cpp index 53c945d4..383af5b4 100644 --- a/src/WebApi_powermeter.cpp +++ b/src/WebApi_powermeter.cpp @@ -16,11 +16,11 @@ #include "WebApi.h" #include "helper.h" -void WebApiPowerMeterClass::init(AsyncWebServer* server) +void WebApiPowerMeterClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/powermeter/status", HTTP_GET, std::bind(&WebApiPowerMeterClass::onStatus, this, _1)); _server->on("/api/powermeter/config", HTTP_GET, std::bind(&WebApiPowerMeterClass::onAdminGet, this, _1)); @@ -38,16 +38,16 @@ void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request) JsonObject root = response->getRoot(); const CONFIG_T& config = Configuration.get(); - root[F("enabled")] = config.PowerMeter_Enabled; - root[F("verbose_logging")] = config.PowerMeter_VerboseLogging; - root[F("source")] = config.PowerMeter_Source; - root[F("interval")] = config.PowerMeter_Interval; - root[F("mqtt_topic_powermeter_1")] = config.PowerMeter_MqttTopicPowerMeter1; - root[F("mqtt_topic_powermeter_2")] = config.PowerMeter_MqttTopicPowerMeter2; - root[F("mqtt_topic_powermeter_3")] = config.PowerMeter_MqttTopicPowerMeter3; - root[F("sdmbaudrate")] = config.PowerMeter_SdmBaudrate; - root[F("sdmaddress")] = config.PowerMeter_SdmAddress; - root[F("http_individual_requests")] = config.PowerMeter_HttpIndividualRequests; + root[F("enabled")] = config.PowerMeter.Enabled; + root[F("verbose_logging")] = config.PowerMeter.VerboseLogging; + root[F("source")] = config.PowerMeter.Source; + root[F("interval")] = config.PowerMeter.Interval; + root[F("mqtt_topic_powermeter_1")] = config.PowerMeter.MqttTopicPowerMeter1; + root[F("mqtt_topic_powermeter_2")] = config.PowerMeter.MqttTopicPowerMeter2; + root[F("mqtt_topic_powermeter_3")] = config.PowerMeter.MqttTopicPowerMeter3; + root[F("sdmbaudrate")] = config.PowerMeter.SdmBaudrate; + root[F("sdmaddress")] = config.PowerMeter.SdmAddress; + root[F("http_individual_requests")] = config.PowerMeter.HttpIndividualRequests; JsonArray httpPhases = root.createNestedArray(F("http_phases")); @@ -55,15 +55,15 @@ void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request) JsonObject phaseObject = httpPhases.createNestedObject(); phaseObject[F("index")] = i + 1; - phaseObject[F("enabled")] = config.Powermeter_Http_Phase[i].Enabled; - phaseObject[F("url")] = String(config.Powermeter_Http_Phase[i].Url); - phaseObject[F("auth_type")]= config.Powermeter_Http_Phase[i].AuthType; - phaseObject[F("username")] = String(config.Powermeter_Http_Phase[i].Username); - phaseObject[F("password")] = String(config.Powermeter_Http_Phase[i].Password); - phaseObject[F("header_key")] = String(config.Powermeter_Http_Phase[i].HeaderKey); - phaseObject[F("header_value")] = String(config.Powermeter_Http_Phase[i].HeaderValue); - phaseObject[F("json_path")] = String(config.Powermeter_Http_Phase[i].JsonPath); - phaseObject[F("timeout")] = config.Powermeter_Http_Phase[i].Timeout; + phaseObject[F("enabled")] = config.PowerMeter.Http_Phase[i].Enabled; + phaseObject[F("url")] = String(config.PowerMeter.Http_Phase[i].Url); + phaseObject[F("auth_type")]= config.PowerMeter.Http_Phase[i].AuthType; + phaseObject[F("username")] = String(config.PowerMeter.Http_Phase[i].Username); + phaseObject[F("password")] = String(config.PowerMeter.Http_Phase[i].Password); + phaseObject[F("header_key")] = String(config.PowerMeter.Http_Phase[i].HeaderKey); + phaseObject[F("header_value")] = String(config.PowerMeter.Http_Phase[i].HeaderValue); + phaseObject[F("json_path")] = String(config.PowerMeter.Http_Phase[i].JsonPath); + phaseObject[F("timeout")] = config.PowerMeter.Http_Phase[i].Timeout; } response->setLength(); @@ -169,30 +169,30 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request) } CONFIG_T& config = Configuration.get(); - config.PowerMeter_Enabled = root[F("enabled")].as(); - config.PowerMeter_VerboseLogging = root[F("verbose_logging")].as(); - config.PowerMeter_Source = root[F("source")].as(); - config.PowerMeter_Interval = root[F("interval")].as(); - strlcpy(config.PowerMeter_MqttTopicPowerMeter1, root[F("mqtt_topic_powermeter_1")].as().c_str(), sizeof(config.PowerMeter_MqttTopicPowerMeter1)); - strlcpy(config.PowerMeter_MqttTopicPowerMeter2, root[F("mqtt_topic_powermeter_2")].as().c_str(), sizeof(config.PowerMeter_MqttTopicPowerMeter2)); - strlcpy(config.PowerMeter_MqttTopicPowerMeter3, root[F("mqtt_topic_powermeter_3")].as().c_str(), sizeof(config.PowerMeter_MqttTopicPowerMeter3)); - config.PowerMeter_SdmBaudrate = root[F("sdmbaudrate")].as(); - config.PowerMeter_SdmAddress = root[F("sdmaddress")].as(); - config.PowerMeter_HttpIndividualRequests = root[F("http_individual_requests")].as(); + config.PowerMeter.Enabled = root[F("enabled")].as(); + config.PowerMeter.VerboseLogging = root[F("verbose_logging")].as(); + config.PowerMeter.Source = root[F("source")].as(); + config.PowerMeter.Interval = root[F("interval")].as(); + strlcpy(config.PowerMeter.MqttTopicPowerMeter1, root[F("mqtt_topic_powermeter_1")].as().c_str(), sizeof(config.PowerMeter.MqttTopicPowerMeter1)); + strlcpy(config.PowerMeter.MqttTopicPowerMeter2, root[F("mqtt_topic_powermeter_2")].as().c_str(), sizeof(config.PowerMeter.MqttTopicPowerMeter2)); + strlcpy(config.PowerMeter.MqttTopicPowerMeter3, root[F("mqtt_topic_powermeter_3")].as().c_str(), sizeof(config.PowerMeter.MqttTopicPowerMeter3)); + config.PowerMeter.SdmBaudrate = root[F("sdmbaudrate")].as(); + config.PowerMeter.SdmAddress = root[F("sdmaddress")].as(); + config.PowerMeter.HttpIndividualRequests = root[F("http_individual_requests")].as(); JsonArray http_phases = root[F("http_phases")]; for (uint8_t i = 0; i < http_phases.size(); i++) { JsonObject phase = http_phases[i].as(); - config.Powermeter_Http_Phase[i].Enabled = (i == 0 ? true : phase[F("enabled")].as()); - strlcpy(config.Powermeter_Http_Phase[i].Url, phase[F("url")].as().c_str(), sizeof(config.Powermeter_Http_Phase[i].Url)); - config.Powermeter_Http_Phase[i].AuthType = phase[F("auth_type")].as(); - strlcpy(config.Powermeter_Http_Phase[i].Username, phase[F("username")].as().c_str(), sizeof(config.Powermeter_Http_Phase[i].Username)); - strlcpy(config.Powermeter_Http_Phase[i].Password, phase[F("password")].as().c_str(), sizeof(config.Powermeter_Http_Phase[i].Password)); - strlcpy(config.Powermeter_Http_Phase[i].HeaderKey, phase[F("header_key")].as().c_str(), sizeof(config.Powermeter_Http_Phase[i].HeaderKey)); - strlcpy(config.Powermeter_Http_Phase[i].HeaderValue, phase[F("header_value")].as().c_str(), sizeof(config.Powermeter_Http_Phase[i].HeaderValue)); - config.Powermeter_Http_Phase[i].Timeout = phase[F("timeout")].as(); - strlcpy(config.Powermeter_Http_Phase[i].JsonPath, phase[F("json_path")].as().c_str(), sizeof(config.Powermeter_Http_Phase[i].JsonPath)); + config.PowerMeter.Http_Phase[i].Enabled = (i == 0 ? true : phase[F("enabled")].as()); + strlcpy(config.PowerMeter.Http_Phase[i].Url, phase[F("url")].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Url)); + config.PowerMeter.Http_Phase[i].AuthType = phase[F("auth_type")].as(); + strlcpy(config.PowerMeter.Http_Phase[i].Username, phase[F("username")].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Username)); + strlcpy(config.PowerMeter.Http_Phase[i].Password, phase[F("password")].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Password)); + strlcpy(config.PowerMeter.Http_Phase[i].HeaderKey, phase[F("header_key")].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].HeaderKey)); + strlcpy(config.PowerMeter.Http_Phase[i].HeaderValue, phase[F("header_value")].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].HeaderValue)); + config.PowerMeter.Http_Phase[i].Timeout = phase[F("timeout")].as(); + strlcpy(config.PowerMeter.Http_Phase[i].JsonPath, phase[F("json_path")].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].JsonPath)); } Configuration.write(); diff --git a/src/WebApi_prometheus.cpp b/src/WebApi_prometheus.cpp index ab5a4148..0f46c97f 100644 --- a/src/WebApi_prometheus.cpp +++ b/src/WebApi_prometheus.cpp @@ -1,7 +1,7 @@ // 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_prometheus.h" #include "Configuration.h" @@ -11,11 +11,11 @@ #include #include "MessageOutput.h" -void WebApiPrometheusClass::init(AsyncWebServer* server) +void WebApiPrometheusClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/prometheus/metrics", HTTP_GET, std::bind(&WebApiPrometheusClass::onPrometheusMetricsGet, this, _1)); } @@ -100,10 +100,10 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques } } -void WebApiPrometheusClass::addField(AsyncResponseStream* stream, String& serial, uint8_t idx, std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, const char* metricName, const char* channelName) +void WebApiPrometheusClass::addField(AsyncResponseStream* stream, const String& serial, const uint8_t idx, std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, const char* metricName, const char* channelName) { if (inv->Statistics()->hasChannelFieldValue(type, channel, fieldId)) { - const char* chanName = (channelName == NULL) ? inv->Statistics()->getChannelFieldName(type, channel, fieldId) : channelName; + const char* chanName = (channelName == nullptr) ? inv->Statistics()->getChannelFieldName(type, channel, fieldId) : channelName; if (idx == 0 && type == TYPE_AC && channel == 0) { stream->printf("# HELP opendtu_%s in %s\n", chanName, inv->Statistics()->getChannelFieldUnit(type, channel, fieldId)); stream->printf("# TYPE opendtu_%s %s\n", chanName, metricName); @@ -119,7 +119,7 @@ void WebApiPrometheusClass::addField(AsyncResponseStream* stream, String& serial } } -void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, String& serial, uint8_t idx, std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel) +void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, const String& serial, const uint8_t idx, std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel) { if (type != TYPE_DC) { return; diff --git a/src/WebApi_security.cpp b/src/WebApi_security.cpp index a2221f9b..274d0eb2 100644 --- a/src/WebApi_security.cpp +++ b/src/WebApi_security.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_security.h" #include "Configuration.h" @@ -9,11 +9,11 @@ #include "helper.h" #include -void WebApiSecurityClass::init(AsyncWebServer* server) +void WebApiSecurityClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/security/config", HTTP_GET, std::bind(&WebApiSecurityClass::onSecurityGet, this, _1)); _server->on("/api/security/config", HTTP_POST, std::bind(&WebApiSecurityClass::onSecurityPost, this, _1)); @@ -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); @@ -59,7 +59,7 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request) return; } - String json = request->getParam("data", true)->value(); + const String json = request->getParam("data", true)->value(); if (json.length() > 1024) { retMsg["message"] = "Data too large!"; @@ -70,7 +70,7 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request) } DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); + const DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; @@ -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 f98b6147..85324f08 100644 --- a/src/WebApi_sysstatus.cpp +++ b/src/WebApi_sysstatus.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_sysstatus.h" #include "Configuration.h" @@ -20,11 +20,11 @@ #define AUTO_GIT_BRANCH "" #endif -void WebApiSysstatusClass::init(AsyncWebServer* server) +void WebApiSysstatusClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/system/status", HTTP_GET, std::bind(&WebApiSysstatusClass::onSystemStatus, this, _1)); } @@ -59,13 +59,13 @@ 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; + 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_vedirect.cpp b/src/WebApi_vedirect.cpp index 1b884265..0e8e22aa 100644 --- a/src/WebApi_vedirect.cpp +++ b/src/WebApi_vedirect.cpp @@ -11,11 +11,11 @@ #include "WebApi_errors.h" #include "helper.h" -void WebApiVedirectClass::init(AsyncWebServer* server) +void WebApiVedirectClass::init(AsyncWebServer& server) { using std::placeholders::_1; - _server = server; + _server = &server; _server->on("/api/vedirect/status", HTTP_GET, std::bind(&WebApiVedirectClass::onVedirectStatus, this, _1)); _server->on("/api/vedirect/config", HTTP_GET, std::bind(&WebApiVedirectClass::onVedirectAdminGet, this, _1)); @@ -36,9 +36,9 @@ void WebApiVedirectClass::onVedirectStatus(AsyncWebServerRequest* request) JsonObject root = response->getRoot(); const CONFIG_T& config = Configuration.get(); - root[F("vedirect_enabled")] = config.Vedirect_Enabled; - root[F("verbose_logging")] = config.Vedirect_VerboseLogging; - root[F("vedirect_updatesonly")] = config.Vedirect_UpdatesOnly; + root[F("vedirect_enabled")] = config.Vedirect.Enabled; + root[F("verbose_logging")] = config.Vedirect.VerboseLogging; + root[F("vedirect_updatesonly")] = config.Vedirect.UpdatesOnly; response->setLength(); request->send(response); @@ -54,9 +54,9 @@ void WebApiVedirectClass::onVedirectAdminGet(AsyncWebServerRequest* request) JsonObject root = response->getRoot(); const CONFIG_T& config = Configuration.get(); - root[F("vedirect_enabled")] = config.Vedirect_Enabled; - root[F("verbose_logging")] = config.Vedirect_VerboseLogging; - root[F("vedirect_updatesonly")] = config.Vedirect_UpdatesOnly; + root[F("vedirect_enabled")] = config.Vedirect.Enabled; + root[F("verbose_logging")] = config.Vedirect.VerboseLogging; + root[F("vedirect_updatesonly")] = config.Vedirect.UpdatesOnly; response->setLength(); request->send(response); @@ -112,12 +112,12 @@ void WebApiVedirectClass::onVedirectAdminPost(AsyncWebServerRequest* request) } CONFIG_T& config = Configuration.get(); - config.Vedirect_Enabled = root[F("vedirect_enabled")].as(); - config.Vedirect_VerboseLogging = root[F("verbose_logging")].as(); - config.Vedirect_UpdatesOnly = root[F("vedirect_updatesonly")].as(); + config.Vedirect.Enabled = root[F("vedirect_enabled")].as(); + config.Vedirect.VerboseLogging = root[F("verbose_logging")].as(); + config.Vedirect.UpdatesOnly = root[F("vedirect_updatesonly")].as(); Configuration.write(); - VictronMppt.init(); + VictronMppt.updateSettings(); retMsg[F("type")] = F("success"); retMsg[F("message")] = F("Settings saved!"); diff --git a/src/WebApi_webapp.cpp b/src/WebApi_webapp.cpp index 90516ad6..260566fa 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,16 +9,18 @@ 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) +void WebApiWebappClass::init(AsyncWebServer& server) { - _server = server; + _server = &server; _server->on("/", HTTP_GET, [](AsyncWebServerRequest* request) { AsyncWebServerResponse* response = request->beginResponse_P(200, "text/html", file_index_html_start, file_index_html_end - file_index_html_start); @@ -54,12 +56,17 @@ 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 bool eTagMatch = false; if (request->hasHeader("If-None-Match")) { - AsyncWebHeader* h = request->getHeader("If-None-Match"); + const AsyncWebHeader* h = request->getHeader("If-None-Match"); if (strncmp(AUTO_GIT_HASH, h->value().c_str(), strlen(AUTO_GIT_HASH)) == 0) { eTagMatch = true; } diff --git a/src/WebApi_ws_Huawei.cpp b/src/WebApi_ws_Huawei.cpp index 4cf8eb84..55f83cd6 100644 --- a/src/WebApi_ws_Huawei.cpp +++ b/src/WebApi_ws_Huawei.cpp @@ -15,7 +15,7 @@ WebApiWsHuaweiLiveClass::WebApiWsHuaweiLiveClass() { } -void WebApiWsHuaweiLiveClass::init(AsyncWebServer* server) +void WebApiWsHuaweiLiveClass::init(AsyncWebServer& server) { using std::placeholders::_1; using std::placeholders::_2; @@ -24,7 +24,7 @@ void WebApiWsHuaweiLiveClass::init(AsyncWebServer* server) using std::placeholders::_5; using std::placeholders::_6; - _server = server; + _server = &server; _server->on("/api/huaweilivedata/status", HTTP_GET, std::bind(&WebApiWsHuaweiLiveClass::onLivedataStatus, this, _1)); _server->addHandler(&_ws); @@ -60,10 +60,10 @@ void WebApiWsHuaweiLiveClass::loop() } if (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); diff --git a/src/WebApi_ws_battery.cpp b/src/WebApi_ws_battery.cpp index a2a74525..475b0b66 100644 --- a/src/WebApi_ws_battery.cpp +++ b/src/WebApi_ws_battery.cpp @@ -15,7 +15,7 @@ WebApiWsBatteryLiveClass::WebApiWsBatteryLiveClass() { } -void WebApiWsBatteryLiveClass::init(AsyncWebServer* server) +void WebApiWsBatteryLiveClass::init(AsyncWebServer& server) { using std::placeholders::_1; using std::placeholders::_2; @@ -24,7 +24,7 @@ void WebApiWsBatteryLiveClass::init(AsyncWebServer* server) using std::placeholders::_5; using std::placeholders::_6; - _server = server; + _server = &server; _server->on("/api/batterylivedata/status", HTTP_GET, std::bind(&WebApiWsBatteryLiveClass::onLivedataStatus, this, _1)); _server->addHandler(&_ws); @@ -58,10 +58,10 @@ void WebApiWsBatteryLiveClass::loop() } if (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); diff --git a/src/WebApi_ws_console.cpp b/src/WebApi_ws_console.cpp index 2837fc39..54159381 100644 --- a/src/WebApi_ws_console.cpp +++ b/src/WebApi_ws_console.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_ws_console.h" #include "Configuration.h" @@ -13,9 +13,9 @@ WebApiWsConsoleClass::WebApiWsConsoleClass() { } -void WebApiWsConsoleClass::init(AsyncWebServer* server) +void WebApiWsConsoleClass::init(AsyncWebServer& server) { - _server = server; + _server = &server; _server->addHandler(&_ws); MessageOutput.register_ws_output(&_ws); } @@ -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 ffbf6b20..20308d77 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.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_ws_live.h" #include "Configuration.h" @@ -19,7 +19,7 @@ WebApiWsLiveClass::WebApiWsLiveClass() { } -void WebApiWsLiveClass::init(AsyncWebServer* server) +void WebApiWsLiveClass::init(AsyncWebServer& server) { using std::placeholders::_1; using std::placeholders::_2; @@ -28,7 +28,7 @@ void WebApiWsLiveClass::init(AsyncWebServer* server) using std::placeholders::_5; using std::placeholders::_6; - _server = server; + _server = &server; _server->on("/api/livedata/status", HTTP_GET, std::bind(&WebApiWsLiveClass::onLivedataStatus, this, _1)); _server->addHandler(&_ws); @@ -75,10 +75,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); @@ -176,14 +176,14 @@ 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; } JsonObject vedirectObj = root.createNestedObject("vedirect"); - vedirectObj[F("enabled")] = Configuration.get().Vedirect_Enabled; + vedirectObj[F("enabled")] = Configuration.get().Vedirect.Enabled; JsonObject totalVeObj = vedirectObj.createNestedObject("total"); addTotalField(totalVeObj, "Power", VictronMppt.getPanelPowerWatts(), "W", 1); @@ -191,21 +191,21 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root) addTotalField(totalVeObj, "YieldTotal", VictronMppt.getYieldTotal(), "kWh", 2); JsonObject huaweiObj = root.createNestedObject("huawei"); - huaweiObj[F("enabled")] = Configuration.get().Huawei_Enabled; + huaweiObj[F("enabled")] = Configuration.get().Huawei.Enabled; const RectifierParameters_t * rp = HuaweiCan.get(); addTotalField(huaweiObj, "Power", rp->output_power, "W", 2); JsonObject batteryObj = root.createNestedObject("battery"); - batteryObj[F("enabled")] = Configuration.get().Battery_Enabled; + batteryObj[F("enabled")] = Configuration.get().Battery.Enabled; addTotalField(batteryObj, "soc", Battery.getStats()->getSoC(), "%", 0); JsonObject powerMeterObj = root.createNestedObject("power_meter"); - powerMeterObj[F("enabled")] = Configuration.get().PowerMeter_Enabled; + powerMeterObj[F("enabled")] = Configuration.get().PowerMeter.Enabled; addTotalField(powerMeterObj, "Power", PowerMeter.getPowerTotal(false), "W", 1); } -void WebApiWsLiveClass::addField(JsonObject& root, uint8_t idx, std::shared_ptr inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, String topic) +void WebApiWsLiveClass::addField(JsonObject& root, uint8_t idx, std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, String topic) { if (inv->Statistics()->hasChannelFieldValue(type, channel, fieldId)) { String chanName; @@ -222,7 +222,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, const float value, const String& unit, const uint8_t digits) { root[name]["v"] = value; root[name]["u"] = unit; diff --git a/src/WebApi_ws_vedirect_live.cpp b/src/WebApi_ws_vedirect_live.cpp index 53c81d12..430eb9ce 100644 --- a/src/WebApi_ws_vedirect_live.cpp +++ b/src/WebApi_ws_vedirect_live.cpp @@ -16,7 +16,7 @@ WebApiWsVedirectLiveClass::WebApiWsVedirectLiveClass() { } -void WebApiWsVedirectLiveClass::init(AsyncWebServer* server) +void WebApiWsVedirectLiveClass::init(AsyncWebServer& server) { using std::placeholders::_1; using std::placeholders::_2; @@ -25,7 +25,7 @@ void WebApiWsVedirectLiveClass::init(AsyncWebServer* server) using std::placeholders::_5; using std::placeholders::_6; - _server = server; + _server = &server; _server->on("/api/vedirectlivedata/status", HTTP_GET, std::bind(&WebApiWsVedirectLiveClass::onLivedataStatus, this, _1)); _server->addHandler(&_ws); @@ -65,10 +65,10 @@ void WebApiWsVedirectLiveClass::loop() } if (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); @@ -142,7 +142,7 @@ void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root) // power limiter state root["dpl"]["PLSTATE"] = -1; - if (Configuration.get().PowerLimiter_Enabled) + if (Configuration.get().PowerLimiter.Enabled) root["dpl"]["PLSTATE"] = PowerLimiter.getPowerLimiterState(); root["dpl"]["PLLIMIT"] = PowerLimiter.getLastRequestedPowerLimit(); } diff --git a/src/main.cpp b/src/main.cpp index b39845ca..b0231774 100644 --- a/src/main.cpp +++ b/src/main.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 "Configuration.h" #include "Datastore.h" @@ -24,6 +24,7 @@ #include "NetworkSettings.h" #include "NtpSettings.h" #include "PinMapping.h" +#include "Scheduler.h" #include "SunPosition.h" #include "Utils.h" #include "WebApi.h" @@ -32,6 +33,7 @@ #include "defaults.h" #include #include +#include void setup() { @@ -44,6 +46,7 @@ void setup() while (!Serial) yield(); #endif + MessageOutput.init(scheduler); MessageOutput.println(); MessageOutput.println("Starting OpenDTU"); @@ -71,11 +74,11 @@ void setup() MessageOutput.print("failed... "); } } - if (Configuration.get().Cfg_Version != CONFIG_VERSION) { + if (Configuration.get().Cfg.Version != CONFIG_VERSION) { MessageOutput.print("migrated... "); Configuration.migrate(); } - CONFIG_T& config = Configuration.get(); + auto& config = Configuration.get(); MessageOutput.println("done"); // Load PinMapping @@ -85,12 +88,12 @@ void setup() } else { MessageOutput.print("using default config "); } - const PinMapping_t& pin = PinMapping.get(); + const auto& pin = PinMapping.get(); MessageOutput.println("done"); // Initialize WiFi MessageOutput.print("Initialize Network... "); - NetworkSettings.init(); + NetworkSettings.init(scheduler); MessageOutput.println("done"); NetworkSettings.applyConfig(); @@ -101,133 +104,89 @@ void setup() // Initialize SunPosition MessageOutput.print("Initialize SunPosition... "); - SunPosition.init(); + SunPosition.init(scheduler); MessageOutput.println("done"); // Initialize MqTT MessageOutput.print("Initialize MqTT... "); MqttSettings.init(); - MqttHandleDtu.init(); - MqttHandleInverter.init(); - MqttHandleInverterTotal.init(); - MqttHandleVedirect.init(); - MqttHandleHass.init(); - MqttHandleVedirectHass.init(); - MqttHandleHuawei.init(); - MqttHandlePowerLimiter.init(); + MqttHandleDtu.init(scheduler); + MqttHandleInverter.init(scheduler); + MqttHandleInverterTotal.init(scheduler); + MqttHandleVedirect.init(scheduler); + MqttHandleHass.init(scheduler); + MqttHandleVedirectHass.init(scheduler); + MqttHandleHuawei.init(scheduler); + MqttHandlePowerLimiter.init(scheduler); MessageOutput.println("done"); // Initialize WebApi MessageOutput.print("Initialize WebApi... "); - WebApi.init(); + WebApi.init(scheduler); MessageOutput.println("done"); // Initialize Display MessageOutput.print("Initialize Display... "); Display.init( + scheduler, static_cast(pin.display_type), pin.display_data, 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"); // Initialize Single LEDs MessageOutput.print("Initialize LEDs... "); - LedSingle.init(); + LedSingle.init(scheduler); MessageOutput.println("done"); // 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(); + const 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"); MessageOutput.println("done"); - InverterSettings.init(); + InverterSettings.init(scheduler); - Datastore.init(); + Datastore.init(scheduler); - VictronMppt.init(); + VictronMppt.init(scheduler); // Power meter - PowerMeter.init(); + PowerMeter.init(scheduler); // Dynamic power limiter - PowerLimiter.init(); + PowerLimiter.init(scheduler); // Initialize Huawei AC-charger PSU / CAN bus - MessageOutput.println(F("Initialize Huawei AC charger interface... ")); + MessageOutput.println("Initialize Huawei AC charger interface... "); if (PinMapping.isValidHuaweiConfig()) { MessageOutput.printf("Huawei AC-charger miso = %d, mosi = %d, clk = %d, irq = %d, cs = %d, power_pin = %d\r\n", pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power); - HuaweiCan.init(pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power); - MessageOutput.println(F("done")); + HuaweiCan.init(scheduler, pin.huawei_miso, pin.huawei_mosi, pin.huawei_clk, pin.huawei_irq, pin.huawei_cs, pin.huawei_power); + MessageOutput.println("done"); } else { - MessageOutput.println(F("Invalid pin config")); + MessageOutput.println("Invalid pin config"); } - Battery.init(); + Battery.init(scheduler); } void loop() { - NetworkSettings.loop(); - yield(); - PowerMeter.loop(); - yield(); - PowerLimiter.loop(); - yield(); - InverterSettings.loop(); - yield(); - Datastore.loop(); - yield(); - VictronMppt.loop(); - yield(); - MqttSettings.loop(); - yield(); - MqttHandleDtu.loop(); - yield(); - MqttHandleInverter.loop(); - yield(); - MqttHandleInverterTotal.loop(); - yield(); - MqttHandleVedirect.loop(); - yield(); - MqttHandleHass.loop(); - yield(); - MqttHandleVedirectHass.loop(); - yield(); - MqttHandleHuawei.loop(); - yield(); - MqttHandlePowerLimiter.loop(); - yield(); - WebApi.loop(); - yield(); - Display.loop(); - yield(); - SunPosition.loop(); - yield(); - MessageOutput.loop(); - yield(); - Battery.loop(); - yield(); - MqttHandlePylontechHass.loop(); - yield(); - HuaweiCan.loop(); - yield(); - LedSingle.loop(); - yield(); + scheduler.execute(); } diff --git a/webapp/index.html b/webapp/index.html index 07f73f91..47122be1 100644 --- a/webapp/index.html +++ b/webapp/index.html @@ -5,6 +5,7 @@ + OpenDTU-onBattery diff --git a/webapp/package.json b/webapp/package.json index c781b53e..c5050c46 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -16,32 +16,32 @@ "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.0", + "vue": "^3.3.13", + "vue-i18n": "^9.8.0", "vue-router": "^4.2.5" }, "devDependencies": { - "@intlify/unplugin-vue-i18n": "^1.5.0", - "@rushstack/eslint-patch": "^1.5.1", + "@intlify/unplugin-vue-i18n": "^1.6.0", + "@rushstack/eslint-patch": "^1.6.1", "@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.10.5", + "@types/sortablejs": "^1.15.7", "@types/spark-md5": "^3.0.4", - "@vitejs/plugin-vue": "^4.5.0", + "@vitejs/plugin-vue": "^4.5.2", "@vue/eslint-config-typescript": "^12.0.0", - "@vue/tsconfig": "^0.4.0", - "eslint": "^8.53.0", - "eslint-plugin-vue": "^9.18.1", + "@vue/tsconfig": "^0.5.1", + "eslint": "^8.56.0", + "eslint-plugin-vue": "^9.19.2", "npm-run-all": "^4.1.5", "sass": "^1.69.5", - "terser": "^5.24.0", - "typescript": "^5.2.2", - "vite": "^5.0.0", + "terser": "^5.26.0", + "typescript": "^5.3.3", + "vite": "^5.0.10", "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/public/favicon.png b/webapp/public/favicon.png index 3378b661..278aac84 100644 Binary files a/webapp/public/favicon.png and b/webapp/public/favicon.png differ 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 diff --git a/webapp/public/zones.json b/webapp/public/zones.json index 28bfa0dc..2d449fe2 100644 --- a/webapp/public/zones.json +++ b/webapp/public/zones.json @@ -11,7 +11,7 @@ "Africa/Blantyre":"CAT-2", "Africa/Brazzaville":"WAT-1", "Africa/Bujumbura":"CAT-2", -"Africa/Cairo":"EET-2", +"Africa/Cairo":"EET-2EEST,M4.5.5/0,M10.5.4/24", "Africa/Casablanca":"<+01>-1", "Africa/Ceuta":"CET-1CEST,M3.5.0,M10.5.0/3", "Africa/Conakry":"GMT0", @@ -72,7 +72,7 @@ "America/Asuncion":"<-04>4<-03>,M10.1.0/0,M3.4.0/0", "America/Atikokan":"EST5", "America/Bahia":"<-03>3", -"America/Bahia_Banderas":"CST6CDT,M4.1.0,M10.5.0", +"America/Bahia_Banderas":"CST6", "America/Barbados":"AST4", "America/Belem":"<-03>3", "America/Belize":"CST6", @@ -87,7 +87,7 @@ "America/Cayenne":"<-03>3", "America/Cayman":"EST5", "America/Chicago":"CST6CDT,M3.2.0,M11.1.0", -"America/Chihuahua":"MST7MDT,M4.1.0,M10.5.0", +"America/Chihuahua":"CST6", "America/Costa_Rica":"CST6", "America/Creston":"MST7", "America/Cuiaba":"<-04>4", @@ -104,7 +104,7 @@ "America/Fort_Nelson":"MST7", "America/Fortaleza":"<-03>3", "America/Glace_Bay":"AST4ADT,M3.2.0,M11.1.0", -"America/Godthab":"<-03>3<-02>,M3.5.0/-2,M10.5.0/-1", +"America/Godthab":"<-02>2<-01>,M3.5.0/-1,M10.5.0/0", "America/Goose_Bay":"AST4ADT,M3.2.0,M11.1.0", "America/Grand_Turk":"EST5EDT,M3.2.0,M11.1.0", "America/Grenada":"AST4", @@ -140,14 +140,14 @@ "America/Marigot":"AST4", "America/Martinique":"AST4", "America/Matamoros":"CST6CDT,M3.2.0,M11.1.0", -"America/Mazatlan":"MST7MDT,M4.1.0,M10.5.0", +"America/Mazatlan":"MST7", "America/Menominee":"CST6CDT,M3.2.0,M11.1.0", -"America/Merida":"CST6CDT,M4.1.0,M10.5.0", +"America/Merida":"CST6", "America/Metlakatla":"AKST9AKDT,M3.2.0,M11.1.0", -"America/Mexico_City":"CST6CDT,M4.1.0,M10.5.0", +"America/Mexico_City":"CST6", "America/Miquelon":"<-03>3<-02>,M3.2.0,M11.1.0", "America/Moncton":"AST4ADT,M3.2.0,M11.1.0", -"America/Monterrey":"CST6CDT,M4.1.0,M10.5.0", +"America/Monterrey":"CST6", "America/Montevideo":"<-03>3", "America/Montreal":"EST5EDT,M3.2.0,M11.1.0", "America/Montserrat":"AST4", @@ -159,8 +159,8 @@ "America/North_Dakota/Beulah":"CST6CDT,M3.2.0,M11.1.0", "America/North_Dakota/Center":"CST6CDT,M3.2.0,M11.1.0", "America/North_Dakota/New_Salem":"CST6CDT,M3.2.0,M11.1.0", -"America/Nuuk":"<-03>3<-02>,M3.5.0/-2,M10.5.0/-1", -"America/Ojinaga":"MST7MDT,M3.2.0,M11.1.0", +"America/Nuuk":"<-02>2<-01>,M3.5.0/-1,M10.5.0/0", +"America/Ojinaga":"CST6CDT,M3.2.0,M11.1.0", "America/Panama":"EST5", "America/Pangnirtung":"EST5EDT,M3.2.0,M11.1.0", "America/Paramaribo":"<-03>3", @@ -214,7 +214,7 @@ "Arctic/Longyearbyen":"CET-1CEST,M3.5.0,M10.5.0/3", "Asia/Aden":"<+03>-3", "Asia/Almaty":"<+06>-6", -"Asia/Amman":"EET-2EEST,M2.5.4/24,M10.5.5/1", +"Asia/Amman":"<+03>-3", "Asia/Anadyr":"<+12>-12", "Asia/Aqtau":"<+05>-5", "Asia/Aqtobe":"<+05>-5", @@ -231,14 +231,14 @@ "Asia/Chita":"<+09>-9", "Asia/Choibalsan":"<+08>-8", "Asia/Colombo":"<+0530>-5:30", -"Asia/Damascus":"EET-2EEST,M3.5.5/0,M10.5.5/0", +"Asia/Damascus":"<+03>-3", "Asia/Dhaka":"<+06>-6", "Asia/Dili":"<+09>-9", "Asia/Dubai":"<+04>-4", "Asia/Dushanbe":"<+05>-5", "Asia/Famagusta":"EET-2EEST,M3.5.0/3,M10.5.0/4", -"Asia/Gaza":"EET-2EEST,M3.4.4/48,M10.5.5/1", -"Asia/Hebron":"EET-2EEST,M3.4.4/48,M10.5.5/1", +"Asia/Gaza":"EET-2EEST,M3.4.4/50,M10.4.4/50", +"Asia/Hebron":"EET-2EEST,M3.4.4/50,M10.4.4/50", "Asia/Ho_Chi_Minh":"<+07>-7", "Asia/Hong_Kong":"HKT-8", "Asia/Hovd":"<+07>-7", @@ -281,7 +281,7 @@ "Asia/Taipei":"CST-8", "Asia/Tashkent":"<+05>-5", "Asia/Tbilisi":"<+04>-4", -"Asia/Tehran":"<+0330>-3:30<+0430>,J79/24,J263/24", +"Asia/Tehran":"<+0330>-3:30", "Asia/Thimphu":"<+06>-6", "Asia/Tokyo":"JST-9", "Asia/Tomsk":"<+07>-7", @@ -373,7 +373,7 @@ "Europe/Jersey":"GMT0BST,M3.5.0/1,M10.5.0", "Europe/Kaliningrad":"EET-2", "Europe/Kiev":"EET-2EEST,M3.5.0/3,M10.5.0/4", -"Europe/Kirov":"<+03>-3", +"Europe/Kirov":"MSK-3", "Europe/Lisbon":"WET0WEST,M3.5.0/1,M10.5.0", "Europe/Ljubljana":"CET-1CEST,M3.5.0,M10.5.0/3", "Europe/London":"GMT0BST,M3.5.0/1,M10.5.0", @@ -406,7 +406,7 @@ "Europe/Vatican":"CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Vienna":"CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Vilnius":"EET-2EEST,M3.5.0/3,M10.5.0/4", -"Europe/Volgograd":"<+03>-3", +"Europe/Volgograd":"MSK-3", "Europe/Warsaw":"CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Zagreb":"CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Zaporozhye":"EET-2EEST,M3.5.0/3,M10.5.0/4", @@ -431,7 +431,7 @@ "Pacific/Efate":"<+11>-11", "Pacific/Enderbury":"<+13>-13", "Pacific/Fakaofo":"<+13>-13", -"Pacific/Fiji":"<+12>-12<+13>,M11.2.0,M1.2.3/99", +"Pacific/Fiji":"<+12>-12", "Pacific/Funafuti":"<+12>-12", "Pacific/Galapagos":"<-06>6", "Pacific/Gambier":"<-09>9", diff --git a/webapp/src/components/FormFooter.vue b/webapp/src/components/FormFooter.vue new file mode 100644 index 00000000..7a18e602 --- /dev/null +++ b/webapp/src/components/FormFooter.vue @@ -0,0 +1,7 @@ + diff --git a/webapp/src/components/GridProfile.vue b/webapp/src/components/GridProfile.vue index 31a14133..6c93caec 100644 --- a/webapp/src/components/GridProfile.vue +++ b/webapp/src/components/GridProfile.vue @@ -6,36 +6,98 @@ \ No newline at end of file diff --git a/webapp/src/views/DtuAdminView.vue b/webapp/src/views/DtuAdminView.vue index 1c29295e..710177fd 100644 --- a/webapp/src/views/DtuAdminView.vue +++ b/webapp/src/views/DtuAdminView.vue @@ -70,7 +70,7 @@ - + @@ -79,6 +79,7 @@ import BasePage from '@/components/BasePage.vue'; import BootstrapAlert from "@/components/BootstrapAlert.vue"; import CardElement from '@/components/CardElement.vue'; +import FormFooter from '@/components/FormFooter.vue'; import InputElement from '@/components/InputElement.vue'; import type { DtuConfig } from "@/types/DtuConfig"; import { authHeader, handleResponse } from '@/utils/authentication'; @@ -90,6 +91,7 @@ export default defineComponent({ BasePage, BootstrapAlert, CardElement, + FormFooter, InputElement, BIconInfoCircle, }, diff --git a/webapp/src/views/HomeView.vue b/webapp/src/views/HomeView.vue index 3d80effd..a28bc57f 100644 --- a/webapp/src/views/HomeView.vue +++ b/webapp/src/views/HomeView.vue @@ -192,7 +192,7 @@ - + @@ -269,6 +274,7 @@ declare interface Inverter { reachable_threshold: number; zero_runtime: boolean; zero_day: boolean; + yieldday_correction: boolean; channel: Array; } diff --git a/webapp/src/views/MqttAdminView.vue b/webapp/src/views/MqttAdminView.vue index 1c5ba237..83bcab86 100644 --- a/webapp/src/views/MqttAdminView.vue +++ b/webapp/src/views/MqttAdminView.vue @@ -103,6 +103,19 @@ v-model="mqttConfigList.mqtt_lwt_offline" type="text" maxlength="20" :placeholder="$t('mqttadmin.LwtOfflineHint')"/> + +
+ +
+ +
+
- + @@ -135,6 +148,7 @@ import BasePage from '@/components/BasePage.vue'; import BootstrapAlert from "@/components/BootstrapAlert.vue"; import CardElement from '@/components/CardElement.vue'; +import FormFooter from '@/components/FormFooter.vue'; import InputElement from '@/components/InputElement.vue'; import type { MqttConfig } from "@/types/MqttConfig"; import { authHeader, handleResponse } from '@/utils/authentication'; @@ -145,6 +159,7 @@ export default defineComponent({ BasePage, BootstrapAlert, CardElement, + FormFooter, InputElement, }, data() { @@ -154,6 +169,11 @@ export default defineComponent({ alertMessage: "", alertType: "info", showAlert: false, + qosTypeList: [ + { key: 0, value: 'QOS0' }, + { key: 1, value: 'QOS1' }, + { key: 2, value: 'QOS2' }, + ], }; }, created() { diff --git a/webapp/src/views/NetworkAdminView.vue b/webapp/src/views/NetworkAdminView.vue index 1019f3e9..d4ee534a 100644 --- a/webapp/src/views/NetworkAdminView.vue +++ b/webapp/src/views/NetworkAdminView.vue @@ -63,7 +63,7 @@ :postfix="$t('networkadmin.Minutes')" :tooltip="$t('networkadmin.ApTimeoutHint')"/> - + @@ -72,6 +72,7 @@ import BasePage from '@/components/BasePage.vue'; import BootstrapAlert from "@/components/BootstrapAlert.vue"; import CardElement from '@/components/CardElement.vue'; +import FormFooter from '@/components/FormFooter.vue'; import InputElement from '@/components/InputElement.vue'; import type { NetworkConfig } from "@/types/NetworkConfig"; import { authHeader, handleResponse } from '@/utils/authentication'; @@ -82,6 +83,7 @@ export default defineComponent({ BasePage, BootstrapAlert, CardElement, + FormFooter, InputElement, }, data() { diff --git a/webapp/src/views/NtpAdminView.vue b/webapp/src/views/NtpAdminView.vue index 69e696c9..468dcac6 100644 --- a/webapp/src/views/NtpAdminView.vue +++ b/webapp/src/views/NtpAdminView.vue @@ -52,7 +52,7 @@ - + @@ -79,6 +79,7 @@ import BasePage from '@/components/BasePage.vue'; import BootstrapAlert from "@/components/BootstrapAlert.vue"; import CardElement from '@/components/CardElement.vue'; import InputElement from '@/components/InputElement.vue'; +import FormFooter from '@/components/FormFooter.vue'; import type { NtpConfig } from "@/types/NtpConfig"; import { authHeader, handleResponse } from '@/utils/authentication'; import { defineComponent } from 'vue'; @@ -89,6 +90,7 @@ export default defineComponent({ BasePage, BootstrapAlert, CardElement, + FormFooter, InputElement, BIconInfoCircle, }, diff --git a/webapp/src/views/SecurityAdminView.vue b/webapp/src/views/SecurityAdminView.vue index c6746dd4..6a6ae3a2 100644 --- a/webapp/src/views/SecurityAdminView.vue +++ b/webapp/src/views/SecurityAdminView.vue @@ -23,7 +23,7 @@ type="checkbox" wide/> - + @@ -32,6 +32,7 @@ import BasePage from '@/components/BasePage.vue'; import BootstrapAlert from "@/components/BootstrapAlert.vue"; import CardElement from '@/components/CardElement.vue'; +import FormFooter from '@/components/FormFooter.vue'; import InputElement from '@/components/InputElement.vue'; import type { SecurityConfig } from '@/types/SecurityConfig'; import { authHeader, handleResponse } from '@/utils/authentication'; @@ -42,6 +43,7 @@ export default defineComponent({ BasePage, BootstrapAlert, CardElement, + FormFooter, InputElement, }, data() { diff --git a/webapp/tsconfig.config.json b/webapp/tsconfig.config.json index 1fccd77c..53248ff7 100644 --- a/webapp/tsconfig.config.json +++ b/webapp/tsconfig.config.json @@ -9,6 +9,7 @@ "cypress.config.*" ], "compilerOptions": { + "noEmit": false, "composite": true, "types": [ "node" diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 0a6eabe7..b154652d 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -17,10 +17,10 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8" integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA== -"@babel/parser@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" - integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== +"@babel/parser@^7.23.5": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" + integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== "@esbuild/android-arm64@0.19.5": version "0.19.5" @@ -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.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.56.0": + version "8.56.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" + integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== "@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.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.0" - "@intlify/shared" "9.7.0" + "@intlify/message-compiler" "9.8.0" + "@intlify/shared" "9.8.0" -"@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.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.0" + "@intlify/shared" "9.8.0" source-map-js "^1.0.2" "@intlify/message-compiler@^9.4.0": @@ -240,15 +240,15 @@ 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.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" - resolved "https://registry.yarnpkg.com/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-1.5.0.tgz#fe2e67d50beefc4b67702a7bcec23062123cb52d" - integrity sha512-jW0MCCdwxybxcwjEfCunAcKjVoxyO3i+cnLL6v+MNGRLUHqrpELF6zQAJUhgAK2afhY7mCliy8RxTFWKdXm26w== +"@intlify/unplugin-vue-i18n@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-1.6.0.tgz#49ab21125c79257e455a6e562a2511c8681b870c" + integrity sha512-IGeFNWxdEvB12E/3Y/+nmIsGeTg5okPsK1XEtUUD/DdkHbVqUbJucMpHKeHF8Px55Qca551pQCs/g+VjNUt6KA== dependencies: "@intlify/bundle-utils" "^7.4.0" "@intlify/shared" "^9.4.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.1": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.6.1.tgz#9ab8f811930d7af3e3d549183a50884f9eb83f36" + integrity sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw== "@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.10.5": + version "20.10.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.5.tgz#47ad460b514096b7ed63a1dae26fad0914ed3ab2" + integrity sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw== 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" @@ -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.2": + version "4.5.2" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.5.2.tgz#1212d81bc83680e14448fefe55abd9fe1ed49ed1" + integrity sha512-UGR3DlzLi/SaVBPX0cnSyE37vqxU3O6chn8l0HJNzQzDia6/Au2A4xKv+iIJW8w2daf80G7TYHhi1pAUjdZ0bQ== -"@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": @@ -584,6 +584,16 @@ estree-walker "^2.0.2" source-map "^0.6.1" +"@vue/compiler-core@3.3.13": + version "3.3.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.3.13.tgz#b3d5f8f84caee5de3f31d95cb568d899fd19c599" + integrity sha512-bwi9HShGu7uaZLOErZgsH2+ojsEdsjerbf2cMXPwmvcgZfVPZ2BVZzCVnwZBxTAYd6Mzbmf6izcUNDkWnBBQ6A== + dependencies: + "@babel/parser" "^7.23.5" + "@vue/shared" "3.3.13" + estree-walker "^2.0.2" + source-map-js "^1.0.2" + "@vue/compiler-core@3.3.2": version "3.3.2" resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.3.2.tgz#39567bd15c7f97add97bfc4d44e814df36eb797b" @@ -594,16 +604,6 @@ estree-walker "^2.0.2" source-map-js "^1.0.2" -"@vue/compiler-core@3.3.8": - version "3.3.8" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.3.8.tgz#301bb60d0245265a88ed5b30e200fbf223acb313" - integrity sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g== - dependencies: - "@babel/parser" "^7.23.0" - "@vue/shared" "3.3.8" - estree-walker "^2.0.2" - source-map-js "^1.0.2" - "@vue/compiler-dom@3.2.47": version "3.2.47" resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz#a0b06caf7ef7056939e563dcaa9cbde30794f305" @@ -612,13 +612,13 @@ "@vue/compiler-core" "3.2.47" "@vue/shared" "3.2.47" -"@vue/compiler-dom@3.3.8": - version "3.3.8" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.3.8.tgz#09d832514b9b8d9415a3816b065d69dbefcc7e9b" - integrity sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ== +"@vue/compiler-dom@3.3.13": + version "3.3.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.3.13.tgz#d029e222e545e7ab00be35aafd3abed167f962bf" + integrity sha512-EYRDpbLadGtNL0Gph+HoKiYqXLqZ0xSSpR5Dvnu/Ep7ggaCbjRDIus1MMxTS2Qm0koXED4xSlvTZaTnI8cYAsw== dependencies: - "@vue/compiler-core" "3.3.8" - "@vue/shared" "3.3.8" + "@vue/compiler-core" "3.3.13" + "@vue/shared" "3.3.13" "@vue/compiler-dom@^3.3.0": version "3.3.2" @@ -628,20 +628,20 @@ "@vue/compiler-core" "3.3.2" "@vue/shared" "3.3.2" -"@vue/compiler-sfc@3.3.8": - version "3.3.8" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.3.8.tgz#40b18e48aa00260950964d1d72157668521be0e1" - integrity sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA== +"@vue/compiler-sfc@3.3.13": + version "3.3.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.3.13.tgz#7b397acefd5c0c3808701d2855be88c4be60155c" + integrity sha512-DQVmHEy/EKIgggvnGRLx21hSqnr1smUS9Aq8tfxiiot8UR0/pXKHN9k78/qQ7etyQTFj5em5nruODON7dBeumw== dependencies: - "@babel/parser" "^7.23.0" - "@vue/compiler-core" "3.3.8" - "@vue/compiler-dom" "3.3.8" - "@vue/compiler-ssr" "3.3.8" - "@vue/reactivity-transform" "3.3.8" - "@vue/shared" "3.3.8" + "@babel/parser" "^7.23.5" + "@vue/compiler-core" "3.3.13" + "@vue/compiler-dom" "3.3.13" + "@vue/compiler-ssr" "3.3.13" + "@vue/reactivity-transform" "3.3.13" + "@vue/shared" "3.3.13" estree-walker "^2.0.2" magic-string "^0.30.5" - postcss "^8.4.31" + postcss "^8.4.32" source-map-js "^1.0.2" "@vue/compiler-sfc@^3.2.47": @@ -668,13 +668,13 @@ "@vue/compiler-dom" "3.2.47" "@vue/shared" "3.2.47" -"@vue/compiler-ssr@3.3.8": - version "3.3.8" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.3.8.tgz#136eed54411e4694815d961048a237191063fbce" - integrity sha512-hXCqQL/15kMVDBuoBYpUnSYT8doDNwsjvm3jTefnXr+ytn294ySnT8NlsFHmTgKNjwpuFy7XVV8yTeLtNl/P6w== +"@vue/compiler-ssr@3.3.13": + version "3.3.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.3.13.tgz#ad8748abff8d738ac9c6a3c47be42020f0fbaa63" + integrity sha512-d/P3bCeUGmkJNS1QUZSAvoCIW4fkOKK3l2deE7zrp0ypJEy+En2AcypIkqvcFQOcw3F0zt2VfMvNsA9JmExTaw== dependencies: - "@vue/compiler-dom" "3.3.8" - "@vue/shared" "3.3.8" + "@vue/compiler-dom" "3.3.13" + "@vue/shared" "3.3.13" "@vue/devtools-api@^6.5.0": version "6.5.0" @@ -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": @@ -715,68 +716,68 @@ estree-walker "^2.0.2" magic-string "^0.25.7" -"@vue/reactivity-transform@3.3.8": - version "3.3.8" - resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.3.8.tgz#6d07649013b0be5c670f0ab6cc7ddd3150ad03f2" - integrity sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw== +"@vue/reactivity-transform@3.3.13": + version "3.3.13" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.3.13.tgz#dc8e9be961865dc666e367e1aaaea0716afa5c90" + integrity sha512-oWnydGH0bBauhXvh5KXUy61xr9gKaMbtsMHk40IK9M4gMuKPJ342tKFarY0eQ6jef8906m35q37wwA8DMZOm5Q== dependencies: - "@babel/parser" "^7.23.0" - "@vue/compiler-core" "3.3.8" - "@vue/shared" "3.3.8" + "@babel/parser" "^7.23.5" + "@vue/compiler-core" "3.3.13" + "@vue/shared" "3.3.13" estree-walker "^2.0.2" magic-string "^0.30.5" -"@vue/reactivity@3.3.8": - version "3.3.8" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.3.8.tgz#cce8a03a3fd3539c3eeda53e277ba365d160dd4d" - integrity sha512-ctLWitmFBu6mtddPyOKpHg8+5ahouoTCRtmAHZAXmolDtuZXfjL2T3OJ6DL6ezBPQB1SmMnpzjiWjCiMYmpIuw== +"@vue/reactivity@3.3.13": + version "3.3.13" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.3.13.tgz#9b1dff3b523a69997b66cba2f86f83839e8285fb" + integrity sha512-fjzCxceMahHhi4AxUBzQqqVhuA21RJ0COaWTbIBl1PruGW1CeY97louZzLi4smpYx+CHfFPPU/CS8NybbGvPKQ== dependencies: - "@vue/shared" "3.3.8" + "@vue/shared" "3.3.13" -"@vue/runtime-core@3.3.8": - version "3.3.8" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.3.8.tgz#fba5a632cbf2b5d29e171489570149cb6975dcdb" - integrity sha512-qurzOlb6q26KWQ/8IShHkMDOuJkQnQcTIp1sdP4I9MbCf9FJeGVRXJFr2mF+6bXh/3Zjr9TDgURXrsCr9bfjUw== +"@vue/runtime-core@3.3.13": + version "3.3.13" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.3.13.tgz#e8414218e8c7db94acfcec6fd12044704adda9cf" + integrity sha512-1TzA5TvGuh2zUwMJgdfvrBABWZ7y8kBwBhm7BXk8rvdx2SsgcGfz2ruv2GzuGZNvL1aKnK8CQMV/jFOrxNQUMA== dependencies: - "@vue/reactivity" "3.3.8" - "@vue/shared" "3.3.8" + "@vue/reactivity" "3.3.13" + "@vue/shared" "3.3.13" -"@vue/runtime-dom@3.3.8": - version "3.3.8" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.3.8.tgz#e2d7aa795cf50914dda9a951887765a594b38af4" - integrity sha512-Noy5yM5UIf9UeFoowBVgghyGGPIDPy1Qlqt0yVsUdAVbqI8eeMSsTqBtauaEoT2UFXUk5S64aWVNJN4MJ2vRdA== +"@vue/runtime-dom@3.3.13": + version "3.3.13" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.3.13.tgz#36b42b479d5a394972f305ca8e95c5f648bf55ef" + integrity sha512-JJkpE8R/hJKXqVTgUoODwS5wqKtOsmJPEqmp90PDVGygtJ4C0PtOkcEYXwhiVEmef6xeXcIlrT3Yo5aQ4qkHhQ== dependencies: - "@vue/runtime-core" "3.3.8" - "@vue/shared" "3.3.8" - csstype "^3.1.2" + "@vue/runtime-core" "3.3.13" + "@vue/shared" "3.3.13" + csstype "^3.1.3" -"@vue/server-renderer@3.3.8": - version "3.3.8" - resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.3.8.tgz#9b1779010e75783edeed8fcfb97d9c95fc3ac5d2" - integrity sha512-zVCUw7RFskvPuNlPn/8xISbrf0zTWsTSdYTsUTN1ERGGZGVnRxM2QZ3x1OR32+vwkkCm0IW6HmJ49IsPm7ilLg== +"@vue/server-renderer@3.3.13": + version "3.3.13" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.3.13.tgz#fccdd0787798173be8929f40f23161c17b60ed36" + integrity sha512-vSnN+nuf6iSqTL3Qgx/9A+BT+0Zf/VJOgF5uMZrKjYPs38GMYyAU1coDyBNHauehXDaP+zl73VhwWv0vBRBHcg== dependencies: - "@vue/compiler-ssr" "3.3.8" - "@vue/shared" "3.3.8" + "@vue/compiler-ssr" "3.3.13" + "@vue/shared" "3.3.13" "@vue/shared@3.2.47": version "3.2.47" resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.47.tgz#e597ef75086c6e896ff5478a6bfc0a7aa4bbd14c" integrity sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ== +"@vue/shared@3.3.13": + version "3.3.13" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.3.13.tgz#4cb73cda958d77ffd389c8640cf7d93a10ac676f" + integrity sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA== + "@vue/shared@3.3.2", "@vue/shared@^3.3.0": version "3.3.2" resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.3.2.tgz#774cd9b4635ce801b70a3fc3713779a5ef5d77c3" integrity sha512-0rFu3h8JbclbnvvKrs7Fe5FNGV9/5X2rPD7KmOzhLSUAiQH5//Hq437Gv0fR5Mev3u/nbtvmLl8XgwCU20/ZfQ== -"@vue/shared@3.3.8": - version "3.3.8" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.3.8.tgz#f044942142e1d3a395f24132e6203a784838542d" - integrity sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw== - -"@vue/tsconfig@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.4.0.tgz#f01e2f6089b5098136fb084a0dd0cdd4533b72b0" - integrity sha512-CPuIReonid9+zOG/CGTT05FXrPYATEqoDGNrEaqS4hwcw5BUNM2FguC0mOwJD4Jr16UpRVl9N0pY3P+srIbqmg== +"@vue/tsconfig@^0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.5.1.tgz#3124ec16cc0c7e04165b88dc091e6b97782fffa9" + integrity sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ== acorn-jsx@^5.3.2: version "5.3.2" @@ -1006,10 +1007,10 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -csstype@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" - integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== +csstype@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== de-indent@^1.0.2: version "1.0.2" @@ -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.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.56.0: + version "8.56.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.56.0.tgz#4957ce8da409dc0809f99ab07a1b94832ab74b15" + integrity sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ== 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/eslintrc" "^2.1.4" + "@eslint/js" "8.56.0" "@humanwhocodes/config-array" "^0.11.13" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -1903,10 +1904,10 @@ nanoid@^3.3.4: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== -nanoid@^3.3.6: - version "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" @@ -2128,12 +2129,12 @@ postcss@^8.1.10: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8.4.31: - version "8.4.31" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" - integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== +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.6" + nanoid "^3.3.7" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -2314,10 +2315,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 +2437,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.26.0: + version "5.26.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.26.0.tgz#ee9f05d929f4189a9c28a0feb889d96d50126fe1" + integrity sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -2482,10 +2483,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.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 +2557,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.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.10: + version "5.0.10" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.10.tgz#1e13ef5c3cf5aa4eed81f5df6d107b3c3f1f6356" + integrity sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw== dependencies: esbuild "^0.19.3" - postcss "^8.4.31" + postcss "^8.4.32" rollup "^4.2.0" optionalDependencies: fsevents "~2.3.3" @@ -2580,13 +2581,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.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.0" - "@intlify/shared" "9.7.0" + "@intlify/core-base" "9.8.0" + "@intlify/shared" "9.8.0" "@vue/devtools-api" "^6.5.0" vue-router@^4.2.5: @@ -2604,25 +2605,25 @@ 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: - version "3.3.8" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.3.8.tgz#532ff071af24f6a69e5ecc53a66858a9ee874ffc" - integrity sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w== +vue@^3.3.13: + version "3.3.13" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.3.13.tgz#f03098fa1b4e7cc88c133bef92260b55e3767002" + integrity sha512-LDnUpQvDgsfc0u/YgtAgTMXJlJQqjkxW1PVcOnJA5cshPleULDjHi7U45pl2VJYazSSvLH8UKcid/kzH8I0a0Q== dependencies: - "@vue/compiler-dom" "3.3.8" - "@vue/compiler-sfc" "3.3.8" - "@vue/runtime-dom" "3.3.8" - "@vue/server-renderer" "3.3.8" - "@vue/shared" "3.3.8" + "@vue/compiler-dom" "3.3.13" + "@vue/compiler-sfc" "3.3.13" + "@vue/runtime-dom" "3.3.13" + "@vue/server-renderer" "3.3.13" + "@vue/shared" "3.3.13" webpack-sources@^3.2.3: version "3.2.3" diff --git a/webapp_dist/favicon.png b/webapp_dist/favicon.png index 3378b661..278aac84 100644 Binary files a/webapp_dist/favicon.png and b/webapp_dist/favicon.png differ diff --git a/webapp_dist/index.html.gz b/webapp_dist/index.html.gz index 7272e15f..be84fc7d 100644 Binary files a/webapp_dist/index.html.gz and b/webapp_dist/index.html.gz differ diff --git a/webapp_dist/js/app.js.gz b/webapp_dist/js/app.js.gz index 81c99055..598a72e4 100644 Binary files a/webapp_dist/js/app.js.gz and b/webapp_dist/js/app.js.gz differ diff --git a/webapp_dist/site.webmanifest b/webapp_dist/site.webmanifest new file mode 100644 index 00000000..3be24609 --- /dev/null +++ b/webapp_dist/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 diff --git a/webapp_dist/zones.json.gz b/webapp_dist/zones.json.gz index f1ea50bf..02f82db6 100644 Binary files a/webapp_dist/zones.json.gz and b/webapp_dist/zones.json.gz differ