diff --git a/include/Configuration.h b/include/Configuration.h index 66049fd08..5031f6228 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -5,7 +5,7 @@ #include #define CONFIG_FILENAME "/config.json" -#define CONFIG_VERSION 0x00011900 // 0.1.24 // make sure to clean all after change +#define CONFIG_VERSION 0x00011a00 // 0.1.26 // make sure to clean all after change #define WIFI_MAX_SSID_STRLEN 32 #define WIFI_MAX_PASSWORD_STRLEN 64 diff --git a/include/JkBmsController.h b/include/JkBmsController.h index 0cb2e46e0..5399951d4 100644 --- a/include/JkBmsController.h +++ b/include/JkBmsController.h @@ -2,6 +2,7 @@ #include #include +#include #include "Battery.h" #include "JkBmsSerialMessage.h" @@ -30,7 +31,7 @@ class Controller : public BatteryProvider { FrameCompleted }; - std::string const& getStatusText(Status status); + frozen::string const& getStatusText(Status status); void announceStatus(Status status); void sendRequest(uint8_t pollInterval); void rxData(uint8_t inbyte); diff --git a/include/JkBmsDataPoints.h b/include/JkBmsDataPoints.h index ccd136b75..b8c1a0b55 100644 --- a/include/JkBmsDataPoints.h +++ b/include/JkBmsDataPoints.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include namespace JkBms { @@ -33,7 +35,7 @@ enum class AlarmBits : uint16_t { #undef ALARM_ENUM }; -static const std::map AlarmBitTexts = { +static const frozen::map AlarmBitTexts = { #define ALARM_TEXT(name, value) { AlarmBits::name, #name }, ALARM_BITS(ALARM_TEXT) #undef ALARM_TEXT @@ -51,7 +53,7 @@ enum class StatusBits : uint16_t { #undef STATUS_ENUM }; -static const std::map StatusBitTexts = { +static const frozen::map StatusBitTexts = { #define STATUS_TEXT(name, value) { StatusBits::name, #name }, STATUS_BITS(STATUS_TEXT) #undef STATUS_TEXT diff --git a/include/PowerLimiter.h b/include/PowerLimiter.h index 40d8aa892..af6ed28f5 100644 --- a/include/PowerLimiter.h +++ b/include/PowerLimiter.h @@ -8,6 +8,7 @@ #include #include #include +#include #define PL_UI_STATE_INACTIVE 0 #define PL_UI_STATE_CHARGING 1 @@ -83,7 +84,7 @@ class PowerLimiterClass { bool _fullSolarPassThroughEnabled = false; bool _verboseLogging = true; - std::string const& getStatusText(Status status); + frozen::string const& getStatusText(Status status); void announceStatus(Status status); bool shutdown(Status status); bool shutdown() { return shutdown(_lastStatus); } diff --git a/include/Utils.h b/include/Utils.h index 6de962b02..4d4bfee37 100644 --- a/include/Utils.h +++ b/include/Utils.h @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include class Utils { @@ -9,4 +10,5 @@ class Utils { static uint64_t generateDtuSerial(); static int getTimezoneOffset(); static void restartDtu(); + static bool checkJsonAlloc(const DynamicJsonDocument& doc, const char* function, const uint16_t line); }; diff --git a/include/VictronSmartShunt.h b/include/VictronSmartShunt.h index c532db6c2..ffb91ee5b 100644 --- a/include/VictronSmartShunt.h +++ b/include/VictronSmartShunt.h @@ -11,6 +11,7 @@ class VictronSmartShunt : public BatteryProvider { std::shared_ptr getStats() const final { return _stats; } private: + uint32_t _lastUpdate = 0; std::shared_ptr _stats = std::make_shared(); }; diff --git a/include/WebApi.h b/include/WebApi.h index b41fd6b92..5c6498f0c 100644 --- a/include/WebApi.h +++ b/include/WebApi.h @@ -6,6 +6,7 @@ #include "WebApi_device.h" #include "WebApi_devinfo.h" #include "WebApi_dtu.h" +#include "WebApi_errors.h" #include "WebApi_eventlog.h" #include "WebApi_firmware.h" #include "WebApi_gridprofile.h" @@ -42,6 +43,8 @@ class WebApiClass { static void sendTooManyRequests(AsyncWebServerRequest* request); + static void writeConfig(JsonVariant& retMsg, const WebApiError code = WebApiError::GenericSuccess, const String& message = "Settings saved!"); + private: void loop(); diff --git a/include/WebApi_Huawei.h b/include/WebApi_Huawei.h index 7484a7fff..021fa015e 100644 --- a/include/WebApi_Huawei.h +++ b/include/WebApi_Huawei.h @@ -8,7 +8,7 @@ class WebApiHuaweiClass { public: void init(AsyncWebServer& server); void loop(); - void getJsonData(JsonObject& root); + void getJsonData(JsonVariant& root); private: void onStatus(AsyncWebServerRequest* request); void onAdminGet(AsyncWebServerRequest* request); diff --git a/include/WebApi_errors.h b/include/WebApi_errors.h index 31ddae036..e5703a83a 100644 --- a/include/WebApi_errors.h +++ b/include/WebApi_errors.h @@ -8,6 +8,7 @@ enum WebApiError { GenericDataTooLarge, GenericParseError, GenericValueMissing, + GenericWriteFailed, DtuBase = 2000, DtuSerialZero, diff --git a/include/WebApi_ws_Huawei.h b/include/WebApi_ws_Huawei.h index 8e61b7f7a..3d1564e6e 100644 --- a/include/WebApi_ws_Huawei.h +++ b/include/WebApi_ws_Huawei.h @@ -3,7 +3,7 @@ #include "ArduinoJson.h" #include -//#include +#include class WebApiWsHuaweiLiveClass { public: @@ -21,4 +21,6 @@ class WebApiWsHuaweiLiveClass { uint32_t _lastWsCleanup = 0; uint32_t _lastUpdateCheck = 0; + + std::mutex _mutex; }; \ No newline at end of file diff --git a/include/WebApi_ws_battery.h b/include/WebApi_ws_battery.h index 3454d71b6..4a4da4b52 100644 --- a/include/WebApi_ws_battery.h +++ b/include/WebApi_ws_battery.h @@ -3,6 +3,7 @@ #include "ArduinoJson.h" #include +#include class WebApiWsBatteryLiveClass { public: @@ -21,4 +22,6 @@ class WebApiWsBatteryLiveClass { uint32_t _lastWsCleanup = 0; uint32_t _lastUpdateCheck = 0; static constexpr uint16_t _responseSize = 1024 + 512; + + std::mutex _mutex; }; \ No newline at end of file diff --git a/include/WebApi_ws_vedirect_live.h b/include/WebApi_ws_vedirect_live.h index 3797b9b0b..f914c592f 100644 --- a/include/WebApi_ws_vedirect_live.h +++ b/include/WebApi_ws_vedirect_live.h @@ -4,6 +4,7 @@ #include "ArduinoJson.h" #include #include +#include class WebApiWsVedirectLiveClass { public: @@ -23,4 +24,6 @@ class WebApiWsVedirectLiveClass { uint32_t _lastWsCleanup = 0; uint32_t _dataAgeMillis = 0; static constexpr uint16_t _responseSize = 1024 + 128; + + std::mutex _mutex; }; \ No newline at end of file diff --git a/include/defaults.h b/include/defaults.h index cb8c322bf..fbbe5368e 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -13,7 +13,7 @@ #define AUTH_USERNAME "admin" #define SECURITY_ALLOW_READONLY true -#define WIFI_RECONNECT_TIMEOUT 15 +#define WIFI_RECONNECT_TIMEOUT 30 #define WIFI_RECONNECT_REDO_TIMEOUT 600 #define WIFI_SSID "" diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp index 0c0c544e6..2978efa73 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp @@ -295,106 +295,90 @@ uint32_t VeDirectFrameHandler::getLastUpdate() const return _lastUpdate; } -template -String const& VeDirectFrameHandler::getAsString(std::map const& values, T val) -{ - auto pos = values.find(val); - if (pos == values.end()) { - static String dummy; - dummy = val; - return dummy; - } - return pos->second; -} - -template String const& VeDirectFrameHandler::getAsString(std::map const& values, uint8_t val); -template String const& VeDirectFrameHandler::getAsString(std::map const& values, uint16_t val); -template String const& VeDirectFrameHandler::getAsString(std::map const& values, uint32_t val); - /* * getPidAsString * This function returns the product id (PID) as readable text. */ -String VeDirectFrameHandler::veStruct::getPidAsString() const +frozen::string const& VeDirectFrameHandler::veStruct::getPidAsString() const { - static const std::map values = { - { 0x0300, F("BlueSolar MPPT 70|15") }, - { 0xA040, F("BlueSolar MPPT 75|50") }, - { 0xA041, F("BlueSolar MPPT 150|35") }, - { 0xA042, F("BlueSolar MPPT 75|15") }, - { 0xA043, F("BlueSolar MPPT 100|15") }, - { 0xA044, F("BlueSolar MPPT 100|30") }, - { 0xA045, F("BlueSolar MPPT 100|50") }, - { 0xA046, F("BlueSolar MPPT 100|70") }, - { 0xA047, F("BlueSolar MPPT 150|100") }, - { 0xA049, F("BlueSolar MPPT 100|50 rev2") }, - { 0xA04A, F("BlueSolar MPPT 100|30 rev2") }, - { 0xA04B, F("BlueSolar MPPT 150|35 rev2") }, - { 0xA04C, F("BlueSolar MPPT 75|10") }, - { 0xA04D, F("BlueSolar MPPT 150|45") }, - { 0xA04E, F("BlueSolar MPPT 150|60") }, - { 0xA04F, F("BlueSolar MPPT 150|85") }, - { 0xA050, F("SmartSolar MPPT 250|100") }, - { 0xA051, F("SmartSolar MPPT 150|100") }, - { 0xA052, F("SmartSolar MPPT 150|85") }, - { 0xA053, F("SmartSolar MPPT 75|15") }, - { 0xA054, F("SmartSolar MPPT 75|10") }, - { 0xA055, F("SmartSolar MPPT 100|15") }, - { 0xA056, F("SmartSolar MPPT 100|30") }, - { 0xA057, F("SmartSolar MPPT 100|50") }, - { 0xA058, F("SmartSolar MPPT 150|35") }, - { 0xA059, F("SmartSolar MPPT 150|10 rev2") }, - { 0xA05A, F("SmartSolar MPPT 150|85 rev2") }, - { 0xA05B, F("SmartSolar MPPT 250|70") }, - { 0xA05C, F("SmartSolar MPPT 250|85") }, - { 0xA05D, F("SmartSolar MPPT 250|60") }, - { 0xA05E, F("SmartSolar MPPT 250|45") }, - { 0xA05F, F("SmartSolar MPPT 100|20") }, - { 0xA060, F("SmartSolar MPPT 100|20 48V") }, - { 0xA061, F("SmartSolar MPPT 150|45") }, - { 0xA062, F("SmartSolar MPPT 150|60") }, - { 0xA063, F("SmartSolar MPPT 150|70") }, - { 0xA064, F("SmartSolar MPPT 250|85 rev2") }, - { 0xA065, F("SmartSolar MPPT 250|100 rev2") }, - { 0xA066, F("BlueSolar MPPT 100|20") }, - { 0xA067, F("BlueSolar MPPT 100|20 48V") }, - { 0xA068, F("SmartSolar MPPT 250|60 rev2") }, - { 0xA069, F("SmartSolar MPPT 250|70 rev2") }, - { 0xA06A, F("SmartSolar MPPT 150|45 rev2") }, - { 0xA06B, F("SmartSolar MPPT 150|60 rev2") }, - { 0xA06C, F("SmartSolar MPPT 150|70 rev2") }, - { 0xA06D, F("SmartSolar MPPT 150|85 rev3") }, - { 0xA06E, F("SmartSolar MPPT 150|100 rev3") }, - { 0xA06F, F("BlueSolar MPPT 150|45 rev2") }, - { 0xA070, F("BlueSolar MPPT 150|60 rev2") }, - { 0xA071, F("BlueSolar MPPT 150|70 rev2") }, - { 0xA102, F("SmartSolar MPPT VE.Can 150|70") }, - { 0xA103, F("SmartSolar MPPT VE.Can 150|45") }, - { 0xA104, F("SmartSolar MPPT VE.Can 150|60") }, - { 0xA105, F("SmartSolar MPPT VE.Can 150|85") }, - { 0xA106, F("SmartSolar MPPT VE.Can 150|100") }, - { 0xA107, F("SmartSolar MPPT VE.Can 250|45") }, - { 0xA108, F("SmartSolar MPPT VE.Can 250|60") }, - { 0xA109, F("SmartSolar MPPT VE.Can 250|80") }, - { 0xA10A, F("SmartSolar MPPT VE.Can 250|85") }, - { 0xA10B, F("SmartSolar MPPT VE.Can 250|100") }, - { 0xA10C, F("SmartSolar MPPT VE.Can 150|70 rev2") }, - { 0xA10D, F("SmartSolar MPPT VE.Can 150|85 rev2") }, - { 0xA10E, F("SmartSolar MPPT VE.Can 150|100 rev2") }, - { 0xA10F, F("BlueSolar MPPT VE.Can 150|100") }, - { 0xA110, F("SmartSolar MPPT RS 450|100") }, - { 0xA112, F("BlueSolar MPPT VE.Can 250|70") }, - { 0xA113, F("BlueSolar MPPT VE.Can 250|100") }, - { 0xA114, F("SmartSolar MPPT VE.Can 250|70 rev2") }, - { 0xA115, F("SmartSolar MPPT VE.Can 250|100 rev2") }, - { 0xA116, F("SmartSolar MPPT VE.Can 250|85 rev2") }, - { 0xA381, F("BMV-712 Smart") }, - { 0xA382, F("BMV-710H Smart") }, - { 0xA383, F("BMV-712 Smart Rev2") }, - { 0xA389, F("SmartShunt 500A/50mV") }, - { 0xA38A, F("SmartShunt 1000A/50mV") }, - { 0xA38B, F("SmartShunt 2000A/50mV") }, - { 0xA3F0, F("SmartShunt 2000A/50mV" ) } + static constexpr frozen::map values = { + { 0x0300, "BlueSolar MPPT 70|15" }, + { 0xA040, "BlueSolar MPPT 75|50" }, + { 0xA041, "BlueSolar MPPT 150|35" }, + { 0xA042, "BlueSolar MPPT 75|15" }, + { 0xA043, "BlueSolar MPPT 100|15" }, + { 0xA044, "BlueSolar MPPT 100|30" }, + { 0xA045, "BlueSolar MPPT 100|50" }, + { 0xA046, "BlueSolar MPPT 100|70" }, + { 0xA047, "BlueSolar MPPT 150|100" }, + { 0xA049, "BlueSolar MPPT 100|50 rev2" }, + { 0xA04A, "BlueSolar MPPT 100|30 rev2" }, + { 0xA04B, "BlueSolar MPPT 150|35 rev2" }, + { 0xA04C, "BlueSolar MPPT 75|10" }, + { 0xA04D, "BlueSolar MPPT 150|45" }, + { 0xA04E, "BlueSolar MPPT 150|60" }, + { 0xA04F, "BlueSolar MPPT 150|85" }, + { 0xA050, "SmartSolar MPPT 250|100" }, + { 0xA051, "SmartSolar MPPT 150|100" }, + { 0xA052, "SmartSolar MPPT 150|85" }, + { 0xA053, "SmartSolar MPPT 75|15" }, + { 0xA054, "SmartSolar MPPT 75|10" }, + { 0xA055, "SmartSolar MPPT 100|15" }, + { 0xA056, "SmartSolar MPPT 100|30" }, + { 0xA057, "SmartSolar MPPT 100|50" }, + { 0xA058, "SmartSolar MPPT 150|35" }, + { 0xA059, "SmartSolar MPPT 150|10 rev2" }, + { 0xA05A, "SmartSolar MPPT 150|85 rev2" }, + { 0xA05B, "SmartSolar MPPT 250|70" }, + { 0xA05C, "SmartSolar MPPT 250|85" }, + { 0xA05D, "SmartSolar MPPT 250|60" }, + { 0xA05E, "SmartSolar MPPT 250|45" }, + { 0xA05F, "SmartSolar MPPT 100|20" }, + { 0xA060, "SmartSolar MPPT 100|20 48V" }, + { 0xA061, "SmartSolar MPPT 150|45" }, + { 0xA062, "SmartSolar MPPT 150|60" }, + { 0xA063, "SmartSolar MPPT 150|70" }, + { 0xA064, "SmartSolar MPPT 250|85 rev2" }, + { 0xA065, "SmartSolar MPPT 250|100 rev2" }, + { 0xA066, "BlueSolar MPPT 100|20" }, + { 0xA067, "BlueSolar MPPT 100|20 48V" }, + { 0xA068, "SmartSolar MPPT 250|60 rev2" }, + { 0xA069, "SmartSolar MPPT 250|70 rev2" }, + { 0xA06A, "SmartSolar MPPT 150|45 rev2" }, + { 0xA06B, "SmartSolar MPPT 150|60 rev2" }, + { 0xA06C, "SmartSolar MPPT 150|70 rev2" }, + { 0xA06D, "SmartSolar MPPT 150|85 rev3" }, + { 0xA06E, "SmartSolar MPPT 150|100 rev3" }, + { 0xA06F, "BlueSolar MPPT 150|45 rev2" }, + { 0xA070, "BlueSolar MPPT 150|60 rev2" }, + { 0xA071, "BlueSolar MPPT 150|70 rev2" }, + { 0xA102, "SmartSolar MPPT VE.Can 150|70" }, + { 0xA103, "SmartSolar MPPT VE.Can 150|45" }, + { 0xA104, "SmartSolar MPPT VE.Can 150|60" }, + { 0xA105, "SmartSolar MPPT VE.Can 150|85" }, + { 0xA106, "SmartSolar MPPT VE.Can 150|100" }, + { 0xA107, "SmartSolar MPPT VE.Can 250|45" }, + { 0xA108, "SmartSolar MPPT VE.Can 250|60" }, + { 0xA109, "SmartSolar MPPT VE.Can 250|80" }, + { 0xA10A, "SmartSolar MPPT VE.Can 250|85" }, + { 0xA10B, "SmartSolar MPPT VE.Can 250|100" }, + { 0xA10C, "SmartSolar MPPT VE.Can 150|70 rev2" }, + { 0xA10D, "SmartSolar MPPT VE.Can 150|85 rev2" }, + { 0xA10E, "SmartSolar MPPT VE.Can 150|100 rev2" }, + { 0xA10F, "BlueSolar MPPT VE.Can 150|100" }, + { 0xA110, "SmartSolar MPPT RS 450|100" }, + { 0xA112, "BlueSolar MPPT VE.Can 250|70" }, + { 0xA113, "BlueSolar MPPT VE.Can 250|100" }, + { 0xA114, "SmartSolar MPPT VE.Can 250|70 rev2" }, + { 0xA115, "SmartSolar MPPT VE.Can 250|100 rev2" }, + { 0xA116, "SmartSolar MPPT VE.Can 250|85 rev2" }, + { 0xA381, "BMV-712 Smart" }, + { 0xA382, "BMV-710H Smart" }, + { 0xA383, "BMV-712 Smart Rev2" }, + { 0xA389, "SmartShunt 500A/50mV" }, + { 0xA38A, "SmartShunt 1000A/50mV" }, + { 0xA38B, "SmartShunt 2000A/50mV" }, + { 0xA3F0, "SmartShunt 2000A/50mV" } }; return getAsString(values, PID); diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h index bc6678b53..1acf56ad6 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h @@ -13,7 +13,8 @@ #include #include -#include +#include +#include #include #define VE_MAX_VALUE_LEN 33 // VE.Direct Protocol: max value size is 33 including /0 @@ -39,14 +40,22 @@ class VeDirectFrameHandler { double I = 0; // battery current in A double E = 0; // efficiency in percent (calculated, moving average) - String getPidAsString() const; // product id as string + frozen::string const& getPidAsString() const; // product ID as string } veStruct; bool textRxEvent(std::string const& who, char* name, char* value, veStruct& frame); bool isDataValid(veStruct const& frame) const; // return true if data valid and not outdated - template - static String const& getAsString(std::map const& values, T val); + template + static frozen::string const& getAsString(frozen::map const& values, T val) + { + auto pos = values.find(val); + if (pos == values.end()) { + static constexpr frozen::string dummy("???"); + return dummy; + } + return pos->second; + } private: void setLastUpdate(); // set timestampt after successful frame read diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp index 5635cd45f..5b8d6afd7 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp @@ -1,5 +1,4 @@ #include -#include #include "VeDirectMpptController.h" void VeDirectMpptController::init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging) @@ -90,18 +89,18 @@ void VeDirectMpptController::frameValidEvent() { * getCsAsString * This function returns the state of operations (CS) as readable text. */ -String VeDirectMpptController::veMpptStruct::getCsAsString() const +frozen::string const& VeDirectMpptController::veMpptStruct::getCsAsString() const { - static const std::map values = { - { 0, F("OFF") }, - { 2, F("Fault") }, - { 3, F("Bulk") }, - { 4, F("Absorbtion") }, - { 5, F("Float") }, - { 7, F("Equalize (manual)") }, - { 245, F("Starting-up") }, - { 247, F("Auto equalize / Recondition") }, - { 252, F("External Control") } + static constexpr frozen::map values = { + { 0, "OFF" }, + { 2, "Fault" }, + { 3, "Bulk" }, + { 4, "Absorbtion" }, + { 5, "Float" }, + { 7, "Equalize (manual)" }, + { 245, "Starting-up" }, + { 247, "Auto equalize / Recondition" }, + { 252, "External Control" } }; return getAsString(values, CS); @@ -111,12 +110,12 @@ String VeDirectMpptController::veMpptStruct::getCsAsString() const * getMpptAsString * This function returns the state of MPPT (MPPT) as readable text. */ -String VeDirectMpptController::veMpptStruct::getMpptAsString() const +frozen::string const& VeDirectMpptController::veMpptStruct::getMpptAsString() const { - static const std::map values = { - { 0, F("OFF") }, - { 1, F("Voltage or current limited") }, - { 2, F("MPP Tracker active") } + static constexpr frozen::map values = { + { 0, "OFF" }, + { 1, "Voltage or current limited" }, + { 2, "MPP Tracker active" } }; return getAsString(values, MPPT); @@ -126,29 +125,29 @@ String VeDirectMpptController::veMpptStruct::getMpptAsString() const * getErrAsString * This function returns error state (ERR) as readable text. */ -String VeDirectMpptController::veMpptStruct::getErrAsString() const +frozen::string const& VeDirectMpptController::veMpptStruct::getErrAsString() const { - static const std::map values = { - { 0, F("No error") }, - { 2, F("Battery voltage too high") }, - { 17, F("Charger temperature too high") }, - { 18, F("Charger over current") }, - { 19, F("Charger current reversed") }, - { 20, F("Bulk time limit exceeded") }, - { 21, F("Current sensor issue(sensor bias/sensor broken)") }, - { 26, F("Terminals overheated") }, - { 28, F("Converter issue (dual converter models only)") }, - { 33, F("Input voltage too high (solar panel)") }, - { 34, F("Input current too high (solar panel)") }, - { 38, F("Input shutdown (due to excessive battery voltage)") }, - { 39, F("Input shutdown (due to current flow during off mode)") }, - { 40, F("Input") }, - { 65, F("Lost communication with one of devices") }, - { 67, F("Synchronisedcharging device configuration issue") }, - { 68, F("BMS connection lost") }, - { 116, F("Factory calibration data lost") }, - { 117, F("Invalid/incompatible firmware") }, - { 118, F("User settings invalid") } + static constexpr frozen::map values = { + { 0, "No error" }, + { 2, "Battery voltage too high" }, + { 17, "Charger temperature too high" }, + { 18, "Charger over current" }, + { 19, "Charger current reversed" }, + { 20, "Bulk time limit exceeded" }, + { 21, "Current sensor issue(sensor bias/sensor broken)" }, + { 26, "Terminals overheated" }, + { 28, "Converter issue (dual converter models only)" }, + { 33, "Input voltage too high (solar panel)" }, + { 34, "Input current too high (solar panel)" }, + { 38, "Input shutdown (due to excessive battery voltage)" }, + { 39, "Input shutdown (due to current flow during off mode)" }, + { 40, "Input" }, + { 65, "Lost communication with one of devices" }, + { 67, "Synchronisedcharging device configuration issue" }, + { 68, "BMS connection lost" }, + { 116, "Factory calibration data lost" }, + { 117, "Invalid/incompatible firmware" }, + { 118, "User settings invalid" } }; return getAsString(values, ERR); @@ -158,19 +157,19 @@ String VeDirectMpptController::veMpptStruct::getErrAsString() const * getOrAsString * This function returns the off reason (OR) as readable text. */ -String VeDirectMpptController::veMpptStruct::getOrAsString() const +frozen::string const& VeDirectMpptController::veMpptStruct::getOrAsString() const { - static const std::map values = { - { 0x00000000, F("Not off") }, - { 0x00000001, F("No input power") }, - { 0x00000002, F("Switched off (power switch)") }, - { 0x00000004, F("Switched off (device moderegister)") }, - { 0x00000008, F("Remote input") }, - { 0x00000010, F("Protection active") }, - { 0x00000020, F("Paygo") }, - { 0x00000040, F("BMS") }, - { 0x00000080, F("Engine shutdown detection") }, - { 0x00000100, F("Analysing input voltage") } + static constexpr frozen::map values = { + { 0x00000000, "Not off" }, + { 0x00000001, "No input power" }, + { 0x00000002, "Switched off (power switch)" }, + { 0x00000004, "Switched off (device moderegister)" }, + { 0x00000008, "Remote input" }, + { 0x00000010, "Protection active" }, + { 0x00000020, "Paygo" }, + { 0x00000040, "BMS" }, + { 0x00000080, "Engine shutdown detection" }, + { 0x00000100, "Analysing input voltage" } }; return getAsString(values, OR); diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.h b/lib/VeDirectFrameHandler/VeDirectMpptController.h index 04e0d8ca4..158772373 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.h +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.h @@ -59,10 +59,10 @@ class VeDirectMpptController : public VeDirectFrameHandler { double H22; // yield yesterday kWh int32_t H23; // maximum power yesterday W - String getMpptAsString() const; // state of mppt as string - String getCsAsString() const; // current state as string - String getErrAsString() const; // error state as string - String getOrAsString() const; // off reason as string + frozen::string const& getMpptAsString() const; // state of mppt as string + frozen::string const& getCsAsString() const; // current state as string + frozen::string const& getErrAsString() const; // error state as string + frozen::string const& getOrAsString() const; // off reason as string }; using spData_t = std::shared_ptr; diff --git a/platformio.ini b/platformio.ini index 11377d008..4526ccef3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,6 +24,7 @@ platform = espressif32@6.5.0 build_flags = -DPIOENV=\"$PIOENV\" -D_TASK_STD_FUNCTION=1 + -D_TASK_THREAD_SAFE=1 -Wall -Wextra -Wunused -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference ; Have to remove -Werror because of ; https://github.com/espressif/arduino-esp32/issues/9044 and diff --git a/src/BatteryStats.cpp b/src/BatteryStats.cpp index 9e975a9c6..c2c9d9d02 100644 --- a/src/BatteryStats.cpp +++ b/src/BatteryStats.cpp @@ -277,7 +277,7 @@ void JkBmsBatteryStats::mqttPublish() const for (auto iter = JkBms::AlarmBitTexts.begin(); iter != JkBms::AlarmBitTexts.end(); ++iter) { auto bit = iter->first; String value = (*oAlarms & static_cast(bit))?"1":"0"; - MqttSettings.publish(String("battery/alarms/") + iter->second.c_str(), value); + MqttSettings.publish(String("battery/alarms/") + iter->second.data(), value); } } @@ -286,7 +286,7 @@ void JkBmsBatteryStats::mqttPublish() const for (auto iter = JkBms::StatusBitTexts.begin(); iter != JkBms::StatusBitTexts.end(); ++iter) { auto bit = iter->first; String value = (*oStatus & static_cast(bit))?"1":"0"; - MqttSettings.publish(String("battery/status/") + iter->second.c_str(), value); + MqttSettings.publish(String("battery/status/") + iter->second.data(), value); } } @@ -338,7 +338,7 @@ void VictronSmartShuntStats::updateFrom(VeDirectShuntController::veShuntStruct c _SoC = shuntData.SOC / 10; _voltage = shuntData.V; _current = shuntData.I; - _modelName = shuntData.getPidAsString(); + _modelName = shuntData.getPidAsString().data(); _chargeCycles = shuntData.H4; _timeToGo = shuntData.TTG / 60; _chargedEnergy = shuntData.H18 / 100; diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 9c166ab00..ff593e22a 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -4,9 +4,11 @@ */ #include "Configuration.h" #include "MessageOutput.h" +#include "Utils.h" #include "defaults.h" #include #include +#include CONFIG_T config; @@ -25,6 +27,10 @@ bool ConfigurationClass::write() DynamicJsonDocument doc(JSON_BUFFER_SIZE); + if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) { + return false; + } + JsonObject cfg = doc.createNestedObject("cfg"); cfg["version"] = config.Cfg.Version; cfg["save_count"] = config.Cfg.SaveCount; @@ -225,6 +231,11 @@ bool ConfigurationClass::read() File f = LittleFS.open(CONFIG_FILENAME, "r", false); DynamicJsonDocument doc(JSON_BUFFER_SIZE); + + if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) { + return false; + } + // Deserialize the JSON document const DeserializationError error = deserializeJson(doc, f); if (error) { @@ -460,6 +471,11 @@ void ConfigurationClass::migrate() } DynamicJsonDocument doc(JSON_BUFFER_SIZE); + + if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) { + return; + } + // Deserialize the JSON document const DeserializationError error = deserializeJson(doc, f); if (error) { @@ -489,6 +505,14 @@ void ConfigurationClass::migrate() config.Dtu.Nrf.PaLevel = dtu["pa_level"]; } + if (config.Cfg.Version < 0x00011a00) { + // This migration fixes this issue: https://github.com/espressif/arduino-esp32/issues/8828 + // It occours when migrating from Core 2.0.9 to 2.0.14 + // which was done by updating ESP32 PlatformIO from 6.3.2 to 6.5.0 + nvs_flash_erase(); + nvs_flash_init(); + } + f.close(); config.Cfg.Version = CONFIG_VERSION; diff --git a/src/JkBmsController.cpp b/src/JkBmsController.cpp index 2422d6d28..3f924030f 100644 --- a/src/JkBmsController.cpp +++ b/src/JkBmsController.cpp @@ -5,7 +5,7 @@ #include "MessageOutput.h" #include "JkBmsDataPoints.h" #include "JkBmsController.h" -#include +#include //#define JKBMS_DUMMY_SERIAL @@ -216,7 +216,7 @@ bool Controller::init(bool verboseLogging) pin.battery_rx, pin.battery_rxen, pin.battery_tx, pin.battery_txen); if (pin.battery_rx < 0 || pin.battery_tx < 0) { - MessageOutput.println(F("[JK BMS] Invalid RX/TX pin config")); + MessageOutput.println("[JK BMS] Invalid RX/TX pin config"); return false; } @@ -229,7 +229,7 @@ bool Controller::init(bool verboseLogging) _txEnablePin = pin.battery_txen; if (_rxEnablePin < 0 || _txEnablePin < 0) { - MessageOutput.println(F("[JK BMS] Invalid transceiver pin config")); + MessageOutput.println("[JK BMS] Invalid transceiver pin config"); return false; } @@ -255,11 +255,11 @@ Controller::Interface Controller::getInterface() const return Interface::Invalid; } -std::string const& Controller::getStatusText(Controller::Status status) +frozen::string const& Controller::getStatusText(Controller::Status status) { - static const std::string missing = "programmer error: missing status text"; + static constexpr frozen::string missing = "programmer error: missing status text"; - static const std::map texts = { + static constexpr frozen::map texts = { { Status::Timeout, "timeout wating for response from BMS" }, { Status::WaitingForPollInterval, "waiting for poll interval to elapse" }, { Status::HwSerialNotAvailableForWrite, "UART is not available for writing" }, @@ -279,7 +279,7 @@ void Controller::announceStatus(Controller::Status status) if (_lastStatus == status && millis() < _lastStatusPrinted + 10 * 1000) { return; } MessageOutput.printf("[%11.3f] JK BMS: %s\r\n", - static_cast(millis())/1000, getStatusText(status).c_str()); + static_cast(millis())/1000, getStatusText(status).data()); _lastStatus = status; _lastStatusPrinted = millis(); diff --git a/src/MqttHandlVedirectHass.cpp b/src/MqttHandlVedirectHass.cpp index 22b0566f2..b176e9410 100644 --- a/src/MqttHandlVedirectHass.cpp +++ b/src/MqttHandlVedirectHass.cpp @@ -7,7 +7,8 @@ #include "MqttSettings.h" #include "NetworkSettings.h" #include "MessageOutput.h" -#include "VictronMppt.h" +#include "VictronMppt.h" +#include "Utils.h" MqttHandleVedirectHassClass MqttHandleVedirectHass; @@ -109,29 +110,32 @@ void MqttHandleVedirectHassClass::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; + if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { + return; + } + 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; + 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]; @@ -160,14 +164,17 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const 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; + if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { + return; + } + 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,12 +189,12 @@ void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject& object) { auto spMpptData = VictronMppt.getData(); String serial = spMpptData->SER; - object[F("name")] = "Victron(" + serial + ")"; - object[F("ids")] = serial; - object[F("cu")] = String(F("http://")) + NetworkSettings.localIP().toString(); - object[F("mf")] = F("OpenDTU"); - object[F("mdl")] = spMpptData->getPidAsString(); - object[F("sw")] = AUTO_GIT_HASH; + object["name"] = "Victron(" + serial + ")"; + object["ids"] = serial; + object["cu"] = String("http://") + NetworkSettings.localIP().toString(); + object["mf"] = "OpenDTU"; + object["mdl"] = spMpptData->getPidAsString(); + object["sw"] = AUTO_GIT_HASH; } void MqttHandleVedirectHassClass::publish(const String& subtopic, const String& payload) diff --git a/src/MqttHandleHass.cpp b/src/MqttHandleHass.cpp index 88553e15e..0f5c293c8 100644 --- a/src/MqttHandleHass.cpp +++ b/src/MqttHandleHass.cpp @@ -135,6 +135,10 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr } DynamicJsonDocument root(1024); + if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { + return; + } + root["name"] = name; root["stat_t"] = stateTopic; root["uniq_id"] = serial + "_ch" + chanNum + "_" + fieldName; @@ -179,6 +183,10 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptrPID != _kvFrame.PID) - MqttSettings.publish(topic + "PID", spMpptData->getPidAsString()); + MqttSettings.publish(topic + "PID", spMpptData->getPidAsString().data()); if (_PublishFull || strcmp(spMpptData->SER, _kvFrame.SER) != 0) MqttSettings.publish(topic + "SER", spMpptData->SER ); if (_PublishFull || strcmp(spMpptData->FW, _kvFrame.FW) != 0) @@ -77,13 +77,13 @@ void MqttHandleVedirectClass::loop() if (_PublishFull || spMpptData->LOAD != _kvFrame.LOAD) MqttSettings.publish(topic + "LOAD", spMpptData->LOAD == true ? "ON": "OFF"); if (_PublishFull || spMpptData->CS != _kvFrame.CS) - MqttSettings.publish(topic + "CS", spMpptData->getCsAsString()); + MqttSettings.publish(topic + "CS", spMpptData->getCsAsString().data()); if (_PublishFull || spMpptData->ERR != _kvFrame.ERR) - MqttSettings.publish(topic + "ERR", spMpptData->getErrAsString()); + MqttSettings.publish(topic + "ERR", spMpptData->getErrAsString().data()); if (_PublishFull || spMpptData->OR != _kvFrame.OR) - MqttSettings.publish(topic + "OR", spMpptData->getOrAsString()); + MqttSettings.publish(topic + "OR", spMpptData->getOrAsString().data()); if (_PublishFull || spMpptData->MPPT != _kvFrame.MPPT) - MqttSettings.publish(topic + "MPPT", spMpptData->getMpptAsString()); + MqttSettings.publish(topic + "MPPT", spMpptData->getMpptAsString().data()); if (_PublishFull || spMpptData->HSDS != _kvFrame.HSDS) { value = spMpptData->HSDS; MqttSettings.publish(topic + "HSDS", value); diff --git a/src/NetworkSettings.cpp b/src/NetworkSettings.cpp index 92a3156dc..c8004f169 100644 --- a/src/NetworkSettings.cpp +++ b/src/NetworkSettings.cpp @@ -268,7 +268,8 @@ void NetworkSettingsClass::applyConfig() MessageOutput.print("new credentials... "); WiFi.begin( Configuration.get().WiFi.Ssid, - Configuration.get().WiFi.Password); + Configuration.get().WiFi.Password, + WIFI_ALL_CHANNEL_SCAN); } else { MessageOutput.print("existing credentials... "); WiFi.begin(); diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index 86cb2751b..e7f2ea2d6 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -14,7 +14,7 @@ #include "MessageOutput.h" #include #include -#include +#include PowerLimiterClass PowerLimiter; @@ -26,11 +26,11 @@ void PowerLimiterClass::init(Scheduler& scheduler) _loopTask.enable(); } -std::string const& PowerLimiterClass::getStatusText(PowerLimiterClass::Status status) +frozen::string const& PowerLimiterClass::getStatusText(PowerLimiterClass::Status status) { - static const std::string missing = "programmer error: missing status text"; + static const frozen::string missing = "programmer error: missing status text"; - static const std::map texts = { + static const frozen::map texts = { { Status::Initializing, "initializing (should not see me)" }, { Status::DisabledByConfig, "disabled by configuration" }, { Status::DisabledByMqtt, "disabled by MQTT" }, @@ -70,7 +70,7 @@ void PowerLimiterClass::announceStatus(PowerLimiterClass::Status status) if (status == Status::DisabledByConfig && _lastStatus == status) { return; } MessageOutput.printf("[%11.3f] DPL: %s\r\n", - static_cast(millis())/1000, getStatusText(status).c_str()); + static_cast(millis())/1000, getStatusText(status).data()); _lastStatus = status; _lastStatusPrinted = millis(); @@ -586,7 +586,7 @@ float PowerLimiterClass::getLoadCorrectedVoltage() { if (!_inverter) { // there should be no need to call this method if no target inverter is known - MessageOutput.println(F("DPL getLoadCorrectedVoltage: no inverter (programmer error)")); + MessageOutput.println("DPL getLoadCorrectedVoltage: no inverter (programmer error)"); return 0.0; } diff --git a/src/PylontechCanReceiver.cpp b/src/PylontechCanReceiver.cpp index 95fafe270..c1b26176b 100644 --- a/src/PylontechCanReceiver.cpp +++ b/src/PylontechCanReceiver.cpp @@ -12,14 +12,14 @@ bool PylontechCanReceiver::init(bool verboseLogging) { _verboseLogging = verboseLogging; - MessageOutput.println(F("[Pylontech] Initialize interface...")); + MessageOutput.println("[Pylontech] Initialize interface..."); const PinMapping_t& pin = PinMapping.get(); MessageOutput.printf("[Pylontech] Interface rx = %d, tx = %d\r\n", pin.battery_rx, pin.battery_tx); if (pin.battery_rx < 0 || pin.battery_tx < 0) { - MessageOutput.println(F("[Pylontech] Invalid pin config")); + MessageOutput.println("[Pylontech] Invalid pin config"); return false; } @@ -35,18 +35,18 @@ bool PylontechCanReceiver::init(bool verboseLogging) esp_err_t twaiLastResult = twai_driver_install(&g_config, &t_config, &f_config); switch (twaiLastResult) { case ESP_OK: - MessageOutput.println(F("[Pylontech] Twai driver installed")); + MessageOutput.println("[Pylontech] Twai driver installed"); break; case ESP_ERR_INVALID_ARG: - MessageOutput.println(F("[Pylontech] Twai driver install - invalid arg")); + MessageOutput.println("[Pylontech] Twai driver install - invalid arg"); return false; break; case ESP_ERR_NO_MEM: - MessageOutput.println(F("[Pylontech] Twai driver install - no memory")); + MessageOutput.println("[Pylontech] Twai driver install - no memory"); return false; break; case ESP_ERR_INVALID_STATE: - MessageOutput.println(F("[Pylontech] Twai driver install - invalid state")); + MessageOutput.println("[Pylontech] Twai driver install - invalid state"); return false; break; } @@ -55,10 +55,10 @@ bool PylontechCanReceiver::init(bool verboseLogging) twaiLastResult = twai_start(); switch (twaiLastResult) { case ESP_OK: - MessageOutput.println(F("[Pylontech] Twai driver started")); + MessageOutput.println("[Pylontech] Twai driver started"); break; case ESP_ERR_INVALID_STATE: - MessageOutput.println(F("[Pylontech] Twai driver start - invalid state")); + MessageOutput.println("[Pylontech] Twai driver start - invalid state"); return false; break; } @@ -72,10 +72,10 @@ void PylontechCanReceiver::deinit() esp_err_t twaiLastResult = twai_stop(); switch (twaiLastResult) { case ESP_OK: - MessageOutput.println(F("[Pylontech] Twai driver stopped")); + MessageOutput.println("[Pylontech] Twai driver stopped"); break; case ESP_ERR_INVALID_STATE: - MessageOutput.println(F("[Pylontech] Twai driver stop - invalid state")); + MessageOutput.println("[Pylontech] Twai driver stop - invalid state"); break; } @@ -83,10 +83,10 @@ void PylontechCanReceiver::deinit() twaiLastResult = twai_driver_uninstall(); switch (twaiLastResult) { case ESP_OK: - MessageOutput.println(F("[Pylontech] Twai driver uninstalled")); + MessageOutput.println("[Pylontech] Twai driver uninstalled"); break; case ESP_ERR_INVALID_STATE: - MessageOutput.println(F("[Pylontech] Twai driver uninstall - invalid state")); + MessageOutput.println("[Pylontech] Twai driver uninstall - invalid state"); break; } } @@ -103,10 +103,10 @@ void PylontechCanReceiver::loop() if (twaiLastResult != ESP_OK) { switch (twaiLastResult) { case ESP_ERR_INVALID_ARG: - MessageOutput.println(F("[Pylontech] Twai driver get status - invalid arg")); + MessageOutput.println("[Pylontech] Twai driver get status - invalid arg"); break; case ESP_ERR_INVALID_STATE: - MessageOutput.println(F("[Pylontech] Twai driver get status - invalid state")); + MessageOutput.println("[Pylontech] Twai driver get status - invalid state"); break; } return; @@ -118,7 +118,7 @@ void PylontechCanReceiver::loop() // Wait for message to be received, function is blocking twai_message_t rx_message; if (twai_receive(&rx_message, pdMS_TO_TICKS(100)) != ESP_OK) { - MessageOutput.println(F("[Pylontech] Failed to receive message")); + MessageOutput.println("[Pylontech] Failed to receive message"); return; } diff --git a/src/Utils.cpp b/src/Utils.cpp index 893509612..386e0ed1f 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -5,6 +5,7 @@ #include "Utils.h" #include "Display_Graphic.h" #include "Led_Single.h" +#include "MessageOutput.h" #include uint32_t Utils::getChipId() @@ -65,3 +66,13 @@ void Utils::restartDtu() yield(); ESP.restart(); } + +bool Utils::checkJsonAlloc(const DynamicJsonDocument& doc, const char* function, const uint16_t line) +{ + if (doc.capacity() == 0) { + MessageOutput.printf("Alloc failed: %s, %d\r\n", function, line); + return false; + } + + return true; +} diff --git a/src/VictronMppt.cpp b/src/VictronMppt.cpp index 0e5fe082c..fd1073a78 100644 --- a/src/VictronMppt.cpp +++ b/src/VictronMppt.cpp @@ -32,7 +32,7 @@ void VictronMpptClass::updateSettings() MessageOutput.printf("[VictronMppt] rx = %d, tx = %d\r\n", rx, tx); if (rx < 0) { - MessageOutput.println(F("[VictronMppt] invalid pin config")); + MessageOutput.println("[VictronMppt] invalid pin config"); return; } diff --git a/src/VictronSmartShunt.cpp b/src/VictronSmartShunt.cpp index 30e75545d..7b6da145a 100644 --- a/src/VictronSmartShunt.cpp +++ b/src/VictronSmartShunt.cpp @@ -7,14 +7,14 @@ bool VictronSmartShunt::init(bool verboseLogging) { - MessageOutput.println(F("[VictronSmartShunt] Initialize interface...")); + MessageOutput.println("[VictronSmartShunt] Initialize interface..."); const PinMapping_t& pin = PinMapping.get(); MessageOutput.printf("[VictronSmartShunt] Interface rx = %d, tx = %d\r\n", pin.battery_rx, pin.battery_tx); if (pin.battery_rx < 0) { - MessageOutput.println(F("[VictronSmartShunt] Invalid pin config")); + MessageOutput.println("[VictronSmartShunt] Invalid pin config"); return false; } @@ -28,5 +28,9 @@ bool VictronSmartShunt::init(bool verboseLogging) void VictronSmartShunt::loop() { VeDirectShunt.loop(); + + if (VeDirectShunt.getLastUpdate() <= _lastUpdate) { return; } + _stats->updateFrom(VeDirectShunt.veFrame); + _lastUpdate = VeDirectShunt.getLastUpdate(); } diff --git a/src/WebApi.cpp b/src/WebApi.cpp index df4c0aa01..6485309fa 100644 --- a/src/WebApi.cpp +++ b/src/WebApi.cpp @@ -117,4 +117,16 @@ void WebApiClass::sendTooManyRequests(AsyncWebServerRequest* request) request->send(response); } +void WebApiClass::writeConfig(JsonVariant& retMsg, const WebApiError code, const String& message) +{ + if (!Configuration.write()) { + retMsg["message"] = "Write failed!"; + retMsg["code"] = WebApiError::GenericWriteFailed; + } else { + retMsg["type"] = "success"; + retMsg["message"] = message; + retMsg["code"] = code; + } +} + WebApiClass WebApi; \ No newline at end of file diff --git a/src/WebApi_Huawei.cpp b/src/WebApi_Huawei.cpp index 586e2ab99..c9cd1df46 100644 --- a/src/WebApi_Huawei.cpp +++ b/src/WebApi_Huawei.cpp @@ -28,7 +28,7 @@ void WebApiHuaweiClass::loop() { } -void WebApiHuaweiClass::getJsonData(JsonObject& root) { +void WebApiHuaweiClass::getJsonData(JsonVariant& root) { const RectifierParameters_t * rp = HuaweiCan.get(); root["data_age"] = (millis() - HuaweiCan.getLastUpdate()) / 1000; @@ -62,7 +62,7 @@ void WebApiHuaweiClass::onStatus(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); getJsonData(root); response->setLength(); @@ -76,7 +76,7 @@ void WebApiHuaweiClass::onPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { @@ -186,7 +186,7 @@ void WebApiHuaweiClass::onAdminGet(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); const CONFIG_T& config = Configuration.get(); root["enabled"] = config.Huawei.Enabled; @@ -208,7 +208,7 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { @@ -261,11 +261,8 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request) 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["type"] = "success"; - retMsg["message"] = "Settings saved!"; - retMsg["code"] = WebApiError::GenericSuccess; + + WebApi.writeConfig(retMsg); response->setLength(); request->send(response); diff --git a/src/WebApi_battery.cpp b/src/WebApi_battery.cpp index 1718dd6b6..f7b5ade3d 100644 --- a/src/WebApi_battery.cpp +++ b/src/WebApi_battery.cpp @@ -35,15 +35,15 @@ void WebApiBatteryClass::onStatus(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& 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("mqtt_topic")] = config.Battery.MqttTopic; + root["enabled"] = config.Battery.Enabled; + root["verbose_logging"] = config.Battery.VerboseLogging; + root["provider"] = config.Battery.Provider; + root["jkbms_interface"] = config.Battery.JkBmsInterface; + root["jkbms_polling_interval"] = config.Battery.JkBmsPollingInterval; + root["mqtt_topic"] = config.Battery.MqttTopic; response->setLength(); request->send(response); @@ -61,12 +61,12 @@ void WebApiBatteryClass::onAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); - retMsg[F("type")] = F("warning"); + auto& retMsg = response->getRoot(); + 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; @@ -75,8 +75,8 @@ void WebApiBatteryClass::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; @@ -86,33 +86,30 @@ void WebApiBatteryClass::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; } - if (!root.containsKey(F("enabled")) || !root.containsKey(F("provider"))) { - retMsg[F("message")] = F("Values are missing!"); - retMsg[F("code")] = WebApiError::GenericValueMissing; + if (!root.containsKey("enabled") || !root.containsKey("provider")) { + retMsg["message"] = "Values are missing!"; + retMsg["code"] = WebApiError::GenericValueMissing; response->setLength(); request->send(response); return; } 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(); - strlcpy(config.Battery.MqttTopic, root[F("mqtt_topic")].as().c_str(), sizeof(config.Battery.MqttTopic)); - Configuration.write(); - - retMsg[F("type")] = F("success"); - retMsg[F("message")] = F("Settings saved!"); - retMsg[F("code")] = WebApiError::GenericSuccess; + config.Battery.Enabled = root["enabled"].as(); + config.Battery.VerboseLogging = root["verbose_logging"].as(); + config.Battery.Provider = root["provider"].as(); + config.Battery.JkBmsInterface = root["jkbms_interface"].as(); + config.Battery.JkBmsPollingInterval = root["jkbms_polling_interval"].as(); + strlcpy(config.Battery.MqttTopic, root["mqtt_topic"].as().c_str(), sizeof(config.Battery.MqttTopic)); + + WebApi.writeConfig(retMsg); response->setLength(); request->send(response); diff --git a/src/WebApi_config.cpp b/src/WebApi_config.cpp index e466c79c9..73df1e3d0 100644 --- a/src/WebApi_config.cpp +++ b/src/WebApi_config.cpp @@ -59,7 +59,7 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { @@ -125,8 +125,8 @@ void WebApiConfigClass::onConfigListGet(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); - JsonArray data = root.createNestedArray("configs"); + auto& root = response->getRoot(); + auto data = root.createNestedArray("configs"); File rootfs = LittleFS.open("/"); File file = rootfs.openNextFile(); diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index 8379b156f..daf42be19 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -33,14 +33,14 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); const CONFIG_T& config = Configuration.get(); const PinMapping_t& pin = PinMapping.get(); - JsonObject curPin = root.createNestedObject("curPin"); + auto curPin = root.createNestedObject("curPin"); curPin["name"] = config.Dev_PinMapping; - JsonObject nrfPinObj = curPin.createNestedObject("nrf24"); + auto nrfPinObj = curPin.createNestedObject("nrf24"); nrfPinObj["clk"] = pin.nrf24_clk; nrfPinObj["cs"] = pin.nrf24_cs; nrfPinObj["en"] = pin.nrf24_en; @@ -48,7 +48,7 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) nrfPinObj["miso"] = pin.nrf24_miso; nrfPinObj["mosi"] = pin.nrf24_mosi; - JsonObject cmtPinObj = curPin.createNestedObject("cmt"); + auto cmtPinObj = curPin.createNestedObject("cmt"); cmtPinObj["clk"] = pin.cmt_clk; cmtPinObj["cs"] = pin.cmt_cs; cmtPinObj["fcs"] = pin.cmt_fcs; @@ -56,7 +56,7 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) cmtPinObj["gpio2"] = pin.cmt_gpio2; cmtPinObj["gpio3"] = pin.cmt_gpio3; - JsonObject ethPinObj = curPin.createNestedObject("eth"); + auto ethPinObj = curPin.createNestedObject("eth"); ethPinObj["enabled"] = pin.eth_enabled; ethPinObj["phy_addr"] = pin.eth_phy_addr; ethPinObj["power"] = pin.eth_power; @@ -65,19 +65,19 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) ethPinObj["type"] = pin.eth_type; ethPinObj["clk_mode"] = pin.eth_clk_mode; - JsonObject displayPinObj = curPin.createNestedObject("display"); + auto displayPinObj = curPin.createNestedObject("display"); displayPinObj["type"] = pin.display_type; displayPinObj["data"] = pin.display_data; displayPinObj["clk"] = pin.display_clk; displayPinObj["cs"] = pin.display_cs; displayPinObj["reset"] = pin.display_reset; - JsonObject ledPinObj = curPin.createNestedObject("led"); + auto ledPinObj = curPin.createNestedObject("led"); for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { ledPinObj["led" + String(i)] = pin.led[i]; } - JsonObject display = root.createNestedObject("display"); + auto display = root.createNestedObject("display"); display["rotation"] = config.Display.Rotation; display["power_safe"] = config.Display.PowerSafe; display["screensaver"] = config.Display.ScreenSaver; @@ -85,9 +85,9 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) display["language"] = config.Display.Language; display["diagramduration"] = config.Display.DiagramDuration; - JsonArray leds = root.createNestedArray("led"); + auto leds = root.createNestedArray("led"); for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { - JsonObject led = leds.createNestedObject(); + auto led = leds.createNestedObject(); led["brightness"] = config.Led_Single[i].Brightness; } @@ -120,7 +120,7 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { @@ -193,11 +193,7 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) Display.setLanguage(config.Display.Language); Display.Diagram().updatePeriod(); - Configuration.write(); - - retMsg["type"] = "success"; - retMsg["message"] = "Settings saved!"; - retMsg["code"] = WebApiError::GenericSuccess; + WebApi.writeConfig(retMsg); response->setLength(); request->send(response); diff --git a/src/WebApi_devinfo.cpp b/src/WebApi_devinfo.cpp index 04b8a581d..b5f3e3707 100644 --- a/src/WebApi_devinfo.cpp +++ b/src/WebApi_devinfo.cpp @@ -28,7 +28,7 @@ void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); uint64_t serial = 0; if (request->hasParam("inv")) { diff --git a/src/WebApi_dtu.cpp b/src/WebApi_dtu.cpp index 45bfc2dec..327d17e34 100644 --- a/src/WebApi_dtu.cpp +++ b/src/WebApi_dtu.cpp @@ -30,7 +30,7 @@ void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); const CONFIG_T& config = Configuration.get(); // DTU Serial is read as HEX @@ -58,7 +58,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { @@ -157,11 +157,8 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) 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"; - retMsg["message"] = "Settings saved!"; - retMsg["code"] = WebApiError::GenericSuccess; + WebApi.writeConfig(retMsg); response->setLength(); request->send(response); diff --git a/src/WebApi_eventlog.cpp b/src/WebApi_eventlog.cpp index e0c8b3164..0551978af 100644 --- a/src/WebApi_eventlog.cpp +++ b/src/WebApi_eventlog.cpp @@ -27,7 +27,7 @@ void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(false, 2048); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); uint64_t serial = 0; if (request->hasParam("inv")) { diff --git a/src/WebApi_gridprofile.cpp b/src/WebApi_gridprofile.cpp index ee945bc44..a5793e772 100644 --- a/src/WebApi_gridprofile.cpp +++ b/src/WebApi_gridprofile.cpp @@ -28,7 +28,7 @@ void WebApiGridProfileClass::onGridProfileStatus(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); uint64_t serial = 0; if (request->hasParam("inv")) { @@ -72,7 +72,7 @@ void WebApiGridProfileClass::onGridProfileRawdata(AsyncWebServerRequest* request } AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); uint64_t serial = 0; if (request->hasParam("inv")) { diff --git a/src/WebApi_inverter.cpp b/src/WebApi_inverter.cpp index 88ddd37e6..7538b2c03 100644 --- a/src/WebApi_inverter.cpp +++ b/src/WebApi_inverter.cpp @@ -36,7 +36,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(false, 768 * INV_MAX_COUNT); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); JsonArray data = root.createNestedArray("inverter"); const CONFIG_T& config = Configuration.get(); @@ -94,7 +94,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { @@ -167,11 +167,8 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request) inverter->Serial = strtoll(root["serial"].as().c_str(), NULL, 16); strncpy(inverter->Name, root["name"].as().c_str(), INV_MAX_NAME_STRLEN); - Configuration.write(); - retMsg["type"] = "success"; - retMsg["message"] = "Inverter created!"; - retMsg["code"] = WebApiError::InverterAdded; + WebApi.writeConfig(retMsg, WebApiError::InverterAdded, "Inverter created!"); response->setLength(); request->send(response); @@ -194,7 +191,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { @@ -294,11 +291,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) arrayCount++; } - Configuration.write(); - - retMsg["type"] = "success"; - retMsg["code"] = WebApiError::InverterChanged; - retMsg["message"] = "Inverter changed!"; + WebApi.writeConfig(retMsg, WebApiError::InverterChanged, "Inverter changed!"); response->setLength(); request->send(response); @@ -340,7 +333,7 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { @@ -395,11 +388,8 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request) inverter.Serial = 0; strncpy(inverter.Name, "", sizeof(inverter.Name)); - Configuration.write(); - retMsg["type"] = "success"; - retMsg["message"] = "Inverter deleted!"; - retMsg["code"] = WebApiError::InverterDeleted; + WebApi.writeConfig(retMsg, WebApiError::InverterDeleted, "Inverter deleted!"); response->setLength(); request->send(response); @@ -414,7 +404,7 @@ void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { @@ -466,11 +456,7 @@ void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request) order++; } - Configuration.write(); - - retMsg["type"] = "success"; - retMsg["message"] = "Inverter order saved!"; - retMsg["code"] = WebApiError::InverterOrdered; + WebApi.writeConfig(retMsg, WebApiError::InverterOrdered, "Inverter order saved!"); response->setLength(); request->send(response); diff --git a/src/WebApi_limit.cpp b/src/WebApi_limit.cpp index 1e5b3f212..8bf0c9426 100644 --- a/src/WebApi_limit.cpp +++ b/src/WebApi_limit.cpp @@ -31,7 +31,7 @@ void WebApiLimitClass::onLimitStatus(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); @@ -64,7 +64,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { diff --git a/src/WebApi_maintenance.cpp b/src/WebApi_maintenance.cpp index 8b65c9352..0c62394cf 100644 --- a/src/WebApi_maintenance.cpp +++ b/src/WebApi_maintenance.cpp @@ -29,7 +29,7 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { diff --git a/src/WebApi_mqtt.cpp b/src/WebApi_mqtt.cpp index c92d787d5..8828547c9 100644 --- a/src/WebApi_mqtt.cpp +++ b/src/WebApi_mqtt.cpp @@ -37,7 +37,7 @@ void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); const CONFIG_T& config = Configuration.get(); root["mqtt_enabled"] = config.Mqtt.Enabled; @@ -72,7 +72,7 @@ void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); const CONFIG_T& config = Configuration.get(); root["mqtt_enabled"] = config.Mqtt.Enabled; @@ -111,7 +111,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { @@ -342,11 +342,8 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) 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"; - retMsg["message"] = "Settings saved!"; - retMsg["code"] = WebApiError::GenericSuccess; + WebApi.writeConfig(retMsg); response->setLength(); request->send(response); diff --git a/src/WebApi_network.cpp b/src/WebApi_network.cpp index a6e91f3d3..b95beb34f 100644 --- a/src/WebApi_network.cpp +++ b/src/WebApi_network.cpp @@ -32,7 +32,7 @@ void WebApiNetworkClass::onNetworkStatus(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); root["sta_status"] = ((WiFi.getMode() & WIFI_STA) != 0); root["sta_ssid"] = WiFi.SSID(); @@ -63,7 +63,7 @@ void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); const CONFIG_T& config = Configuration.get(); root["hostname"] = config.WiFi.Hostname; @@ -89,7 +89,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { @@ -238,11 +238,8 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) } config.WiFi.ApTimeout = root["aptimeout"].as(); config.Mdns.Enabled = root["mdnsenabled"].as(); - Configuration.write(); - retMsg["type"] = "success"; - retMsg["message"] = "Settings saved!"; - retMsg["code"] = WebApiError::GenericSuccess; + WebApi.writeConfig(retMsg); response->setLength(); request->send(response); diff --git a/src/WebApi_ntp.cpp b/src/WebApi_ntp.cpp index ed841ba93..2eb4c087d 100644 --- a/src/WebApi_ntp.cpp +++ b/src/WebApi_ntp.cpp @@ -35,7 +35,7 @@ void WebApiNtpClass::onNtpStatus(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); const CONFIG_T& config = Configuration.get(); root["ntp_server"] = config.Ntp.Server; @@ -80,7 +80,7 @@ void WebApiNtpClass::onNtpAdminGet(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); const CONFIG_T& config = Configuration.get(); root["ntp_server"] = config.Ntp.Server; @@ -101,7 +101,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { @@ -179,11 +179,8 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request) config.Ntp.Latitude = root["latitude"].as(); config.Ntp.Longitude = root["longitude"].as(); config.Ntp.SunsetType = root["sunsettype"].as(); - Configuration.write(); - retMsg["type"] = "success"; - retMsg["message"] = "Settings saved!"; - retMsg["code"] = WebApiError::GenericSuccess; + WebApi.writeConfig(retMsg); response->setLength(); request->send(response); @@ -201,7 +198,7 @@ void WebApiNtpClass::onNtpTimeGet(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); struct tm timeinfo; if (!getLocalTime(&timeinfo, 5)) { @@ -228,7 +225,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { diff --git a/src/WebApi_power.cpp b/src/WebApi_power.cpp index 3fa47984c..697c1e87a 100644 --- a/src/WebApi_power.cpp +++ b/src/WebApi_power.cpp @@ -29,7 +29,7 @@ void WebApiPowerClass::onPowerStatus(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); @@ -57,7 +57,7 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { diff --git a/src/WebApi_powerlimiter.cpp b/src/WebApi_powerlimiter.cpp index 361ff5634..c2c6456ba 100644 --- a/src/WebApi_powerlimiter.cpp +++ b/src/WebApi_powerlimiter.cpp @@ -34,30 +34,30 @@ void WebApiPowerLimiterClass::loop() void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request) { AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& 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["enabled"] = config.PowerLimiter.Enabled; + root["verbose_logging"] = config.PowerLimiter.VerboseLogging; + root["solar_passthrough_enabled"] = config.PowerLimiter.SolarPassThroughEnabled; + root["solar_passthrough_losses"] = config.PowerLimiter.SolarPassThroughLosses; + root["battery_drain_strategy"] = config.PowerLimiter.BatteryDrainStategy; + root["is_inverter_behind_powermeter"] = config.PowerLimiter.IsInverterBehindPowerMeter; + root["inverter_id"] = config.PowerLimiter.InverterId; + root["inverter_channel_id"] = config.PowerLimiter.InverterChannelId; + root["target_power_consumption"] = config.PowerLimiter.TargetPowerConsumption; + root["target_power_consumption_hysteresis"] = config.PowerLimiter.TargetPowerConsumptionHysteresis; + root["lower_power_limit"] = config.PowerLimiter.LowerPowerLimit; + root["upper_power_limit"] = config.PowerLimiter.UpperPowerLimit; + root["battery_soc_start_threshold"] = config.PowerLimiter.BatterySocStartThreshold; + root["battery_soc_stop_threshold"] = config.PowerLimiter.BatterySocStopThreshold; + root["voltage_start_threshold"] = static_cast(config.PowerLimiter.VoltageStartThreshold * 100 +0.5) / 100.0; + root["voltage_stop_threshold"] = static_cast(config.PowerLimiter.VoltageStopThreshold * 100 +0.5) / 100.0;; + root["voltage_load_correction_factor"] = config.PowerLimiter.VoltageLoadCorrectionFactor; + root["inverter_restart_hour"] = config.PowerLimiter.RestartHour; + root["full_solar_passthrough_soc"] = config.PowerLimiter.FullSolarPassThroughSoc; + root["full_solar_passthrough_start_voltage"] = static_cast(config.PowerLimiter.FullSolarPassThroughStartVoltage * 100 + 0.5) / 100.0; + root["full_solar_passthrough_stop_voltage"] = static_cast(config.PowerLimiter.FullSolarPassThroughStopVoltage * 100 + 0.5) / 100.0; response->setLength(); request->send(response); @@ -79,11 +79,11 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); - retMsg[F("type")] = F("warning"); + auto& retMsg = response->getRoot(); + retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { - retMsg[F("message")] = F("No values found!"); + retMsg["message"] = "No values found!"; response->setLength(); request->send(response); return; @@ -92,7 +92,7 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request) String json = request->getParam("data", true)->value(); if (json.length() > 1024) { - retMsg[F("message")] = F("Data too large!"); + retMsg["message"] = "Data too large!"; response->setLength(); request->send(response); return; @@ -102,7 +102,7 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request) DeserializationError error = deserializeJson(root, json); if (error) { - retMsg[F("message")] = F("Failed to parse data!"); + retMsg["message"] = "Failed to parse data!"; response->setLength(); request->send(response); return; @@ -115,8 +115,8 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request) && root.containsKey("target_power_consumption") && root.containsKey("target_power_consumption_hysteresis") )) { - 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; @@ -124,40 +124,35 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request) CONFIG_T& config = Configuration.get(); - config.PowerLimiter.Enabled = root[F("enabled")].as(); + config.PowerLimiter.Enabled = root["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.VerboseLogging = root["verbose_logging"].as(); + config.PowerLimiter.SolarPassThroughEnabled = root["solar_passthrough_enabled"].as(); + config.PowerLimiter.SolarPassThroughLosses = root["solar_passthrough_losses"].as(); + config.PowerLimiter.BatteryDrainStategy= root["battery_drain_strategy"].as(); + config.PowerLimiter.IsInverterBehindPowerMeter = root["is_inverter_behind_powermeter"].as(); + config.PowerLimiter.InverterId = root["inverter_id"].as(); + config.PowerLimiter.InverterChannelId = root["inverter_channel_id"].as(); + config.PowerLimiter.TargetPowerConsumption = root["target_power_consumption"].as(); + config.PowerLimiter.TargetPowerConsumptionHysteresis = root["target_power_consumption_hysteresis"].as(); + config.PowerLimiter.LowerPowerLimit = root["lower_power_limit"].as(); + config.PowerLimiter.UpperPowerLimit = root["upper_power_limit"].as(); + config.PowerLimiter.BatterySocStartThreshold = root["battery_soc_start_threshold"].as(); + config.PowerLimiter.BatterySocStopThreshold = root["battery_soc_stop_threshold"].as(); + config.PowerLimiter.VoltageStartThreshold = root["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 = root["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.VoltageLoadCorrectionFactor = root["voltage_load_correction_factor"].as(); + config.PowerLimiter.RestartHour = root["inverter_restart_hour"].as(); + config.PowerLimiter.FullSolarPassThroughSoc = root["full_solar_passthrough_soc"].as(); + config.PowerLimiter.FullSolarPassThroughStartVoltage = static_cast(root["full_solar_passthrough_start_voltage"].as() * 100) / 100.0; + config.PowerLimiter.FullSolarPassThroughStopVoltage = static_cast(root["full_solar_passthrough_stop_voltage"].as() * 100) / 100.0; - - - Configuration.write(); - - PowerLimiter.calcNextInverterRestart(); - - retMsg[F("type")] = F("success"); - retMsg[F("message")] = F("Settings saved!"); + WebApi.writeConfig(retMsg); response->setLength(); request->send(response); + + PowerLimiter.calcNextInverterRestart(); } diff --git a/src/WebApi_powermeter.cpp b/src/WebApi_powermeter.cpp index 8e8a4e652..d91f251b6 100644 --- a/src/WebApi_powermeter.cpp +++ b/src/WebApi_powermeter.cpp @@ -35,35 +35,35 @@ void WebApiPowerMeterClass::loop() void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request) { AsyncJsonResponse* response = new AsyncJsonResponse(false, 2048); - JsonObject root = response->getRoot(); + auto& 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["enabled"] = config.PowerMeter.Enabled; + root["verbose_logging"] = config.PowerMeter.VerboseLogging; + root["source"] = config.PowerMeter.Source; + root["interval"] = config.PowerMeter.Interval; + root["mqtt_topic_powermeter_1"] = config.PowerMeter.MqttTopicPowerMeter1; + root["mqtt_topic_powermeter_2"] = config.PowerMeter.MqttTopicPowerMeter2; + root["mqtt_topic_powermeter_3"] = config.PowerMeter.MqttTopicPowerMeter3; + root["sdmbaudrate"] = config.PowerMeter.SdmBaudrate; + root["sdmaddress"] = config.PowerMeter.SdmAddress; + root["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests; - JsonArray httpPhases = root.createNestedArray(F("http_phases")); + JsonArray httpPhases = root.createNestedArray("http_phases"); for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) { 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["index"] = i + 1; + phaseObject["enabled"] = config.PowerMeter.Http_Phase[i].Enabled; + phaseObject["url"] = String(config.PowerMeter.Http_Phase[i].Url); + phaseObject["auth_type"]= config.PowerMeter.Http_Phase[i].AuthType; + phaseObject["username"] = String(config.PowerMeter.Http_Phase[i].Username); + phaseObject["password"] = String(config.PowerMeter.Http_Phase[i].Password); + phaseObject["header_key"] = String(config.PowerMeter.Http_Phase[i].HeaderKey); + phaseObject["header_value"] = String(config.PowerMeter.Http_Phase[i].HeaderValue); + phaseObject["json_path"] = String(config.PowerMeter.Http_Phase[i].JsonPath); + phaseObject["timeout"] = config.PowerMeter.Http_Phase[i].Timeout; } response->setLength(); @@ -86,11 +86,11 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); - retMsg[F("type")] = F("warning"); + auto& retMsg = response->getRoot(); + retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { - retMsg[F("message")] = F("No values found!"); + retMsg["message"] = "No values found!"; response->setLength(); request->send(response); return; @@ -99,7 +99,7 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request) String json = request->getParam("data", true)->value(); if (json.length() > 4096) { - retMsg[F("message")] = F("Data too large!"); + retMsg["message"] = "Data too large!"; response->setLength(); request->send(response); return; @@ -109,49 +109,49 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request) DeserializationError error = deserializeJson(root, json); if (error) { - retMsg[F("message")] = F("Failed to parse data!"); + retMsg["message"] = "Failed to parse data!"; response->setLength(); request->send(response); return; } if (!(root.containsKey("enabled") && root.containsKey("source"))) { - retMsg[F("message")] = F("Values are missing!"); + retMsg["message"] = "Values are missing!"; response->setLength(); request->send(response); return; } - if (root[F("source")].as() == PowerMeter.SOURCE_HTTP) { - JsonArray http_phases = root[F("http_phases")]; + if (root["source"].as() == PowerMeter.SOURCE_HTTP) { + JsonArray http_phases = root["http_phases"]; for (uint8_t i = 0; i < http_phases.size(); i++) { JsonObject phase = http_phases[i].as(); - if (i > 0 && !phase[F("enabled")].as()) { + if (i > 0 && !phase["enabled"].as()) { continue; } - if (i == 0 || phase[F("http_individual_requests")].as()) { + if (i == 0 || phase["http_individual_requests"].as()) { if (!phase.containsKey("url") - || (!phase[F("url")].as().startsWith("http://") - && !phase[F("url")].as().startsWith("https://"))) { - retMsg[F("message")] = F("URL must either start with http:// or https://!"); + || (!phase["url"].as().startsWith("http://") + && !phase["url"].as().startsWith("https://"))) { + retMsg["message"] = "URL must either start with http:// or https://!"; response->setLength(); request->send(response); return; } - if ((phase[F("auth_type")].as() != Auth::none) - && ( phase[F("username")].as().length() == 0 || phase[F("password")].as().length() == 0)) { - retMsg[F("message")] = F("Username or password must not be empty!"); + if ((phase["auth_type"].as() != Auth::none) + && ( phase["username"].as().length() == 0 || phase["password"].as().length() == 0)) { + retMsg["message"] = "Username or password must not be empty!"; response->setLength(); request->send(response); return; } if (!phase.containsKey("timeout") - || phase[F("timeout")].as() <= 0) { - retMsg[F("message")] = F("Timeout must be greater than 0 ms!"); + || phase["timeout"].as() <= 0) { + retMsg["message"] = "Timeout must be greater than 0 ms!"; response->setLength(); request->send(response); return; @@ -159,8 +159,8 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request) } if (!phase.containsKey("json_path") - || phase[F("json_path")].as().length() == 0) { - retMsg[F("message")] = F("Json path must not be empty!"); + || phase["json_path"].as().length() == 0) { + retMsg["message"] = "Json path must not be empty!"; response->setLength(); request->send(response); return; @@ -169,36 +169,33 @@ 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(); - - JsonArray http_phases = root[F("http_phases")]; + config.PowerMeter.Enabled = root["enabled"].as(); + config.PowerMeter.VerboseLogging = root["verbose_logging"].as(); + config.PowerMeter.Source = root["source"].as(); + config.PowerMeter.Interval = root["interval"].as(); + strlcpy(config.PowerMeter.MqttTopicPowerMeter1, root["mqtt_topic_powermeter_1"].as().c_str(), sizeof(config.PowerMeter.MqttTopicPowerMeter1)); + strlcpy(config.PowerMeter.MqttTopicPowerMeter2, root["mqtt_topic_powermeter_2"].as().c_str(), sizeof(config.PowerMeter.MqttTopicPowerMeter2)); + strlcpy(config.PowerMeter.MqttTopicPowerMeter3, root["mqtt_topic_powermeter_3"].as().c_str(), sizeof(config.PowerMeter.MqttTopicPowerMeter3)); + config.PowerMeter.SdmBaudrate = root["sdmbaudrate"].as(); + config.PowerMeter.SdmAddress = root["sdmaddress"].as(); + config.PowerMeter.HttpIndividualRequests = root["http_individual_requests"].as(); + + JsonArray http_phases = root["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["enabled"].as()); + strlcpy(config.PowerMeter.Http_Phase[i].Url, phase["url"].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Url)); + config.PowerMeter.Http_Phase[i].AuthType = phase["auth_type"].as(); + strlcpy(config.PowerMeter.Http_Phase[i].Username, phase["username"].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Username)); + strlcpy(config.PowerMeter.Http_Phase[i].Password, phase["password"].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Password)); + strlcpy(config.PowerMeter.Http_Phase[i].HeaderKey, phase["header_key"].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].HeaderKey)); + strlcpy(config.PowerMeter.Http_Phase[i].HeaderValue, phase["header_value"].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].HeaderValue)); + config.PowerMeter.Http_Phase[i].Timeout = phase["timeout"].as(); + strlcpy(config.PowerMeter.Http_Phase[i].JsonPath, phase["json_path"].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].JsonPath)); } - Configuration.write(); - - retMsg[F("type")] = F("success"); - retMsg[F("message")] = F("Settings saved!"); + WebApi.writeConfig(retMsg); response->setLength(); request->send(response); @@ -217,11 +214,11 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request) } AsyncJsonResponse* asyncJsonResponse = new AsyncJsonResponse(); - JsonObject retMsg = asyncJsonResponse->getRoot(); - retMsg[F("type")] = F("warning"); + auto& retMsg = asyncJsonResponse->getRoot(); + retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { - retMsg[F("message")] = F("No values found!"); + retMsg["message"] = "No values found!"; asyncJsonResponse->setLength(); request->send(asyncJsonResponse); return; @@ -230,7 +227,7 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request) String json = request->getParam("data", true)->value(); if (json.length() > 2048) { - retMsg[F("message")] = F("Data too large!"); + retMsg["message"] = "Data too large!"; asyncJsonResponse->setLength(); request->send(asyncJsonResponse); return; @@ -240,7 +237,7 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request) DeserializationError error = deserializeJson(root, json); if (error) { - retMsg[F("message")] = F("Failed to parse data!"); + retMsg["message"] = "Failed to parse data!"; asyncJsonResponse->setLength(); request->send(asyncJsonResponse); return; @@ -249,7 +246,7 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request) if (!root.containsKey("url") || !root.containsKey("auth_type") || !root.containsKey("username") || !root.containsKey("password") || !root.containsKey("header_key") || !root.containsKey("header_value") || !root.containsKey("timeout") || !root.containsKey("json_path")) { - retMsg[F("message")] = F("Missing fields!"); + retMsg["message"] = "Missing fields!"; asyncJsonResponse->setLength(); request->send(asyncJsonResponse); return; @@ -269,7 +266,7 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request) snprintf_P(response, sizeof(response), "%s", HttpPowerMeter.httpPowerMeterError); } - retMsg[F("message")] = F(response); + retMsg["message"] = response; asyncJsonResponse->setLength(); request->send(asyncJsonResponse); } diff --git a/src/WebApi_prometheus.cpp b/src/WebApi_prometheus.cpp index 0f46c97f3..b2e566545 100644 --- a/src/WebApi_prometheus.cpp +++ b/src/WebApi_prometheus.cpp @@ -54,6 +54,14 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques stream->print("# TYPE opendtu_free_heap_size gauge\n"); stream->printf("opendtu_free_heap_size %zu\n", ESP.getFreeHeap()); + stream->print("# HELP opendtu_biggest_heap_block Biggest free heap block\n"); + stream->print("# TYPE opendtu_biggest_heap_block gauge\n"); + stream->printf("opendtu_biggest_heap_block %zu\n", ESP.getMaxAllocHeap()); + + stream->print("# HELP opendtu_heap_min_free Minimum free memory since boot\n"); + stream->print("# TYPE opendtu_heap_min_free gauge\n"); + stream->printf("opendtu_heap_min_free %zu\n", ESP.getMinFreeHeap()); + stream->print("# HELP wifi_rssi WiFi RSSI\n"); stream->print("# TYPE wifi_rssi gauge\n"); stream->printf("wifi_rssi %d\n", WiFi.RSSI()); diff --git a/src/WebApi_security.cpp b/src/WebApi_security.cpp index 274d0eb24..8e6815d8e 100644 --- a/src/WebApi_security.cpp +++ b/src/WebApi_security.cpp @@ -31,7 +31,7 @@ void WebApiSecurityClass::onSecurityGet(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); const CONFIG_T& config = Configuration.get(); root["password"] = config.Security.Password; @@ -48,7 +48,7 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { @@ -101,11 +101,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(); - Configuration.write(); - retMsg["type"] = "success"; - retMsg["message"] = "Settings saved!"; - retMsg["code"] = WebApiError::GenericSuccess; + WebApi.writeConfig(retMsg); response->setLength(); request->send(response); @@ -118,7 +115,7 @@ void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); + auto& retMsg = response->getRoot(); retMsg["type"] = "success"; retMsg["message"] = "Authentication successful!"; retMsg["code"] = WebApiError::SecurityAuthSuccess; diff --git a/src/WebApi_sysstatus.cpp b/src/WebApi_sysstatus.cpp index 85324f08f..8e67673b1 100644 --- a/src/WebApi_sysstatus.cpp +++ b/src/WebApi_sysstatus.cpp @@ -40,7 +40,7 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& root = response->getRoot(); root["hostname"] = NetworkSettings.getHostname(); @@ -49,6 +49,8 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request) root["heap_total"] = ESP.getHeapSize(); root["heap_used"] = ESP.getHeapSize() - ESP.getFreeHeap(); + root["heap_max_block"] = ESP.getMaxAllocHeap(); + root["heap_min_free"] = ESP.getMinFreeHeap(); root["sketch_total"] = ESP.getFreeSketchSpace(); root["sketch_used"] = ESP.getSketchSize(); root["littlefs_total"] = LittleFS.totalBytes(); diff --git a/src/WebApi_vedirect.cpp b/src/WebApi_vedirect.cpp index 0e8e22aad..b2276687e 100644 --- a/src/WebApi_vedirect.cpp +++ b/src/WebApi_vedirect.cpp @@ -33,12 +33,12 @@ void WebApiVedirectClass::onVedirectStatus(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& 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["vedirect_enabled"] = config.Vedirect.Enabled; + root["verbose_logging"] = config.Vedirect.VerboseLogging; + root["vedirect_updatesonly"] = config.Vedirect.UpdatesOnly; response->setLength(); request->send(response); @@ -51,12 +51,12 @@ void WebApiVedirectClass::onVedirectAdminGet(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot(); + auto& 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["vedirect_enabled"] = config.Vedirect.Enabled; + root["verbose_logging"] = config.Vedirect.VerboseLogging; + root["vedirect_updatesonly"] = config.Vedirect.UpdatesOnly; response->setLength(); request->send(response); @@ -69,12 +69,12 @@ void WebApiVedirectClass::onVedirectAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject retMsg = response->getRoot(); - retMsg[F("type")] = F("warning"); + auto& retMsg = response->getRoot(); + 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; @@ -83,8 +83,8 @@ void WebApiVedirectClass::onVedirectAdminPost(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; @@ -94,8 +94,8 @@ void WebApiVedirectClass::onVedirectAdminPost(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; @@ -104,25 +104,22 @@ void WebApiVedirectClass::onVedirectAdminPost(AsyncWebServerRequest* request) if (!root.containsKey("vedirect_enabled") || !root.containsKey("verbose_logging") || !root.containsKey("vedirect_updatesonly") ) { - 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.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(); + config.Vedirect.Enabled = root["vedirect_enabled"].as(); + config.Vedirect.VerboseLogging = root["verbose_logging"].as(); + config.Vedirect.UpdatesOnly = root["vedirect_updatesonly"].as(); - VictronMppt.updateSettings(); - - retMsg[F("type")] = F("success"); - retMsg[F("message")] = F("Settings saved!"); - retMsg[F("code")] = WebApiError::GenericSuccess; + WebApi.writeConfig(retMsg); response->setLength(); request->send(response); + + VictronMppt.updateSettings(); } diff --git a/src/WebApi_ws_Huawei.cpp b/src/WebApi_ws_Huawei.cpp index 55f83cd6b..174496551 100644 --- a/src/WebApi_ws_Huawei.cpp +++ b/src/WebApi_ws_Huawei.cpp @@ -7,6 +7,7 @@ #include "Configuration.h" #include "Huawei_can.h" #include "MessageOutput.h" +#include "Utils.h" #include "WebApi.h" #include "defaults.h" @@ -50,16 +51,15 @@ void WebApiWsHuaweiLiveClass::loop() _lastUpdateCheck = millis(); try { - String buffer; - // free JsonDocument as soon as possible - { - DynamicJsonDocument root(1024); + std::lock_guard lock(_mutex); + DynamicJsonDocument root(1024); + if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { JsonVariant var = root; generateJsonResponse(var); + + String buffer; serializeJson(root, buffer); - } - if (buffer) { if (Configuration.get().Security.AllowReadonly) { _ws.setAuthentication("", ""); } else { @@ -69,7 +69,9 @@ void WebApiWsHuaweiLiveClass::loop() _ws.textAll(buffer); } } catch (std::bad_alloc& bad_alloc) { - MessageOutput.printf("Calling /api/livedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what()); + MessageOutput.printf("Calling /api/huaweilivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what()); + } catch (const std::exception& exc) { + MessageOutput.printf("Unknown exception in /api/huaweilivedata/status. Reason: \"%s\".\r\n", exc.what()); } } @@ -78,26 +80,26 @@ void WebApiWsHuaweiLiveClass::generateJsonResponse(JsonVariant& 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"] = "%"; } @@ -122,15 +124,19 @@ void WebApiWsHuaweiLiveClass::onLivedataStatus(AsyncWebServerRequest* request) return; } try { + std::lock_guard lock(_mutex); AsyncJsonResponse* response = new AsyncJsonResponse(false, 1024U); - JsonVariant root = response->getRoot().as(); + auto& root = response->getRoot(); + generateJsonResponse(root); response->setLength(); request->send(response); } catch (std::bad_alloc& bad_alloc) { - MessageOutput.printf("Calling /api/livedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what()); - + MessageOutput.printf("Calling /api/huaweilivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what()); + WebApi.sendTooManyRequests(request); + } catch (const std::exception& exc) { + MessageOutput.printf("Unknown exception in /api/huaweilivedata/status. Reason: \"%s\".\r\n", exc.what()); WebApi.sendTooManyRequests(request); } } \ No newline at end of file diff --git a/src/WebApi_ws_battery.cpp b/src/WebApi_ws_battery.cpp index 475b0b667..f3ee725db 100644 --- a/src/WebApi_ws_battery.cpp +++ b/src/WebApi_ws_battery.cpp @@ -9,6 +9,7 @@ #include "MessageOutput.h" #include "WebApi.h" #include "defaults.h" +#include "Utils.h" WebApiWsBatteryLiveClass::WebApiWsBatteryLiveClass() : _ws("/batterylivedata") @@ -48,16 +49,15 @@ void WebApiWsBatteryLiveClass::loop() _lastUpdateCheck = millis(); try { - String buffer; - // free JsonDocument as soon as possible - { - DynamicJsonDocument root(_responseSize); + std::lock_guard lock(_mutex); + DynamicJsonDocument root(_responseSize); + if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { JsonVariant var = root; generateJsonResponse(var); + + String buffer; serializeJson(root, buffer); - } - if (buffer) { if (Configuration.get().Security.AllowReadonly) { _ws.setAuthentication("", ""); } else { @@ -68,6 +68,8 @@ void WebApiWsBatteryLiveClass::loop() } } catch (std::bad_alloc& bad_alloc) { MessageOutput.printf("Calling /api/batterylivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what()); + } catch (const std::exception& exc) { + MessageOutput.printf("Unknown exception in /api/batterylivedata/status. Reason: \"%s\".\r\n", exc.what()); } } @@ -91,15 +93,18 @@ void WebApiWsBatteryLiveClass::onLivedataStatus(AsyncWebServerRequest* request) return; } try { + std::lock_guard lock(_mutex); AsyncJsonResponse* response = new AsyncJsonResponse(false, _responseSize); - JsonVariant root = response->getRoot().as(); + auto& root = response->getRoot(); generateJsonResponse(root); response->setLength(); request->send(response); } catch (std::bad_alloc& bad_alloc) { MessageOutput.printf("Calling /api/batterylivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what()); - + WebApi.sendTooManyRequests(request); + } catch (const std::exception& exc) { + MessageOutput.printf("Unknown exception in /api/batterylivedata/status. Reason: \"%s\".\r\n", exc.what()); WebApi.sendTooManyRequests(request); } } \ No newline at end of file diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index f3a427292..da92a27c8 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -6,6 +6,7 @@ #include "Configuration.h" #include "Datastore.h" #include "MessageOutput.h" +#include "Utils.h" #include "WebApi.h" #include "Battery.h" #include "Huawei_can.h" @@ -67,20 +68,12 @@ void WebApiWsLiveClass::loop() try { std::lock_guard lock(_mutex); - DynamicJsonDocument root(4200 * INV_MAX_COUNT); // TODO(helge) check if this calculation is correct + DynamicJsonDocument root(4200 * INV_MAX_COUNT); + if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { + JsonVariant var = root; + generateJsonResponse(var); - // TODO(helge) temporary dump of memory usage if allocation of DynamicJsonDocument fails (will be fixed in upstream repo) - if (root.capacity() == 0) { - MessageOutput.printf("Calling /api/livedata/status has temporarily run out of resources. Reason: Alloc memory for DynamicJsonDocument failed (FreeHeap = %d, MaxAllocHeap = %d, MinFreeHeap = %d).\r\n", ESP.getFreeHeap(), ESP.getMaxAllocHeap(), ESP.getMinFreeHeap()); - _lastWsPublish = millis(); - return; - } - - JsonVariant var = root; - generateJsonResponse(var); - - String buffer; - if (buffer) { + String buffer; serializeJson(root, buffer); if (Configuration.get().Security.AllowReadonly) { @@ -191,7 +184,7 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root) } JsonObject vedirectObj = root.createNestedObject("vedirect"); - vedirectObj[F("enabled")] = Configuration.get().Vedirect.Enabled; + vedirectObj["enabled"] = Configuration.get().Vedirect.Enabled; JsonObject totalVeObj = vedirectObj.createNestedObject("total"); addTotalField(totalVeObj, "Power", VictronMppt.getPanelPowerWatts(), "W", 1); @@ -199,16 +192,16 @@ 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["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["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["enabled"] = Configuration.get().PowerMeter.Enabled; addTotalField(powerMeterObj, "Power", PowerMeter.getPowerTotal(false), "W", 1); } @@ -255,7 +248,7 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request) try { std::lock_guard lock(_mutex); AsyncJsonResponse* response = new AsyncJsonResponse(false, 4200 * INV_MAX_COUNT); - JsonVariant root = response->getRoot(); + auto& root = response->getRoot(); generateJsonResponse(root); diff --git a/src/WebApi_ws_vedirect_live.cpp b/src/WebApi_ws_vedirect_live.cpp index 430eb9ce7..b353f4a67 100644 --- a/src/WebApi_ws_vedirect_live.cpp +++ b/src/WebApi_ws_vedirect_live.cpp @@ -6,6 +6,7 @@ #include "AsyncJson.h" #include "Configuration.h" #include "MessageOutput.h" +#include "Utils.h" #include "WebApi.h" #include "defaults.h" #include "PowerLimiter.h" @@ -55,16 +56,15 @@ void WebApiWsVedirectLiveClass::loop() if (millis() - _lastWsPublish > (10 * 1000) || lastDataAgeMillis > _dataAgeMillis) { try { - String buffer; - // free JsonDocument as soon as possible - { - DynamicJsonDocument root(_responseSize); + std::lock_guard lock(_mutex); + DynamicJsonDocument root(_responseSize); + if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { JsonVariant var = root; generateJsonResponse(var); + + String buffer; serializeJson(root, buffer); - } - - if (buffer) { + if (Configuration.get().Security.AllowReadonly) { _ws.setAuthentication("", ""); } else { @@ -76,6 +76,8 @@ void WebApiWsVedirectLiveClass::loop() } catch (std::bad_alloc& bad_alloc) { MessageOutput.printf("Calling /api/vedirectlivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what()); + } catch (const std::exception& exc) { + MessageOutput.printf("Unknown exception in /api/vedirectlivedata/status. Reason: \"%s\".\r\n", exc.what()); } _lastWsPublish = millis(); @@ -168,8 +170,9 @@ void WebApiWsVedirectLiveClass::onLivedataStatus(AsyncWebServerRequest* request) return; } try { + std::lock_guard lock(_mutex); AsyncJsonResponse* response = new AsyncJsonResponse(false, _responseSize); - JsonVariant root = response->getRoot(); + auto& root = response->getRoot(); generateJsonResponse(root); @@ -177,8 +180,10 @@ void WebApiWsVedirectLiveClass::onLivedataStatus(AsyncWebServerRequest* request) request->send(response); } catch (std::bad_alloc& bad_alloc) { - MessageOutput.printf("Calling /api/livedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what()); - + MessageOutput.printf("Calling /api/vedirectlivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what()); + WebApi.sendTooManyRequests(request); + } catch (const std::exception& exc) { + MessageOutput.printf("Unknown exception in /api/vedirectlivedata/status. Reason: \"%s\".\r\n", exc.what()); WebApi.sendTooManyRequests(request); } } \ No newline at end of file diff --git a/webapp/package.json b/webapp/package.json index 0b3f4b119..8b36555a9 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -18,7 +18,7 @@ "mitt": "^3.0.1", "sortablejs": "^1.15.1", "spark-md5": "^3.0.2", - "vue": "^3.4.3", + "vue": "^3.4.5", "vue-i18n": "^9.8.0", "vue-router": "^4.2.5" }, @@ -36,7 +36,7 @@ "eslint": "^8.56.0", "eslint-plugin-vue": "^9.19.2", "npm-run-all": "^4.1.5", - "sass": "^1.69.6", + "sass": "^1.69.7", "terser": "^5.26.0", "typescript": "^5.3.3", "vite": "^5.0.10", diff --git a/webapp/src/components/HeapDetails.vue b/webapp/src/components/HeapDetails.vue new file mode 100644 index 000000000..04c589319 --- /dev/null +++ b/webapp/src/components/HeapDetails.vue @@ -0,0 +1,55 @@ + + + diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index ac0714002..4d15f0ec2 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -48,6 +48,7 @@ "1003": "Daten zu groß!", "1004": "Fehler beim interpretieren der Daten!", "1005": "Benötigte Werte fehlen!", + "1006": "Schreiben fehlgeschlagen!", "2001": "Die Seriennummer darf nicht 0 sein!", "2002": "Das Abfraginterval muss größer als 0 sein!", "2003": "Ungültige Sendeleistung angegeben!", @@ -248,6 +249,13 @@ "LittleFs": "LittleFs", "Sketch": "Sketch" }, + "heapdetails": { + "HeapDetails": "Detailinformationen zum Heap", + "TotalFree": "Insgesamt frei", + "LargestFreeBlock": "Größter zusammenhängender freier Block", + "MaxUsage": "Maximale Speichernutzung seit Start", + "Fragmentation": "Grad der Fragmentierung" + }, "radioinfo": { "RadioInformation": "Funkmodulinformationen", "Status": "{module} Status", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index c819fea44..19e729177 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -48,6 +48,7 @@ "1003": "Data too large!", "1004": "Failed to parse data!", "1005": "Values are missing!", + "1006": "Write failed!", "2001": "Serial cannot be zero!", "2002": "Poll interval must be greater zero!", "2003": "Invalid power level setting!", @@ -249,6 +250,13 @@ "LittleFs": "LittleFs", "Sketch": "Sketch" }, + "heapdetails": { + "HeapDetails": "Heap Details", + "TotalFree": "Total free", + "LargestFreeBlock": "Biggest contiguous free block", + "MaxUsage": "Maximum usage since start", + "Fragmentation": "Level of fragmentation" + }, "radioinfo": { "RadioInformation": "Radio Information", "Status": "{module} Status", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index 8f1e88efa..341c68d4b 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -48,6 +48,7 @@ "1003": "Données trop importantes !", "1004": "Échec de l'analyse des données !", "1005": "Certaines valeurs sont manquantes !", + "1006": "Write failed!", "2001": "Le numéro de série ne peut pas être nul !", "2002": "L'intervalle de sondage doit être supérieur à zéro !", "2003": "Réglage du niveau de puissance invalide !", @@ -248,6 +249,13 @@ "LittleFs": "LittleFs", "Sketch": "Sketch" }, + "heapdetails": { + "HeapDetails": "Heap Details", + "TotalFree": "Total free", + "LargestFreeBlock": "Biggest contiguous free block", + "MaxUsage": "Maximum usage since start", + "Fragmentation": "Level of fragmentation" + }, "radioinfo": { "RadioInformation": "Informations sur la radio", "Status": "{module} Statut", diff --git a/webapp/src/types/SystemStatus.ts b/webapp/src/types/SystemStatus.ts index 4c5578581..22c382976 100644 --- a/webapp/src/types/SystemStatus.ts +++ b/webapp/src/types/SystemStatus.ts @@ -22,6 +22,8 @@ export interface SystemStatus { // MemoryInfo heap_total: number; heap_used: number; + heap_max_block: number; + heap_min_free: number; littlefs_total: number; littlefs_used: number; sketch_total: number; diff --git a/webapp/src/views/SystemInfoView.vue b/webapp/src/views/SystemInfoView.vue index 15b02af00..f9e5388d9 100644 --- a/webapp/src/views/SystemInfoView.vue +++ b/webapp/src/views/SystemInfoView.vue @@ -6,6 +6,8 @@
+ +
@@ -16,6 +18,7 @@ import BasePage from '@/components/BasePage.vue'; import FirmwareInfo from "@/components/FirmwareInfo.vue"; import HardwareInfo from "@/components/HardwareInfo.vue"; import MemoryInfo from "@/components/MemoryInfo.vue"; +import HeapDetails from "@/components/HeapDetails.vue"; import RadioInfo from "@/components/RadioInfo.vue"; import type { SystemStatus } from '@/types/SystemStatus'; import { authHeader, handleResponse } from '@/utils/authentication'; @@ -27,6 +30,7 @@ export default defineComponent({ FirmwareInfo, HardwareInfo, MemoryInfo, + HeapDetails, RadioInfo, }, data() { diff --git a/webapp/yarn.lock b/webapp/yarn.lock index cec8f0396..abaacadeb 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -594,13 +594,13 @@ estree-walker "^2.0.2" source-map-js "^1.0.2" -"@vue/compiler-core@3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.3.tgz#8e8f88273f061cf0a49bf958255f5f0621f12d8b" - integrity sha512-u8jzgFg0EDtSrb/hG53Wwh1bAOQFtc1ZCegBpA/glyvTlgHl+tq13o1zvRfLbegYUw/E4mSTGOiCnAJ9SJ+lsg== +"@vue/compiler-core@3.4.5": + version "3.4.5" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.5.tgz#9565aebaadef8649eb7c8e150a5f4f4e2542667d" + integrity sha512-Daka7P1z2AgKjzuueWXhwzIsKu0NkLB6vGbNVEV2iJ8GJTrzraZo/Sk4GWCMRtd/qVi3zwnk+Owbd/xSZbwHtQ== dependencies: "@babel/parser" "^7.23.6" - "@vue/shared" "3.4.3" + "@vue/shared" "3.4.5" entities "^4.5.0" estree-walker "^2.0.2" source-map-js "^1.0.2" @@ -613,13 +613,13 @@ "@vue/compiler-core" "3.2.47" "@vue/shared" "3.2.47" -"@vue/compiler-dom@3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.3.tgz#bea8acde9585d5ce92a3f11c062c863fb33e44d7" - integrity sha512-oGF1E9/htI6JWj/lTJgr6UgxNCtNHbM6xKVreBWeZL9QhRGABRVoWGAzxmtBfSOd+w0Zi5BY0Es/tlJrN6WgEg== +"@vue/compiler-dom@3.4.5": + version "3.4.5" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.5.tgz#c53c9d7715b777b1d6d2adcbc491bfd4f9510edd" + integrity sha512-J8YlxknJVd90SXFJ4HwGANSAXsx5I0lK30sO/zvYV7s5gXf7gZR7r/1BmZ2ju7RGH1lnc6bpBc6nL61yW+PsAQ== dependencies: - "@vue/compiler-core" "3.4.3" - "@vue/shared" "3.4.3" + "@vue/compiler-core" "3.4.5" + "@vue/shared" "3.4.5" "@vue/compiler-dom@^3.3.0": version "3.3.2" @@ -629,16 +629,16 @@ "@vue/compiler-core" "3.3.2" "@vue/shared" "3.3.2" -"@vue/compiler-sfc@3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.3.tgz#a9d35b2deef38576dedd9938851c032fb2ca8617" - integrity sha512-NuJqb5is9I4uzv316VRUDYgIlPZCG8D+ARt5P4t5UDShIHKL25J3TGZAUryY/Aiy0DsY7srJnZL5ryB6DD63Zw== +"@vue/compiler-sfc@3.4.5": + version "3.4.5" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.5.tgz#f93f986dfc5c7f72b9a5e00b48be75d9116cc948" + integrity sha512-jauvkDuSSUbP0ebhfNqljhShA90YEfX/0wZ+w40oZF43IjGyWYjqYaJbvMJwGOd+9+vODW6eSvnk28f0SGV7OQ== dependencies: "@babel/parser" "^7.23.6" - "@vue/compiler-core" "3.4.3" - "@vue/compiler-dom" "3.4.3" - "@vue/compiler-ssr" "3.4.3" - "@vue/shared" "3.4.3" + "@vue/compiler-core" "3.4.5" + "@vue/compiler-dom" "3.4.5" + "@vue/compiler-ssr" "3.4.5" + "@vue/shared" "3.4.5" estree-walker "^2.0.2" magic-string "^0.30.5" postcss "^8.4.32" @@ -668,13 +668,13 @@ "@vue/compiler-dom" "3.2.47" "@vue/shared" "3.2.47" -"@vue/compiler-ssr@3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.3.tgz#c3f641a15a04893b5bc3278f3dac65bed44dce1d" - integrity sha512-wnYQtMBkeFSxgSSQbYGQeXPhQacQiog2c6AlvMldQH6DB+gSXK/0F6DVXAJfEiuBSgBhUc8dwrrG5JQcqwalsA== +"@vue/compiler-ssr@3.4.5": + version "3.4.5" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.5.tgz#d412a4c9b10d69172a5ce0ec78de98dad441a58d" + integrity sha512-DDdEcDzj2lWTMfUMMtEpLDhURai9LhM0zSZ219jCt7b2Vyl0/jy3keFgCPMitG0V1S1YG4Cmws3lWHWdxHQOpg== dependencies: - "@vue/compiler-dom" "3.4.3" - "@vue/shared" "3.4.3" + "@vue/compiler-dom" "3.4.5" + "@vue/shared" "3.4.5" "@vue/devtools-api@^6.5.0": version "6.5.0" @@ -716,37 +716,37 @@ estree-walker "^2.0.2" magic-string "^0.25.7" -"@vue/reactivity@3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.4.3.tgz#95287b5950b328df4a942a7cf14a0e13487f1eac" - integrity sha512-q5f9HLDU+5aBKizXHAx0w4whkIANs1Muiq9R5YXm0HtorSlflqv9u/ohaMxuuhHWCji4xqpQ1eL04WvmAmGnFg== +"@vue/reactivity@3.4.5": + version "3.4.5" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.4.5.tgz#68bc91cd356eed95dc5e9e0570e3f7becaee578b" + integrity sha512-BcWkKvjdvqJwb7BhhFkXPLDCecX4d4a6GATvCduJQDLv21PkPowAE5GKuIE5p6RC07/Lp9FMkkq4AYCTVF5KlQ== dependencies: - "@vue/shared" "3.4.3" + "@vue/shared" "3.4.5" -"@vue/runtime-core@3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.4.3.tgz#fe7649a93d9b20b9b351cd699f69f0e34a26e3ab" - integrity sha512-C1r6QhB1qY7D591RCSFhMULyzL9CuyrGc+3PpB0h7dU4Qqw6GNyo4BNFjHZVvsWncrUlKX3DIKg0Y7rNNr06NQ== +"@vue/runtime-core@3.4.5": + version "3.4.5" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.4.5.tgz#2bf253a6f6b0430af1aacf0fdfd8f5782feefce9" + integrity sha512-wh9ELIOQKeWT9SaUPdLrsxRkZv14jp+SJm9aiQGWio+/MWNM3Lib0wE6CoKEqQ9+SCYyGjDBhTOTtO47kCgbkg== dependencies: - "@vue/reactivity" "3.4.3" - "@vue/shared" "3.4.3" + "@vue/reactivity" "3.4.5" + "@vue/shared" "3.4.5" -"@vue/runtime-dom@3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.4.3.tgz#54a6115cfba364f20cdf5a44c2ff87337a57def8" - integrity sha512-wrsprg7An5Ec+EhPngWdPuzkp0BEUxAKaQtN9dPU/iZctPyD9aaXmVtehPJerdQxQale6gEnhpnfywNw3zOv2A== +"@vue/runtime-dom@3.4.5": + version "3.4.5" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.4.5.tgz#b43736d66c32f6038778024587592cb9d68495de" + integrity sha512-n5ewvOjyG3IEpqGBahdPXODFSpVlSz3H4LF76Sx0XAqpIOqyJ5bIb2PrdYuH2ogBMAQPh+o5tnoH4nJpBr8U0Q== dependencies: - "@vue/runtime-core" "3.4.3" - "@vue/shared" "3.4.3" + "@vue/runtime-core" "3.4.5" + "@vue/shared" "3.4.5" csstype "^3.1.3" -"@vue/server-renderer@3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.4.3.tgz#c508f58b9f83f0959085d5aa6854eac9141b4bc6" - integrity sha512-BUxt8oVGMKKsqSkM1uU3d3Houyfy4WAc2SpSQRebNd+XJGATVkW/rO129jkyL+kpB/2VRKzE63zwf5RtJ3XuZw== +"@vue/server-renderer@3.4.5": + version "3.4.5" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.4.5.tgz#4bfa7aa763217d8b2d4767d2c8d968a9d40352c1" + integrity sha512-jOFc/VE87yvifQpNju12VcqimH8pBLxdcT+t3xMeiED1K6DfH9SORyhFEoZlW5TG2Vwfn3Ul5KE+1aC99xnSBg== dependencies: - "@vue/compiler-ssr" "3.4.3" - "@vue/shared" "3.4.3" + "@vue/compiler-ssr" "3.4.5" + "@vue/shared" "3.4.5" "@vue/shared@3.2.47": version "3.2.47" @@ -758,10 +758,10 @@ resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.3.2.tgz#774cd9b4635ce801b70a3fc3713779a5ef5d77c3" integrity sha512-0rFu3h8JbclbnvvKrs7Fe5FNGV9/5X2rPD7KmOzhLSUAiQH5//Hq437Gv0fR5Mev3u/nbtvmLl8XgwCU20/ZfQ== -"@vue/shared@3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.3.tgz#01d54b32b9796c85c853c670d9395a813f23a8c2" - integrity sha512-rIwlkkP1n4uKrRzivAKPZIEkHiuwY5mmhMJ2nZKCBLz8lTUlE73rQh4n1OnnMurXt1vcUNyH4ZPfdh8QweTjpQ== +"@vue/shared@3.4.5": + version "3.4.5" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.5.tgz#c8b4eb6399a7fc986565ea736d938b3a1579256d" + integrity sha512-6XptuzlMvN4l4cDnDw36pdGEV+9njYkQ1ZE0Q6iZLwrKefKaOJyiFmcP3/KBDHbt72cJZGtllAc1GaHe6XGAyg== "@vue/tsconfig@^0.5.1": version "0.5.1" @@ -2238,10 +2238,10 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -sass@^1.69.6: - version "1.69.6" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.69.6.tgz#88ae1f93facc46d2da9b0bdd652d65068bcfa397" - integrity sha512-qbRr3k9JGHWXCvZU77SD2OTwUlC+gNT+61JOLcmLm+XqH4h/5D+p4IIsxvpkB89S9AwJOyb5+rWNpIucaFxSFQ== +sass@^1.69.7: + version "1.69.7" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.69.7.tgz#6e7e1c8f51e8162faec3e9619babc7da780af3b7" + integrity sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -2608,16 +2608,16 @@ vue-tsc@^1.8.27: "@vue/language-core" "1.8.27" semver "^7.5.4" -vue@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.3.tgz#e1ba36a64134dcedc12cfb2c28e7cd15ba121f04" - integrity sha512-GjN+culMAGv/mUbkIv8zMKItno8npcj5gWlXkSxf1SPTQf8eJ4A+YfHIvQFyL1IfuJcMl3soA7SmN1fRxbf/wA== - dependencies: - "@vue/compiler-dom" "3.4.3" - "@vue/compiler-sfc" "3.4.3" - "@vue/runtime-dom" "3.4.3" - "@vue/server-renderer" "3.4.3" - "@vue/shared" "3.4.3" +vue@^3.4.5: + version "3.4.5" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.5.tgz#c08b9d903a20faaf4df7270bf2fa7487741b2294" + integrity sha512-VH6nHFhLPjgu2oh5vEBXoNZxsGHuZNr3qf4PHClwJWw6IDqw6B3x+4J+ABdoZ0aJuT8Zi0zf3GpGlLQCrGWHrw== + dependencies: + "@vue/compiler-dom" "3.4.5" + "@vue/compiler-sfc" "3.4.5" + "@vue/runtime-dom" "3.4.5" + "@vue/server-renderer" "3.4.5" + "@vue/shared" "3.4.5" webpack-sources@^3.2.3: version "3.2.3" diff --git a/webapp_dist/index.html.gz b/webapp_dist/index.html.gz index be84fc7db..3f62e3dc5 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 095f90816..21d61999a 100644 Binary files a/webapp_dist/js/app.js.gz and b/webapp_dist/js/app.js.gz differ diff --git a/webapp_dist/zones.json.gz b/webapp_dist/zones.json.gz index 02f82db68..6e5a69eae 100644 Binary files a/webapp_dist/zones.json.gz and b/webapp_dist/zones.json.gz differ