diff --git a/include/Configuration.h b/include/Configuration.h index 92b3bff69..a78b80626 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -39,8 +39,6 @@ #define POWERMETER_MAX_HTTP_JSON_PATH_STRLEN 256 #define POWERMETER_HTTP_TIMEOUT 1000 -#define JSON_BUFFER_SIZE 15360 - struct CHANNEL_CONFIG_T { uint16_t MaxChannelPower; char Name[CHAN_MAX_NAME_STRLEN]; diff --git a/include/MqttHandleHass.h b/include/MqttHandleHass.h index feb867435..a76cb0c7b 100644 --- a/include/MqttHandleHass.h +++ b/include/MqttHandleHass.h @@ -66,10 +66,10 @@ class MqttHandleHassClass { void publishInverterNumber(std::shared_ptr inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, const int16_t min = 1, const int16_t max = 100); void publishInverterBinarySensor(std::shared_ptr inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off); - static void createInverterInfo(DynamicJsonDocument& doc, std::shared_ptr inv); - static void createDtuInfo(DynamicJsonDocument& doc); + static void createInverterInfo(JsonDocument& doc, std::shared_ptr inv); + static void createDtuInfo(JsonDocument& doc); - static void createDeviceInfo(DynamicJsonDocument& doc, const String& name, const String& identifiers, const String& configuration_url, const String& manufacturer, const String& model, const String& sw_version, const String& via_device = ""); + static void createDeviceInfo(JsonDocument& doc, const String& name, const String& identifiers, const String& configuration_url, const String& manufacturer, const String& model, const String& sw_version, const String& via_device = ""); static String getDtuUniqueId(); static String getDtuUrl(); diff --git a/include/Utils.h b/include/Utils.h index 35d648bc6..f81e73180 100644 --- a/include/Utils.h +++ b/include/Utils.h @@ -10,7 +10,6 @@ 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); - static bool checkJsonOverflow(const DynamicJsonDocument& doc, const char* function, const uint16_t line); + static bool checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line); static void removeAllFiles(); }; diff --git a/include/WebApi.h b/include/WebApi.h index 299ed63cf..0a433b8c3 100644 --- a/include/WebApi.h +++ b/include/WebApi.h @@ -25,6 +25,7 @@ #include "WebApi_webapp.h" #include "WebApi_ws_console.h" #include "WebApi_ws_live.h" +#include #include "WebApi_ws_vedirect_live.h" #include "WebApi_vedirect.h" #include "WebApi_ws_Huawei.h" @@ -45,6 +46,10 @@ class WebApiClass { static void writeConfig(JsonVariant& retMsg, const WebApiError code = WebApiError::GenericSuccess, const String& message = "Settings saved!"); + static bool parseRequestData(AsyncWebServerRequest* request, AsyncJsonResponse* response, JsonDocument& json_document); + static uint64_t parseSerialFromRequest(AsyncWebServerRequest* request, String param_name = "inv"); + static bool sendJsonResponse(AsyncWebServerRequest* request, AsyncJsonResponse* response, const char* function, const uint16_t line); + private: AsyncWebServer _server; diff --git a/include/WebApi_errors.h b/include/WebApi_errors.h index efb890c5c..97d61b220 100644 --- a/include/WebApi_errors.h +++ b/include/WebApi_errors.h @@ -5,10 +5,11 @@ enum WebApiError { GenericBase = 1000, GenericSuccess, GenericNoValueFound, - GenericDataTooLarge, + GenericDataTooLarge, // not used anymore GenericParseError, GenericValueMissing, GenericWriteFailed, + GenericInternalServerError, DtuBase = 2000, DtuSerialZero, diff --git a/include/WebApi_mqtt.h b/include/WebApi_mqtt.h index b259752b1..6e428249e 100644 --- a/include/WebApi_mqtt.h +++ b/include/WebApi_mqtt.h @@ -4,8 +4,6 @@ #include #include -#define MQTT_JSON_DOC_SIZE 10240 - class WebApiMqttClass { public: void init(AsyncWebServer& server, Scheduler& scheduler); diff --git a/include/WebApi_ws_Huawei.h b/include/WebApi_ws_Huawei.h index c8248aeca..38025bb61 100644 --- a/include/WebApi_ws_Huawei.h +++ b/include/WebApi_ws_Huawei.h @@ -12,7 +12,7 @@ class WebApiWsHuaweiLiveClass { void init(AsyncWebServer& server, Scheduler& scheduler); private: - void generateJsonResponse(JsonVariant& root); + void generateCommonJsonResponse(JsonVariant& root); void onLivedataStatus(AsyncWebServerRequest* request); void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); diff --git a/include/WebApi_ws_battery.h b/include/WebApi_ws_battery.h index 9882649b9..17c63d42f 100644 --- a/include/WebApi_ws_battery.h +++ b/include/WebApi_ws_battery.h @@ -12,7 +12,7 @@ class WebApiWsBatteryLiveClass { void init(AsyncWebServer& server, Scheduler& scheduler); private: - void generateJsonResponse(JsonVariant& root); + void generateCommonJsonResponse(JsonVariant& root); void onLivedataStatus(AsyncWebServerRequest* request); void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); diff --git a/include/WebApi_ws_vedirect_live.h b/include/WebApi_ws_vedirect_live.h index 1d471cf33..b9890834e 100644 --- a/include/WebApi_ws_vedirect_live.h +++ b/include/WebApi_ws_vedirect_live.h @@ -14,7 +14,7 @@ class WebApiWsVedirectLiveClass { void init(AsyncWebServer& server, Scheduler& scheduler); private: - void generateJsonResponse(JsonVariant& root, bool fullUpdate); + void generateCommonJsonResponse(JsonVariant& root, bool fullUpdate); static void populateJson(const JsonObject &root, const VeDirectMpptController::data_t &mpptData); void onLivedataStatus(AsyncWebServerRequest* request); void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); diff --git a/lib/Hoymiles/src/HoymilesRadio.h b/lib/Hoymiles/src/HoymilesRadio.h index 33b8c613b..cb2a947cd 100644 --- a/lib/Hoymiles/src/HoymilesRadio.h +++ b/lib/Hoymiles/src/HoymilesRadio.h @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include "TimeoutHelper.h" #include "commands/CommandAbstract.h" #include "types.h" -#include #include +#include +#include class HoymilesRadio { public: @@ -43,4 +43,4 @@ class HoymilesRadio { bool _busyFlag = false; TimeoutHelper _rxTimeout; -}; \ No newline at end of file +}; diff --git a/lib/Hoymiles/src/inverters/HMT_4CH.cpp b/lib/Hoymiles/src/inverters/HMT_4CH.cpp index 609e3350f..c84eff478 100644 --- a/lib/Hoymiles/src/inverters/HMT_4CH.cpp +++ b/lib/Hoymiles/src/inverters/HMT_4CH.cpp @@ -70,7 +70,7 @@ bool HMT_4CH::isValidSerial(const uint64_t serial) String HMT_4CH::typeName() const { - return F("HMT-1600/1800/2000-4T"); + return "HMT-1600/1800/2000-4T"; } const byteAssign_t* HMT_4CH::getByteAssignment() const diff --git a/lib/Hoymiles/src/inverters/HMT_6CH.cpp b/lib/Hoymiles/src/inverters/HMT_6CH.cpp index f8b9f4075..2c3dd5f3a 100644 --- a/lib/Hoymiles/src/inverters/HMT_6CH.cpp +++ b/lib/Hoymiles/src/inverters/HMT_6CH.cpp @@ -84,7 +84,7 @@ bool HMT_6CH::isValidSerial(const uint64_t serial) String HMT_6CH::typeName() const { - return F("HMT-1800/2250-6T"); + return "HMT-1800/2250-6T"; } const byteAssign_t* HMT_6CH::getByteAssignment() const diff --git a/lib/ThreadSafeQueue/README.md b/lib/ThreadSafeQueue/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/lib/ThreadSafeQueue/library.json b/lib/ThreadSafeQueue/library.json new file mode 100644 index 000000000..768cb8b23 --- /dev/null +++ b/lib/ThreadSafeQueue/library.json @@ -0,0 +1,13 @@ +{ + "name": "ThreadSafeQueue", + "keywords": "queue, threadsafe", + "description": "An Arduino for ESP32 thread safe queue implementation", + "authors": { + "name": "Thomas Basler" + }, + "version": "0.0.1", + "frameworks": "arduino", + "platforms": [ + "espressif32" + ] +} diff --git a/lib/ThreadSafeQueue/ThreadSafeQueue.h b/lib/ThreadSafeQueue/src/ThreadSafeQueue.h similarity index 100% rename from lib/ThreadSafeQueue/ThreadSafeQueue.h rename to lib/ThreadSafeQueue/src/ThreadSafeQueue.h diff --git a/lib/TimeoutHelper/README.md b/lib/TimeoutHelper/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/lib/TimeoutHelper/library.json b/lib/TimeoutHelper/library.json new file mode 100644 index 000000000..0e0472ba6 --- /dev/null +++ b/lib/TimeoutHelper/library.json @@ -0,0 +1,13 @@ +{ + "name": "TimeoutHelper", + "keywords": "timeout", + "description": "An Arduino for ESP32 timeout helper", + "authors": { + "name": "Thomas Basler" + }, + "version": "0.0.1", + "frameworks": "arduino", + "platforms": [ + "espressif32" + ] +} diff --git a/lib/TimeoutHelper/TimeoutHelper.cpp b/lib/TimeoutHelper/src/TimeoutHelper.cpp similarity index 100% rename from lib/TimeoutHelper/TimeoutHelper.cpp rename to lib/TimeoutHelper/src/TimeoutHelper.cpp diff --git a/lib/TimeoutHelper/TimeoutHelper.h b/lib/TimeoutHelper/src/TimeoutHelper.h similarity index 100% rename from lib/TimeoutHelper/TimeoutHelper.h rename to lib/TimeoutHelper/src/TimeoutHelper.h diff --git a/patches/async_tcp/event_queue_size.patch b/patches/async_tcp/event_queue_size.patch new file mode 100644 index 000000000..1280d46a8 --- /dev/null +++ b/patches/async_tcp/event_queue_size.patch @@ -0,0 +1,26 @@ +diff --color -ruN a/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.cpp b/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.cpp +--- a/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.cpp ++++ b/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.cpp +@@ -97,7 +97,7 @@ + + static inline bool _init_async_event_queue(){ + if(!_async_queue){ +- _async_queue = xQueueCreate(32, sizeof(lwip_event_packet_t *)); ++ _async_queue = xQueueCreate(CONFIG_ASYNC_TCP_EVENT_QUEUE_SIZE, sizeof(lwip_event_packet_t *)); + if(!_async_queue){ + return false; + } +diff --color -ruN a/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.h b/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.h +--- a/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.h ++++ b/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.h +@@ -53,6 +53,10 @@ + #define CONFIG_ASYNC_TCP_STACK_SIZE 8192 * 2 + #endif + ++#ifndef CONFIG_ASYNC_TCP_EVENT_QUEUE_SIZE ++#define CONFIG_ASYNC_TCP_EVENT_QUEUE_SIZE 32 ++#endif ++ + class AsyncClient; + + #define ASYNC_MAX_ACK_TIME 5000 diff --git a/patches/esp32c3/EspAsyncWebserver.patch b/patches/esp32c3/EspAsyncWebserver.patch deleted file mode 100644 index 079c164d4..000000000 --- a/patches/esp32c3/EspAsyncWebserver.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/.pio/libdeps/$$$env$$$/ESP Async WebServer/src/AsyncWebSocket.cpp b/.pio/libdeps/$$$env$$$/ESP Async WebServer/src/AsyncWebSocket.cpp -index 12be5f8..8505f73 100644 ---- a/.pio/libdeps/$$$env$$$/ESP Async WebServer/src/AsyncWebSocket.cpp -+++ b/.pio/libdeps/$$$env$$$/ESP Async WebServer/src/AsyncWebSocket.cpp -@@ -737,7 +737,7 @@ void AsyncWebSocketClient::binary(const __FlashStringHelper *data, size_t len) - IPAddress AsyncWebSocketClient::remoteIP() const - { - if (!_client) -- return IPAddress(0U); -+ return IPAddress((uint32_t)0); - - return _client->remoteIP(); - } diff --git a/platformio.ini b/platformio.ini index ce9998ae4..696c8e94e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,12 +19,13 @@ extra_configs = custom_ci_action = generic,generic_esp32,generic_esp32s3,generic_esp32s3_usb framework = arduino -platform = espressif32@6.5.0 +platform = espressif32@6.6.0 build_flags = -DPIOENV=\"$PIOENV\" -D_TASK_STD_FUNCTION=1 -D_TASK_THREAD_SAFE=1 + -DCONFIG_ASYNC_TCP_EVENT_QUEUE_SIZE=128 -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 @@ -36,8 +37,8 @@ build_unflags = -std=gnu++11 lib_deps = - mathieucarbou/ESP Async WebServer @ 2.8.1 - bblanchon/ArduinoJson @ ^6.21.5 + mathieucarbou/ESP Async WebServer @ 2.9.0 + bblanchon/ArduinoJson @ ^7.0.4 https://github.com/bertmelis/espMqttClient.git#v1.6.0 nrf24/RF24 @ ^1.4.8 olikraus/U8g2 @ ^2.35.15 @@ -64,7 +65,7 @@ board_build.embed_files = webapp_dist/js/app.js.gz webapp_dist/site.webmanifest -custom_patches = +custom_patches = async_tcp monitor_filters = esp32_exception_decoder, time, log2file, colorize monitor_speed = 115200 @@ -92,13 +93,13 @@ build_flags = ${env.build_flags} [env:generic_esp32c3] board = esp32-c3-devkitc-02 -custom_patches = ${env.custom_patches},esp32c3 +custom_patches = ${env.custom_patches} build_flags = ${env.build_flags} [env:generic_esp32c3_usb] board = esp32-c3-devkitc-02 -custom_patches = ${env.custom_patches},esp32c3 +custom_patches = ${env.custom_patches} build_flags = ${env.build_flags} -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 diff --git a/src/Configuration.cpp b/src/Configuration.cpp index d79953787..70d0ba813 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -25,17 +25,13 @@ bool ConfigurationClass::write() } config.Cfg.SaveCount++; - DynamicJsonDocument doc(JSON_BUFFER_SIZE); + JsonDocument doc; - if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) { - return false; - } - - JsonObject cfg = doc.createNestedObject("cfg"); + JsonObject cfg = doc["cfg"].to(); cfg["version"] = config.Cfg.Version; cfg["save_count"] = config.Cfg.SaveCount; - JsonObject wifi = doc.createNestedObject("wifi"); + JsonObject wifi = doc["wifi"].to(); wifi["ssid"] = config.WiFi.Ssid; wifi["password"] = config.WiFi.Password; wifi["ip"] = IPAddress(config.WiFi.Ip).toString(); @@ -47,10 +43,10 @@ bool ConfigurationClass::write() wifi["hostname"] = config.WiFi.Hostname; wifi["aptimeout"] = config.WiFi.ApTimeout; - JsonObject mdns = doc.createNestedObject("mdns"); + JsonObject mdns = doc["mdns"].to(); mdns["enabled"] = config.Mdns.Enabled; - JsonObject ntp = doc.createNestedObject("ntp"); + JsonObject ntp = doc["ntp"].to(); ntp["server"] = config.Ntp.Server; ntp["timezone"] = config.Ntp.Timezone; ntp["timezone_descr"] = config.Ntp.TimezoneDescr; @@ -58,7 +54,7 @@ bool ConfigurationClass::write() ntp["longitude"] = config.Ntp.Longitude; ntp["sunsettype"] = config.Ntp.SunsetType; - JsonObject mqtt = doc.createNestedObject("mqtt"); + JsonObject mqtt = doc["mqtt"].to(); mqtt["enabled"] = config.Mqtt.Enabled; mqtt["verbose_logging"] = config.Mqtt.VerboseLogging; mqtt["hostname"] = config.Mqtt.Hostname; @@ -70,27 +66,27 @@ bool ConfigurationClass::write() mqtt["publish_interval"] = config.Mqtt.PublishInterval; mqtt["clean_session"] = config.Mqtt.CleanSession; - JsonObject mqtt_lwt = mqtt.createNestedObject("lwt"); + JsonObject mqtt_lwt = mqtt["lwt"].to(); mqtt_lwt["topic"] = config.Mqtt.Lwt.Topic; mqtt_lwt["value_online"] = config.Mqtt.Lwt.Value_Online; mqtt_lwt["value_offline"] = config.Mqtt.Lwt.Value_Offline; mqtt_lwt["qos"] = config.Mqtt.Lwt.Qos; - JsonObject mqtt_tls = mqtt.createNestedObject("tls"); + JsonObject mqtt_tls = mqtt["tls"].to(); mqtt_tls["enabled"] = config.Mqtt.Tls.Enabled; mqtt_tls["root_ca_cert"] = config.Mqtt.Tls.RootCaCert; mqtt_tls["certlogin"] = config.Mqtt.Tls.CertLogin; mqtt_tls["client_cert"] = config.Mqtt.Tls.ClientCert; mqtt_tls["client_key"] = config.Mqtt.Tls.ClientKey; - JsonObject mqtt_hass = mqtt.createNestedObject("hass"); + JsonObject mqtt_hass = mqtt["hass"].to(); mqtt_hass["enabled"] = config.Mqtt.Hass.Enabled; mqtt_hass["retain"] = config.Mqtt.Hass.Retain; mqtt_hass["topic"] = config.Mqtt.Hass.Topic; mqtt_hass["individual_panels"] = config.Mqtt.Hass.IndividualPanels; mqtt_hass["expire"] = config.Mqtt.Hass.Expire; - JsonObject dtu = doc.createNestedObject("dtu"); + JsonObject dtu = doc["dtu"].to(); dtu["serial"] = config.Dtu.Serial; dtu["poll_interval"] = config.Dtu.PollInterval; dtu["verbose_logging"] = config.Dtu.VerboseLogging; @@ -99,14 +95,14 @@ bool ConfigurationClass::write() dtu["cmt_frequency"] = config.Dtu.Cmt.Frequency; dtu["cmt_country_mode"] = config.Dtu.Cmt.CountryMode; - JsonObject security = doc.createNestedObject("security"); + JsonObject security = doc["security"].to(); security["password"] = config.Security.Password; security["allow_readonly"] = config.Security.AllowReadonly; - JsonObject device = doc.createNestedObject("device"); + JsonObject device = doc["device"].to(); device["pinmapping"] = config.Dev_PinMapping; - JsonObject display = device.createNestedObject("display"); + JsonObject display = device["display"].to(); display["powersafe"] = config.Display.PowerSafe; display["screensaver"] = config.Display.ScreenSaver; display["rotation"] = config.Display.Rotation; @@ -115,15 +111,15 @@ bool ConfigurationClass::write() display["diagram_duration"] = config.Display.Diagram.Duration; display["diagram_mode"] = config.Display.Diagram.Mode; - JsonArray leds = device.createNestedArray("led"); + JsonArray leds = device["led"].to(); for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { - JsonObject led = leds.createNestedObject(); + JsonObject led = leds.add(); led["brightness"] = config.Led_Single[i].Brightness; } - JsonArray inverters = doc.createNestedArray("inverters"); + JsonArray inverters = doc["inverters"].to(); for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { - JsonObject inv = inverters.createNestedObject(); + JsonObject inv = inverters.add(); inv["serial"] = config.Inverter[i].Serial; inv["name"] = config.Inverter[i].Name; inv["order"] = config.Inverter[i].Order; @@ -136,21 +132,21 @@ bool ConfigurationClass::write() inv["zero_day"] = config.Inverter[i].ZeroYieldDayOnMidnight; inv["yieldday_correction"] = config.Inverter[i].YieldDayCorrection; - JsonArray channel = inv.createNestedArray("channel"); + JsonArray channel = inv["channel"].to(); for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { - JsonObject chanData = channel.createNestedObject(); + JsonObject chanData = channel.add(); chanData["name"] = config.Inverter[i].channel[c].Name; chanData["max_power"] = config.Inverter[i].channel[c].MaxChannelPower; chanData["yield_total_offset"] = config.Inverter[i].channel[c].YieldTotalOffset; } } - JsonObject vedirect = doc.createNestedObject("vedirect"); + JsonObject vedirect = doc["vedirect"].to(); vedirect["enabled"] = config.Vedirect.Enabled; vedirect["verbose_logging"] = config.Vedirect.VerboseLogging; vedirect["updates_only"] = config.Vedirect.UpdatesOnly; - JsonObject powermeter = doc.createNestedObject("powermeter"); + JsonObject powermeter = doc["powermeter"].to(); powermeter["enabled"] = config.PowerMeter.Enabled; powermeter["verbose_logging"] = config.PowerMeter.VerboseLogging; powermeter["interval"] = config.PowerMeter.Interval; @@ -162,9 +158,9 @@ bool ConfigurationClass::write() powermeter["sdmaddress"] = config.PowerMeter.SdmAddress; powermeter["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests; - JsonArray powermeter_http_phases = powermeter.createNestedArray("http_phases"); + JsonArray powermeter_http_phases = powermeter["http_phases"].to(); for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) { - JsonObject powermeter_phase = powermeter_http_phases.createNestedObject(); + JsonObject powermeter_phase = powermeter_http_phases.add(); powermeter_phase["enabled"] = config.PowerMeter.Http_Phase[i].Enabled; powermeter_phase["url"] = config.PowerMeter.Http_Phase[i].Url; @@ -179,7 +175,7 @@ bool ConfigurationClass::write() powermeter_phase["sign_inverted"] = config.PowerMeter.Http_Phase[i].SignInverted; } - JsonObject powerlimiter = doc.createNestedObject("powerlimiter"); + JsonObject powerlimiter = doc["powerlimiter"].to(); powerlimiter["enabled"] = config.PowerLimiter.Enabled; powerlimiter["verbose_logging"] = config.PowerLimiter.VerboseLogging; powerlimiter["solar_passtrough_enabled"] = config.PowerLimiter.SolarPassThroughEnabled; @@ -206,7 +202,7 @@ bool ConfigurationClass::write() powerlimiter["full_solar_passthrough_start_voltage"] = config.PowerLimiter.FullSolarPassThroughStartVoltage; powerlimiter["full_solar_passthrough_stop_voltage"] = config.PowerLimiter.FullSolarPassThroughStopVoltage; - JsonObject battery = doc.createNestedObject("battery"); + JsonObject battery = doc["battery"].to(); battery["enabled"] = config.Battery.Enabled; battery["verbose_logging"] = config.Battery.VerboseLogging; battery["provider"] = config.Battery.Provider; @@ -215,7 +211,7 @@ bool ConfigurationClass::write() battery["mqtt_topic"] = config.Battery.MqttSocTopic; battery["mqtt_voltage_topic"] = config.Battery.MqttVoltageTopic; - JsonObject huawei = doc.createNestedObject("huawei"); + JsonObject huawei = doc["huawei"].to(); huawei["enabled"] = config.Huawei.Enabled; huawei["verbose_logging"] = config.Huawei.VerboseLogging; huawei["can_controller_frequency"] = config.Huawei.CAN_Controller_Frequency; @@ -229,6 +225,10 @@ bool ConfigurationClass::write() huawei["stop_batterysoc_threshold"] = config.Huawei.Auto_Power_Stop_BatterySoC_Threshold; huawei["target_power_consumption"] = config.Huawei.Auto_Power_Target_Power_Consumption; + if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) { + return false; + } + // Serialize JSON to file if (serializeJson(doc, f) == 0) { MessageOutput.println("Failed to write file"); @@ -243,11 +243,7 @@ bool ConfigurationClass::read() { File f = LittleFS.open(CONFIG_FILENAME, "r", false); - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - - if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) { - return false; - } + JsonDocument doc; // Deserialize the JSON document const DeserializationError error = deserializeJson(doc, f); @@ -255,6 +251,10 @@ bool ConfigurationClass::read() MessageOutput.println("Failed to read file, using default configuration"); } + if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) { + return false; + } + JsonObject cfg = doc["cfg"]; config.Cfg.Version = cfg["version"] | CONFIG_VERSION; config.Cfg.SaveCount = cfg["save_count"] | 0; @@ -497,11 +497,7 @@ void ConfigurationClass::migrate() return; } - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - - if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) { - return; - } + JsonDocument doc; // Deserialize the JSON document const DeserializationError error = deserializeJson(doc, f); @@ -510,6 +506,10 @@ void ConfigurationClass::migrate() return; } + if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) { + return; + } + if (config.Cfg.Version < 0x00011700) { JsonArray inverters = doc["inverters"]; for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { diff --git a/src/InverterSettings.cpp b/src/InverterSettings.cpp index 04ec1e009..19605af14 100644 --- a/src/InverterSettings.cpp +++ b/src/InverterSettings.cpp @@ -51,9 +51,9 @@ void InverterSettingsClass::init(Scheduler& scheduler) if (PinMapping.isValidCmt2300Config()) { Hoymiles.initCMT(pin.cmt_sdio, pin.cmt_clk, pin.cmt_cs, pin.cmt_fcs, pin.cmt_gpio2, pin.cmt_gpio3); - MessageOutput.println(F(" Setting country mode... ")); + MessageOutput.println(" Setting country mode... "); Hoymiles.getRadioCmt()->setCountryMode(static_cast(config.Dtu.Cmt.CountryMode)); - MessageOutput.println(F(" Setting CMT target frequency... ")); + MessageOutput.println(" Setting CMT target frequency... "); Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency); } diff --git a/src/MqttHandlVedirectHass.cpp b/src/MqttHandlVedirectHass.cpp index 2fd75aa70..a3ddb8884 100644 --- a/src/MqttHandlVedirectHass.cpp +++ b/src/MqttHandlVedirectHass.cpp @@ -124,10 +124,8 @@ void MqttHandleVedirectHassClass::publishSensor(const char *caption, const char statTopic.concat("/"); statTopic.concat(subTopic); - DynamicJsonDocument root(1024); - if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { - return; - } + JsonDocument root; + root["name"] = caption; root["stat_t"] = statTopic; root["uniq_id"] = serial + "_" + sensorId; @@ -140,7 +138,7 @@ void MqttHandleVedirectHassClass::publishSensor(const char *caption, const char root["unit_of_meas"] = unitOfMeasurement; } - JsonObject deviceObj = root.createNestedObject("dev"); + JsonObject deviceObj = root["dev"].to(); createDeviceInfo(deviceObj, mpptData); if (Configuration.get().Mqtt.Hass.Expire) { @@ -153,7 +151,9 @@ void MqttHandleVedirectHassClass::publishSensor(const char *caption, const char root["stat_cla"] = stateClass; } - if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } + if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { + return; + } char buffer[512]; serializeJson(root, buffer); @@ -182,10 +182,7 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char *caption, const statTopic.concat("/"); statTopic.concat(subTopic); - DynamicJsonDocument root(1024); - if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { - return; - } + JsonDocument root; root["name"] = caption; root["uniq_id"] = serial + "_" + sensorId; root["stat_t"] = statTopic; @@ -196,10 +193,12 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char *caption, const root["icon"] = icon; } - JsonObject deviceObj = root.createNestedObject("dev"); + JsonObject deviceObj = root["dev"].to(); createDeviceInfo(deviceObj, mpptData); - if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } + if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { + return; + } char buffer[512]; serializeJson(root, buffer); diff --git a/src/MqttHandleBatteryHass.cpp b/src/MqttHandleBatteryHass.cpp index 75912817f..28a713334 100644 --- a/src/MqttHandleBatteryHass.cpp +++ b/src/MqttHandleBatteryHass.cpp @@ -144,10 +144,7 @@ void MqttHandleBatteryHassClass::publishSensor(const char* caption, const char* // statTopic.concat("/"); statTopic.concat(subTopic); - DynamicJsonDocument root(1024); - if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { - return; - } + JsonDocument root; root["name"] = caption; root["stat_t"] = statTopic; root["uniq_id"] = serial + "_" + sensorId; @@ -160,7 +157,7 @@ void MqttHandleBatteryHassClass::publishSensor(const char* caption, const char* root["unit_of_meas"] = unitOfMeasurement; } - JsonObject deviceObj = root.createNestedObject("dev"); + JsonObject deviceObj = root["dev"].to(); createDeviceInfo(deviceObj); if (Configuration.get().Mqtt.Hass.Expire) { @@ -173,7 +170,9 @@ void MqttHandleBatteryHassClass::publishSensor(const char* caption, const char* root["stat_cla"] = stateClass; } - if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } + if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { + return; + } char buffer[512]; serializeJson(root, buffer); @@ -201,10 +200,8 @@ void MqttHandleBatteryHassClass::publishBinarySensor(const char* caption, const // statTopic.concat("/"); statTopic.concat(subTopic); - DynamicJsonDocument root(1024); - if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { - return; - } + JsonDocument root; + root["name"] = caption; root["uniq_id"] = serial + "_" + sensorId; root["stat_t"] = statTopic; @@ -215,10 +212,12 @@ void MqttHandleBatteryHassClass::publishBinarySensor(const char* caption, const root["icon"] = icon; } - JsonObject deviceObj = root.createNestedObject("dev"); + auto deviceObj = root["dev"].to(); createDeviceInfo(deviceObj); - if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } + if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { + return; + } char buffer[512]; serializeJson(root, buffer); diff --git a/src/MqttHandleHass.cpp b/src/MqttHandleHass.cpp index 21ff0fa2b..0de7bdedb 100644 --- a/src/MqttHandleHass.cpp +++ b/src/MqttHandleHass.cpp @@ -137,10 +137,7 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr name = "CH" + chanNum + " " + fieldName; } - DynamicJsonDocument root(1024); - if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { - return; - } + JsonDocument root; root["name"] = name; root["stat_t"] = stateTopic; @@ -163,6 +160,10 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr root["stat_cla"] = stateCls; } + if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { + return; + } + String buffer; serializeJson(root, buffer); publish(configTopic, buffer); @@ -185,10 +186,7 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptr inv) +void MqttHandleHassClass::createInverterInfo(JsonDocument& root, std::shared_ptr inv) { createDeviceInfo( root, @@ -378,7 +382,7 @@ void MqttHandleHassClass::createInverterInfo(DynamicJsonDocument& root, std::sha getDtuUniqueId()); } -void MqttHandleHassClass::createDtuInfo(DynamicJsonDocument& root) +void MqttHandleHassClass::createDtuInfo(JsonDocument& root) { createDeviceInfo( root, @@ -391,12 +395,12 @@ void MqttHandleHassClass::createDtuInfo(DynamicJsonDocument& root) } void MqttHandleHassClass::createDeviceInfo( - DynamicJsonDocument& root, + JsonDocument& root, const String& name, const String& identifiers, const String& configuration_url, const String& manufacturer, const String& model, const String& sw_version, const String& via_device) { - auto object = root.createNestedObject("dev"); + auto object = root["dev"].to(); object["name"] = name; object["ids"] = identifiers; diff --git a/src/MqttHandlePowerLimiterHass.cpp b/src/MqttHandlePowerLimiterHass.cpp index 9576a84ad..6699fe9ea 100644 --- a/src/MqttHandlePowerLimiterHass.cpp +++ b/src/MqttHandlePowerLimiterHass.cpp @@ -112,10 +112,7 @@ void MqttHandlePowerLimiterHassClass::publishSelect( const String cmdTopic = MqttSettings.getPrefix() + "powerlimiter/cmd/" + commandTopic; const String statTopic = MqttSettings.getPrefix() + "powerlimiter/status/" + stateTopic; - DynamicJsonDocument root(1024); - if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { - return; - } + JsonDocument root; root["name"] = caption; root["uniq_id"] = selectId; @@ -125,15 +122,17 @@ void MqttHandlePowerLimiterHassClass::publishSelect( root["ent_cat"] = category; root["cmd_t"] = cmdTopic; root["stat_t"] = statTopic; - JsonArray options = root.createNestedArray("options"); + JsonArray options = root["options"].to(); options.add("0"); options.add("1"); options.add("2"); - JsonObject deviceObj = root.createNestedObject("dev"); + JsonObject deviceObj = root["dev"].to(); createDeviceInfo(deviceObj); - if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } + if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { + return; + } String buffer; serializeJson(root, buffer); @@ -155,10 +154,7 @@ void MqttHandlePowerLimiterHassClass::publishNumber( const String cmdTopic = MqttSettings.getPrefix() + "powerlimiter/cmd/" + commandTopic; const String statTopic = MqttSettings.getPrefix() + "powerlimiter/status/" + stateTopic; - DynamicJsonDocument root(1024); - if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { - return; - } + JsonDocument root; root["name"] = caption; root["uniq_id"] = numberId; @@ -178,10 +174,12 @@ void MqttHandlePowerLimiterHassClass::publishNumber( root["exp_aft"] = config.Mqtt.PublishInterval * 3; } - JsonObject deviceObj = root.createNestedObject("dev"); + JsonObject deviceObj = root["dev"].to(); createDeviceInfo(deviceObj); - if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } + if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { + return; + } String buffer; serializeJson(root, buffer); diff --git a/src/PinMapping.cpp b/src/PinMapping.cpp index 516e6de1d..a3c888861 100644 --- a/src/PinMapping.cpp +++ b/src/PinMapping.cpp @@ -8,8 +8,6 @@ #include #include -#define JSON_BUFFER_SIZE 6144 - #ifndef DISPLAY_TYPE #define DISPLAY_TYPE 0U #endif @@ -242,7 +240,7 @@ bool PinMappingClass::init(const String& deviceMapping) return false; } - DynamicJsonDocument doc(JSON_BUFFER_SIZE); + JsonDocument doc; // Deserialize the JSON document DeserializationError error = deserializeJson(doc, f); if (error) { diff --git a/src/Utils.cpp b/src/Utils.cpp index 938b002da..6bedd2cbd 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -69,9 +69,9 @@ void Utils::restartDtu() ESP.restart(); } -bool Utils::checkJsonAlloc(const DynamicJsonDocument& doc, const char* function, const uint16_t line) +bool Utils::checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line) { - if (doc.capacity() == 0) { + if (doc.overflowed()) { MessageOutput.printf("Alloc failed: %s, %d\r\n", function, line); return false; } @@ -79,16 +79,6 @@ bool Utils::checkJsonAlloc(const DynamicJsonDocument& doc, const char* function, return true; } -bool Utils::checkJsonOverflow(const DynamicJsonDocument& doc, const char* function, const uint16_t line) -{ - if (doc.overflowed()) { - MessageOutput.printf("DynamicJsonDocument overflowed: %s, %d\r\n", function, line); - return true; - } - - return false; -} - /// @brief Remove all files but the PINMAPPING_FILENAME void Utils::removeAllFiles() { diff --git a/src/WebApi.cpp b/src/WebApi.cpp index d00f08702..c2dcf3f10 100644 --- a/src/WebApi.cpp +++ b/src/WebApi.cpp @@ -4,6 +4,7 @@ */ #include "WebApi.h" #include "Configuration.h" +#include "MessageOutput.h" #include "defaults.h" #include @@ -93,4 +94,58 @@ void WebApiClass::writeConfig(JsonVariant& retMsg, const WebApiError code, const } } +bool WebApiClass::parseRequestData(AsyncWebServerRequest* request, AsyncJsonResponse* response, JsonDocument& json_document) +{ + auto& retMsg = response->getRoot(); + retMsg["type"] = "warning"; + + if (!request->hasParam("data", true)) { + retMsg["message"] = "No values found!"; + retMsg["code"] = WebApiError::GenericNoValueFound; + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); + return false; + } + + const String json = request->getParam("data", true)->value(); + const DeserializationError error = deserializeJson(json_document, json); + if (error) { + retMsg["message"] = "Failed to parse data!"; + retMsg["code"] = WebApiError::GenericParseError; + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); + return false; + } + + return true; +} + +uint64_t WebApiClass::parseSerialFromRequest(AsyncWebServerRequest* request, String param_name) +{ + if (request->hasParam(param_name)) { + String s = request->getParam(param_name)->value(); + return strtoll(s.c_str(), NULL, 16); + } + + return 0; +} + +bool WebApiClass::sendJsonResponse(AsyncWebServerRequest* request, AsyncJsonResponse* response, const char* function, const uint16_t line) +{ + bool ret_val = true; + if (response->overflowed()) { + auto& root = response->getRoot(); + + root.clear(); + root["message"] = String("500 Internal Server Error: ") + function + ", " + line; + root["code"] = WebApiError::GenericInternalServerError; + root["type"] = "danger"; + response->setCode(500); + MessageOutput.printf("WebResponse failed: %s, %d\r\n", function, line); + ret_val = false; + } + + response->setLength(); + request->send(response); + return ret_val; +} + WebApiClass WebApi; diff --git a/src/WebApi_Huawei.cpp b/src/WebApi_Huawei.cpp index 594243b5c..da6b38b39 100644 --- a/src/WebApi_Huawei.cpp +++ b/src/WebApi_Huawei.cpp @@ -72,40 +72,16 @@ void WebApiHuaweiClass::onPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); float value; uint8_t online = true; float minimal_voltage; - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (root.containsKey("online")) { online = root["online"].as(); @@ -164,12 +140,9 @@ void WebApiHuaweiClass::onPost(AsyncWebServerRequest* request) } } - retMsg["type"] = "success"; - retMsg["message"] = "Settings saved!"; - retMsg["code"] = WebApiError::GenericSuccess; + WebApi.writeConfig(retMsg); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } @@ -207,39 +180,14 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request) if (!WebApi.checkCredentials(request)) { return; } - - AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - String json = request->getParam("data", true)->value(); - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("enabled")) || !(root.containsKey("can_controller_frequency")) || @@ -271,8 +219,7 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); // TODO(schlimmchen): HuaweiCan has no real concept of the fact that the // config might change. at least not regarding CAN parameters. until that diff --git a/src/WebApi_battery.cpp b/src/WebApi_battery.cpp index 3f26d83cc..798957d3b 100644 --- a/src/WebApi_battery.cpp +++ b/src/WebApi_battery.cpp @@ -59,43 +59,17 @@ void WebApiBatteryClass::onAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!root.containsKey("enabled") || !root.containsKey("provider")) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -110,8 +84,7 @@ void WebApiBatteryClass::onAdminPost(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); Battery.updateSettings(); MqttHandleBatteryHass.forceUpdate(); diff --git a/src/WebApi_config.cpp b/src/WebApi_config.cpp index 29f353192..a67be42fa 100644 --- a/src/WebApi_config.cpp +++ b/src/WebApi_config.cpp @@ -53,51 +53,24 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - const String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(1024); - const DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("delete"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (root["delete"].as() == false) { retMsg["message"] = "Not deleted anything!"; retMsg["code"] = WebApiError::ConfigNotDeleted; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -105,8 +78,7 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request) retMsg["message"] = "Configuration resettet. Rebooting now..."; retMsg["code"] = WebApiError::ConfigSuccess; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); Utils::removeAllFiles(); Utils::restartDtu(); @@ -120,7 +92,7 @@ void WebApiConfigClass::onConfigListGet(AsyncWebServerRequest* request) AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); - auto data = root.createNestedArray("configs"); + auto data = root["configs"].to(); File rootfs = LittleFS.open("/"); File file = rootfs.openNextFile(); @@ -128,15 +100,14 @@ void WebApiConfigClass::onConfigListGet(AsyncWebServerRequest* request) if (file.isDirectory()) { continue; } - JsonObject obj = data.createNestedObject(); + JsonObject obj = data.add(); obj["name"] = String(file.name()); file = rootfs.openNextFile(); } file.close(); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiConfigClass::onConfigUploadFinish(AsyncWebServerRequest* request) diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index 9ab8d4fa2..e6c3170a8 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -26,15 +26,15 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) return; } - AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); + AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); const CONFIG_T& config = Configuration.get(); const PinMapping_t& pin = PinMapping.get(); - auto curPin = root.createNestedObject("curPin"); + auto curPin = root["curPin"].to(); curPin["name"] = config.Dev_PinMapping; - auto nrfPinObj = curPin.createNestedObject("nrf24"); + auto nrfPinObj = curPin["nrf24"].to(); nrfPinObj["clk"] = pin.nrf24_clk; nrfPinObj["cs"] = pin.nrf24_cs; nrfPinObj["en"] = pin.nrf24_en; @@ -42,7 +42,7 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) nrfPinObj["miso"] = pin.nrf24_miso; nrfPinObj["mosi"] = pin.nrf24_mosi; - auto cmtPinObj = curPin.createNestedObject("cmt"); + auto cmtPinObj = curPin["cmt"].to(); cmtPinObj["clk"] = pin.cmt_clk; cmtPinObj["cs"] = pin.cmt_cs; cmtPinObj["fcs"] = pin.cmt_fcs; @@ -50,7 +50,7 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) cmtPinObj["gpio2"] = pin.cmt_gpio2; cmtPinObj["gpio3"] = pin.cmt_gpio3; - auto ethPinObj = curPin.createNestedObject("eth"); + auto ethPinObj = curPin["eth"].to(); ethPinObj["enabled"] = pin.eth_enabled; ethPinObj["phy_addr"] = pin.eth_phy_addr; ethPinObj["power"] = pin.eth_power; @@ -59,19 +59,19 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) ethPinObj["type"] = pin.eth_type; ethPinObj["clk_mode"] = pin.eth_clk_mode; - auto displayPinObj = curPin.createNestedObject("display"); + auto displayPinObj = curPin["display"].to(); 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; - auto ledPinObj = curPin.createNestedObject("led"); + auto ledPinObj = curPin["led"].to(); for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { ledPinObj["led" + String(i)] = pin.led[i]; } - auto display = root.createNestedObject("display"); + auto display = root["display"].to(); display["rotation"] = config.Display.Rotation; display["power_safe"] = config.Display.PowerSafe; display["screensaver"] = config.Display.ScreenSaver; @@ -80,25 +80,25 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) display["diagramduration"] = config.Display.Diagram.Duration; display["diagrammode"] = config.Display.Diagram.Mode; - auto leds = root.createNestedArray("led"); + auto leds = root["led"].to(); for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { - auto led = leds.createNestedObject(); + auto led = leds.add(); led["brightness"] = config.Led_Single[i].Brightness; } - auto victronPinObj = curPin.createNestedObject("victron"); + auto victronPinObj = curPin["victron"].to(); victronPinObj["rx"] = pin.victron_rx; victronPinObj["tx"] = pin.victron_tx; victronPinObj["rx2"] = pin.victron_rx2; victronPinObj["tx2"] = pin.victron_tx2; - JsonObject batteryPinObj = curPin.createNestedObject("battery"); + auto batteryPinObj = curPin["battery"].to(); batteryPinObj["rx"] = pin.battery_rx; batteryPinObj["rxen"] = pin.battery_rxen; batteryPinObj["tx"] = pin.battery_tx; batteryPinObj["txen"] = pin.battery_txen; - JsonObject huaweiPinObj = curPin.createNestedObject("huawei"); + auto huaweiPinObj = curPin["huawei"].to(); huaweiPinObj["miso"] = pin.huawei_miso; huaweiPinObj["mosi"] = pin.huawei_mosi; huaweiPinObj["clk"] = pin.huawei_clk; @@ -106,8 +106,7 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) huaweiPinObj["cs"] = pin.huawei_cs; huaweiPinObj["power"] = pin.huawei_power; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) @@ -116,45 +115,19 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) return; } - AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - const String json = request->getParam("data", true)->value(); - - if (json.length() > MQTT_JSON_DOC_SIZE) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(MQTT_JSON_DOC_SIZE); - const DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("curPin") || root.containsKey("display"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -162,8 +135,7 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) retMsg["message"] = "Pin mapping must between 1 and " STR(DEV_MAX_MAPPING_NAME_STRLEN) " characters long!"; retMsg["code"] = WebApiError::HardwarePinMappingLength; retMsg["param"]["max"] = DEV_MAX_MAPPING_NAME_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -194,8 +166,7 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); if (performRestart) { Utils::restartDtu(); diff --git a/src/WebApi_devinfo.cpp b/src/WebApi_devinfo.cpp index 212a7f7d5..449cd1772 100644 --- a/src/WebApi_devinfo.cpp +++ b/src/WebApi_devinfo.cpp @@ -23,13 +23,7 @@ void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request) AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); - - uint64_t serial = 0; - if (request->hasParam("inv")) { - String s = request->getParam("inv")->value(); - serial = strtoll(s.c_str(), NULL, 16); - } - + auto serial = WebApi.parseSerialFromRequest(request); auto inv = Hoymiles.getInverterBySerial(serial); if (inv != nullptr) { @@ -43,6 +37,5 @@ void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request) root["fw_build_datetime"] = inv->DevInfo()->getFwBuildDateTimeStr(); } - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } diff --git a/src/WebApi_dtu.cpp b/src/WebApi_dtu.cpp index 2ccb20b27..8e587f852 100644 --- a/src/WebApi_dtu.cpp +++ b/src/WebApi_dtu.cpp @@ -63,10 +63,10 @@ void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request) root["cmt_country"] = config.Dtu.Cmt.CountryMode; root["cmt_chan_width"] = Hoymiles.getRadioCmt()->getChannelWidth(); - auto data = root.createNestedArray("country_def"); + auto data = root["country_def"].to(); auto countryDefs = Hoymiles.getRadioCmt()->getCountryFrequencyList(); for (const auto& definition : countryDefs) { - auto obj = data.createNestedObject(); + auto obj = data.add(); obj["freq_default"] = definition.definition.Freq_Default; obj["freq_min"] = definition.definition.Freq_Min; obj["freq_max"] = definition.definition.Freq_Max; @@ -74,8 +74,7 @@ void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request) obj["freq_legal_max"] = definition.definition.Freq_Legal_Max; } - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) @@ -85,37 +84,12 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - const String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(1024); - const DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("serial") && root.containsKey("pollinterval") @@ -126,8 +100,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) && root.containsKey("cmt_country"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -137,40 +110,35 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) if (serial == 0) { retMsg["message"] = "Serial cannot be zero!"; retMsg["code"] = WebApiError::DtuSerialZero; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (root["pollinterval"].as() == 0) { retMsg["message"] = "Poll interval must be greater zero!"; retMsg["code"] = WebApiError::DtuPollZero; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (root["nrf_palevel"].as() > 3) { retMsg["message"] = "Invalid power level setting!"; retMsg["code"] = WebApiError::DtuInvalidPowerLevel; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (root["cmt_palevel"].as() < -10 || root["cmt_palevel"].as() > 20) { retMsg["message"] = "Invalid power level setting!"; retMsg["code"] = WebApiError::DtuInvalidPowerLevel; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (root["cmt_country"].as() >= CountryModeId_t::CountryModeId_Max) { retMsg["message"] = "Invalid country setting!"; retMsg["code"] = WebApiError::DtuInvalidCmtCountry; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -183,8 +151,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) retMsg["code"] = WebApiError::DtuInvalidCmtFrequency; retMsg["param"]["min"] = FrequencyDefinition.Freq_Min; retMsg["param"]["max"] = FrequencyDefinition.Freq_Max; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -200,8 +167,8 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); _applyDataTask.enable(); + _applyDataTask.restart(); } diff --git a/src/WebApi_eventlog.cpp b/src/WebApi_eventlog.cpp index 51e85affa..ec8b78c30 100644 --- a/src/WebApi_eventlog.cpp +++ b/src/WebApi_eventlog.cpp @@ -20,14 +20,9 @@ void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request) return; } - AsyncJsonResponse* response = new AsyncJsonResponse(false, 2048); + AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); - - uint64_t serial = 0; - if (request->hasParam("inv")) { - String s = request->getParam("inv")->value(); - serial = strtoll(s.c_str(), NULL, 16); - } + auto serial = WebApi.parseSerialFromRequest(request); AlarmMessageLocale_t locale = AlarmMessageLocale_t::EN; if (request->hasParam("locale")) { @@ -47,10 +42,10 @@ void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request) uint8_t logEntryCount = inv->EventLog()->getEntryCount(); root["count"] = logEntryCount; - JsonArray eventsArray = root.createNestedArray("events"); + JsonArray eventsArray = root["events"].to(); for (uint8_t logEntry = 0; logEntry < logEntryCount; logEntry++) { - JsonObject eventsObject = eventsArray.createNestedObject(); + JsonObject eventsObject = eventsArray.add(); AlarmLogEntry_t entry; inv->EventLog()->getLogEntry(logEntry, entry, locale); @@ -62,6 +57,5 @@ void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request) } } - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } diff --git a/src/WebApi_gridprofile.cpp b/src/WebApi_gridprofile.cpp index 60c340fa0..9fc05b032 100644 --- a/src/WebApi_gridprofile.cpp +++ b/src/WebApi_gridprofile.cpp @@ -21,32 +21,26 @@ void WebApiGridProfileClass::onGridProfileStatus(AsyncWebServerRequest* request) return; } - AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192); + AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); - - uint64_t serial = 0; - if (request->hasParam("inv")) { - String s = request->getParam("inv")->value(); - serial = strtoll(s.c_str(), NULL, 16); - } - + auto serial = WebApi.parseSerialFromRequest(request); auto inv = Hoymiles.getInverterBySerial(serial); if (inv != nullptr) { root["name"] = inv->GridProfile()->getProfileName(); root["version"] = inv->GridProfile()->getProfileVersion(); - auto jsonSections = root.createNestedArray("sections"); + auto jsonSections = root["sections"].to(); auto profSections = inv->GridProfile()->getProfile(); for (auto &profSection : profSections) { - auto jsonSection = jsonSections.createNestedObject(); + auto jsonSection = jsonSections.add(); jsonSection["name"] = profSection.SectionName; - auto jsonItems = jsonSection.createNestedArray("items"); + auto jsonItems = jsonSection["items"].to(); for (auto &profItem : profSection.items) { - auto jsonItem = jsonItems.createNestedObject(); + auto jsonItem = jsonItems.add(); jsonItem["n"] = profItem.Name; jsonItem["u"] = profItem.Unit; @@ -55,8 +49,7 @@ void WebApiGridProfileClass::onGridProfileStatus(AsyncWebServerRequest* request) } } - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiGridProfileClass::onGridProfileRawdata(AsyncWebServerRequest* request) @@ -65,24 +58,17 @@ void WebApiGridProfileClass::onGridProfileRawdata(AsyncWebServerRequest* request return; } - AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096); + AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); - - uint64_t serial = 0; - if (request->hasParam("inv")) { - String s = request->getParam("inv")->value(); - serial = strtoll(s.c_str(), NULL, 16); - } - + auto serial = WebApi.parseSerialFromRequest(request); auto inv = Hoymiles.getInverterBySerial(serial); if (inv != nullptr) { - auto raw = root.createNestedArray("raw"); + auto raw = root["raw"].to(); auto data = inv->GridProfile()->getRawData(); copyArray(&data[0], data.size(), raw); } - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } diff --git a/src/WebApi_inverter.cpp b/src/WebApi_inverter.cpp index 5f5e41016..2d9a56344 100644 --- a/src/WebApi_inverter.cpp +++ b/src/WebApi_inverter.cpp @@ -29,15 +29,15 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request) return; } - AsyncJsonResponse* response = new AsyncJsonResponse(false, 768 * INV_MAX_COUNT); + AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); - JsonArray data = root.createNestedArray("inverter"); + JsonArray data = root["inverter"].to(); const CONFIG_T& config = Configuration.get(); for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { if (config.Inverter[i].Serial > 0) { - JsonObject obj = data.createNestedObject(); + JsonObject obj = data.add(); obj["id"] = i; obj["name"] = String(config.Inverter[i].Name); obj["order"] = config.Inverter[i].Order; @@ -67,9 +67,9 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request) max_channels = inv->Statistics()->getChannelsByType(TYPE_DC).size(); } - JsonArray channel = obj.createNestedArray("channel"); + JsonArray channel = obj["channel"].to(); for (uint8_t c = 0; c < max_channels; c++) { - JsonObject chanData = channel.createNestedObject(); + JsonObject chanData = channel.add(); chanData["name"] = config.Inverter[i].channel[c].Name; chanData["max_power"] = config.Inverter[i].channel[c].MaxChannelPower; chanData["yield_total_offset"] = config.Inverter[i].channel[c].YieldTotalOffset; @@ -77,8 +77,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request) } } - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request) @@ -88,44 +87,18 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - const String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); - return; - } - - DynamicJsonDocument root(1024); - const DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("serial") && root.containsKey("name"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -135,8 +108,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request) if (serial == 0) { retMsg["message"] = "Serial must be a number > 0!"; retMsg["code"] = WebApiError::InverterSerialZero; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -144,8 +116,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request) retMsg["message"] = "Name must between 1 and " STR(INV_MAX_NAME_STRLEN) " characters long!"; retMsg["code"] = WebApiError::InverterNameLength; retMsg["param"]["max"] = INV_MAX_NAME_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -155,8 +126,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request) retMsg["message"] = "Only " STR(INV_MAX_COUNT) " inverters are supported!"; retMsg["code"] = WebApiError::InverterCount; retMsg["param"]["max"] = INV_MAX_COUNT; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -167,8 +137,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg, WebApiError::InverterAdded, "Inverter created!"); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); auto inv = Hoymiles.addInverter(inverter->Name, inverter->Serial); @@ -188,51 +157,24 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - const String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(1024); - const DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("id") && root.containsKey("serial") && root.containsKey("name") && root.containsKey("channel"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (root["id"].as() > INV_MAX_COUNT - 1) { retMsg["message"] = "Invalid ID specified!"; retMsg["code"] = WebApiError::InverterInvalidId; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -242,8 +184,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) if (serial == 0) { retMsg["message"] = "Serial must be a number > 0!"; retMsg["code"] = WebApiError::InverterSerialZero; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -251,8 +192,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) retMsg["message"] = "Name must between 1 and " STR(INV_MAX_NAME_STRLEN) " characters long!"; retMsg["code"] = WebApiError::InverterNameLength; retMsg["param"]["max"] = INV_MAX_NAME_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -260,8 +200,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) if (channelArray.size() == 0 || channelArray.size() > INV_MAX_CHAN_COUNT) { retMsg["message"] = "Invalid amount of max channel setting given!"; retMsg["code"] = WebApiError::InverterInvalidMaxChannel; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -293,8 +232,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg, WebApiError::InverterChanged, "Inverter changed!"); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); std::shared_ptr inv = Hoymiles.getInverterBySerial(old_serial); @@ -333,51 +271,24 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - const String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(1024); - const DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("id"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (root["id"].as() > INV_MAX_COUNT - 1) { retMsg["message"] = "Invalid ID specified!"; retMsg["code"] = WebApiError::InverterInvalidId; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -390,8 +301,7 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg, WebApiError::InverterDeleted, "Inverter deleted!"); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); MqttHandleHass.forceUpdate(); } @@ -403,43 +313,17 @@ void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - const String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(1024); - const DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("order"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -457,6 +341,5 @@ void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg, WebApiError::InverterOrdered, "Inverter order saved!"); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } diff --git a/src/WebApi_limit.cpp b/src/WebApi_limit.cpp index b5b9e1726..9a622deae 100644 --- a/src/WebApi_limit.cpp +++ b/src/WebApi_limit.cpp @@ -47,8 +47,7 @@ void WebApiLimitClass::onLimitStatus(AsyncWebServerRequest* request) root[serial]["limit_set_status"] = limitStatus; } - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request) @@ -58,45 +57,19 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - const String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(1024); - const DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("serial") && root.containsKey("limit_value") && root.containsKey("limit_type"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -106,8 +79,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request) if (serial == 0) { retMsg["message"] = "Serial must be a number > 0!"; retMsg["code"] = WebApiError::LimitSerialZero; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -115,8 +87,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request) retMsg["message"] = "Limit must between 0 and " STR(MAX_INVERTER_LIMIT) "!"; retMsg["code"] = WebApiError::LimitInvalidLimit; retMsg["param"]["max"] = MAX_INVERTER_LIMIT; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -127,8 +98,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request) retMsg["message"] = "Invalid type specified!"; retMsg["code"] = WebApiError::LimitInvalidType; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -139,8 +109,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request) if (inv == nullptr) { retMsg["message"] = "Invalid inverter specified!"; retMsg["code"] = WebApiError::LimitInvalidInverter; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -150,6 +119,5 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request) retMsg["message"] = "Settings saved!"; retMsg["code"] = WebApiError::GenericSuccess; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } diff --git a/src/WebApi_maintenance.cpp b/src/WebApi_maintenance.cpp index ba257efa8..1504f9d75 100644 --- a/src/WebApi_maintenance.cpp +++ b/src/WebApi_maintenance.cpp @@ -22,44 +22,18 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request) return; } - AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - const String json = request->getParam("data", true)->value(); - - if (json.length() > MQTT_JSON_DOC_SIZE) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(MQTT_JSON_DOC_SIZE); - const DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("reboot"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -68,14 +42,12 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request) retMsg["message"] = "Reboot triggered!"; retMsg["code"] = WebApiError::MaintenanceRebootTriggered; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); Utils::restartDtu(); } else { retMsg["message"] = "Reboot cancled!"; retMsg["code"] = WebApiError::MaintenanceRebootCancled; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } } diff --git a/src/WebApi_mqtt.cpp b/src/WebApi_mqtt.cpp index 9e7411bfe..a032a34db 100644 --- a/src/WebApi_mqtt.cpp +++ b/src/WebApi_mqtt.cpp @@ -30,7 +30,7 @@ void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request) return; } - AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); + AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); const CONFIG_T& config = Configuration.get(); @@ -55,8 +55,7 @@ void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request) root["mqtt_hass_topic"] = config.Mqtt.Hass.Topic; root["mqtt_hass_individualpanels"] = config.Mqtt.Hass.IndividualPanels; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request) @@ -65,7 +64,7 @@ void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request) return; } - AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); + AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); const CONFIG_T& config = Configuration.get(); @@ -94,8 +93,7 @@ void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request) root["mqtt_hass_topic"] = config.Mqtt.Hass.Topic; root["mqtt_hass_individualpanels"] = config.Mqtt.Hass.IndividualPanels; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) @@ -104,38 +102,13 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) return; } - AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - const String json = request->getParam("data", true)->value(); - - if (json.length() > MQTT_JSON_DOC_SIZE) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(MQTT_JSON_DOC_SIZE); - const DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("mqtt_enabled") && root.containsKey("mqtt_verbose_logging") @@ -162,8 +135,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) && root.containsKey("mqtt_hass_individualpanels"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -172,8 +144,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) retMsg["message"] = "MqTT Server must between 1 and " STR(MQTT_MAX_HOSTNAME_STRLEN) " characters long!"; retMsg["code"] = WebApiError::MqttHostnameLength; retMsg["param"]["max"] = MQTT_MAX_HOSTNAME_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -181,48 +152,42 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) retMsg["message"] = "Username must not be longer than " STR(MQTT_MAX_USERNAME_STRLEN) " characters!"; retMsg["code"] = WebApiError::MqttUsernameLength; retMsg["param"]["max"] = MQTT_MAX_USERNAME_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (root["mqtt_password"].as().length() > MQTT_MAX_PASSWORD_STRLEN) { retMsg["message"] = "Password must not be longer than " STR(MQTT_MAX_PASSWORD_STRLEN) " characters!"; retMsg["code"] = WebApiError::MqttPasswordLength; retMsg["param"]["max"] = MQTT_MAX_PASSWORD_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (root["mqtt_topic"].as().length() > MQTT_MAX_TOPIC_STRLEN) { retMsg["message"] = "Topic must not be longer than " STR(MQTT_MAX_TOPIC_STRLEN) " characters!"; retMsg["code"] = WebApiError::MqttTopicLength; retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (root["mqtt_topic"].as().indexOf(' ') != -1) { retMsg["message"] = "Topic must not contain space characters!"; retMsg["code"] = WebApiError::MqttTopicCharacter; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (!root["mqtt_topic"].as().endsWith("/")) { retMsg["message"] = "Topic must end with a slash (/)!"; retMsg["code"] = WebApiError::MqttTopicTrailingSlash; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (root["mqtt_port"].as() == 0 || root["mqtt_port"].as() > 65535) { retMsg["message"] = "Port must be a number between 1 and 65535!"; retMsg["code"] = WebApiError::MqttPort; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -232,8 +197,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) retMsg["message"] = "Certificates must not be longer than " STR(MQTT_MAX_CERT_STRLEN) " characters!"; retMsg["code"] = WebApiError::MqttCertificateLength; retMsg["param"]["max"] = MQTT_MAX_CERT_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -241,16 +205,14 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) retMsg["message"] = "LWT topic must not be longer than " STR(MQTT_MAX_TOPIC_STRLEN) " characters!"; retMsg["code"] = WebApiError::MqttLwtTopicLength; retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (root["mqtt_lwt_topic"].as().indexOf(' ') != -1) { retMsg["message"] = "LWT topic must not contain space characters!"; retMsg["code"] = WebApiError::MqttLwtTopicCharacter; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -258,8 +220,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) retMsg["message"] = "LWT online value must not be longer than " STR(MQTT_MAX_LWTVALUE_STRLEN) " characters!"; retMsg["code"] = WebApiError::MqttLwtOnlineLength; retMsg["param"]["max"] = MQTT_MAX_LWTVALUE_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -267,8 +228,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) retMsg["message"] = "LWT offline value must not be longer than " STR(MQTT_MAX_LWTVALUE_STRLEN) " characters!"; retMsg["code"] = WebApiError::MqttLwtOfflineLength; retMsg["param"]["max"] = MQTT_MAX_LWTVALUE_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -276,8 +236,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) retMsg["message"] = "LWT QoS must not be greater than " STR(2) "!"; retMsg["code"] = WebApiError::MqttLwtQos; retMsg["param"]["max"] = 2; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -286,8 +245,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) retMsg["code"] = WebApiError::MqttPublishInterval; retMsg["param"]["min"] = 5; retMsg["param"]["max"] = 65535; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -296,16 +254,14 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) retMsg["message"] = "Hass topic must not be longer than " STR(MQTT_MAX_TOPIC_STRLEN) " characters!"; retMsg["code"] = WebApiError::MqttHassTopicLength; retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (root["mqtt_hass_topic"].as().indexOf(' ') != -1) { retMsg["message"] = "Hass topic must not contain space characters!"; retMsg["code"] = WebApiError::MqttHassTopicCharacter; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } } @@ -339,8 +295,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); MqttSettings.performReconnect(); MqttHandleHass.forceUpdate(); diff --git a/src/WebApi_network.cpp b/src/WebApi_network.cpp index 12f637adc..7fec44b2a 100644 --- a/src/WebApi_network.cpp +++ b/src/WebApi_network.cpp @@ -46,8 +46,7 @@ void WebApiNetworkClass::onNetworkStatus(AsyncWebServerRequest* request) root["ap_mac"] = WiFi.softAPmacAddress(); root["ap_stationnum"] = WiFi.softAPgetStationNum(); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request) @@ -72,8 +71,7 @@ void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request) root["aptimeout"] = config.WiFi.ApTimeout; root["mdnsenabled"] = config.Mdns.Enabled; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) @@ -83,37 +81,12 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - const String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(1024); - const DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("ssid") && root.containsKey("password") @@ -127,8 +100,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) && root.containsKey("aptimeout"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -136,68 +108,59 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) if (!ipaddress.fromString(root["ipaddress"].as())) { retMsg["message"] = "IP address is invalid!"; retMsg["code"] = WebApiError::NetworkIpInvalid; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } IPAddress netmask; if (!netmask.fromString(root["netmask"].as())) { retMsg["message"] = "Netmask is invalid!"; retMsg["code"] = WebApiError::NetworkNetmaskInvalid; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } IPAddress gateway; if (!gateway.fromString(root["gateway"].as())) { retMsg["message"] = "Gateway is invalid!"; retMsg["code"] = WebApiError::NetworkGatewayInvalid; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } IPAddress dns1; if (!dns1.fromString(root["dns1"].as())) { retMsg["message"] = "DNS Server IP 1 is invalid!"; retMsg["code"] = WebApiError::NetworkDns1Invalid; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } IPAddress dns2; if (!dns2.fromString(root["dns2"].as())) { retMsg["message"] = "DNS Server IP 2 is invalid!"; retMsg["code"] = WebApiError::NetworkDns2Invalid; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (root["hostname"].as().length() == 0 || root["hostname"].as().length() > WIFI_MAX_HOSTNAME_STRLEN) { retMsg["message"] = "Hostname must between 1 and " STR(WIFI_MAX_HOSTNAME_STRLEN) " characters long!"; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (NetworkSettings.NetworkMode() == network_mode::WiFi) { if (root["ssid"].as().length() == 0 || root["ssid"].as().length() > WIFI_MAX_SSID_STRLEN) { retMsg["message"] = "SSID must between 1 and " STR(WIFI_MAX_SSID_STRLEN) " characters long!"; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } } if (root["password"].as().length() > WIFI_MAX_PASSWORD_STRLEN - 1) { retMsg["message"] = "Password must not be longer than " STR(WIFI_MAX_PASSWORD_STRLEN) " characters long!"; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } if (root["aptimeout"].as() > 99999) { retMsg["message"] = "ApTimeout must be a number between 0 and 99999!"; retMsg["code"] = WebApiError::NetworkApTimeoutInvalid; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -235,8 +198,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); NetworkSettings.enableAdminMode(); NetworkSettings.applyConfig(); diff --git a/src/WebApi_ntp.cpp b/src/WebApi_ntp.cpp index 02bbfb105..d50e0f02f 100644 --- a/src/WebApi_ntp.cpp +++ b/src/WebApi_ntp.cpp @@ -63,8 +63,7 @@ void WebApiNtpClass::onNtpStatus(AsyncWebServerRequest* request) root["sun_isSunsetAvailable"] = SunPosition.isSunsetAvailable(); root["sun_isDayPeriod"] = SunPosition.isDayPeriod(); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiNtpClass::onNtpAdminGet(AsyncWebServerRequest* request) @@ -84,8 +83,7 @@ void WebApiNtpClass::onNtpAdminGet(AsyncWebServerRequest* request) root["latitude"] = config.Ntp.Latitude; root["sunsettype"] = config.Ntp.SunsetType; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request) @@ -95,37 +93,12 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - const String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(1024); - const DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("ntp_server") && root.containsKey("ntp_timezone") @@ -134,8 +107,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request) && root.containsKey("sunsettype"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -143,8 +115,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request) retMsg["message"] = "NTP Server must between 1 and " STR(NTP_MAX_SERVER_STRLEN) " characters long!"; retMsg["code"] = WebApiError::NtpServerLength; retMsg["param"]["max"] = NTP_MAX_SERVER_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -152,8 +123,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request) retMsg["message"] = "Timezone must between 1 and " STR(NTP_MAX_TIMEZONE_STRLEN) " characters long!"; retMsg["code"] = WebApiError::NtpTimezoneLength; retMsg["param"]["max"] = NTP_MAX_TIMEZONE_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -161,8 +131,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request) retMsg["message"] = "Timezone description must between 1 and " STR(NTP_MAX_TIMEZONEDESCR_STRLEN) " characters long!"; retMsg["code"] = WebApiError::NtpTimezoneDescriptionLength; retMsg["param"]["max"] = NTP_MAX_TIMEZONEDESCR_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -176,8 +145,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); NtpSettings.setServer(); NtpSettings.setTimezone(); @@ -208,8 +176,7 @@ void WebApiNtpClass::onNtpTimeGet(AsyncWebServerRequest* request) root["minute"] = timeinfo.tm_min; root["second"] = timeinfo.tm_sec; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request) @@ -219,37 +186,12 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - const String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(1024); - const DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("year") && root.containsKey("month") @@ -259,8 +201,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request) && root.containsKey("second"))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -269,8 +210,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request) retMsg["code"] = WebApiError::NtpYearInvalid; retMsg["param"]["min"] = 2022; retMsg["param"]["max"] = 2100; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -279,8 +219,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request) retMsg["code"] = WebApiError::NtpMonthInvalid; retMsg["param"]["min"] = 1; retMsg["param"]["max"] = 12; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -289,8 +228,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request) retMsg["code"] = WebApiError::NtpDayInvalid; retMsg["param"]["min"] = 1; retMsg["param"]["max"] = 31; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -299,8 +237,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request) retMsg["code"] = WebApiError::NtpHourInvalid; retMsg["param"]["min"] = 0; retMsg["param"]["max"] = 23; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -309,8 +246,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request) retMsg["code"] = WebApiError::NtpMinuteInvalid; retMsg["param"]["min"] = 0; retMsg["param"]["max"] = 59; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -319,8 +255,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request) retMsg["code"] = WebApiError::NtpSecondInvalid; retMsg["param"]["min"] = 0; retMsg["param"]["max"] = 59; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -341,6 +276,5 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request) retMsg["message"] = "Time updated!"; retMsg["code"] = WebApiError::NtpTimeUpdated; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } diff --git a/src/WebApi_power.cpp b/src/WebApi_power.cpp index 08fe9c051..b2b2ce42e 100644 --- a/src/WebApi_power.cpp +++ b/src/WebApi_power.cpp @@ -40,8 +40,7 @@ void WebApiPowerClass::onPowerStatus(AsyncWebServerRequest* request) root[inv->serialString()]["power_set_status"] = limitStatus; } - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request) @@ -51,45 +50,19 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - const String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(1024); - const DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("serial") && (root.containsKey("power") || root.containsKey("restart")))) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -99,8 +72,7 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request) if (serial == 0) { retMsg["message"] = "Serial must be a number > 0!"; retMsg["code"] = WebApiError::PowerSerialZero; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -108,8 +80,7 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request) if (inv == nullptr) { retMsg["message"] = "Invalid inverter specified!"; retMsg["code"] = WebApiError::PowerInvalidInverter; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -126,6 +97,5 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request) retMsg["message"] = "Settings saved!"; retMsg["code"] = WebApiError::GenericSuccess; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } diff --git a/src/WebApi_powerlimiter.cpp b/src/WebApi_powerlimiter.cpp index 4c1ebc280..114ced773 100644 --- a/src/WebApi_powerlimiter.cpp +++ b/src/WebApi_powerlimiter.cpp @@ -27,10 +27,9 @@ void WebApiPowerLimiterClass::init(AsyncWebServer& server, Scheduler& scheduler) void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request) { - auto const& config = Configuration.get(); - - AsyncJsonResponse* response = new AsyncJsonResponse(false, 512); + AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); + auto const& config = Configuration.get(); root["enabled"] = config.PowerLimiter.Enabled; root["verbose_logging"] = config.PowerLimiter.VerboseLogging; @@ -57,8 +56,7 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request) 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); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiPowerLimiterClass::onMetaData(AsyncWebServerRequest* request) @@ -72,14 +70,14 @@ void WebApiPowerLimiterClass::onMetaData(AsyncWebServerRequest* request) if (config.Inverter[i].Serial != 0) { ++invAmount; } } - AsyncJsonResponse* response = new AsyncJsonResponse(false, 256 + 256 * invAmount); + AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); root["power_meter_enabled"] = config.PowerMeter.Enabled; root["battery_enabled"] = config.Battery.Enabled; root["charge_controller_enabled"] = config.Vedirect.Enabled; - JsonObject inverters = root.createNestedObject("inverters"); + JsonObject inverters = root["inverters"].to(); for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { if (config.Inverter[i].Serial == 0) { continue; } @@ -87,7 +85,7 @@ void WebApiPowerLimiterClass::onMetaData(AsyncWebServerRequest* request) // rather than the hex represenation as used when handling the inverter // serial elsewhere in the web application, because in this case, the // serial is actually not displayed but only used as a value/index. - JsonObject obj = inverters.createNestedObject(String(config.Inverter[i].Serial)); + JsonObject obj = inverters[String(config.Inverter[i].Serial)].to(); obj["pos"] = i; obj["name"] = String(config.Inverter[i].Name); obj["poll_enable"] = config.Inverter[i].Poll_Enable; @@ -105,8 +103,7 @@ void WebApiPowerLimiterClass::onMetaData(AsyncWebServerRequest* request) } } - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiPowerLimiterClass::onAdminGet(AsyncWebServerRequest* request) @@ -125,34 +122,12 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - response->setLength(); - request->send(response); - return; - } - - DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); // we were not actually checking for all the keys we (unconditionally) // access below for a long time, and it is technically not needed if users diff --git a/src/WebApi_powermeter.cpp b/src/WebApi_powermeter.cpp index bd708c4f3..8ca492b01 100644 --- a/src/WebApi_powermeter.cpp +++ b/src/WebApi_powermeter.cpp @@ -45,7 +45,7 @@ void WebApiPowerMeterClass::decodeJsonPhaseConfig(JsonObject const& json, PowerM void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request) { - AsyncJsonResponse* response = new AsyncJsonResponse(false, 2048); + AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); const CONFIG_T& config = Configuration.get(); @@ -60,11 +60,11 @@ void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request) root["sdmaddress"] = config.PowerMeter.SdmAddress; root["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests; - JsonArray httpPhases = root.createNestedArray("http_phases"); - + auto httpPhases = root["http_phases"].to(); + for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) { - JsonObject phaseObject = httpPhases.createNestedObject(); - + auto phaseObject = httpPhases.add(); + phaseObject["index"] = i + 1; phaseObject["enabled"] = config.PowerMeter.Http_Phase[i].Enabled; phaseObject["url"] = String(config.PowerMeter.Http_Phase[i].Url); @@ -79,8 +79,7 @@ void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request) phaseObject["sign_inverted"] = config.PowerMeter.Http_Phase[i].SignInverted; } - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiPowerMeterClass::onAdminGet(AsyncWebServerRequest* request) @@ -99,34 +98,12 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - response->setLength(); - request->send(response); - return; - } - - String json = request->getParam("data", true)->value(); - - if (json.length() > 4096) { - retMsg["message"] = "Data too large!"; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(4096); - DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!(root.containsKey("enabled") && root.containsKey("source"))) { retMsg["message"] = "Values are missing!"; @@ -201,8 +178,8 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); + // reboot requiered as per https://github.com/helgeerbe/OpenDTU-OnBattery/issues/565#issuecomment-1872552559 yield(); @@ -218,34 +195,12 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request) } AsyncJsonResponse* asyncJsonResponse = new AsyncJsonResponse(); - auto& retMsg = asyncJsonResponse->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - asyncJsonResponse->setLength(); - request->send(asyncJsonResponse); - return; - } - - String json = request->getParam("data", true)->value(); - - if (json.length() > 2048) { - retMsg["message"] = "Data too large!"; - asyncJsonResponse->setLength(); - request->send(asyncJsonResponse); + JsonDocument root; + if (!WebApi.parseRequestData(request, asyncJsonResponse, root)) { return; } - DynamicJsonDocument root(2048); - DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - asyncJsonResponse->setLength(); - request->send(asyncJsonResponse); - return; - } + auto& retMsg = asyncJsonResponse->getRoot(); if (!root.containsKey("url") || !root.containsKey("auth_type") || !root.containsKey("username") || !root.containsKey("password") || !root.containsKey("header_key") || !root.containsKey("header_value") diff --git a/src/WebApi_security.cpp b/src/WebApi_security.cpp index b95ebb299..eb0f27d20 100644 --- a/src/WebApi_security.cpp +++ b/src/WebApi_security.cpp @@ -31,8 +31,7 @@ void WebApiSecurityClass::onSecurityGet(AsyncWebServerRequest* request) root["password"] = config.Security.Password; root["allow_readonly"] = config.Security.AllowReadonly; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request) @@ -42,44 +41,18 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - const String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(1024); - const DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!root.containsKey("password") && root.containsKey("allow_readonly")) { retMsg["message"] = "Values are missing!"; retMsg["code"] = WebApiError::GenericValueMissing; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -87,8 +60,7 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request) retMsg["message"] = "Password must between 8 and " STR(WIFI_MAX_PASSWORD_STRLEN) " characters long!"; retMsg["code"] = WebApiError::SecurityPasswordLength; retMsg["param"]["max"] = WIFI_MAX_PASSWORD_STRLEN; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); return; } @@ -98,8 +70,7 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request) @@ -114,6 +85,5 @@ void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request) retMsg["message"] = "Authentication successful!"; retMsg["code"] = WebApiError::SecurityAuthSuccess; - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } diff --git a/src/WebApi_sysstatus.cpp b/src/WebApi_sysstatus.cpp index b8c366b35..62495063b 100644 --- a/src/WebApi_sysstatus.cpp +++ b/src/WebApi_sysstatus.cpp @@ -81,6 +81,5 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request) root["cmt_configured"] = PinMapping.isValidCmt2300Config(); root["cmt_connected"] = Hoymiles.getRadioCmt()->isConnected(); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } diff --git a/src/WebApi_vedirect.cpp b/src/WebApi_vedirect.cpp index 4e1e352b2..2499ebed3 100644 --- a/src/WebApi_vedirect.cpp +++ b/src/WebApi_vedirect.cpp @@ -66,37 +66,12 @@ void WebApiVedirectClass::onVedirectAdminPost(AsyncWebServerRequest* request) } AsyncJsonResponse* response = new AsyncJsonResponse(); - auto& retMsg = response->getRoot(); - retMsg["type"] = "warning"; - - if (!request->hasParam("data", true)) { - retMsg["message"] = "No values found!"; - retMsg["code"] = WebApiError::GenericNoValueFound; - response->setLength(); - request->send(response); - return; - } - - String json = request->getParam("data", true)->value(); - - if (json.length() > 1024) { - retMsg["message"] = "Data too large!"; - retMsg["code"] = WebApiError::GenericDataTooLarge; - response->setLength(); - request->send(response); + JsonDocument root; + if (!WebApi.parseRequestData(request, response, root)) { return; } - DynamicJsonDocument root(1024); - DeserializationError error = deserializeJson(root, json); - - if (error) { - retMsg["message"] = "Failed to parse data!"; - retMsg["code"] = WebApiError::GenericParseError; - response->setLength(); - request->send(response); - return; - } + auto& retMsg = response->getRoot(); if (!root.containsKey("vedirect_enabled") || !root.containsKey("verbose_logging") || @@ -115,8 +90,8 @@ void WebApiVedirectClass::onVedirectAdminPost(AsyncWebServerRequest* request) WebApi.writeConfig(retMsg); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); + VictronMppt.updateSettings(); diff --git a/src/WebApi_ws_Huawei.cpp b/src/WebApi_ws_Huawei.cpp index c674e05d6..e8e23615a 100644 --- a/src/WebApi_ws_Huawei.cpp +++ b/src/WebApi_ws_Huawei.cpp @@ -59,22 +59,15 @@ void WebApiWsHuaweiLiveClass::sendDataTaskCb() try { std::lock_guard lock(_mutex); - DynamicJsonDocument root(1024); - if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { - JsonVariant var = root; - generateJsonResponse(var); + JsonDocument root; + JsonVariant var = root; - if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } + generateCommonJsonResponse(var); + if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { String buffer; serializeJson(root, buffer); - if (Configuration.get().Security.AllowReadonly) { - _ws.setAuthentication("", ""); - } else { - _ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password); - } - _ws.textAll(buffer); } } catch (std::bad_alloc& bad_alloc) { @@ -84,7 +77,7 @@ void WebApiWsHuaweiLiveClass::sendDataTaskCb() } } -void WebApiWsHuaweiLiveClass::generateJsonResponse(JsonVariant& root) +void WebApiWsHuaweiLiveClass::generateCommonJsonResponse(JsonVariant& root) { const RectifierParameters_t * rp = HuaweiCan.get(); @@ -134,13 +127,13 @@ void WebApiWsHuaweiLiveClass::onLivedataStatus(AsyncWebServerRequest* request) } try { std::lock_guard lock(_mutex); - AsyncJsonResponse* response = new AsyncJsonResponse(false, 1024U); + AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); - generateJsonResponse(root); + generateCommonJsonResponse(root); + + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); - response->setLength(); - request->send(response); } catch (std::bad_alloc& bad_alloc) { MessageOutput.printf("Calling /api/huaweilivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what()); WebApi.sendTooManyRequests(request); diff --git a/src/WebApi_ws_battery.cpp b/src/WebApi_ws_battery.cpp index 39aaf7289..466540228 100644 --- a/src/WebApi_ws_battery.cpp +++ b/src/WebApi_ws_battery.cpp @@ -62,12 +62,12 @@ void WebApiWsBatteryLiveClass::sendDataTaskCb() try { std::lock_guard lock(_mutex); - DynamicJsonDocument root(_responseSize); - if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { - JsonVariant var = root; - generateJsonResponse(var); + JsonDocument root; + JsonVariant var = root; + + generateCommonJsonResponse(var); - if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } + if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { // battery provider does not generate a card, e.g., MQTT provider if (root.isNull()) { return; } @@ -90,7 +90,7 @@ void WebApiWsBatteryLiveClass::sendDataTaskCb() } } -void WebApiWsBatteryLiveClass::generateJsonResponse(JsonVariant& root) +void WebApiWsBatteryLiveClass::generateCommonJsonResponse(JsonVariant& root) { Battery.getStats()->getLiveViewData(root); } @@ -111,12 +111,11 @@ void WebApiWsBatteryLiveClass::onLivedataStatus(AsyncWebServerRequest* request) } try { std::lock_guard lock(_mutex); - AsyncJsonResponse* response = new AsyncJsonResponse(false, _responseSize); + AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); - generateJsonResponse(root); + generateCommonJsonResponse(root); - response->setLength(); - request->send(response); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } 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); diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index 505f167ae..ab54d479f 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -61,11 +61,11 @@ void WebApiWsLiveClass::generateOnBatteryJsonResponse(JsonVariant& root, bool al auto victronAge = VictronMppt.getDataAgeMillis(); if (all || (victronAge > 0 && (millis() - _lastPublishVictron) > victronAge)) { - JsonObject vedirectObj = root.createNestedObject("vedirect"); + auto vedirectObj = root["vedirect"].to(); vedirectObj["enabled"] = config.Vedirect.Enabled; if (config.Vedirect.Enabled) { - JsonObject totalVeObj = vedirectObj.createNestedObject("total"); + auto totalVeObj = vedirectObj["total"].to(); addTotalField(totalVeObj, "Power", VictronMppt.getPanelPowerWatts(), "W", 1); addTotalField(totalVeObj, "YieldDay", VictronMppt.getYieldDay() * 1000, "Wh", 0); addTotalField(totalVeObj, "YieldTotal", VictronMppt.getYieldTotal(), "kWh", 2); @@ -75,7 +75,7 @@ void WebApiWsLiveClass::generateOnBatteryJsonResponse(JsonVariant& root, bool al } if (all || (HuaweiCan.getLastUpdate() - _lastPublishHuawei) < halfOfAllMillis ) { - JsonObject huaweiObj = root.createNestedObject("huawei"); + auto huaweiObj = root["huawei"].to(); huaweiObj["enabled"] = config.Huawei.Enabled; if (config.Huawei.Enabled) { @@ -88,7 +88,7 @@ void WebApiWsLiveClass::generateOnBatteryJsonResponse(JsonVariant& root, bool al auto spStats = Battery.getStats(); if (all || spStats->updateAvailable(_lastPublishBattery)) { - JsonObject batteryObj = root.createNestedObject("battery"); + auto batteryObj = root["battery"].to(); batteryObj["enabled"] = config.Battery.Enabled; if (config.Battery.Enabled) { @@ -99,7 +99,7 @@ void WebApiWsLiveClass::generateOnBatteryJsonResponse(JsonVariant& root, bool al } if (all || (PowerMeter.getLastPowerMeterUpdate() - _lastPublishPowerMeter) < halfOfAllMillis) { - JsonObject powerMeterObj = root.createNestedObject("power_meter"); + auto powerMeterObj = root["power_meter"].to(); powerMeterObj["enabled"] = config.PowerMeter.Enabled; if (config.PowerMeter.Enabled) { @@ -112,9 +112,7 @@ void WebApiWsLiveClass::generateOnBatteryJsonResponse(JsonVariant& root, bool al void WebApiWsLiveClass::sendOnBatteryStats() { - DynamicJsonDocument root(1024); - if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { return; } - + JsonDocument root; JsonVariant var = root; bool all = (millis() - _lastPublishOnBatteryFull) > 10 * 1000; @@ -123,12 +121,12 @@ void WebApiWsLiveClass::sendOnBatteryStats() if (root.isNull()) { return; } - if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } - - String buffer; - serializeJson(root, buffer); + if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { + String buffer; + serializeJson(root, buffer); - _ws.textAll(buffer); + _ws.textAll(buffer);; + } } void WebApiWsLiveClass::sendDataTaskCb() @@ -156,19 +154,20 @@ void WebApiWsLiveClass::sendDataTaskCb() try { std::lock_guard lock(_mutex); - DynamicJsonDocument root(4096); - if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { - continue; - } + JsonDocument root; JsonVariant var = root; - auto invArray = var.createNestedArray("inverters"); - auto invObject = invArray.createNestedObject(); + auto invArray = var["inverters"].to(); + auto invObject = invArray.add(); generateCommonJsonResponse(var); generateInverterCommonJsonResponse(invObject, inv); generateInverterChannelJsonResponse(invObject, inv); + if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { + continue; + } + String buffer; serializeJson(root, buffer); @@ -184,12 +183,12 @@ void WebApiWsLiveClass::sendDataTaskCb() void WebApiWsLiveClass::generateCommonJsonResponse(JsonVariant& root) { - JsonObject totalObj = root.createNestedObject("total"); + auto totalObj = root["total"].to(); addTotalField(totalObj, "Power", Datastore.getTotalAcPowerEnabled(), "W", Datastore.getTotalAcPowerDigits()); addTotalField(totalObj, "YieldDay", Datastore.getTotalAcYieldDayEnabled(), "Wh", Datastore.getTotalAcYieldDayDigits()); addTotalField(totalObj, "YieldTotal", Datastore.getTotalAcYieldTotalEnabled(), "kWh", Datastore.getTotalAcYieldTotalDigits()); - JsonObject hintObj = root.createNestedObject("hints"); + JsonObject hintObj = root["hints"].to(); struct tm timeinfo; hintObj["time_sync"] = !getLocalTime(&timeinfo, 5); hintObj["radio_problem"] = (Hoymiles.getRadioNrf()->isInitialized() && (!Hoymiles.getRadioNrf()->isConnected() || !Hoymiles.getRadioNrf()->isPVariant())) || (Hoymiles.getRadioCmt()->isInitialized() && (!Hoymiles.getRadioCmt()->isConnected())); @@ -227,7 +226,7 @@ void WebApiWsLiveClass::generateInverterChannelJsonResponse(JsonObject& root, st // Loop all channels for (auto& t : inv->Statistics()->getChannelTypes()) { - JsonObject chanTypeObj = root.createNestedObject(inv->Statistics()->getChannelTypeName(t)); + auto chanTypeObj = root[inv->Statistics()->getChannelTypeName(t)].to(); for (auto& c : inv->Statistics()->getChannelsByType(t)) { if (t == TYPE_DC) { chanTypeObj[String(static_cast(c))]["name"]["u"] = inv_cfg->channel[c].Name; @@ -304,21 +303,15 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request) try { std::lock_guard lock(_mutex); - AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096); + AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); - - JsonArray invArray = root.createNestedArray("inverters"); - - uint64_t serial = 0; - if (request->hasParam("inv")) { - String s = request->getParam("inv")->value(); - serial = strtoll(s.c_str(), NULL, 16); - } + auto invArray = root["inverters"].to(); + auto serial = WebApi.parseSerialFromRequest(request); if (serial > 0) { auto inv = Hoymiles.getInverterBySerial(serial); if (inv != nullptr) { - JsonObject invObject = invArray.createNestedObject(); + JsonObject invObject = invArray.add(); generateInverterCommonJsonResponse(invObject, inv); generateInverterChannelJsonResponse(invObject, inv); } @@ -330,7 +323,7 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request) continue; } - JsonObject invObject = invArray.createNestedObject(); + JsonObject invObject = invArray.add(); generateInverterCommonJsonResponse(invObject, inv); } } @@ -339,9 +332,10 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request) generateOnBatteryJsonResponse(root, true); - response->setLength(); - request->send(response); + generateOnBatteryJsonResponse(root, true); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); + } catch (const std::bad_alloc& bad_alloc) { MessageOutput.printf("Calling /api/livedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what()); WebApi.sendTooManyRequests(request); diff --git a/src/WebApi_ws_vedirect_live.cpp b/src/WebApi_ws_vedirect_live.cpp index b113cecf0..9a92b363d 100644 --- a/src/WebApi_ws_vedirect_live.cpp +++ b/src/WebApi_ws_vedirect_live.cpp @@ -86,25 +86,17 @@ void WebApiWsVedirectLiveClass::sendDataTaskCb() if (fullUpdate || updateAvailable) { try { std::lock_guard lock(_mutex); - DynamicJsonDocument root(responseSize()); - if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { - JsonVariant var = root; - generateJsonResponse(var, fullUpdate); + JsonDocument root; + JsonVariant var = root; - if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } + generateCommonJsonResponse(var, fullUpdate); + if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { String buffer; serializeJson(root, buffer); - if (Configuration.get().Security.AllowReadonly) { - _ws.setAuthentication("", ""); - } else { - _ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password); - } - - _ws.textAll(buffer); + _ws.textAll(buffer);; } - } 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) { @@ -117,9 +109,9 @@ void WebApiWsVedirectLiveClass::sendDataTaskCb() } } -void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root, bool fullUpdate) +void WebApiWsVedirectLiveClass::generateCommonJsonResponse(JsonVariant& root, bool fullUpdate) { - const JsonObject &array = root["vedirect"].createNestedObject("instances"); + auto array = root["vedirect"]["instances"].to(); root["vedirect"]["full_update"] = fullUpdate; for (size_t idx = 0; idx < VictronMppt.controllerAmount(); ++idx) { @@ -131,7 +123,7 @@ void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root, bool ful String serial(optMpptData->serialNr_SER); if (serial.isEmpty()) { continue; } // serial required as index - const JsonObject &nested = array.createNestedObject(serial); + JsonObject nested = array[serial].to(); nested["data_age_ms"] = VictronMppt.getDataAgeMillis(idx); populateJson(nested, *optMpptData); } @@ -149,9 +141,9 @@ void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDir root["product_id"] = mpptData.getPidAsString(); root["firmware_version"] = String(mpptData.firmwareNr_FW); - const JsonObject &values = root.createNestedObject("values"); + const JsonObject values = root["values"].to(); - const JsonObject &device = values.createNestedObject("device"); + const JsonObject device = values["device"].to(); device["LOAD"] = mpptData.loadOutputState_LOAD ? "ON" : "OFF"; device["CS"] = mpptData.getCsAsString(); device["MPPT"] = mpptData.getMpptAsString(); @@ -165,7 +157,7 @@ void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDir device["MpptTemperature"]["d"] = "1"; } - const JsonObject &output = values.createNestedObject("output"); + const JsonObject output = values["output"].to(); output["P"]["v"] = mpptData.batteryOutputPower_W; output["P"]["u"] = "W"; output["P"]["d"] = 0; @@ -179,7 +171,7 @@ void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDir output["E"]["u"] = "%"; output["E"]["d"] = 1; - const JsonObject &input = values.createNestedObject("input"); + const JsonObject input = values["input"].to(); if (mpptData.NetworkTotalDcInputPowerMilliWatts.first > 0) { input["NetworkPower"]["v"] = mpptData.NetworkTotalDcInputPowerMilliWatts.second / 1000.0; input["NetworkPower"]["u"] = "W"; @@ -233,14 +225,12 @@ void WebApiWsVedirectLiveClass::onLivedataStatus(AsyncWebServerRequest* request) } try { std::lock_guard lock(_mutex); - AsyncJsonResponse* response = new AsyncJsonResponse(false, responseSize()); + AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); - generateJsonResponse(root, true/*fullUpdate*/); - - response->setLength(); - request->send(response); + generateCommonJsonResponse(root, true/*fullUpdate*/); + WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } 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()); WebApi.sendTooManyRequests(request); diff --git a/webapp/.eslintrc.cjs b/webapp/.eslintrc.cjs deleted file mode 100644 index ade85716e..000000000 --- a/webapp/.eslintrc.cjs +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-env node */ -require('@rushstack/eslint-patch/modern-module-resolution') - -module.exports = { - root: true, - 'extends': [ - 'plugin:vue/vue3-essential', - 'eslint:recommended', - '@vue/eslint-config-typescript' - ], - parserOptions: { - ecmaVersion: 'latest' - } -} diff --git a/webapp/eslint.config.js b/webapp/eslint.config.js new file mode 100644 index 000000000..91657b987 --- /dev/null +++ b/webapp/eslint.config.js @@ -0,0 +1,36 @@ +/* eslint-env node */ +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +import { FlatCompat } from "@eslint/eslintrc"; +import js from "@eslint/js"; +import pluginVue from 'eslint-plugin-vue' + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, +}); + +export default [ + js.configs.recommended, + ...pluginVue.configs['flat/essential'], + ...compat.extends("@vue/eslint-config-typescript/recommended"), + { + files: [ + "**/*.vue", + "**/*.js", + "**/*.jsx", + "**/*.cjs", + "**/*.mjs", + "**/*.ts", + "**/*.tsx", + "**/*.cts", + "**/*.mts", + ], + languageOptions: { + ecmaVersion: 'latest' + }, + } + ] diff --git a/webapp/package.json b/webapp/package.json index 5e92ee74d..86b30904f 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -9,7 +9,7 @@ "preview": "vite preview --port 4173", "build-only": "vite build", "type-check": "vue-tsc --noEmit", - "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" + "lint": "eslint ." }, "dependencies": { "@popperjs/core": "^2.11.8", @@ -19,31 +19,30 @@ "sortablejs": "^1.15.2", "spark-md5": "^3.0.2", "vue": "^3.4.21", - "vue-i18n": "^9.10.2", + "vue-i18n": "^9.12.0", "vue-router": "^4.3.0" }, "devDependencies": { "@intlify/unplugin-vue-i18n": "^4.0.0", - "@rushstack/eslint-patch": "^1.10.1", "@tsconfig/node18": "^18.2.4", "@types/bootstrap": "^5.2.10", - "@types/node": "^20.12.2", + "@types/node": "^20.12.7", "@types/pulltorefreshjs": "^0.1.7", "@types/sortablejs": "^1.15.8", "@types/spark-md5": "^3.0.4", "@vitejs/plugin-vue": "^5.0.4", "@vue/eslint-config-typescript": "^13.0.0", "@vue/tsconfig": "^0.5.1", - "eslint": "^8.57.0", - "eslint-plugin-vue": "^9.24.0", + "eslint": "^9.0.0", + "eslint-plugin-vue": "^9.24.1", "npm-run-all": "^4.1.5", "pulltorefreshjs": "^0.1.22", - "sass": "^1.72.0", - "terser": "^5.30.0", - "typescript": "^5.4.3", - "vite": "^5.2.7", + "sass": "^1.75.0", + "terser": "^5.30.3", + "typescript": "^5.4.5", + "vite": "^5.2.8", "vite-plugin-compression": "^0.5.1", "vite-plugin-css-injected-by-js": "^3.5.0", - "vue-tsc": "^2.0.7" + "vue-tsc": "^2.0.13" } } diff --git a/webapp/src/components/BasePage.vue b/webapp/src/components/BasePage.vue index 0ec43d36f..3ca9c12b5 100644 --- a/webapp/src/components/BasePage.vue +++ b/webapp/src/components/BasePage.vue @@ -48,15 +48,14 @@ export default defineComponent({ showReload: { type: Boolean, required: false, default: false }, }, mounted() { - var self = this; console.log("init"); PullToRefresh.init({ mainElement: 'body', // above which element? instructionsPullToRefresh: this.$t('base.Pull'), instructionsReleaseToRefresh: this.$t('base.Release'), instructionsRefreshing: this.$t('base.Refreshing'), - onRefresh: function() { - self.$emit('reload'); + onRefresh: () => { + this.$emit('reload'); } }); }, diff --git a/webapp/src/components/BootstrapAlert.vue b/webapp/src/components/BootstrapAlert.vue index df96fb620..a629863db 100644 --- a/webapp/src/components/BootstrapAlert.vue +++ b/webapp/src/components/BootstrapAlert.vue @@ -52,7 +52,7 @@ export default defineComponent({ _countDownTimeout = undefined; }; - var countDown = ref(); + const countDown = ref(); watch(() => props.modelValue, () => { countDown.value = parseCountDown(props.modelValue); }); @@ -116,4 +116,4 @@ export default defineComponent({ }; }, }); - \ No newline at end of file + diff --git a/webapp/src/components/FirmwareInfo.vue b/webapp/src/components/FirmwareInfo.vue index 531187872..af6c95c05 100644 --- a/webapp/src/components/FirmwareInfo.vue +++ b/webapp/src/components/FirmwareInfo.vue @@ -87,10 +87,10 @@ export default defineComponent({ }, computed: { modelAllowVersionInfo: { - get(): any { + get(): boolean { return !!this.allowVersionInfo; }, - set(value: any) { + set(value: boolean) { this.$emit('update:allowVersionInfo', value); }, }, diff --git a/webapp/src/components/InputElement.vue b/webapp/src/components/InputElement.vue index eff8e9f66..f12a11720 100644 --- a/webapp/src/components/InputElement.vue +++ b/webapp/src/components/InputElement.vue @@ -83,10 +83,12 @@ export default defineComponent({ }, computed: { model: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any get(): any { if (this.type === 'checkbox') return !!this.modelValue; return this.modelValue; }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any set(value: any) { this.$emit('update:modelValue', value); }, @@ -112,4 +114,4 @@ export default defineComponent({ } }, }); - \ No newline at end of file + diff --git a/webapp/src/components/InputSerial.vue b/webapp/src/components/InputSerial.vue index 9f5ee343b..3669da622 100644 --- a/webapp/src/components/InputSerial.vue +++ b/webapp/src/components/InputSerial.vue @@ -28,9 +28,11 @@ export default defineComponent({ }, computed: { model: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any get(): any { return this.modelValue; }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any set(value: any) { this.$emit('update:modelValue', value); }, diff --git a/webapp/src/components/NavBar.vue b/webapp/src/components/NavBar.vue index c8cf8a337..6577a4a6d 100644 --- a/webapp/src/components/NavBar.vue +++ b/webapp/src/components/NavBar.vue @@ -168,8 +168,8 @@ export default defineComponent({ }, isEaster() { const easter = this.getEasterSunday(this.now.getFullYear()); - var easterStart = new Date(easter); - var easterEnd = new Date(easter); + const easterStart = new Date(easter); + const easterEnd = new Date(easter); easterStart.setDate(easterStart.getDate() - 2); easterEnd.setDate(easterEnd.getDate() + 1); return this.now >= easterStart && this.now < easterEnd; @@ -192,18 +192,18 @@ export default defineComponent({ this.$refs.navbarCollapse && (this.$refs.navbarCollapse as HTMLElement).classList.remove("show"); }, getEasterSunday(year: number): Date { - var f = Math.floor; - var G = year % 19; - var C = f(year / 100); - var H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30; - var I = H - f(H / 28) * (1 - f(29 / (H + 1)) * f((21 - G) / 11)); - var J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7; - var L = I - J; - var month = 3 + f((L + 40) / 44); - var day = L + 28 - 31 * f(month / 4); + const f = Math.floor; + const G = year % 19; + const C = f(year / 100); + const H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30; + const I = H - f(H / 28) * (1 - f(29 / (H + 1)) * f((21 - G) / 11)); + const J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7; + const L = I - J; + const month = 3 + f((L + 40) / 44); + const day = L + 28 - 31 * f(month / 4); return new Date(year, month - 1, day); } }, }); - \ No newline at end of file + diff --git a/webapp/src/components/PinInfo.vue b/webapp/src/components/PinInfo.vue index 3d4616adb..c1e84b810 100644 --- a/webapp/src/components/PinInfo.vue +++ b/webapp/src/components/PinInfo.vue @@ -84,9 +84,11 @@ export default defineComponent({ let comCur = 999999; if (this.selectedPinAssignment && category in this.selectedPinAssignment) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any comSel = (this.selectedPinAssignment as any)[category][prop]; } if (this.currentPinAssignment && category in this.currentPinAssignment) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any comCur = (this.currentPinAssignment as any)[category][prop]; } diff --git a/webapp/src/components/VedirectView.vue b/webapp/src/components/VedirectView.vue index e79b265fe..a1ed9bc3c 100644 --- a/webapp/src/components/VedirectView.vue +++ b/webapp/src/components/VedirectView.vue @@ -164,7 +164,7 @@ export default defineComponent({ this.socket.onmessage = (event) => { console.log(event); - var root = JSON.parse(event.data); + const root = JSON.parse(event.data); this.dplData = root["dpl"]; if (root["vedirect"]["full_update"] === true) { this.vedirect = root["vedirect"]; @@ -192,7 +192,7 @@ export default defineComponent({ clearTimeout(this.dataAgeTimers[serial]); } - var nextMs = 1000 - (this.vedirect.instances[serial].data_age_ms % 1000); + const nextMs = 1000 - (this.vedirect.instances[serial].data_age_ms % 1000); this.dataAgeTimers[serial] = setTimeout(() => { this.doDataAging(serial); }, nextMs); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 00760d0e1..c9e363d38 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -41,6 +41,9 @@ "Release": "Loslassen zum Aktualisieren", "Close": "Schließen" }, + "Error": { + "Oops": "Oops!" + }, "localeswitcher": { "Dark": "Dunkel", "Light": "Hell", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 475dc3cd7..c779dc43c 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -41,6 +41,9 @@ "Release": "Release to refresh", "Close": "Close" }, + "Error": { + "Oops": "Oops!" + }, "localeswitcher": { "Dark": "Dark", "Light": "Light", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index 2cbc86f4b..972f11932 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -41,6 +41,9 @@ "Release": "Release to refresh", "Close": "Fermer" }, + "Error": { + "Oops": "Oops!" + }, "localeswitcher": { "Dark": "Sombre", "Light": "Clair", diff --git a/webapp/src/router/index.ts b/webapp/src/router/index.ts index 6fe0493d2..4e584fdcd 100644 --- a/webapp/src/router/index.ts +++ b/webapp/src/router/index.ts @@ -5,6 +5,7 @@ import ConfigAdminView from '@/views/ConfigAdminView.vue'; import ConsoleInfoView from '@/views/ConsoleInfoView.vue'; import DeviceAdminView from '@/views/DeviceAdminView.vue' import DtuAdminView from '@/views/DtuAdminView.vue'; +import ErrorView from '@/views/ErrorView.vue'; import FirmwareUpgradeView from '@/views/FirmwareUpgradeView.vue'; import HomeView from '@/views/HomeView.vue'; import VedirectAdminView from '@/views/VedirectAdminView.vue' @@ -38,6 +39,11 @@ const router = createRouter({ name: 'Login', component: LoginView }, + { + path: '/error?status=:status&message=:message', + name: 'Error', + component: ErrorView + }, { path: '/about', name: 'About', @@ -151,4 +157,4 @@ const router = createRouter({ ] }); -export default router; \ No newline at end of file +export default router; diff --git a/webapp/src/utils/authentication.ts b/webapp/src/utils/authentication.ts index d1f87e3d8..e0e96b705 100644 --- a/webapp/src/utils/authentication.ts +++ b/webapp/src/utils/authentication.ts @@ -41,7 +41,7 @@ export function isLoggedIn(): boolean { return (localStorage.getItem('user') != null); } -export function login(username: String, password: String) { +export function login(username: string, password: string) { const requestOptions = { method: 'GET', headers: { @@ -74,9 +74,11 @@ export function handleResponse(response: Response, emitter: Emitter - -
-
-
- - -
-
-
-
- - -
-
-
- -
- - - - - - \ No newline at end of file + + + + + diff --git a/webapp/src/views/ErrorView.vue b/webapp/src/views/ErrorView.vue new file mode 100644 index 000000000..e9cd84d84 --- /dev/null +++ b/webapp/src/views/ErrorView.vue @@ -0,0 +1,18 @@ + + + diff --git a/webapp/src/views/FirmwareUpgradeView.vue b/webapp/src/views/FirmwareUpgradeView.vue index 738460791..f7bf0d75f 100644 --- a/webapp/src/views/FirmwareUpgradeView.vue +++ b/webapp/src/views/FirmwareUpgradeView.vue @@ -191,7 +191,7 @@ export default defineComponent({ const remoteHostUrl = "/api/system/status"; // Use a simple fetch request to check if the remote host is reachable - fetch(remoteHostUrl, { method: 'HEAD' }) + fetch(remoteHostUrl, { method: 'GET' }) .then(response => { // Check if the response status is OK (200-299 range) if (response.ok) { diff --git a/webapp/src/views/HomeView.vue b/webapp/src/views/HomeView.vue index c51d51e8d..46b362917 100644 --- a/webapp/src/views/HomeView.vue +++ b/webapp/src/views/HomeView.vue @@ -5,7 +5,7 @@