Merge remote-tracking branch 'tbnobody/OpenDTU/master' into merge-v24.4.12

This commit is contained in:
helgeerbe 2024-04-25 20:59:19 +02:00
commit fdc5054480
81 changed files with 993 additions and 1444 deletions

View File

@ -39,8 +39,6 @@
#define POWERMETER_MAX_HTTP_JSON_PATH_STRLEN 256 #define POWERMETER_MAX_HTTP_JSON_PATH_STRLEN 256
#define POWERMETER_HTTP_TIMEOUT 1000 #define POWERMETER_HTTP_TIMEOUT 1000
#define JSON_BUFFER_SIZE 15360
struct CHANNEL_CONFIG_T { struct CHANNEL_CONFIG_T {
uint16_t MaxChannelPower; uint16_t MaxChannelPower;
char Name[CHAN_MAX_NAME_STRLEN]; char Name[CHAN_MAX_NAME_STRLEN];

View File

@ -66,10 +66,10 @@ private:
void publishInverterNumber(std::shared_ptr<InverterAbstract> 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 publishInverterNumber(std::shared_ptr<InverterAbstract> 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<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off); void publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off);
static void createInverterInfo(DynamicJsonDocument& doc, std::shared_ptr<InverterAbstract> inv); static void createInverterInfo(JsonDocument& doc, std::shared_ptr<InverterAbstract> inv);
static void createDtuInfo(DynamicJsonDocument& doc); 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 getDtuUniqueId();
static String getDtuUrl(); static String getDtuUrl();

View File

@ -10,7 +10,6 @@ public:
static uint64_t generateDtuSerial(); static uint64_t generateDtuSerial();
static int getTimezoneOffset(); static int getTimezoneOffset();
static void restartDtu(); static void restartDtu();
static bool checkJsonAlloc(const DynamicJsonDocument& doc, const char* function, const uint16_t line); static bool checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line);
static bool checkJsonOverflow(const DynamicJsonDocument& doc, const char* function, const uint16_t line);
static void removeAllFiles(); static void removeAllFiles();
}; };

View File

@ -25,6 +25,7 @@
#include "WebApi_webapp.h" #include "WebApi_webapp.h"
#include "WebApi_ws_console.h" #include "WebApi_ws_console.h"
#include "WebApi_ws_live.h" #include "WebApi_ws_live.h"
#include <AsyncJson.h>
#include "WebApi_ws_vedirect_live.h" #include "WebApi_ws_vedirect_live.h"
#include "WebApi_vedirect.h" #include "WebApi_vedirect.h"
#include "WebApi_ws_Huawei.h" #include "WebApi_ws_Huawei.h"
@ -45,6 +46,10 @@ public:
static void writeConfig(JsonVariant& retMsg, const WebApiError code = WebApiError::GenericSuccess, const String& message = "Settings saved!"); 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: private:
AsyncWebServer _server; AsyncWebServer _server;

View File

@ -5,10 +5,11 @@ enum WebApiError {
GenericBase = 1000, GenericBase = 1000,
GenericSuccess, GenericSuccess,
GenericNoValueFound, GenericNoValueFound,
GenericDataTooLarge, GenericDataTooLarge, // not used anymore
GenericParseError, GenericParseError,
GenericValueMissing, GenericValueMissing,
GenericWriteFailed, GenericWriteFailed,
GenericInternalServerError,
DtuBase = 2000, DtuBase = 2000,
DtuSerialZero, DtuSerialZero,

View File

@ -4,8 +4,6 @@
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h> #include <TaskSchedulerDeclarations.h>
#define MQTT_JSON_DOC_SIZE 10240
class WebApiMqttClass { class WebApiMqttClass {
public: public:
void init(AsyncWebServer& server, Scheduler& scheduler); void init(AsyncWebServer& server, Scheduler& scheduler);

View File

@ -12,7 +12,7 @@ public:
void init(AsyncWebServer& server, Scheduler& scheduler); void init(AsyncWebServer& server, Scheduler& scheduler);
private: private:
void generateJsonResponse(JsonVariant& root); void generateCommonJsonResponse(JsonVariant& root);
void onLivedataStatus(AsyncWebServerRequest* request); void onLivedataStatus(AsyncWebServerRequest* request);
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);

View File

@ -12,7 +12,7 @@ public:
void init(AsyncWebServer& server, Scheduler& scheduler); void init(AsyncWebServer& server, Scheduler& scheduler);
private: private:
void generateJsonResponse(JsonVariant& root); void generateCommonJsonResponse(JsonVariant& root);
void onLivedataStatus(AsyncWebServerRequest* request); void onLivedataStatus(AsyncWebServerRequest* request);
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);

View File

@ -14,7 +14,7 @@ public:
void init(AsyncWebServer& server, Scheduler& scheduler); void init(AsyncWebServer& server, Scheduler& scheduler);
private: private:
void generateJsonResponse(JsonVariant& root, bool fullUpdate); void generateCommonJsonResponse(JsonVariant& root, bool fullUpdate);
static void populateJson(const JsonObject &root, const VeDirectMpptController::data_t &mpptData); static void populateJson(const JsonObject &root, const VeDirectMpptController::data_t &mpptData);
void onLivedataStatus(AsyncWebServerRequest* request); void onLivedataStatus(AsyncWebServerRequest* request);
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);

View File

@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include "TimeoutHelper.h"
#include "commands/CommandAbstract.h" #include "commands/CommandAbstract.h"
#include "types.h" #include "types.h"
#include <memory>
#include <ThreadSafeQueue.h> #include <ThreadSafeQueue.h>
#include <TimeoutHelper.h>
#include <memory>
class HoymilesRadio { class HoymilesRadio {
public: public:

View File

@ -70,7 +70,7 @@ bool HMT_4CH::isValidSerial(const uint64_t serial)
String HMT_4CH::typeName() const 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 const byteAssign_t* HMT_4CH::getByteAssignment() const

View File

@ -84,7 +84,7 @@ bool HMT_6CH::isValidSerial(const uint64_t serial)
String HMT_6CH::typeName() const String HMT_6CH::typeName() const
{ {
return F("HMT-1800/2250-6T"); return "HMT-1800/2250-6T";
} }
const byteAssign_t* HMT_6CH::getByteAssignment() const const byteAssign_t* HMT_6CH::getByteAssignment() const

View File

View File

@ -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"
]
}

View File

View File

@ -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"
]
}

View File

@ -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

View File

@ -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();
}

View File

@ -19,12 +19,13 @@ extra_configs =
custom_ci_action = generic,generic_esp32,generic_esp32s3,generic_esp32s3_usb custom_ci_action = generic,generic_esp32,generic_esp32s3,generic_esp32s3_usb
framework = arduino framework = arduino
platform = espressif32@6.5.0 platform = espressif32@6.6.0
build_flags = build_flags =
-DPIOENV=\"$PIOENV\" -DPIOENV=\"$PIOENV\"
-D_TASK_STD_FUNCTION=1 -D_TASK_STD_FUNCTION=1
-D_TASK_THREAD_SAFE=1 -D_TASK_THREAD_SAFE=1
-DCONFIG_ASYNC_TCP_EVENT_QUEUE_SIZE=128
-Wall -Wextra -Wunused -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference -Wall -Wextra -Wunused -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference
; Have to remove -Werror because of ; Have to remove -Werror because of
; https://github.com/espressif/arduino-esp32/issues/9044 and ; https://github.com/espressif/arduino-esp32/issues/9044 and
@ -36,8 +37,8 @@ build_unflags =
-std=gnu++11 -std=gnu++11
lib_deps = lib_deps =
mathieucarbou/ESP Async WebServer @ 2.8.1 mathieucarbou/ESP Async WebServer @ 2.9.0
bblanchon/ArduinoJson @ ^6.21.5 bblanchon/ArduinoJson @ ^7.0.4
https://github.com/bertmelis/espMqttClient.git#v1.6.0 https://github.com/bertmelis/espMqttClient.git#v1.6.0
nrf24/RF24 @ ^1.4.8 nrf24/RF24 @ ^1.4.8
olikraus/U8g2 @ ^2.35.15 olikraus/U8g2 @ ^2.35.15
@ -64,7 +65,7 @@ board_build.embed_files =
webapp_dist/js/app.js.gz webapp_dist/js/app.js.gz
webapp_dist/site.webmanifest webapp_dist/site.webmanifest
custom_patches = custom_patches = async_tcp
monitor_filters = esp32_exception_decoder, time, log2file, colorize monitor_filters = esp32_exception_decoder, time, log2file, colorize
monitor_speed = 115200 monitor_speed = 115200
@ -92,13 +93,13 @@ build_flags = ${env.build_flags}
[env:generic_esp32c3] [env:generic_esp32c3]
board = esp32-c3-devkitc-02 board = esp32-c3-devkitc-02
custom_patches = ${env.custom_patches},esp32c3 custom_patches = ${env.custom_patches}
build_flags = ${env.build_flags} build_flags = ${env.build_flags}
[env:generic_esp32c3_usb] [env:generic_esp32c3_usb]
board = esp32-c3-devkitc-02 board = esp32-c3-devkitc-02
custom_patches = ${env.custom_patches},esp32c3 custom_patches = ${env.custom_patches}
build_flags = ${env.build_flags} build_flags = ${env.build_flags}
-DARDUINO_USB_MODE=1 -DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_CDC_ON_BOOT=1

View File

@ -25,17 +25,13 @@ bool ConfigurationClass::write()
} }
config.Cfg.SaveCount++; config.Cfg.SaveCount++;
DynamicJsonDocument doc(JSON_BUFFER_SIZE); JsonDocument doc;
if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) { JsonObject cfg = doc["cfg"].to<JsonObject>();
return false;
}
JsonObject cfg = doc.createNestedObject("cfg");
cfg["version"] = config.Cfg.Version; cfg["version"] = config.Cfg.Version;
cfg["save_count"] = config.Cfg.SaveCount; cfg["save_count"] = config.Cfg.SaveCount;
JsonObject wifi = doc.createNestedObject("wifi"); JsonObject wifi = doc["wifi"].to<JsonObject>();
wifi["ssid"] = config.WiFi.Ssid; wifi["ssid"] = config.WiFi.Ssid;
wifi["password"] = config.WiFi.Password; wifi["password"] = config.WiFi.Password;
wifi["ip"] = IPAddress(config.WiFi.Ip).toString(); wifi["ip"] = IPAddress(config.WiFi.Ip).toString();
@ -47,10 +43,10 @@ bool ConfigurationClass::write()
wifi["hostname"] = config.WiFi.Hostname; wifi["hostname"] = config.WiFi.Hostname;
wifi["aptimeout"] = config.WiFi.ApTimeout; wifi["aptimeout"] = config.WiFi.ApTimeout;
JsonObject mdns = doc.createNestedObject("mdns"); JsonObject mdns = doc["mdns"].to<JsonObject>();
mdns["enabled"] = config.Mdns.Enabled; mdns["enabled"] = config.Mdns.Enabled;
JsonObject ntp = doc.createNestedObject("ntp"); JsonObject ntp = doc["ntp"].to<JsonObject>();
ntp["server"] = config.Ntp.Server; ntp["server"] = config.Ntp.Server;
ntp["timezone"] = config.Ntp.Timezone; ntp["timezone"] = config.Ntp.Timezone;
ntp["timezone_descr"] = config.Ntp.TimezoneDescr; ntp["timezone_descr"] = config.Ntp.TimezoneDescr;
@ -58,7 +54,7 @@ bool ConfigurationClass::write()
ntp["longitude"] = config.Ntp.Longitude; ntp["longitude"] = config.Ntp.Longitude;
ntp["sunsettype"] = config.Ntp.SunsetType; ntp["sunsettype"] = config.Ntp.SunsetType;
JsonObject mqtt = doc.createNestedObject("mqtt"); JsonObject mqtt = doc["mqtt"].to<JsonObject>();
mqtt["enabled"] = config.Mqtt.Enabled; mqtt["enabled"] = config.Mqtt.Enabled;
mqtt["verbose_logging"] = config.Mqtt.VerboseLogging; mqtt["verbose_logging"] = config.Mqtt.VerboseLogging;
mqtt["hostname"] = config.Mqtt.Hostname; mqtt["hostname"] = config.Mqtt.Hostname;
@ -70,27 +66,27 @@ bool ConfigurationClass::write()
mqtt["publish_interval"] = config.Mqtt.PublishInterval; mqtt["publish_interval"] = config.Mqtt.PublishInterval;
mqtt["clean_session"] = config.Mqtt.CleanSession; mqtt["clean_session"] = config.Mqtt.CleanSession;
JsonObject mqtt_lwt = mqtt.createNestedObject("lwt"); JsonObject mqtt_lwt = mqtt["lwt"].to<JsonObject>();
mqtt_lwt["topic"] = config.Mqtt.Lwt.Topic; mqtt_lwt["topic"] = config.Mqtt.Lwt.Topic;
mqtt_lwt["value_online"] = config.Mqtt.Lwt.Value_Online; mqtt_lwt["value_online"] = config.Mqtt.Lwt.Value_Online;
mqtt_lwt["value_offline"] = config.Mqtt.Lwt.Value_Offline; mqtt_lwt["value_offline"] = config.Mqtt.Lwt.Value_Offline;
mqtt_lwt["qos"] = config.Mqtt.Lwt.Qos; mqtt_lwt["qos"] = config.Mqtt.Lwt.Qos;
JsonObject mqtt_tls = mqtt.createNestedObject("tls"); JsonObject mqtt_tls = mqtt["tls"].to<JsonObject>();
mqtt_tls["enabled"] = config.Mqtt.Tls.Enabled; mqtt_tls["enabled"] = config.Mqtt.Tls.Enabled;
mqtt_tls["root_ca_cert"] = config.Mqtt.Tls.RootCaCert; mqtt_tls["root_ca_cert"] = config.Mqtt.Tls.RootCaCert;
mqtt_tls["certlogin"] = config.Mqtt.Tls.CertLogin; mqtt_tls["certlogin"] = config.Mqtt.Tls.CertLogin;
mqtt_tls["client_cert"] = config.Mqtt.Tls.ClientCert; mqtt_tls["client_cert"] = config.Mqtt.Tls.ClientCert;
mqtt_tls["client_key"] = config.Mqtt.Tls.ClientKey; mqtt_tls["client_key"] = config.Mqtt.Tls.ClientKey;
JsonObject mqtt_hass = mqtt.createNestedObject("hass"); JsonObject mqtt_hass = mqtt["hass"].to<JsonObject>();
mqtt_hass["enabled"] = config.Mqtt.Hass.Enabled; mqtt_hass["enabled"] = config.Mqtt.Hass.Enabled;
mqtt_hass["retain"] = config.Mqtt.Hass.Retain; mqtt_hass["retain"] = config.Mqtt.Hass.Retain;
mqtt_hass["topic"] = config.Mqtt.Hass.Topic; mqtt_hass["topic"] = config.Mqtt.Hass.Topic;
mqtt_hass["individual_panels"] = config.Mqtt.Hass.IndividualPanels; mqtt_hass["individual_panels"] = config.Mqtt.Hass.IndividualPanels;
mqtt_hass["expire"] = config.Mqtt.Hass.Expire; mqtt_hass["expire"] = config.Mqtt.Hass.Expire;
JsonObject dtu = doc.createNestedObject("dtu"); JsonObject dtu = doc["dtu"].to<JsonObject>();
dtu["serial"] = config.Dtu.Serial; dtu["serial"] = config.Dtu.Serial;
dtu["poll_interval"] = config.Dtu.PollInterval; dtu["poll_interval"] = config.Dtu.PollInterval;
dtu["verbose_logging"] = config.Dtu.VerboseLogging; dtu["verbose_logging"] = config.Dtu.VerboseLogging;
@ -99,14 +95,14 @@ bool ConfigurationClass::write()
dtu["cmt_frequency"] = config.Dtu.Cmt.Frequency; dtu["cmt_frequency"] = config.Dtu.Cmt.Frequency;
dtu["cmt_country_mode"] = config.Dtu.Cmt.CountryMode; dtu["cmt_country_mode"] = config.Dtu.Cmt.CountryMode;
JsonObject security = doc.createNestedObject("security"); JsonObject security = doc["security"].to<JsonObject>();
security["password"] = config.Security.Password; security["password"] = config.Security.Password;
security["allow_readonly"] = config.Security.AllowReadonly; security["allow_readonly"] = config.Security.AllowReadonly;
JsonObject device = doc.createNestedObject("device"); JsonObject device = doc["device"].to<JsonObject>();
device["pinmapping"] = config.Dev_PinMapping; device["pinmapping"] = config.Dev_PinMapping;
JsonObject display = device.createNestedObject("display"); JsonObject display = device["display"].to<JsonObject>();
display["powersafe"] = config.Display.PowerSafe; display["powersafe"] = config.Display.PowerSafe;
display["screensaver"] = config.Display.ScreenSaver; display["screensaver"] = config.Display.ScreenSaver;
display["rotation"] = config.Display.Rotation; display["rotation"] = config.Display.Rotation;
@ -115,15 +111,15 @@ bool ConfigurationClass::write()
display["diagram_duration"] = config.Display.Diagram.Duration; display["diagram_duration"] = config.Display.Diagram.Duration;
display["diagram_mode"] = config.Display.Diagram.Mode; display["diagram_mode"] = config.Display.Diagram.Mode;
JsonArray leds = device.createNestedArray("led"); JsonArray leds = device["led"].to<JsonArray>();
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
JsonObject led = leds.createNestedObject(); JsonObject led = leds.add<JsonObject>();
led["brightness"] = config.Led_Single[i].Brightness; led["brightness"] = config.Led_Single[i].Brightness;
} }
JsonArray inverters = doc.createNestedArray("inverters"); JsonArray inverters = doc["inverters"].to<JsonArray>();
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
JsonObject inv = inverters.createNestedObject(); JsonObject inv = inverters.add<JsonObject>();
inv["serial"] = config.Inverter[i].Serial; inv["serial"] = config.Inverter[i].Serial;
inv["name"] = config.Inverter[i].Name; inv["name"] = config.Inverter[i].Name;
inv["order"] = config.Inverter[i].Order; inv["order"] = config.Inverter[i].Order;
@ -136,21 +132,21 @@ bool ConfigurationClass::write()
inv["zero_day"] = config.Inverter[i].ZeroYieldDayOnMidnight; inv["zero_day"] = config.Inverter[i].ZeroYieldDayOnMidnight;
inv["yieldday_correction"] = config.Inverter[i].YieldDayCorrection; inv["yieldday_correction"] = config.Inverter[i].YieldDayCorrection;
JsonArray channel = inv.createNestedArray("channel"); JsonArray channel = inv["channel"].to<JsonArray>();
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
JsonObject chanData = channel.createNestedObject(); JsonObject chanData = channel.add<JsonObject>();
chanData["name"] = config.Inverter[i].channel[c].Name; chanData["name"] = config.Inverter[i].channel[c].Name;
chanData["max_power"] = config.Inverter[i].channel[c].MaxChannelPower; chanData["max_power"] = config.Inverter[i].channel[c].MaxChannelPower;
chanData["yield_total_offset"] = config.Inverter[i].channel[c].YieldTotalOffset; chanData["yield_total_offset"] = config.Inverter[i].channel[c].YieldTotalOffset;
} }
} }
JsonObject vedirect = doc.createNestedObject("vedirect"); JsonObject vedirect = doc["vedirect"].to<JsonObject>();
vedirect["enabled"] = config.Vedirect.Enabled; vedirect["enabled"] = config.Vedirect.Enabled;
vedirect["verbose_logging"] = config.Vedirect.VerboseLogging; vedirect["verbose_logging"] = config.Vedirect.VerboseLogging;
vedirect["updates_only"] = config.Vedirect.UpdatesOnly; vedirect["updates_only"] = config.Vedirect.UpdatesOnly;
JsonObject powermeter = doc.createNestedObject("powermeter"); JsonObject powermeter = doc["powermeter"].to<JsonObject>();
powermeter["enabled"] = config.PowerMeter.Enabled; powermeter["enabled"] = config.PowerMeter.Enabled;
powermeter["verbose_logging"] = config.PowerMeter.VerboseLogging; powermeter["verbose_logging"] = config.PowerMeter.VerboseLogging;
powermeter["interval"] = config.PowerMeter.Interval; powermeter["interval"] = config.PowerMeter.Interval;
@ -162,9 +158,9 @@ bool ConfigurationClass::write()
powermeter["sdmaddress"] = config.PowerMeter.SdmAddress; powermeter["sdmaddress"] = config.PowerMeter.SdmAddress;
powermeter["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests; powermeter["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests;
JsonArray powermeter_http_phases = powermeter.createNestedArray("http_phases"); JsonArray powermeter_http_phases = powermeter["http_phases"].to<JsonArray>();
for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) { for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) {
JsonObject powermeter_phase = powermeter_http_phases.createNestedObject(); JsonObject powermeter_phase = powermeter_http_phases.add<JsonObject>();
powermeter_phase["enabled"] = config.PowerMeter.Http_Phase[i].Enabled; powermeter_phase["enabled"] = config.PowerMeter.Http_Phase[i].Enabled;
powermeter_phase["url"] = config.PowerMeter.Http_Phase[i].Url; 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; powermeter_phase["sign_inverted"] = config.PowerMeter.Http_Phase[i].SignInverted;
} }
JsonObject powerlimiter = doc.createNestedObject("powerlimiter"); JsonObject powerlimiter = doc["powerlimiter"].to<JsonObject>();
powerlimiter["enabled"] = config.PowerLimiter.Enabled; powerlimiter["enabled"] = config.PowerLimiter.Enabled;
powerlimiter["verbose_logging"] = config.PowerLimiter.VerboseLogging; powerlimiter["verbose_logging"] = config.PowerLimiter.VerboseLogging;
powerlimiter["solar_passtrough_enabled"] = config.PowerLimiter.SolarPassThroughEnabled; 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_start_voltage"] = config.PowerLimiter.FullSolarPassThroughStartVoltage;
powerlimiter["full_solar_passthrough_stop_voltage"] = config.PowerLimiter.FullSolarPassThroughStopVoltage; powerlimiter["full_solar_passthrough_stop_voltage"] = config.PowerLimiter.FullSolarPassThroughStopVoltage;
JsonObject battery = doc.createNestedObject("battery"); JsonObject battery = doc["battery"].to<JsonObject>();
battery["enabled"] = config.Battery.Enabled; battery["enabled"] = config.Battery.Enabled;
battery["verbose_logging"] = config.Battery.VerboseLogging; battery["verbose_logging"] = config.Battery.VerboseLogging;
battery["provider"] = config.Battery.Provider; battery["provider"] = config.Battery.Provider;
@ -215,7 +211,7 @@ bool ConfigurationClass::write()
battery["mqtt_topic"] = config.Battery.MqttSocTopic; battery["mqtt_topic"] = config.Battery.MqttSocTopic;
battery["mqtt_voltage_topic"] = config.Battery.MqttVoltageTopic; battery["mqtt_voltage_topic"] = config.Battery.MqttVoltageTopic;
JsonObject huawei = doc.createNestedObject("huawei"); JsonObject huawei = doc["huawei"].to<JsonObject>();
huawei["enabled"] = config.Huawei.Enabled; huawei["enabled"] = config.Huawei.Enabled;
huawei["verbose_logging"] = config.Huawei.VerboseLogging; huawei["verbose_logging"] = config.Huawei.VerboseLogging;
huawei["can_controller_frequency"] = config.Huawei.CAN_Controller_Frequency; huawei["can_controller_frequency"] = config.Huawei.CAN_Controller_Frequency;
@ -228,6 +224,10 @@ bool ConfigurationClass::write()
huawei["upper_power_limit"] = config.Huawei.Auto_Power_Upper_Power_Limit; huawei["upper_power_limit"] = config.Huawei.Auto_Power_Upper_Power_Limit;
huawei["stop_batterysoc_threshold"] = config.Huawei.Auto_Power_Stop_BatterySoC_Threshold; huawei["stop_batterysoc_threshold"] = config.Huawei.Auto_Power_Stop_BatterySoC_Threshold;
if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
return false;
}
// Serialize JSON to file // Serialize JSON to file
if (serializeJson(doc, f) == 0) { if (serializeJson(doc, f) == 0) {
MessageOutput.println("Failed to write file"); MessageOutput.println("Failed to write file");
@ -242,11 +242,7 @@ bool ConfigurationClass::read()
{ {
File f = LittleFS.open(CONFIG_FILENAME, "r", false); File f = LittleFS.open(CONFIG_FILENAME, "r", false);
DynamicJsonDocument doc(JSON_BUFFER_SIZE); JsonDocument doc;
if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
return false;
}
// Deserialize the JSON document // Deserialize the JSON document
const DeserializationError error = deserializeJson(doc, f); const DeserializationError error = deserializeJson(doc, f);
@ -254,6 +250,10 @@ bool ConfigurationClass::read()
MessageOutput.println("Failed to read file, using default configuration"); MessageOutput.println("Failed to read file, using default configuration");
} }
if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
return false;
}
JsonObject cfg = doc["cfg"]; JsonObject cfg = doc["cfg"];
config.Cfg.Version = cfg["version"] | CONFIG_VERSION; config.Cfg.Version = cfg["version"] | CONFIG_VERSION;
config.Cfg.SaveCount = cfg["save_count"] | 0; config.Cfg.SaveCount = cfg["save_count"] | 0;
@ -495,11 +495,7 @@ void ConfigurationClass::migrate()
return; return;
} }
DynamicJsonDocument doc(JSON_BUFFER_SIZE); JsonDocument doc;
if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
return;
}
// Deserialize the JSON document // Deserialize the JSON document
const DeserializationError error = deserializeJson(doc, f); const DeserializationError error = deserializeJson(doc, f);
@ -508,6 +504,10 @@ void ConfigurationClass::migrate()
return; return;
} }
if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
return;
}
if (config.Cfg.Version < 0x00011700) { if (config.Cfg.Version < 0x00011700) {
JsonArray inverters = doc["inverters"]; JsonArray inverters = doc["inverters"];
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {

View File

@ -51,9 +51,9 @@ void InverterSettingsClass::init(Scheduler& scheduler)
if (PinMapping.isValidCmt2300Config()) { if (PinMapping.isValidCmt2300Config()) {
Hoymiles.initCMT(pin.cmt_sdio, pin.cmt_clk, pin.cmt_cs, pin.cmt_fcs, pin.cmt_gpio2, pin.cmt_gpio3); 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<CountryModeId_t>(config.Dtu.Cmt.CountryMode)); Hoymiles.getRadioCmt()->setCountryMode(static_cast<CountryModeId_t>(config.Dtu.Cmt.CountryMode));
MessageOutput.println(F(" Setting CMT target frequency... ")); MessageOutput.println(" Setting CMT target frequency... ");
Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency); Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency);
} }

View File

@ -124,10 +124,8 @@ void MqttHandleVedirectHassClass::publishSensor(const char *caption, const char
statTopic.concat("/"); statTopic.concat("/");
statTopic.concat(subTopic); statTopic.concat(subTopic);
DynamicJsonDocument root(1024); JsonDocument root;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
root["name"] = caption; root["name"] = caption;
root["stat_t"] = statTopic; root["stat_t"] = statTopic;
root["uniq_id"] = serial + "_" + sensorId; root["uniq_id"] = serial + "_" + sensorId;
@ -140,7 +138,7 @@ void MqttHandleVedirectHassClass::publishSensor(const char *caption, const char
root["unit_of_meas"] = unitOfMeasurement; root["unit_of_meas"] = unitOfMeasurement;
} }
JsonObject deviceObj = root.createNestedObject("dev"); JsonObject deviceObj = root["dev"].to<JsonObject>();
createDeviceInfo(deviceObj, mpptData); createDeviceInfo(deviceObj, mpptData);
if (Configuration.get().Mqtt.Hass.Expire) { if (Configuration.get().Mqtt.Hass.Expire) {
@ -153,7 +151,9 @@ void MqttHandleVedirectHassClass::publishSensor(const char *caption, const char
root["stat_cla"] = stateClass; root["stat_cla"] = stateClass;
} }
if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
char buffer[512]; char buffer[512];
serializeJson(root, buffer); serializeJson(root, buffer);
@ -182,10 +182,7 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char *caption, const
statTopic.concat("/"); statTopic.concat("/");
statTopic.concat(subTopic); statTopic.concat(subTopic);
DynamicJsonDocument root(1024); JsonDocument root;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
root["name"] = caption; root["name"] = caption;
root["uniq_id"] = serial + "_" + sensorId; root["uniq_id"] = serial + "_" + sensorId;
root["stat_t"] = statTopic; root["stat_t"] = statTopic;
@ -196,10 +193,12 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char *caption, const
root["icon"] = icon; root["icon"] = icon;
} }
JsonObject deviceObj = root.createNestedObject("dev"); JsonObject deviceObj = root["dev"].to<JsonObject>();
createDeviceInfo(deviceObj, mpptData); createDeviceInfo(deviceObj, mpptData);
if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
char buffer[512]; char buffer[512];
serializeJson(root, buffer); serializeJson(root, buffer);

View File

@ -144,10 +144,7 @@ void MqttHandleBatteryHassClass::publishSensor(const char* caption, const char*
// statTopic.concat("/"); // statTopic.concat("/");
statTopic.concat(subTopic); statTopic.concat(subTopic);
DynamicJsonDocument root(1024); JsonDocument root;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
root["name"] = caption; root["name"] = caption;
root["stat_t"] = statTopic; root["stat_t"] = statTopic;
root["uniq_id"] = serial + "_" + sensorId; root["uniq_id"] = serial + "_" + sensorId;
@ -160,7 +157,7 @@ void MqttHandleBatteryHassClass::publishSensor(const char* caption, const char*
root["unit_of_meas"] = unitOfMeasurement; root["unit_of_meas"] = unitOfMeasurement;
} }
JsonObject deviceObj = root.createNestedObject("dev"); JsonObject deviceObj = root["dev"].to<JsonObject>();
createDeviceInfo(deviceObj); createDeviceInfo(deviceObj);
if (Configuration.get().Mqtt.Hass.Expire) { if (Configuration.get().Mqtt.Hass.Expire) {
@ -173,7 +170,9 @@ void MqttHandleBatteryHassClass::publishSensor(const char* caption, const char*
root["stat_cla"] = stateClass; root["stat_cla"] = stateClass;
} }
if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
char buffer[512]; char buffer[512];
serializeJson(root, buffer); serializeJson(root, buffer);
@ -201,10 +200,8 @@ void MqttHandleBatteryHassClass::publishBinarySensor(const char* caption, const
// statTopic.concat("/"); // statTopic.concat("/");
statTopic.concat(subTopic); statTopic.concat(subTopic);
DynamicJsonDocument root(1024); JsonDocument root;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
root["name"] = caption; root["name"] = caption;
root["uniq_id"] = serial + "_" + sensorId; root["uniq_id"] = serial + "_" + sensorId;
root["stat_t"] = statTopic; root["stat_t"] = statTopic;
@ -215,10 +212,12 @@ void MqttHandleBatteryHassClass::publishBinarySensor(const char* caption, const
root["icon"] = icon; root["icon"] = icon;
} }
JsonObject deviceObj = root.createNestedObject("dev"); auto deviceObj = root["dev"].to<JsonObject>();
createDeviceInfo(deviceObj); createDeviceInfo(deviceObj);
if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
char buffer[512]; char buffer[512];
serializeJson(root, buffer); serializeJson(root, buffer);

View File

@ -137,10 +137,7 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr<InverterAbstract>
name = "CH" + chanNum + " " + fieldName; name = "CH" + chanNum + " " + fieldName;
} }
DynamicJsonDocument root(1024); JsonDocument root;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
root["name"] = name; root["name"] = name;
root["stat_t"] = stateTopic; root["stat_t"] = stateTopic;
@ -163,6 +160,10 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr<InverterAbstract>
root["stat_cla"] = stateCls; root["stat_cla"] = stateCls;
} }
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
String buffer; String buffer;
serializeJson(root, buffer); serializeJson(root, buffer);
publish(configTopic, buffer); publish(configTopic, buffer);
@ -185,10 +186,7 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptr<InverterAbstract
const String cmdTopic = MqttSettings.getPrefix() + serial + "/" + subTopic; const String cmdTopic = MqttSettings.getPrefix() + serial + "/" + subTopic;
DynamicJsonDocument root(1024); JsonDocument root;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
root["name"] = caption; root["name"] = caption;
root["uniq_id"] = serial + "_" + buttonId; root["uniq_id"] = serial + "_" + buttonId;
@ -204,6 +202,10 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptr<InverterAbstract
createInverterInfo(root, inv); createInverterInfo(root, inv);
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
String buffer; String buffer;
serializeJson(root, buffer); serializeJson(root, buffer);
publish(configTopic, buffer); publish(configTopic, buffer);
@ -227,10 +229,7 @@ void MqttHandleHassClass::publishInverterNumber(
const String cmdTopic = MqttSettings.getPrefix() + serial + "/" + commandTopic; const String cmdTopic = MqttSettings.getPrefix() + serial + "/" + commandTopic;
const String statTopic = MqttSettings.getPrefix() + serial + "/" + stateTopic; const String statTopic = MqttSettings.getPrefix() + serial + "/" + stateTopic;
DynamicJsonDocument root(1024); JsonDocument root;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
root["name"] = caption; root["name"] = caption;
root["uniq_id"] = serial + "_" + buttonId; root["uniq_id"] = serial + "_" + buttonId;
@ -246,6 +245,10 @@ void MqttHandleHassClass::publishInverterNumber(
createInverterInfo(root, inv); createInverterInfo(root, inv);
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
String buffer; String buffer;
serializeJson(root, buffer); serializeJson(root, buffer);
publish(configTopic, buffer); publish(configTopic, buffer);
@ -265,10 +268,7 @@ void MqttHandleHassClass::publishInverterBinarySensor(std::shared_ptr<InverterAb
const String statTopic = MqttSettings.getPrefix() + serial + "/" + subTopic; const String statTopic = MqttSettings.getPrefix() + serial + "/" + subTopic;
DynamicJsonDocument root(1024); JsonDocument root;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
root["name"] = caption; root["name"] = caption;
root["uniq_id"] = serial + "_" + sensorId; root["uniq_id"] = serial + "_" + sensorId;
@ -278,6 +278,10 @@ void MqttHandleHassClass::publishInverterBinarySensor(std::shared_ptr<InverterAb
createInverterInfo(root, inv); createInverterInfo(root, inv);
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
String buffer; String buffer;
serializeJson(root, buffer); serializeJson(root, buffer);
publish(configTopic, buffer); publish(configTopic, buffer);
@ -293,10 +297,7 @@ void MqttHandleHassClass::publishDtuSensor(const char* name, const char* device_
topic = id; topic = id;
} }
DynamicJsonDocument root(1024); JsonDocument root;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
root["name"] = name; root["name"] = name;
root["uniq_id"] = getDtuUniqueId() + "_" + id; root["uniq_id"] = getDtuUniqueId() + "_" + id;
@ -322,6 +323,8 @@ void MqttHandleHassClass::publishDtuSensor(const char* name, const char* device_
createDtuInfo(root); createDtuInfo(root);
String buffer; String buffer;
const String configTopic = "sensor/" + getDtuUniqueId() + "/" + id + "/config"; const String configTopic = "sensor/" + getDtuUniqueId() + "/" + id + "/config";
serializeJson(root, buffer); serializeJson(root, buffer);
@ -339,10 +342,7 @@ void MqttHandleHassClass::publishDtuBinarySensor(const char* name, const char* d
topic = String("dtu/") + "/" + id; topic = String("dtu/") + "/" + id;
} }
DynamicJsonDocument root(1024); JsonDocument root;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
root["name"] = name; root["name"] = name;
root["uniq_id"] = getDtuUniqueId() + "_" + id; root["uniq_id"] = getDtuUniqueId() + "_" + id;
@ -359,13 +359,17 @@ void MqttHandleHassClass::publishDtuBinarySensor(const char* name, const char* d
createDtuInfo(root); createDtuInfo(root);
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
String buffer; String buffer;
const String configTopic = "binary_sensor/" + getDtuUniqueId() + "/" + id + "/config"; const String configTopic = "binary_sensor/" + getDtuUniqueId() + "/" + id + "/config";
serializeJson(root, buffer); serializeJson(root, buffer);
publish(configTopic, buffer); publish(configTopic, buffer);
} }
void MqttHandleHassClass::createInverterInfo(DynamicJsonDocument& root, std::shared_ptr<InverterAbstract> inv) void MqttHandleHassClass::createInverterInfo(JsonDocument& root, std::shared_ptr<InverterAbstract> inv)
{ {
createDeviceInfo( createDeviceInfo(
root, root,
@ -378,7 +382,7 @@ void MqttHandleHassClass::createInverterInfo(DynamicJsonDocument& root, std::sha
getDtuUniqueId()); getDtuUniqueId());
} }
void MqttHandleHassClass::createDtuInfo(DynamicJsonDocument& root) void MqttHandleHassClass::createDtuInfo(JsonDocument& root)
{ {
createDeviceInfo( createDeviceInfo(
root, root,
@ -391,12 +395,12 @@ void MqttHandleHassClass::createDtuInfo(DynamicJsonDocument& root)
} }
void MqttHandleHassClass::createDeviceInfo( void MqttHandleHassClass::createDeviceInfo(
DynamicJsonDocument& root, JsonDocument& root,
const String& name, const String& identifiers, const String& configuration_url, const String& name, const String& identifiers, const String& configuration_url,
const String& manufacturer, const String& model, const String& sw_version, const String& manufacturer, const String& model, const String& sw_version,
const String& via_device) const String& via_device)
{ {
auto object = root.createNestedObject("dev"); auto object = root["dev"].to<JsonObject>();
object["name"] = name; object["name"] = name;
object["ids"] = identifiers; object["ids"] = identifiers;

View File

@ -112,10 +112,7 @@ void MqttHandlePowerLimiterHassClass::publishSelect(
const String cmdTopic = MqttSettings.getPrefix() + "powerlimiter/cmd/" + commandTopic; const String cmdTopic = MqttSettings.getPrefix() + "powerlimiter/cmd/" + commandTopic;
const String statTopic = MqttSettings.getPrefix() + "powerlimiter/status/" + stateTopic; const String statTopic = MqttSettings.getPrefix() + "powerlimiter/status/" + stateTopic;
DynamicJsonDocument root(1024); JsonDocument root;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
root["name"] = caption; root["name"] = caption;
root["uniq_id"] = selectId; root["uniq_id"] = selectId;
@ -125,15 +122,17 @@ void MqttHandlePowerLimiterHassClass::publishSelect(
root["ent_cat"] = category; root["ent_cat"] = category;
root["cmd_t"] = cmdTopic; root["cmd_t"] = cmdTopic;
root["stat_t"] = statTopic; root["stat_t"] = statTopic;
JsonArray options = root.createNestedArray("options"); JsonArray options = root["options"].to<JsonArray>();
options.add("0"); options.add("0");
options.add("1"); options.add("1");
options.add("2"); options.add("2");
JsonObject deviceObj = root.createNestedObject("dev"); JsonObject deviceObj = root["dev"].to<JsonObject>();
createDeviceInfo(deviceObj); createDeviceInfo(deviceObj);
if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
String buffer; String buffer;
serializeJson(root, buffer); serializeJson(root, buffer);
@ -155,10 +154,7 @@ void MqttHandlePowerLimiterHassClass::publishNumber(
const String cmdTopic = MqttSettings.getPrefix() + "powerlimiter/cmd/" + commandTopic; const String cmdTopic = MqttSettings.getPrefix() + "powerlimiter/cmd/" + commandTopic;
const String statTopic = MqttSettings.getPrefix() + "powerlimiter/status/" + stateTopic; const String statTopic = MqttSettings.getPrefix() + "powerlimiter/status/" + stateTopic;
DynamicJsonDocument root(1024); JsonDocument root;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
root["name"] = caption; root["name"] = caption;
root["uniq_id"] = numberId; root["uniq_id"] = numberId;
@ -178,10 +174,12 @@ void MqttHandlePowerLimiterHassClass::publishNumber(
root["exp_aft"] = config.Mqtt.PublishInterval * 3; root["exp_aft"] = config.Mqtt.PublishInterval * 3;
} }
JsonObject deviceObj = root.createNestedObject("dev"); JsonObject deviceObj = root["dev"].to<JsonObject>();
createDeviceInfo(deviceObj); createDeviceInfo(deviceObj);
if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
String buffer; String buffer;
serializeJson(root, buffer); serializeJson(root, buffer);

View File

@ -8,8 +8,6 @@
#include <LittleFS.h> #include <LittleFS.h>
#include <string.h> #include <string.h>
#define JSON_BUFFER_SIZE 6144
#ifndef DISPLAY_TYPE #ifndef DISPLAY_TYPE
#define DISPLAY_TYPE 0U #define DISPLAY_TYPE 0U
#endif #endif
@ -234,7 +232,7 @@ bool PinMappingClass::init(const String& deviceMapping)
return false; return false;
} }
DynamicJsonDocument doc(JSON_BUFFER_SIZE); JsonDocument doc;
// Deserialize the JSON document // Deserialize the JSON document
DeserializationError error = deserializeJson(doc, f); DeserializationError error = deserializeJson(doc, f);
if (error) { if (error) {

View File

@ -69,9 +69,9 @@ void Utils::restartDtu()
ESP.restart(); 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); MessageOutput.printf("Alloc failed: %s, %d\r\n", function, line);
return false; return false;
} }
@ -79,16 +79,6 @@ bool Utils::checkJsonAlloc(const DynamicJsonDocument& doc, const char* function,
return true; 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 /// @brief Remove all files but the PINMAPPING_FILENAME
void Utils::removeAllFiles() void Utils::removeAllFiles()
{ {

View File

@ -4,6 +4,7 @@
*/ */
#include "WebApi.h" #include "WebApi.h"
#include "Configuration.h" #include "Configuration.h"
#include "MessageOutput.h"
#include "defaults.h" #include "defaults.h"
#include <AsyncJson.h> #include <AsyncJson.h>
@ -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; WebApiClass WebApi;

View File

@ -93,7 +93,7 @@ void WebApiHuaweiClass::onPost(AsyncWebServerRequest* request)
return; return;
} }
DynamicJsonDocument root(1024); JsonDocument root;
DeserializationError error = deserializeJson(root, json); DeserializationError error = deserializeJson(root, json);
float value; float value;
uint8_t online = true; uint8_t online = true;
@ -164,12 +164,9 @@ void WebApiHuaweiClass::onPost(AsyncWebServerRequest* request)
} }
} }
retMsg["type"] = "success"; WebApi.writeConfig(retMsg);
retMsg["message"] = "Settings saved!";
retMsg["code"] = WebApiError::GenericSuccess;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
@ -229,7 +226,7 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request)
return; return;
} }
DynamicJsonDocument root(1024); JsonDocument root;
DeserializationError error = deserializeJson(root, json); DeserializationError error = deserializeJson(root, json);
if (error) { if (error) {
@ -268,8 +265,7 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request)
config.Huawei.Auto_Power_Stop_BatterySoC_Threshold = root["stop_batterysoc_threshold"]; config.Huawei.Auto_Power_Stop_BatterySoC_Threshold = root["stop_batterysoc_threshold"];
WebApi.writeConfig(retMsg); WebApi.writeConfig(retMsg);
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
// TODO(schlimmchen): HuaweiCan has no real concept of the fact that the // TODO(schlimmchen): HuaweiCan has no real concept of the fact that the
// config might change. at least not regarding CAN parameters. until that // config might change. at least not regarding CAN parameters. until that

View File

@ -80,7 +80,7 @@ void WebApiBatteryClass::onAdminPost(AsyncWebServerRequest* request)
return; return;
} }
DynamicJsonDocument root(1024); JsonDocument root;
DeserializationError error = deserializeJson(root, json); DeserializationError error = deserializeJson(root, json);
if (error) { if (error) {
@ -94,8 +94,7 @@ void WebApiBatteryClass::onAdminPost(AsyncWebServerRequest* request)
if (!root.containsKey("enabled") || !root.containsKey("provider")) { if (!root.containsKey("enabled") || !root.containsKey("provider")) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -110,8 +109,7 @@ void WebApiBatteryClass::onAdminPost(AsyncWebServerRequest* request)
WebApi.writeConfig(retMsg); WebApi.writeConfig(retMsg);
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
Battery.updateSettings(); Battery.updateSettings();
MqttHandleBatteryHass.forceUpdate(); MqttHandleBatteryHass.forceUpdate();

View File

@ -53,51 +53,24 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
} }
AsyncJsonResponse* response = new AsyncJsonResponse(); AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot(); 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);
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;
}
if (!(root.containsKey("delete"))) { if (!(root.containsKey("delete"))) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (root["delete"].as<bool>() == false) { if (root["delete"].as<bool>() == false) {
retMsg["message"] = "Not deleted anything!"; retMsg["message"] = "Not deleted anything!";
retMsg["code"] = WebApiError::ConfigNotDeleted; retMsg["code"] = WebApiError::ConfigNotDeleted;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -105,8 +78,7 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
retMsg["message"] = "Configuration resettet. Rebooting now..."; retMsg["message"] = "Configuration resettet. Rebooting now...";
retMsg["code"] = WebApiError::ConfigSuccess; retMsg["code"] = WebApiError::ConfigSuccess;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
Utils::removeAllFiles(); Utils::removeAllFiles();
Utils::restartDtu(); Utils::restartDtu();
@ -120,7 +92,7 @@ void WebApiConfigClass::onConfigListGet(AsyncWebServerRequest* request)
AsyncJsonResponse* response = new AsyncJsonResponse(); AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot(); auto& root = response->getRoot();
auto data = root.createNestedArray("configs"); auto data = root["configs"].to<JsonArray>();
File rootfs = LittleFS.open("/"); File rootfs = LittleFS.open("/");
File file = rootfs.openNextFile(); File file = rootfs.openNextFile();
@ -128,15 +100,14 @@ void WebApiConfigClass::onConfigListGet(AsyncWebServerRequest* request)
if (file.isDirectory()) { if (file.isDirectory()) {
continue; continue;
} }
JsonObject obj = data.createNestedObject(); JsonObject obj = data.add<JsonObject>();
obj["name"] = String(file.name()); obj["name"] = String(file.name());
file = rootfs.openNextFile(); file = rootfs.openNextFile();
} }
file.close(); file.close();
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiConfigClass::onConfigUploadFinish(AsyncWebServerRequest* request) void WebApiConfigClass::onConfigUploadFinish(AsyncWebServerRequest* request)

View File

@ -26,15 +26,15 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
return; return;
} }
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot(); auto& root = response->getRoot();
const CONFIG_T& config = Configuration.get(); const CONFIG_T& config = Configuration.get();
const PinMapping_t& pin = PinMapping.get(); const PinMapping_t& pin = PinMapping.get();
auto curPin = root.createNestedObject("curPin"); auto curPin = root["curPin"].to<JsonObject>();
curPin["name"] = config.Dev_PinMapping; curPin["name"] = config.Dev_PinMapping;
auto nrfPinObj = curPin.createNestedObject("nrf24"); auto nrfPinObj = curPin["nrf24"].to<JsonObject>();
nrfPinObj["clk"] = pin.nrf24_clk; nrfPinObj["clk"] = pin.nrf24_clk;
nrfPinObj["cs"] = pin.nrf24_cs; nrfPinObj["cs"] = pin.nrf24_cs;
nrfPinObj["en"] = pin.nrf24_en; nrfPinObj["en"] = pin.nrf24_en;
@ -42,7 +42,7 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
nrfPinObj["miso"] = pin.nrf24_miso; nrfPinObj["miso"] = pin.nrf24_miso;
nrfPinObj["mosi"] = pin.nrf24_mosi; nrfPinObj["mosi"] = pin.nrf24_mosi;
auto cmtPinObj = curPin.createNestedObject("cmt"); auto cmtPinObj = curPin["cmt"].to<JsonObject>();
cmtPinObj["clk"] = pin.cmt_clk; cmtPinObj["clk"] = pin.cmt_clk;
cmtPinObj["cs"] = pin.cmt_cs; cmtPinObj["cs"] = pin.cmt_cs;
cmtPinObj["fcs"] = pin.cmt_fcs; cmtPinObj["fcs"] = pin.cmt_fcs;
@ -50,7 +50,7 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
cmtPinObj["gpio2"] = pin.cmt_gpio2; cmtPinObj["gpio2"] = pin.cmt_gpio2;
cmtPinObj["gpio3"] = pin.cmt_gpio3; cmtPinObj["gpio3"] = pin.cmt_gpio3;
auto ethPinObj = curPin.createNestedObject("eth"); auto ethPinObj = curPin["eth"].to<JsonObject>();
ethPinObj["enabled"] = pin.eth_enabled; ethPinObj["enabled"] = pin.eth_enabled;
ethPinObj["phy_addr"] = pin.eth_phy_addr; ethPinObj["phy_addr"] = pin.eth_phy_addr;
ethPinObj["power"] = pin.eth_power; ethPinObj["power"] = pin.eth_power;
@ -59,19 +59,19 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
ethPinObj["type"] = pin.eth_type; ethPinObj["type"] = pin.eth_type;
ethPinObj["clk_mode"] = pin.eth_clk_mode; ethPinObj["clk_mode"] = pin.eth_clk_mode;
auto displayPinObj = curPin.createNestedObject("display"); auto displayPinObj = curPin["display"].to<JsonObject>();
displayPinObj["type"] = pin.display_type; displayPinObj["type"] = pin.display_type;
displayPinObj["data"] = pin.display_data; displayPinObj["data"] = pin.display_data;
displayPinObj["clk"] = pin.display_clk; displayPinObj["clk"] = pin.display_clk;
displayPinObj["cs"] = pin.display_cs; displayPinObj["cs"] = pin.display_cs;
displayPinObj["reset"] = pin.display_reset; displayPinObj["reset"] = pin.display_reset;
auto ledPinObj = curPin.createNestedObject("led"); auto ledPinObj = curPin["led"].to<JsonObject>();
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
ledPinObj["led" + String(i)] = pin.led[i]; ledPinObj["led" + String(i)] = pin.led[i];
} }
auto display = root.createNestedObject("display"); auto display = root["display"].to<JsonObject>();
display["rotation"] = config.Display.Rotation; display["rotation"] = config.Display.Rotation;
display["power_safe"] = config.Display.PowerSafe; display["power_safe"] = config.Display.PowerSafe;
display["screensaver"] = config.Display.ScreenSaver; display["screensaver"] = config.Display.ScreenSaver;
@ -80,25 +80,25 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
display["diagramduration"] = config.Display.Diagram.Duration; display["diagramduration"] = config.Display.Diagram.Duration;
display["diagrammode"] = config.Display.Diagram.Mode; display["diagrammode"] = config.Display.Diagram.Mode;
auto leds = root.createNestedArray("led"); auto leds = root["led"].to<JsonArray>();
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) { for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
auto led = leds.createNestedObject(); auto led = leds.add<JsonObject>();
led["brightness"] = config.Led_Single[i].Brightness; led["brightness"] = config.Led_Single[i].Brightness;
} }
auto victronPinObj = curPin.createNestedObject("victron"); auto victronPinObj = curPin["victron"].to<JsonObject>();
victronPinObj["rx"] = pin.victron_rx; victronPinObj["rx"] = pin.victron_rx;
victronPinObj["tx"] = pin.victron_tx; victronPinObj["tx"] = pin.victron_tx;
victronPinObj["rx2"] = pin.victron_rx2; victronPinObj["rx2"] = pin.victron_rx2;
victronPinObj["tx2"] = pin.victron_tx2; victronPinObj["tx2"] = pin.victron_tx2;
JsonObject batteryPinObj = curPin.createNestedObject("battery"); auto batteryPinObj = curPin["battery"].to<JsonObject>();
batteryPinObj["rx"] = pin.battery_rx; batteryPinObj["rx"] = pin.battery_rx;
batteryPinObj["rxen"] = pin.battery_rxen; batteryPinObj["rxen"] = pin.battery_rxen;
batteryPinObj["tx"] = pin.battery_tx; batteryPinObj["tx"] = pin.battery_tx;
batteryPinObj["txen"] = pin.battery_txen; batteryPinObj["txen"] = pin.battery_txen;
JsonObject huaweiPinObj = curPin.createNestedObject("huawei"); auto huaweiPinObj = curPin["huawei"].to<JsonObject>();
huaweiPinObj["miso"] = pin.huawei_miso; huaweiPinObj["miso"] = pin.huawei_miso;
huaweiPinObj["mosi"] = pin.huawei_mosi; huaweiPinObj["mosi"] = pin.huawei_mosi;
huaweiPinObj["clk"] = pin.huawei_clk; huaweiPinObj["clk"] = pin.huawei_clk;
@ -106,8 +106,7 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
huaweiPinObj["cs"] = pin.huawei_cs; huaweiPinObj["cs"] = pin.huawei_cs;
huaweiPinObj["power"] = pin.huawei_power; huaweiPinObj["power"] = pin.huawei_power;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
@ -116,45 +115,19 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
return; return;
} }
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot(); 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);
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;
}
if (!(root.containsKey("curPin") if (!(root.containsKey("curPin")
|| root.containsKey("display"))) { || root.containsKey("display"))) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; 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["message"] = "Pin mapping must between 1 and " STR(DEV_MAX_MAPPING_NAME_STRLEN) " characters long!";
retMsg["code"] = WebApiError::HardwarePinMappingLength; retMsg["code"] = WebApiError::HardwarePinMappingLength;
retMsg["param"]["max"] = DEV_MAX_MAPPING_NAME_STRLEN; retMsg["param"]["max"] = DEV_MAX_MAPPING_NAME_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -194,8 +166,7 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
WebApi.writeConfig(retMsg); WebApi.writeConfig(retMsg);
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
if (performRestart) { if (performRestart) {
Utils::restartDtu(); Utils::restartDtu();

View File

@ -23,13 +23,7 @@ void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request)
AsyncJsonResponse* response = new AsyncJsonResponse(); AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot(); auto& root = response->getRoot();
auto serial = WebApi.parseSerialFromRequest(request);
uint64_t serial = 0;
if (request->hasParam("inv")) {
String s = request->getParam("inv")->value();
serial = strtoll(s.c_str(), NULL, 16);
}
auto inv = Hoymiles.getInverterBySerial(serial); auto inv = Hoymiles.getInverterBySerial(serial);
if (inv != nullptr) { if (inv != nullptr) {
@ -43,6 +37,5 @@ void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request)
root["fw_build_datetime"] = inv->DevInfo()->getFwBuildDateTimeStr(); root["fw_build_datetime"] = inv->DevInfo()->getFwBuildDateTimeStr();
} }
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }

View File

@ -63,10 +63,10 @@ void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request)
root["cmt_country"] = config.Dtu.Cmt.CountryMode; root["cmt_country"] = config.Dtu.Cmt.CountryMode;
root["cmt_chan_width"] = Hoymiles.getRadioCmt()->getChannelWidth(); root["cmt_chan_width"] = Hoymiles.getRadioCmt()->getChannelWidth();
auto data = root.createNestedArray("country_def"); auto data = root["country_def"].to<JsonArray>();
auto countryDefs = Hoymiles.getRadioCmt()->getCountryFrequencyList(); auto countryDefs = Hoymiles.getRadioCmt()->getCountryFrequencyList();
for (const auto& definition : countryDefs) { for (const auto& definition : countryDefs) {
auto obj = data.createNestedObject(); auto obj = data.add<JsonObject>();
obj["freq_default"] = definition.definition.Freq_Default; obj["freq_default"] = definition.definition.Freq_Default;
obj["freq_min"] = definition.definition.Freq_Min; obj["freq_min"] = definition.definition.Freq_Min;
obj["freq_max"] = definition.definition.Freq_Max; 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; obj["freq_legal_max"] = definition.definition.Freq_Legal_Max;
} }
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request) void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
@ -85,37 +84,12 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
} }
AsyncJsonResponse* response = new AsyncJsonResponse(); AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot(); 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);
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;
}
if (!(root.containsKey("serial") if (!(root.containsKey("serial")
&& root.containsKey("pollinterval") && root.containsKey("pollinterval")
@ -126,8 +100,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
&& root.containsKey("cmt_country"))) { && root.containsKey("cmt_country"))) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -137,40 +110,35 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
if (serial == 0) { if (serial == 0) {
retMsg["message"] = "Serial cannot be zero!"; retMsg["message"] = "Serial cannot be zero!";
retMsg["code"] = WebApiError::DtuSerialZero; retMsg["code"] = WebApiError::DtuSerialZero;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (root["pollinterval"].as<uint32_t>() == 0) { if (root["pollinterval"].as<uint32_t>() == 0) {
retMsg["message"] = "Poll interval must be greater zero!"; retMsg["message"] = "Poll interval must be greater zero!";
retMsg["code"] = WebApiError::DtuPollZero; retMsg["code"] = WebApiError::DtuPollZero;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (root["nrf_palevel"].as<uint8_t>() > 3) { if (root["nrf_palevel"].as<uint8_t>() > 3) {
retMsg["message"] = "Invalid power level setting!"; retMsg["message"] = "Invalid power level setting!";
retMsg["code"] = WebApiError::DtuInvalidPowerLevel; retMsg["code"] = WebApiError::DtuInvalidPowerLevel;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (root["cmt_palevel"].as<int8_t>() < -10 || root["cmt_palevel"].as<int8_t>() > 20) { if (root["cmt_palevel"].as<int8_t>() < -10 || root["cmt_palevel"].as<int8_t>() > 20) {
retMsg["message"] = "Invalid power level setting!"; retMsg["message"] = "Invalid power level setting!";
retMsg["code"] = WebApiError::DtuInvalidPowerLevel; retMsg["code"] = WebApiError::DtuInvalidPowerLevel;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (root["cmt_country"].as<uint8_t>() >= CountryModeId_t::CountryModeId_Max) { if (root["cmt_country"].as<uint8_t>() >= CountryModeId_t::CountryModeId_Max) {
retMsg["message"] = "Invalid country setting!"; retMsg["message"] = "Invalid country setting!";
retMsg["code"] = WebApiError::DtuInvalidCmtCountry; retMsg["code"] = WebApiError::DtuInvalidCmtCountry;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -183,8 +151,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
retMsg["code"] = WebApiError::DtuInvalidCmtFrequency; retMsg["code"] = WebApiError::DtuInvalidCmtFrequency;
retMsg["param"]["min"] = FrequencyDefinition.Freq_Min; retMsg["param"]["min"] = FrequencyDefinition.Freq_Min;
retMsg["param"]["max"] = FrequencyDefinition.Freq_Max; retMsg["param"]["max"] = FrequencyDefinition.Freq_Max;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -200,8 +167,8 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
WebApi.writeConfig(retMsg); WebApi.writeConfig(retMsg);
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
_applyDataTask.enable(); _applyDataTask.enable();
_applyDataTask.restart();
} }

View File

@ -20,14 +20,9 @@ void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request)
return; return;
} }
AsyncJsonResponse* response = new AsyncJsonResponse(false, 2048); AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot(); auto& root = response->getRoot();
auto serial = WebApi.parseSerialFromRequest(request);
uint64_t serial = 0;
if (request->hasParam("inv")) {
String s = request->getParam("inv")->value();
serial = strtoll(s.c_str(), NULL, 16);
}
AlarmMessageLocale_t locale = AlarmMessageLocale_t::EN; AlarmMessageLocale_t locale = AlarmMessageLocale_t::EN;
if (request->hasParam("locale")) { if (request->hasParam("locale")) {
@ -47,10 +42,10 @@ void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request)
uint8_t logEntryCount = inv->EventLog()->getEntryCount(); uint8_t logEntryCount = inv->EventLog()->getEntryCount();
root["count"] = logEntryCount; root["count"] = logEntryCount;
JsonArray eventsArray = root.createNestedArray("events"); JsonArray eventsArray = root["events"].to<JsonArray>();
for (uint8_t logEntry = 0; logEntry < logEntryCount; logEntry++) { for (uint8_t logEntry = 0; logEntry < logEntryCount; logEntry++) {
JsonObject eventsObject = eventsArray.createNestedObject(); JsonObject eventsObject = eventsArray.add<JsonObject>();
AlarmLogEntry_t entry; AlarmLogEntry_t entry;
inv->EventLog()->getLogEntry(logEntry, entry, locale); inv->EventLog()->getLogEntry(logEntry, entry, locale);
@ -62,6 +57,5 @@ void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request)
} }
} }
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }

View File

@ -21,32 +21,26 @@ void WebApiGridProfileClass::onGridProfileStatus(AsyncWebServerRequest* request)
return; return;
} }
AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192); AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot(); auto& root = response->getRoot();
auto serial = WebApi.parseSerialFromRequest(request);
uint64_t serial = 0;
if (request->hasParam("inv")) {
String s = request->getParam("inv")->value();
serial = strtoll(s.c_str(), NULL, 16);
}
auto inv = Hoymiles.getInverterBySerial(serial); auto inv = Hoymiles.getInverterBySerial(serial);
if (inv != nullptr) { if (inv != nullptr) {
root["name"] = inv->GridProfile()->getProfileName(); root["name"] = inv->GridProfile()->getProfileName();
root["version"] = inv->GridProfile()->getProfileVersion(); root["version"] = inv->GridProfile()->getProfileVersion();
auto jsonSections = root.createNestedArray("sections"); auto jsonSections = root["sections"].to<JsonArray>();
auto profSections = inv->GridProfile()->getProfile(); auto profSections = inv->GridProfile()->getProfile();
for (auto &profSection : profSections) { for (auto &profSection : profSections) {
auto jsonSection = jsonSections.createNestedObject(); auto jsonSection = jsonSections.add<JsonObject>();
jsonSection["name"] = profSection.SectionName; jsonSection["name"] = profSection.SectionName;
auto jsonItems = jsonSection.createNestedArray("items"); auto jsonItems = jsonSection["items"].to<JsonArray>();
for (auto &profItem : profSection.items) { for (auto &profItem : profSection.items) {
auto jsonItem = jsonItems.createNestedObject(); auto jsonItem = jsonItems.add<JsonObject>();
jsonItem["n"] = profItem.Name; jsonItem["n"] = profItem.Name;
jsonItem["u"] = profItem.Unit; jsonItem["u"] = profItem.Unit;
@ -55,8 +49,7 @@ void WebApiGridProfileClass::onGridProfileStatus(AsyncWebServerRequest* request)
} }
} }
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiGridProfileClass::onGridProfileRawdata(AsyncWebServerRequest* request) void WebApiGridProfileClass::onGridProfileRawdata(AsyncWebServerRequest* request)
@ -65,24 +58,17 @@ void WebApiGridProfileClass::onGridProfileRawdata(AsyncWebServerRequest* request
return; return;
} }
AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096); AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot(); auto& root = response->getRoot();
auto serial = WebApi.parseSerialFromRequest(request);
uint64_t serial = 0;
if (request->hasParam("inv")) {
String s = request->getParam("inv")->value();
serial = strtoll(s.c_str(), NULL, 16);
}
auto inv = Hoymiles.getInverterBySerial(serial); auto inv = Hoymiles.getInverterBySerial(serial);
if (inv != nullptr) { if (inv != nullptr) {
auto raw = root.createNestedArray("raw"); auto raw = root["raw"].to<JsonArray>();
auto data = inv->GridProfile()->getRawData(); auto data = inv->GridProfile()->getRawData();
copyArray(&data[0], data.size(), raw); copyArray(&data[0], data.size(), raw);
} }
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }

View File

@ -29,15 +29,15 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
return; return;
} }
AsyncJsonResponse* response = new AsyncJsonResponse(false, 768 * INV_MAX_COUNT); AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot(); auto& root = response->getRoot();
JsonArray data = root.createNestedArray("inverter"); JsonArray data = root["inverter"].to<JsonArray>();
const CONFIG_T& config = Configuration.get(); const CONFIG_T& config = Configuration.get();
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
if (config.Inverter[i].Serial > 0) { if (config.Inverter[i].Serial > 0) {
JsonObject obj = data.createNestedObject(); JsonObject obj = data.add<JsonObject>();
obj["id"] = i; obj["id"] = i;
obj["name"] = String(config.Inverter[i].Name); obj["name"] = String(config.Inverter[i].Name);
obj["order"] = config.Inverter[i].Order; obj["order"] = config.Inverter[i].Order;
@ -67,9 +67,9 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
max_channels = inv->Statistics()->getChannelsByType(TYPE_DC).size(); max_channels = inv->Statistics()->getChannelsByType(TYPE_DC).size();
} }
JsonArray channel = obj.createNestedArray("channel"); JsonArray channel = obj["channel"].to<JsonArray>();
for (uint8_t c = 0; c < max_channels; c++) { for (uint8_t c = 0; c < max_channels; c++) {
JsonObject chanData = channel.createNestedObject(); JsonObject chanData = channel.add<JsonObject>();
chanData["name"] = config.Inverter[i].channel[c].Name; chanData["name"] = config.Inverter[i].channel[c].Name;
chanData["max_power"] = config.Inverter[i].channel[c].MaxChannelPower; chanData["max_power"] = config.Inverter[i].channel[c].MaxChannelPower;
chanData["yield_total_offset"] = config.Inverter[i].channel[c].YieldTotalOffset; chanData["yield_total_offset"] = config.Inverter[i].channel[c].YieldTotalOffset;
@ -77,8 +77,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
} }
} }
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request) void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
@ -88,44 +87,18 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
} }
AsyncJsonResponse* response = new AsyncJsonResponse(); AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot(); 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);
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;
}
if (!(root.containsKey("serial") if (!(root.containsKey("serial")
&& root.containsKey("name"))) { && root.containsKey("name"))) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -135,8 +108,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
if (serial == 0) { if (serial == 0) {
retMsg["message"] = "Serial must be a number > 0!"; retMsg["message"] = "Serial must be a number > 0!";
retMsg["code"] = WebApiError::InverterSerialZero; retMsg["code"] = WebApiError::InverterSerialZero;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; 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["message"] = "Name must between 1 and " STR(INV_MAX_NAME_STRLEN) " characters long!";
retMsg["code"] = WebApiError::InverterNameLength; retMsg["code"] = WebApiError::InverterNameLength;
retMsg["param"]["max"] = INV_MAX_NAME_STRLEN; retMsg["param"]["max"] = INV_MAX_NAME_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -155,8 +126,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
retMsg["message"] = "Only " STR(INV_MAX_COUNT) " inverters are supported!"; retMsg["message"] = "Only " STR(INV_MAX_COUNT) " inverters are supported!";
retMsg["code"] = WebApiError::InverterCount; retMsg["code"] = WebApiError::InverterCount;
retMsg["param"]["max"] = INV_MAX_COUNT; retMsg["param"]["max"] = INV_MAX_COUNT;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -167,8 +137,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
WebApi.writeConfig(retMsg, WebApiError::InverterAdded, "Inverter created!"); WebApi.writeConfig(retMsg, WebApiError::InverterAdded, "Inverter created!");
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
auto inv = Hoymiles.addInverter(inverter->Name, inverter->Serial); auto inv = Hoymiles.addInverter(inverter->Name, inverter->Serial);
@ -188,51 +157,24 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
} }
AsyncJsonResponse* response = new AsyncJsonResponse(); AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot(); 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);
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;
}
if (!(root.containsKey("id") && root.containsKey("serial") && root.containsKey("name") && root.containsKey("channel"))) { if (!(root.containsKey("id") && root.containsKey("serial") && root.containsKey("name") && root.containsKey("channel"))) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (root["id"].as<uint8_t>() > INV_MAX_COUNT - 1) { if (root["id"].as<uint8_t>() > INV_MAX_COUNT - 1) {
retMsg["message"] = "Invalid ID specified!"; retMsg["message"] = "Invalid ID specified!";
retMsg["code"] = WebApiError::InverterInvalidId; retMsg["code"] = WebApiError::InverterInvalidId;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -242,8 +184,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
if (serial == 0) { if (serial == 0) {
retMsg["message"] = "Serial must be a number > 0!"; retMsg["message"] = "Serial must be a number > 0!";
retMsg["code"] = WebApiError::InverterSerialZero; retMsg["code"] = WebApiError::InverterSerialZero;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; 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["message"] = "Name must between 1 and " STR(INV_MAX_NAME_STRLEN) " characters long!";
retMsg["code"] = WebApiError::InverterNameLength; retMsg["code"] = WebApiError::InverterNameLength;
retMsg["param"]["max"] = INV_MAX_NAME_STRLEN; retMsg["param"]["max"] = INV_MAX_NAME_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -260,8 +200,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
if (channelArray.size() == 0 || channelArray.size() > INV_MAX_CHAN_COUNT) { if (channelArray.size() == 0 || channelArray.size() > INV_MAX_CHAN_COUNT) {
retMsg["message"] = "Invalid amount of max channel setting given!"; retMsg["message"] = "Invalid amount of max channel setting given!";
retMsg["code"] = WebApiError::InverterInvalidMaxChannel; retMsg["code"] = WebApiError::InverterInvalidMaxChannel;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -293,8 +232,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
WebApi.writeConfig(retMsg, WebApiError::InverterChanged, "Inverter changed!"); WebApi.writeConfig(retMsg, WebApiError::InverterChanged, "Inverter changed!");
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterBySerial(old_serial); std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterBySerial(old_serial);
@ -333,51 +271,24 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
} }
AsyncJsonResponse* response = new AsyncJsonResponse(); AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot(); 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);
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;
}
if (!(root.containsKey("id"))) { if (!(root.containsKey("id"))) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (root["id"].as<uint8_t>() > INV_MAX_COUNT - 1) { if (root["id"].as<uint8_t>() > INV_MAX_COUNT - 1) {
retMsg["message"] = "Invalid ID specified!"; retMsg["message"] = "Invalid ID specified!";
retMsg["code"] = WebApiError::InverterInvalidId; retMsg["code"] = WebApiError::InverterInvalidId;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -390,8 +301,7 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
WebApi.writeConfig(retMsg, WebApiError::InverterDeleted, "Inverter deleted!"); WebApi.writeConfig(retMsg, WebApiError::InverterDeleted, "Inverter deleted!");
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
MqttHandleHass.forceUpdate(); MqttHandleHass.forceUpdate();
} }
@ -403,43 +313,17 @@ void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request)
} }
AsyncJsonResponse* response = new AsyncJsonResponse(); AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot(); 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);
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;
}
if (!(root.containsKey("order"))) { if (!(root.containsKey("order"))) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -457,6 +341,5 @@ void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request)
WebApi.writeConfig(retMsg, WebApiError::InverterOrdered, "Inverter order saved!"); WebApi.writeConfig(retMsg, WebApiError::InverterOrdered, "Inverter order saved!");
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }

View File

@ -47,8 +47,7 @@ void WebApiLimitClass::onLimitStatus(AsyncWebServerRequest* request)
root[serial]["limit_set_status"] = limitStatus; root[serial]["limit_set_status"] = limitStatus;
} }
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request) void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
@ -58,45 +57,19 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
} }
AsyncJsonResponse* response = new AsyncJsonResponse(); AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot(); 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);
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;
}
if (!(root.containsKey("serial") if (!(root.containsKey("serial")
&& root.containsKey("limit_value") && root.containsKey("limit_value")
&& root.containsKey("limit_type"))) { && root.containsKey("limit_type"))) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -106,8 +79,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
if (serial == 0) { if (serial == 0) {
retMsg["message"] = "Serial must be a number > 0!"; retMsg["message"] = "Serial must be a number > 0!";
retMsg["code"] = WebApiError::LimitSerialZero; retMsg["code"] = WebApiError::LimitSerialZero;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -115,8 +87,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
retMsg["message"] = "Limit must between 0 and " STR(MAX_INVERTER_LIMIT) "!"; retMsg["message"] = "Limit must between 0 and " STR(MAX_INVERTER_LIMIT) "!";
retMsg["code"] = WebApiError::LimitInvalidLimit; retMsg["code"] = WebApiError::LimitInvalidLimit;
retMsg["param"]["max"] = MAX_INVERTER_LIMIT; retMsg["param"]["max"] = MAX_INVERTER_LIMIT;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -127,8 +98,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
retMsg["message"] = "Invalid type specified!"; retMsg["message"] = "Invalid type specified!";
retMsg["code"] = WebApiError::LimitInvalidType; retMsg["code"] = WebApiError::LimitInvalidType;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -139,8 +109,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
if (inv == nullptr) { if (inv == nullptr) {
retMsg["message"] = "Invalid inverter specified!"; retMsg["message"] = "Invalid inverter specified!";
retMsg["code"] = WebApiError::LimitInvalidInverter; retMsg["code"] = WebApiError::LimitInvalidInverter;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -150,6 +119,5 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
retMsg["message"] = "Settings saved!"; retMsg["message"] = "Settings saved!";
retMsg["code"] = WebApiError::GenericSuccess; retMsg["code"] = WebApiError::GenericSuccess;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }

View File

@ -22,44 +22,18 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
return; return;
} }
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot(); 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);
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;
}
if (!(root.containsKey("reboot"))) { if (!(root.containsKey("reboot"))) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -68,14 +42,12 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
retMsg["message"] = "Reboot triggered!"; retMsg["message"] = "Reboot triggered!";
retMsg["code"] = WebApiError::MaintenanceRebootTriggered; retMsg["code"] = WebApiError::MaintenanceRebootTriggered;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
Utils::restartDtu(); Utils::restartDtu();
} else { } else {
retMsg["message"] = "Reboot cancled!"; retMsg["message"] = "Reboot cancled!";
retMsg["code"] = WebApiError::MaintenanceRebootCancled; retMsg["code"] = WebApiError::MaintenanceRebootCancled;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
} }

View File

@ -30,7 +30,7 @@ void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request)
return; return;
} }
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot(); auto& root = response->getRoot();
const CONFIG_T& config = Configuration.get(); 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_topic"] = config.Mqtt.Hass.Topic;
root["mqtt_hass_individualpanels"] = config.Mqtt.Hass.IndividualPanels; root["mqtt_hass_individualpanels"] = config.Mqtt.Hass.IndividualPanels;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request) void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request)
@ -65,7 +64,7 @@ void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request)
return; return;
} }
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot(); auto& root = response->getRoot();
const CONFIG_T& config = Configuration.get(); 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_topic"] = config.Mqtt.Hass.Topic;
root["mqtt_hass_individualpanels"] = config.Mqtt.Hass.IndividualPanels; root["mqtt_hass_individualpanels"] = config.Mqtt.Hass.IndividualPanels;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
@ -104,38 +102,13 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
return; return;
} }
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE); AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot(); 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);
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;
}
if (!(root.containsKey("mqtt_enabled") if (!(root.containsKey("mqtt_enabled")
&& root.containsKey("mqtt_verbose_logging") && root.containsKey("mqtt_verbose_logging")
@ -162,8 +135,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
&& root.containsKey("mqtt_hass_individualpanels"))) { && root.containsKey("mqtt_hass_individualpanels"))) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; 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["message"] = "MqTT Server must between 1 and " STR(MQTT_MAX_HOSTNAME_STRLEN) " characters long!";
retMsg["code"] = WebApiError::MqttHostnameLength; retMsg["code"] = WebApiError::MqttHostnameLength;
retMsg["param"]["max"] = MQTT_MAX_HOSTNAME_STRLEN; retMsg["param"]["max"] = MQTT_MAX_HOSTNAME_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; 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["message"] = "Username must not be longer than " STR(MQTT_MAX_USERNAME_STRLEN) " characters!";
retMsg["code"] = WebApiError::MqttUsernameLength; retMsg["code"] = WebApiError::MqttUsernameLength;
retMsg["param"]["max"] = MQTT_MAX_USERNAME_STRLEN; retMsg["param"]["max"] = MQTT_MAX_USERNAME_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (root["mqtt_password"].as<String>().length() > MQTT_MAX_PASSWORD_STRLEN) { if (root["mqtt_password"].as<String>().length() > MQTT_MAX_PASSWORD_STRLEN) {
retMsg["message"] = "Password must not be longer than " STR(MQTT_MAX_PASSWORD_STRLEN) " characters!"; retMsg["message"] = "Password must not be longer than " STR(MQTT_MAX_PASSWORD_STRLEN) " characters!";
retMsg["code"] = WebApiError::MqttPasswordLength; retMsg["code"] = WebApiError::MqttPasswordLength;
retMsg["param"]["max"] = MQTT_MAX_PASSWORD_STRLEN; retMsg["param"]["max"] = MQTT_MAX_PASSWORD_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (root["mqtt_topic"].as<String>().length() > MQTT_MAX_TOPIC_STRLEN) { if (root["mqtt_topic"].as<String>().length() > MQTT_MAX_TOPIC_STRLEN) {
retMsg["message"] = "Topic must not be longer than " STR(MQTT_MAX_TOPIC_STRLEN) " characters!"; retMsg["message"] = "Topic must not be longer than " STR(MQTT_MAX_TOPIC_STRLEN) " characters!";
retMsg["code"] = WebApiError::MqttTopicLength; retMsg["code"] = WebApiError::MqttTopicLength;
retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN; retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (root["mqtt_topic"].as<String>().indexOf(' ') != -1) { if (root["mqtt_topic"].as<String>().indexOf(' ') != -1) {
retMsg["message"] = "Topic must not contain space characters!"; retMsg["message"] = "Topic must not contain space characters!";
retMsg["code"] = WebApiError::MqttTopicCharacter; retMsg["code"] = WebApiError::MqttTopicCharacter;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (!root["mqtt_topic"].as<String>().endsWith("/")) { if (!root["mqtt_topic"].as<String>().endsWith("/")) {
retMsg["message"] = "Topic must end with a slash (/)!"; retMsg["message"] = "Topic must end with a slash (/)!";
retMsg["code"] = WebApiError::MqttTopicTrailingSlash; retMsg["code"] = WebApiError::MqttTopicTrailingSlash;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (root["mqtt_port"].as<uint>() == 0 || root["mqtt_port"].as<uint>() > 65535) { if (root["mqtt_port"].as<uint>() == 0 || root["mqtt_port"].as<uint>() > 65535) {
retMsg["message"] = "Port must be a number between 1 and 65535!"; retMsg["message"] = "Port must be a number between 1 and 65535!";
retMsg["code"] = WebApiError::MqttPort; retMsg["code"] = WebApiError::MqttPort;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; 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["message"] = "Certificates must not be longer than " STR(MQTT_MAX_CERT_STRLEN) " characters!";
retMsg["code"] = WebApiError::MqttCertificateLength; retMsg["code"] = WebApiError::MqttCertificateLength;
retMsg["param"]["max"] = MQTT_MAX_CERT_STRLEN; retMsg["param"]["max"] = MQTT_MAX_CERT_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; 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["message"] = "LWT topic must not be longer than " STR(MQTT_MAX_TOPIC_STRLEN) " characters!";
retMsg["code"] = WebApiError::MqttLwtTopicLength; retMsg["code"] = WebApiError::MqttLwtTopicLength;
retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN; retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (root["mqtt_lwt_topic"].as<String>().indexOf(' ') != -1) { if (root["mqtt_lwt_topic"].as<String>().indexOf(' ') != -1) {
retMsg["message"] = "LWT topic must not contain space characters!"; retMsg["message"] = "LWT topic must not contain space characters!";
retMsg["code"] = WebApiError::MqttLwtTopicCharacter; retMsg["code"] = WebApiError::MqttLwtTopicCharacter;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; 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["message"] = "LWT online value must not be longer than " STR(MQTT_MAX_LWTVALUE_STRLEN) " characters!";
retMsg["code"] = WebApiError::MqttLwtOnlineLength; retMsg["code"] = WebApiError::MqttLwtOnlineLength;
retMsg["param"]["max"] = MQTT_MAX_LWTVALUE_STRLEN; retMsg["param"]["max"] = MQTT_MAX_LWTVALUE_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; 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["message"] = "LWT offline value must not be longer than " STR(MQTT_MAX_LWTVALUE_STRLEN) " characters!";
retMsg["code"] = WebApiError::MqttLwtOfflineLength; retMsg["code"] = WebApiError::MqttLwtOfflineLength;
retMsg["param"]["max"] = MQTT_MAX_LWTVALUE_STRLEN; retMsg["param"]["max"] = MQTT_MAX_LWTVALUE_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -276,8 +236,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
retMsg["message"] = "LWT QoS must not be greater than " STR(2) "!"; retMsg["message"] = "LWT QoS must not be greater than " STR(2) "!";
retMsg["code"] = WebApiError::MqttLwtQos; retMsg["code"] = WebApiError::MqttLwtQos;
retMsg["param"]["max"] = 2; retMsg["param"]["max"] = 2;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -286,8 +245,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
retMsg["code"] = WebApiError::MqttPublishInterval; retMsg["code"] = WebApiError::MqttPublishInterval;
retMsg["param"]["min"] = 5; retMsg["param"]["min"] = 5;
retMsg["param"]["max"] = 65535; retMsg["param"]["max"] = 65535;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; 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["message"] = "Hass topic must not be longer than " STR(MQTT_MAX_TOPIC_STRLEN) " characters!";
retMsg["code"] = WebApiError::MqttHassTopicLength; retMsg["code"] = WebApiError::MqttHassTopicLength;
retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN; retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (root["mqtt_hass_topic"].as<String>().indexOf(' ') != -1) { if (root["mqtt_hass_topic"].as<String>().indexOf(' ') != -1) {
retMsg["message"] = "Hass topic must not contain space characters!"; retMsg["message"] = "Hass topic must not contain space characters!";
retMsg["code"] = WebApiError::MqttHassTopicCharacter; retMsg["code"] = WebApiError::MqttHassTopicCharacter;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
} }
@ -339,8 +295,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
WebApi.writeConfig(retMsg); WebApi.writeConfig(retMsg);
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
MqttSettings.performReconnect(); MqttSettings.performReconnect();
MqttHandleHass.forceUpdate(); MqttHandleHass.forceUpdate();

View File

@ -46,8 +46,7 @@ void WebApiNetworkClass::onNetworkStatus(AsyncWebServerRequest* request)
root["ap_mac"] = WiFi.softAPmacAddress(); root["ap_mac"] = WiFi.softAPmacAddress();
root["ap_stationnum"] = WiFi.softAPgetStationNum(); root["ap_stationnum"] = WiFi.softAPgetStationNum();
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request) void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request)
@ -72,8 +71,7 @@ void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request)
root["aptimeout"] = config.WiFi.ApTimeout; root["aptimeout"] = config.WiFi.ApTimeout;
root["mdnsenabled"] = config.Mdns.Enabled; root["mdnsenabled"] = config.Mdns.Enabled;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request) void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
@ -83,37 +81,12 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
} }
AsyncJsonResponse* response = new AsyncJsonResponse(); AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot(); 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);
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;
}
if (!(root.containsKey("ssid") if (!(root.containsKey("ssid")
&& root.containsKey("password") && root.containsKey("password")
@ -127,8 +100,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
&& root.containsKey("aptimeout"))) { && root.containsKey("aptimeout"))) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -136,68 +108,59 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
if (!ipaddress.fromString(root["ipaddress"].as<String>())) { if (!ipaddress.fromString(root["ipaddress"].as<String>())) {
retMsg["message"] = "IP address is invalid!"; retMsg["message"] = "IP address is invalid!";
retMsg["code"] = WebApiError::NetworkIpInvalid; retMsg["code"] = WebApiError::NetworkIpInvalid;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
IPAddress netmask; IPAddress netmask;
if (!netmask.fromString(root["netmask"].as<String>())) { if (!netmask.fromString(root["netmask"].as<String>())) {
retMsg["message"] = "Netmask is invalid!"; retMsg["message"] = "Netmask is invalid!";
retMsg["code"] = WebApiError::NetworkNetmaskInvalid; retMsg["code"] = WebApiError::NetworkNetmaskInvalid;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
IPAddress gateway; IPAddress gateway;
if (!gateway.fromString(root["gateway"].as<String>())) { if (!gateway.fromString(root["gateway"].as<String>())) {
retMsg["message"] = "Gateway is invalid!"; retMsg["message"] = "Gateway is invalid!";
retMsg["code"] = WebApiError::NetworkGatewayInvalid; retMsg["code"] = WebApiError::NetworkGatewayInvalid;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
IPAddress dns1; IPAddress dns1;
if (!dns1.fromString(root["dns1"].as<String>())) { if (!dns1.fromString(root["dns1"].as<String>())) {
retMsg["message"] = "DNS Server IP 1 is invalid!"; retMsg["message"] = "DNS Server IP 1 is invalid!";
retMsg["code"] = WebApiError::NetworkDns1Invalid; retMsg["code"] = WebApiError::NetworkDns1Invalid;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
IPAddress dns2; IPAddress dns2;
if (!dns2.fromString(root["dns2"].as<String>())) { if (!dns2.fromString(root["dns2"].as<String>())) {
retMsg["message"] = "DNS Server IP 2 is invalid!"; retMsg["message"] = "DNS Server IP 2 is invalid!";
retMsg["code"] = WebApiError::NetworkDns2Invalid; retMsg["code"] = WebApiError::NetworkDns2Invalid;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (root["hostname"].as<String>().length() == 0 || root["hostname"].as<String>().length() > WIFI_MAX_HOSTNAME_STRLEN) { if (root["hostname"].as<String>().length() == 0 || root["hostname"].as<String>().length() > WIFI_MAX_HOSTNAME_STRLEN) {
retMsg["message"] = "Hostname must between 1 and " STR(WIFI_MAX_HOSTNAME_STRLEN) " characters long!"; retMsg["message"] = "Hostname must between 1 and " STR(WIFI_MAX_HOSTNAME_STRLEN) " characters long!";
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (NetworkSettings.NetworkMode() == network_mode::WiFi) { if (NetworkSettings.NetworkMode() == network_mode::WiFi) {
if (root["ssid"].as<String>().length() == 0 || root["ssid"].as<String>().length() > WIFI_MAX_SSID_STRLEN) { if (root["ssid"].as<String>().length() == 0 || root["ssid"].as<String>().length() > WIFI_MAX_SSID_STRLEN) {
retMsg["message"] = "SSID must between 1 and " STR(WIFI_MAX_SSID_STRLEN) " characters long!"; retMsg["message"] = "SSID must between 1 and " STR(WIFI_MAX_SSID_STRLEN) " characters long!";
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
} }
if (root["password"].as<String>().length() > WIFI_MAX_PASSWORD_STRLEN - 1) { if (root["password"].as<String>().length() > WIFI_MAX_PASSWORD_STRLEN - 1) {
retMsg["message"] = "Password must not be longer than " STR(WIFI_MAX_PASSWORD_STRLEN) " characters long!"; retMsg["message"] = "Password must not be longer than " STR(WIFI_MAX_PASSWORD_STRLEN) " characters long!";
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
if (root["aptimeout"].as<uint>() > 99999) { if (root["aptimeout"].as<uint>() > 99999) {
retMsg["message"] = "ApTimeout must be a number between 0 and 99999!"; retMsg["message"] = "ApTimeout must be a number between 0 and 99999!";
retMsg["code"] = WebApiError::NetworkApTimeoutInvalid; retMsg["code"] = WebApiError::NetworkApTimeoutInvalid;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -235,8 +198,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
WebApi.writeConfig(retMsg); WebApi.writeConfig(retMsg);
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
NetworkSettings.enableAdminMode(); NetworkSettings.enableAdminMode();
NetworkSettings.applyConfig(); NetworkSettings.applyConfig();

View File

@ -63,8 +63,7 @@ void WebApiNtpClass::onNtpStatus(AsyncWebServerRequest* request)
root["sun_isSunsetAvailable"] = SunPosition.isSunsetAvailable(); root["sun_isSunsetAvailable"] = SunPosition.isSunsetAvailable();
root["sun_isDayPeriod"] = SunPosition.isDayPeriod(); root["sun_isDayPeriod"] = SunPosition.isDayPeriod();
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiNtpClass::onNtpAdminGet(AsyncWebServerRequest* request) void WebApiNtpClass::onNtpAdminGet(AsyncWebServerRequest* request)
@ -84,8 +83,7 @@ void WebApiNtpClass::onNtpAdminGet(AsyncWebServerRequest* request)
root["latitude"] = config.Ntp.Latitude; root["latitude"] = config.Ntp.Latitude;
root["sunsettype"] = config.Ntp.SunsetType; root["sunsettype"] = config.Ntp.SunsetType;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request) void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
@ -95,37 +93,12 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
} }
AsyncJsonResponse* response = new AsyncJsonResponse(); AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot(); 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);
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;
}
if (!(root.containsKey("ntp_server") if (!(root.containsKey("ntp_server")
&& root.containsKey("ntp_timezone") && root.containsKey("ntp_timezone")
@ -134,8 +107,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
&& root.containsKey("sunsettype"))) { && root.containsKey("sunsettype"))) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; 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["message"] = "NTP Server must between 1 and " STR(NTP_MAX_SERVER_STRLEN) " characters long!";
retMsg["code"] = WebApiError::NtpServerLength; retMsg["code"] = WebApiError::NtpServerLength;
retMsg["param"]["max"] = NTP_MAX_SERVER_STRLEN; retMsg["param"]["max"] = NTP_MAX_SERVER_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; 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["message"] = "Timezone must between 1 and " STR(NTP_MAX_TIMEZONE_STRLEN) " characters long!";
retMsg["code"] = WebApiError::NtpTimezoneLength; retMsg["code"] = WebApiError::NtpTimezoneLength;
retMsg["param"]["max"] = NTP_MAX_TIMEZONE_STRLEN; retMsg["param"]["max"] = NTP_MAX_TIMEZONE_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; 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["message"] = "Timezone description must between 1 and " STR(NTP_MAX_TIMEZONEDESCR_STRLEN) " characters long!";
retMsg["code"] = WebApiError::NtpTimezoneDescriptionLength; retMsg["code"] = WebApiError::NtpTimezoneDescriptionLength;
retMsg["param"]["max"] = NTP_MAX_TIMEZONEDESCR_STRLEN; retMsg["param"]["max"] = NTP_MAX_TIMEZONEDESCR_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -176,8 +145,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
WebApi.writeConfig(retMsg); WebApi.writeConfig(retMsg);
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
NtpSettings.setServer(); NtpSettings.setServer();
NtpSettings.setTimezone(); NtpSettings.setTimezone();
@ -208,8 +176,7 @@ void WebApiNtpClass::onNtpTimeGet(AsyncWebServerRequest* request)
root["minute"] = timeinfo.tm_min; root["minute"] = timeinfo.tm_min;
root["second"] = timeinfo.tm_sec; root["second"] = timeinfo.tm_sec;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request) void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
@ -219,37 +186,12 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
} }
AsyncJsonResponse* response = new AsyncJsonResponse(); AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot(); 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);
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;
}
if (!(root.containsKey("year") if (!(root.containsKey("year")
&& root.containsKey("month") && root.containsKey("month")
@ -259,8 +201,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
&& root.containsKey("second"))) { && root.containsKey("second"))) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -269,8 +210,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
retMsg["code"] = WebApiError::NtpYearInvalid; retMsg["code"] = WebApiError::NtpYearInvalid;
retMsg["param"]["min"] = 2022; retMsg["param"]["min"] = 2022;
retMsg["param"]["max"] = 2100; retMsg["param"]["max"] = 2100;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -279,8 +219,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
retMsg["code"] = WebApiError::NtpMonthInvalid; retMsg["code"] = WebApiError::NtpMonthInvalid;
retMsg["param"]["min"] = 1; retMsg["param"]["min"] = 1;
retMsg["param"]["max"] = 12; retMsg["param"]["max"] = 12;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -289,8 +228,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
retMsg["code"] = WebApiError::NtpDayInvalid; retMsg["code"] = WebApiError::NtpDayInvalid;
retMsg["param"]["min"] = 1; retMsg["param"]["min"] = 1;
retMsg["param"]["max"] = 31; retMsg["param"]["max"] = 31;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -299,8 +237,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
retMsg["code"] = WebApiError::NtpHourInvalid; retMsg["code"] = WebApiError::NtpHourInvalid;
retMsg["param"]["min"] = 0; retMsg["param"]["min"] = 0;
retMsg["param"]["max"] = 23; retMsg["param"]["max"] = 23;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -309,8 +246,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
retMsg["code"] = WebApiError::NtpMinuteInvalid; retMsg["code"] = WebApiError::NtpMinuteInvalid;
retMsg["param"]["min"] = 0; retMsg["param"]["min"] = 0;
retMsg["param"]["max"] = 59; retMsg["param"]["max"] = 59;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -319,8 +255,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
retMsg["code"] = WebApiError::NtpSecondInvalid; retMsg["code"] = WebApiError::NtpSecondInvalid;
retMsg["param"]["min"] = 0; retMsg["param"]["min"] = 0;
retMsg["param"]["max"] = 59; retMsg["param"]["max"] = 59;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -341,6 +276,5 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
retMsg["message"] = "Time updated!"; retMsg["message"] = "Time updated!";
retMsg["code"] = WebApiError::NtpTimeUpdated; retMsg["code"] = WebApiError::NtpTimeUpdated;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }

View File

@ -40,8 +40,7 @@ void WebApiPowerClass::onPowerStatus(AsyncWebServerRequest* request)
root[inv->serialString()]["power_set_status"] = limitStatus; root[inv->serialString()]["power_set_status"] = limitStatus;
} }
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request) void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
@ -51,45 +50,19 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
} }
AsyncJsonResponse* response = new AsyncJsonResponse(); AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot(); 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);
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;
}
if (!(root.containsKey("serial") if (!(root.containsKey("serial")
&& (root.containsKey("power") && (root.containsKey("power")
|| root.containsKey("restart")))) { || root.containsKey("restart")))) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -99,8 +72,7 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
if (serial == 0) { if (serial == 0) {
retMsg["message"] = "Serial must be a number > 0!"; retMsg["message"] = "Serial must be a number > 0!";
retMsg["code"] = WebApiError::PowerSerialZero; retMsg["code"] = WebApiError::PowerSerialZero;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -108,8 +80,7 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
if (inv == nullptr) { if (inv == nullptr) {
retMsg["message"] = "Invalid inverter specified!"; retMsg["message"] = "Invalid inverter specified!";
retMsg["code"] = WebApiError::PowerInvalidInverter; retMsg["code"] = WebApiError::PowerInvalidInverter;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -126,6 +97,5 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
retMsg["message"] = "Settings saved!"; retMsg["message"] = "Settings saved!";
retMsg["code"] = WebApiError::GenericSuccess; retMsg["code"] = WebApiError::GenericSuccess;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }

View File

@ -27,10 +27,9 @@ void WebApiPowerLimiterClass::init(AsyncWebServer& server, Scheduler& scheduler)
void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request) void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request)
{ {
auto const& config = Configuration.get(); AsyncJsonResponse* response = new AsyncJsonResponse();
AsyncJsonResponse* response = new AsyncJsonResponse(false, 512);
auto& root = response->getRoot(); auto& root = response->getRoot();
auto const& config = Configuration.get();
root["enabled"] = config.PowerLimiter.Enabled; root["enabled"] = config.PowerLimiter.Enabled;
root["verbose_logging"] = config.PowerLimiter.VerboseLogging; root["verbose_logging"] = config.PowerLimiter.VerboseLogging;
@ -57,8 +56,7 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request)
root["full_solar_passthrough_start_voltage"] = static_cast<int>(config.PowerLimiter.FullSolarPassThroughStartVoltage * 100 + 0.5) / 100.0; root["full_solar_passthrough_start_voltage"] = static_cast<int>(config.PowerLimiter.FullSolarPassThroughStartVoltage * 100 + 0.5) / 100.0;
root["full_solar_passthrough_stop_voltage"] = static_cast<int>(config.PowerLimiter.FullSolarPassThroughStopVoltage * 100 + 0.5) / 100.0; root["full_solar_passthrough_stop_voltage"] = static_cast<int>(config.PowerLimiter.FullSolarPassThroughStopVoltage * 100 + 0.5) / 100.0;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiPowerLimiterClass::onMetaData(AsyncWebServerRequest* request) void WebApiPowerLimiterClass::onMetaData(AsyncWebServerRequest* request)
@ -72,14 +70,14 @@ void WebApiPowerLimiterClass::onMetaData(AsyncWebServerRequest* request)
if (config.Inverter[i].Serial != 0) { ++invAmount; } if (config.Inverter[i].Serial != 0) { ++invAmount; }
} }
AsyncJsonResponse* response = new AsyncJsonResponse(false, 256 + 256 * invAmount); AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot(); auto& root = response->getRoot();
root["power_meter_enabled"] = config.PowerMeter.Enabled; root["power_meter_enabled"] = config.PowerMeter.Enabled;
root["battery_enabled"] = config.Battery.Enabled; root["battery_enabled"] = config.Battery.Enabled;
root["charge_controller_enabled"] = config.Vedirect.Enabled; root["charge_controller_enabled"] = config.Vedirect.Enabled;
JsonObject inverters = root.createNestedObject("inverters"); JsonObject inverters = root["inverters"].to<JsonObject>();
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
if (config.Inverter[i].Serial == 0) { continue; } 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 // rather than the hex represenation as used when handling the inverter
// serial elsewhere in the web application, because in this case, the // serial elsewhere in the web application, because in this case, the
// serial is actually not displayed but only used as a value/index. // 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<JsonObject>();
obj["pos"] = i; obj["pos"] = i;
obj["name"] = String(config.Inverter[i].Name); obj["name"] = String(config.Inverter[i].Name);
obj["poll_enable"] = config.Inverter[i].Poll_Enable; obj["poll_enable"] = config.Inverter[i].Poll_Enable;
@ -105,8 +103,7 @@ void WebApiPowerLimiterClass::onMetaData(AsyncWebServerRequest* request)
} }
} }
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiPowerLimiterClass::onAdminGet(AsyncWebServerRequest* request) void WebApiPowerLimiterClass::onAdminGet(AsyncWebServerRequest* request)
@ -144,13 +141,12 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
return; return;
} }
DynamicJsonDocument root(1024); JsonDocument root;
DeserializationError error = deserializeJson(root, json); DeserializationError error = deserializeJson(root, json);
if (error) { if (error) {
retMsg["message"] = "Failed to parse data!"; retMsg["message"] = "Failed to parse data!";
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }

View File

@ -45,7 +45,7 @@ void WebApiPowerMeterClass::decodeJsonPhaseConfig(JsonObject const& json, PowerM
void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request) void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request)
{ {
AsyncJsonResponse* response = new AsyncJsonResponse(false, 2048); AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot(); auto& root = response->getRoot();
const CONFIG_T& config = Configuration.get(); const CONFIG_T& config = Configuration.get();
@ -60,10 +60,10 @@ void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request)
root["sdmaddress"] = config.PowerMeter.SdmAddress; root["sdmaddress"] = config.PowerMeter.SdmAddress;
root["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests; root["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests;
JsonArray httpPhases = root.createNestedArray("http_phases"); auto httpPhases = root["http_phases"].to<JsonArray>();
for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) { for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) {
JsonObject phaseObject = httpPhases.createNestedObject(); auto phaseObject = httpPhases.add<JsonObject>();
phaseObject["index"] = i + 1; phaseObject["index"] = i + 1;
phaseObject["enabled"] = config.PowerMeter.Http_Phase[i].Enabled; phaseObject["enabled"] = config.PowerMeter.Http_Phase[i].Enabled;
@ -79,8 +79,7 @@ void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request)
phaseObject["sign_inverted"] = config.PowerMeter.Http_Phase[i].SignInverted; phaseObject["sign_inverted"] = config.PowerMeter.Http_Phase[i].SignInverted;
} }
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiPowerMeterClass::onAdminGet(AsyncWebServerRequest* request) void WebApiPowerMeterClass::onAdminGet(AsyncWebServerRequest* request)
@ -118,7 +117,7 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
return; return;
} }
DynamicJsonDocument root(4096); JsonDocument root;
DeserializationError error = deserializeJson(root, json); DeserializationError error = deserializeJson(root, json);
if (error) { if (error) {
@ -201,8 +200,8 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
WebApi.writeConfig(retMsg); WebApi.writeConfig(retMsg);
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
// reboot requiered as per https://github.com/helgeerbe/OpenDTU-OnBattery/issues/565#issuecomment-1872552559 // reboot requiered as per https://github.com/helgeerbe/OpenDTU-OnBattery/issues/565#issuecomment-1872552559
yield(); yield();
@ -237,7 +236,7 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
return; return;
} }
DynamicJsonDocument root(2048); JsonDocument root;
DeserializationError error = deserializeJson(root, json); DeserializationError error = deserializeJson(root, json);
if (error) { if (error) {

View File

@ -31,8 +31,7 @@ void WebApiSecurityClass::onSecurityGet(AsyncWebServerRequest* request)
root["password"] = config.Security.Password; root["password"] = config.Security.Password;
root["allow_readonly"] = config.Security.AllowReadonly; root["allow_readonly"] = config.Security.AllowReadonly;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request) void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
@ -42,44 +41,18 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
} }
AsyncJsonResponse* response = new AsyncJsonResponse(); AsyncJsonResponse* response = new AsyncJsonResponse();
JsonDocument root;
if (!WebApi.parseRequestData(request, response, root)) {
return;
}
auto& retMsg = response->getRoot(); 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);
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;
}
if (!root.containsKey("password") if (!root.containsKey("password")
&& root.containsKey("allow_readonly")) { && root.containsKey("allow_readonly")) {
retMsg["message"] = "Values are missing!"; retMsg["message"] = "Values are missing!";
retMsg["code"] = WebApiError::GenericValueMissing; retMsg["code"] = WebApiError::GenericValueMissing;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; 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["message"] = "Password must between 8 and " STR(WIFI_MAX_PASSWORD_STRLEN) " characters long!";
retMsg["code"] = WebApiError::SecurityPasswordLength; retMsg["code"] = WebApiError::SecurityPasswordLength;
retMsg["param"]["max"] = WIFI_MAX_PASSWORD_STRLEN; retMsg["param"]["max"] = WIFI_MAX_PASSWORD_STRLEN;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
return; return;
} }
@ -98,8 +70,7 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
WebApi.writeConfig(retMsg); WebApi.writeConfig(retMsg);
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }
void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request) void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request)
@ -114,6 +85,5 @@ void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request)
retMsg["message"] = "Authentication successful!"; retMsg["message"] = "Authentication successful!";
retMsg["code"] = WebApiError::SecurityAuthSuccess; retMsg["code"] = WebApiError::SecurityAuthSuccess;
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }

View File

@ -81,6 +81,5 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request)
root["cmt_configured"] = PinMapping.isValidCmt2300Config(); root["cmt_configured"] = PinMapping.isValidCmt2300Config();
root["cmt_connected"] = Hoymiles.getRadioCmt()->isConnected(); root["cmt_connected"] = Hoymiles.getRadioCmt()->isConnected();
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} }

View File

@ -87,7 +87,7 @@ void WebApiVedirectClass::onVedirectAdminPost(AsyncWebServerRequest* request)
return; return;
} }
DynamicJsonDocument root(1024); JsonDocument root;
DeserializationError error = deserializeJson(root, json); DeserializationError error = deserializeJson(root, json);
if (error) { if (error) {
@ -115,8 +115,8 @@ void WebApiVedirectClass::onVedirectAdminPost(AsyncWebServerRequest* request)
WebApi.writeConfig(retMsg); WebApi.writeConfig(retMsg);
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
VictronMppt.updateSettings(); VictronMppt.updateSettings();

View File

@ -59,22 +59,15 @@ void WebApiWsHuaweiLiveClass::sendDataTaskCb()
try { try {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
DynamicJsonDocument root(1024); JsonDocument root;
if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
JsonVariant var = root; JsonVariant var = root;
generateJsonResponse(var);
if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } generateCommonJsonResponse(var);
if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
String buffer; String buffer;
serializeJson(root, 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) { } 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(); const RectifierParameters_t * rp = HuaweiCan.get();
@ -134,13 +127,13 @@ void WebApiWsHuaweiLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
} }
try { try {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
AsyncJsonResponse* response = new AsyncJsonResponse(false, 1024U); AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot(); 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) { } 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()); MessageOutput.printf("Calling /api/huaweilivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
WebApi.sendTooManyRequests(request); WebApi.sendTooManyRequests(request);

View File

@ -62,12 +62,12 @@ void WebApiWsBatteryLiveClass::sendDataTaskCb()
try { try {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
DynamicJsonDocument root(_responseSize); JsonDocument root;
if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
JsonVariant var = root; JsonVariant var = root;
generateJsonResponse(var);
if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } generateCommonJsonResponse(var);
if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
// battery provider does not generate a card, e.g., MQTT provider // battery provider does not generate a card, e.g., MQTT provider
if (root.isNull()) { return; } 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); Battery.getStats()->getLiveViewData(root);
} }
@ -111,12 +111,11 @@ void WebApiWsBatteryLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
} }
try { try {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
AsyncJsonResponse* response = new AsyncJsonResponse(false, _responseSize); AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot(); auto& root = response->getRoot();
generateJsonResponse(root); generateCommonJsonResponse(root);
response->setLength(); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
request->send(response);
} catch (std::bad_alloc& bad_alloc) { } 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()); MessageOutput.printf("Calling /api/batterylivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
WebApi.sendTooManyRequests(request); WebApi.sendTooManyRequests(request);

View File

@ -61,11 +61,11 @@ void WebApiWsLiveClass::generateOnBatteryJsonResponse(JsonVariant& root, bool al
auto victronAge = VictronMppt.getDataAgeMillis(); auto victronAge = VictronMppt.getDataAgeMillis();
if (all || (victronAge > 0 && (millis() - _lastPublishVictron) > victronAge)) { if (all || (victronAge > 0 && (millis() - _lastPublishVictron) > victronAge)) {
JsonObject vedirectObj = root.createNestedObject("vedirect"); auto vedirectObj = root["vedirect"].to<JsonObject>();
vedirectObj["enabled"] = config.Vedirect.Enabled; vedirectObj["enabled"] = config.Vedirect.Enabled;
if (config.Vedirect.Enabled) { if (config.Vedirect.Enabled) {
JsonObject totalVeObj = vedirectObj.createNestedObject("total"); auto totalVeObj = vedirectObj["total"].to<JsonObject>();
addTotalField(totalVeObj, "Power", VictronMppt.getPanelPowerWatts(), "W", 1); addTotalField(totalVeObj, "Power", VictronMppt.getPanelPowerWatts(), "W", 1);
addTotalField(totalVeObj, "YieldDay", VictronMppt.getYieldDay() * 1000, "Wh", 0); addTotalField(totalVeObj, "YieldDay", VictronMppt.getYieldDay() * 1000, "Wh", 0);
addTotalField(totalVeObj, "YieldTotal", VictronMppt.getYieldTotal(), "kWh", 2); addTotalField(totalVeObj, "YieldTotal", VictronMppt.getYieldTotal(), "kWh", 2);
@ -75,7 +75,7 @@ void WebApiWsLiveClass::generateOnBatteryJsonResponse(JsonVariant& root, bool al
} }
if (all || (HuaweiCan.getLastUpdate() - _lastPublishHuawei) < halfOfAllMillis ) { if (all || (HuaweiCan.getLastUpdate() - _lastPublishHuawei) < halfOfAllMillis ) {
JsonObject huaweiObj = root.createNestedObject("huawei"); auto huaweiObj = root["huawei"].to<JsonObject>();
huaweiObj["enabled"] = config.Huawei.Enabled; huaweiObj["enabled"] = config.Huawei.Enabled;
if (config.Huawei.Enabled) { if (config.Huawei.Enabled) {
@ -88,7 +88,7 @@ void WebApiWsLiveClass::generateOnBatteryJsonResponse(JsonVariant& root, bool al
auto spStats = Battery.getStats(); auto spStats = Battery.getStats();
if (all || spStats->updateAvailable(_lastPublishBattery)) { if (all || spStats->updateAvailable(_lastPublishBattery)) {
JsonObject batteryObj = root.createNestedObject("battery"); auto batteryObj = root["battery"].to<JsonObject>();
batteryObj["enabled"] = config.Battery.Enabled; batteryObj["enabled"] = config.Battery.Enabled;
if (config.Battery.Enabled) { if (config.Battery.Enabled) {
@ -99,7 +99,7 @@ void WebApiWsLiveClass::generateOnBatteryJsonResponse(JsonVariant& root, bool al
} }
if (all || (PowerMeter.getLastPowerMeterUpdate() - _lastPublishPowerMeter) < halfOfAllMillis) { if (all || (PowerMeter.getLastPowerMeterUpdate() - _lastPublishPowerMeter) < halfOfAllMillis) {
JsonObject powerMeterObj = root.createNestedObject("power_meter"); auto powerMeterObj = root["power_meter"].to<JsonObject>();
powerMeterObj["enabled"] = config.PowerMeter.Enabled; powerMeterObj["enabled"] = config.PowerMeter.Enabled;
if (config.PowerMeter.Enabled) { if (config.PowerMeter.Enabled) {
@ -112,9 +112,7 @@ void WebApiWsLiveClass::generateOnBatteryJsonResponse(JsonVariant& root, bool al
void WebApiWsLiveClass::sendOnBatteryStats() void WebApiWsLiveClass::sendOnBatteryStats()
{ {
DynamicJsonDocument root(1024); JsonDocument root;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { return; }
JsonVariant var = root; JsonVariant var = root;
bool all = (millis() - _lastPublishOnBatteryFull) > 10 * 1000; bool all = (millis() - _lastPublishOnBatteryFull) > 10 * 1000;
@ -123,12 +121,12 @@ void WebApiWsLiveClass::sendOnBatteryStats()
if (root.isNull()) { return; } if (root.isNull()) { return; }
if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
String buffer; String buffer;
serializeJson(root, buffer); serializeJson(root, buffer);
_ws.textAll(buffer); _ws.textAll(buffer);;
}
} }
void WebApiWsLiveClass::sendDataTaskCb() void WebApiWsLiveClass::sendDataTaskCb()
@ -156,19 +154,20 @@ void WebApiWsLiveClass::sendDataTaskCb()
try { try {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
DynamicJsonDocument root(4096); JsonDocument root;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
continue;
}
JsonVariant var = root; JsonVariant var = root;
auto invArray = var.createNestedArray("inverters"); auto invArray = var["inverters"].to<JsonArray>();
auto invObject = invArray.createNestedObject(); auto invObject = invArray.add<JsonObject>();
generateCommonJsonResponse(var); generateCommonJsonResponse(var);
generateInverterCommonJsonResponse(invObject, inv); generateInverterCommonJsonResponse(invObject, inv);
generateInverterChannelJsonResponse(invObject, inv); generateInverterChannelJsonResponse(invObject, inv);
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
continue;
}
String buffer; String buffer;
serializeJson(root, buffer); serializeJson(root, buffer);
@ -184,12 +183,12 @@ void WebApiWsLiveClass::sendDataTaskCb()
void WebApiWsLiveClass::generateCommonJsonResponse(JsonVariant& root) void WebApiWsLiveClass::generateCommonJsonResponse(JsonVariant& root)
{ {
JsonObject totalObj = root.createNestedObject("total"); auto totalObj = root["total"].to<JsonObject>();
addTotalField(totalObj, "Power", Datastore.getTotalAcPowerEnabled(), "W", Datastore.getTotalAcPowerDigits()); addTotalField(totalObj, "Power", Datastore.getTotalAcPowerEnabled(), "W", Datastore.getTotalAcPowerDigits());
addTotalField(totalObj, "YieldDay", Datastore.getTotalAcYieldDayEnabled(), "Wh", Datastore.getTotalAcYieldDayDigits()); addTotalField(totalObj, "YieldDay", Datastore.getTotalAcYieldDayEnabled(), "Wh", Datastore.getTotalAcYieldDayDigits());
addTotalField(totalObj, "YieldTotal", Datastore.getTotalAcYieldTotalEnabled(), "kWh", Datastore.getTotalAcYieldTotalDigits()); addTotalField(totalObj, "YieldTotal", Datastore.getTotalAcYieldTotalEnabled(), "kWh", Datastore.getTotalAcYieldTotalDigits());
JsonObject hintObj = root.createNestedObject("hints"); JsonObject hintObj = root["hints"].to<JsonObject>();
struct tm timeinfo; struct tm timeinfo;
hintObj["time_sync"] = !getLocalTime(&timeinfo, 5); hintObj["time_sync"] = !getLocalTime(&timeinfo, 5);
hintObj["radio_problem"] = (Hoymiles.getRadioNrf()->isInitialized() && (!Hoymiles.getRadioNrf()->isConnected() || !Hoymiles.getRadioNrf()->isPVariant())) || (Hoymiles.getRadioCmt()->isInitialized() && (!Hoymiles.getRadioCmt()->isConnected())); 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 // Loop all channels
for (auto& t : inv->Statistics()->getChannelTypes()) { for (auto& t : inv->Statistics()->getChannelTypes()) {
JsonObject chanTypeObj = root.createNestedObject(inv->Statistics()->getChannelTypeName(t)); auto chanTypeObj = root[inv->Statistics()->getChannelTypeName(t)].to<JsonObject>();
for (auto& c : inv->Statistics()->getChannelsByType(t)) { for (auto& c : inv->Statistics()->getChannelsByType(t)) {
if (t == TYPE_DC) { if (t == TYPE_DC) {
chanTypeObj[String(static_cast<uint8_t>(c))]["name"]["u"] = inv_cfg->channel[c].Name; chanTypeObj[String(static_cast<uint8_t>(c))]["name"]["u"] = inv_cfg->channel[c].Name;
@ -304,21 +303,15 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
try { try {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096); AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot(); auto& root = response->getRoot();
auto invArray = root["inverters"].to<JsonArray>();
JsonArray invArray = root.createNestedArray("inverters"); auto serial = WebApi.parseSerialFromRequest(request);
uint64_t serial = 0;
if (request->hasParam("inv")) {
String s = request->getParam("inv")->value();
serial = strtoll(s.c_str(), NULL, 16);
}
if (serial > 0) { if (serial > 0) {
auto inv = Hoymiles.getInverterBySerial(serial); auto inv = Hoymiles.getInverterBySerial(serial);
if (inv != nullptr) { if (inv != nullptr) {
JsonObject invObject = invArray.createNestedObject(); JsonObject invObject = invArray.add<JsonObject>();
generateInverterCommonJsonResponse(invObject, inv); generateInverterCommonJsonResponse(invObject, inv);
generateInverterChannelJsonResponse(invObject, inv); generateInverterChannelJsonResponse(invObject, inv);
} }
@ -330,7 +323,7 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
continue; continue;
} }
JsonObject invObject = invArray.createNestedObject(); JsonObject invObject = invArray.add<JsonObject>();
generateInverterCommonJsonResponse(invObject, inv); generateInverterCommonJsonResponse(invObject, inv);
} }
} }
@ -339,8 +332,9 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
generateOnBatteryJsonResponse(root, true); generateOnBatteryJsonResponse(root, true);
response->setLength(); generateOnBatteryJsonResponse(root, true);
request->send(response);
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
} catch (const std::bad_alloc& bad_alloc) { } 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()); MessageOutput.printf("Calling /api/livedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());

View File

@ -86,25 +86,17 @@ void WebApiWsVedirectLiveClass::sendDataTaskCb()
if (fullUpdate || updateAvailable) { if (fullUpdate || updateAvailable) {
try { try {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
DynamicJsonDocument root(responseSize()); JsonDocument root;
if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
JsonVariant var = root; JsonVariant var = root;
generateJsonResponse(var, fullUpdate);
if (Utils::checkJsonOverflow(root, __FUNCTION__, __LINE__)) { return; } generateCommonJsonResponse(var, fullUpdate);
if (Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
String buffer; String buffer;
serializeJson(root, buffer); serializeJson(root, buffer);
if (Configuration.get().Security.AllowReadonly) { _ws.textAll(buffer);;
_ws.setAuthentication("", "");
} else {
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password);
} }
_ws.textAll(buffer);
}
} catch (std::bad_alloc& bad_alloc) { } 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()); MessageOutput.printf("Calling /api/vedirectlivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
} catch (const std::exception& exc) { } 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<JsonObject>();
root["vedirect"]["full_update"] = fullUpdate; root["vedirect"]["full_update"] = fullUpdate;
for (size_t idx = 0; idx < VictronMppt.controllerAmount(); ++idx) { 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); String serial(optMpptData->serialNr_SER);
if (serial.isEmpty()) { continue; } // serial required as index if (serial.isEmpty()) { continue; } // serial required as index
const JsonObject &nested = array.createNestedObject(serial); JsonObject nested = array[serial].to<JsonObject>();
nested["data_age_ms"] = VictronMppt.getDataAgeMillis(idx); nested["data_age_ms"] = VictronMppt.getDataAgeMillis(idx);
populateJson(nested, *optMpptData); populateJson(nested, *optMpptData);
} }
@ -149,9 +141,9 @@ void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDir
root["product_id"] = mpptData.getPidAsString(); root["product_id"] = mpptData.getPidAsString();
root["firmware_version"] = String(mpptData.firmwareNr_FW); root["firmware_version"] = String(mpptData.firmwareNr_FW);
const JsonObject &values = root.createNestedObject("values"); const JsonObject values = root["values"].to<JsonObject>();
const JsonObject &device = values.createNestedObject("device"); const JsonObject device = values["device"].to<JsonObject>();
device["LOAD"] = mpptData.loadOutputState_LOAD ? "ON" : "OFF"; device["LOAD"] = mpptData.loadOutputState_LOAD ? "ON" : "OFF";
device["CS"] = mpptData.getCsAsString(); device["CS"] = mpptData.getCsAsString();
device["MPPT"] = mpptData.getMpptAsString(); device["MPPT"] = mpptData.getMpptAsString();
@ -165,7 +157,7 @@ void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDir
device["MpptTemperature"]["d"] = "1"; device["MpptTemperature"]["d"] = "1";
} }
const JsonObject &output = values.createNestedObject("output"); const JsonObject output = values["output"].to<JsonObject>();
output["P"]["v"] = mpptData.batteryOutputPower_W; output["P"]["v"] = mpptData.batteryOutputPower_W;
output["P"]["u"] = "W"; output["P"]["u"] = "W";
output["P"]["d"] = 0; output["P"]["d"] = 0;
@ -179,7 +171,7 @@ void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDir
output["E"]["u"] = "%"; output["E"]["u"] = "%";
output["E"]["d"] = 1; output["E"]["d"] = 1;
const JsonObject &input = values.createNestedObject("input"); const JsonObject input = values["input"].to<JsonObject>();
if (mpptData.NetworkTotalDcInputPowerMilliWatts.first > 0) { if (mpptData.NetworkTotalDcInputPowerMilliWatts.first > 0) {
input["NetworkPower"]["v"] = mpptData.NetworkTotalDcInputPowerMilliWatts.second / 1000.0; input["NetworkPower"]["v"] = mpptData.NetworkTotalDcInputPowerMilliWatts.second / 1000.0;
input["NetworkPower"]["u"] = "W"; input["NetworkPower"]["u"] = "W";
@ -233,14 +225,12 @@ void WebApiWsVedirectLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
} }
try { try {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
AsyncJsonResponse* response = new AsyncJsonResponse(false, responseSize()); AsyncJsonResponse* response = new AsyncJsonResponse();
auto& root = response->getRoot(); auto& root = response->getRoot();
generateJsonResponse(root, true/*fullUpdate*/); generateCommonJsonResponse(root, true/*fullUpdate*/);
response->setLength();
request->send(response);
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
} catch (std::bad_alloc& bad_alloc) { } 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()); MessageOutput.printf("Calling /api/vedirectlivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
WebApi.sendTooManyRequests(request); WebApi.sendTooManyRequests(request);

View File

@ -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'
}
}

36
webapp/eslint.config.js Normal file
View File

@ -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'
},
}
]

View File

@ -9,7 +9,7 @@
"preview": "vite preview --port 4173", "preview": "vite preview --port 4173",
"build-only": "vite build", "build-only": "vite build",
"type-check": "vue-tsc --noEmit", "type-check": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" "lint": "eslint ."
}, },
"dependencies": { "dependencies": {
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
@ -19,31 +19,30 @@
"sortablejs": "^1.15.2", "sortablejs": "^1.15.2",
"spark-md5": "^3.0.2", "spark-md5": "^3.0.2",
"vue": "^3.4.21", "vue": "^3.4.21",
"vue-i18n": "^9.10.2", "vue-i18n": "^9.12.0",
"vue-router": "^4.3.0" "vue-router": "^4.3.0"
}, },
"devDependencies": { "devDependencies": {
"@intlify/unplugin-vue-i18n": "^4.0.0", "@intlify/unplugin-vue-i18n": "^4.0.0",
"@rushstack/eslint-patch": "^1.10.1",
"@tsconfig/node18": "^18.2.4", "@tsconfig/node18": "^18.2.4",
"@types/bootstrap": "^5.2.10", "@types/bootstrap": "^5.2.10",
"@types/node": "^20.12.2", "@types/node": "^20.12.7",
"@types/pulltorefreshjs": "^0.1.7", "@types/pulltorefreshjs": "^0.1.7",
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@types/spark-md5": "^3.0.4", "@types/spark-md5": "^3.0.4",
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.0.4",
"@vue/eslint-config-typescript": "^13.0.0", "@vue/eslint-config-typescript": "^13.0.0",
"@vue/tsconfig": "^0.5.1", "@vue/tsconfig": "^0.5.1",
"eslint": "^8.57.0", "eslint": "^9.0.0",
"eslint-plugin-vue": "^9.24.0", "eslint-plugin-vue": "^9.24.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"pulltorefreshjs": "^0.1.22", "pulltorefreshjs": "^0.1.22",
"sass": "^1.72.0", "sass": "^1.75.0",
"terser": "^5.30.0", "terser": "^5.30.3",
"typescript": "^5.4.3", "typescript": "^5.4.5",
"vite": "^5.2.7", "vite": "^5.2.8",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-css-injected-by-js": "^3.5.0", "vite-plugin-css-injected-by-js": "^3.5.0",
"vue-tsc": "^2.0.7" "vue-tsc": "^2.0.13"
} }
} }

View File

@ -48,15 +48,14 @@ export default defineComponent({
showReload: { type: Boolean, required: false, default: false }, showReload: { type: Boolean, required: false, default: false },
}, },
mounted() { mounted() {
var self = this;
console.log("init"); console.log("init");
PullToRefresh.init({ PullToRefresh.init({
mainElement: 'body', // above which element? mainElement: 'body', // above which element?
instructionsPullToRefresh: this.$t('base.Pull'), instructionsPullToRefresh: this.$t('base.Pull'),
instructionsReleaseToRefresh: this.$t('base.Release'), instructionsReleaseToRefresh: this.$t('base.Release'),
instructionsRefreshing: this.$t('base.Refreshing'), instructionsRefreshing: this.$t('base.Refreshing'),
onRefresh: function() { onRefresh: () => {
self.$emit('reload'); this.$emit('reload');
} }
}); });
}, },

View File

@ -52,7 +52,7 @@ export default defineComponent({
_countDownTimeout = undefined; _countDownTimeout = undefined;
}; };
var countDown = ref(); const countDown = ref();
watch(() => props.modelValue, () => { watch(() => props.modelValue, () => {
countDown.value = parseCountDown(props.modelValue); countDown.value = parseCountDown(props.modelValue);
}); });

View File

@ -87,10 +87,10 @@ export default defineComponent({
}, },
computed: { computed: {
modelAllowVersionInfo: { modelAllowVersionInfo: {
get(): any { get(): boolean {
return !!this.allowVersionInfo; return !!this.allowVersionInfo;
}, },
set(value: any) { set(value: boolean) {
this.$emit('update:allowVersionInfo', value); this.$emit('update:allowVersionInfo', value);
}, },
}, },

View File

@ -83,10 +83,12 @@ export default defineComponent({
}, },
computed: { computed: {
model: { model: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get(): any { get(): any {
if (this.type === 'checkbox') return !!this.modelValue; if (this.type === 'checkbox') return !!this.modelValue;
return this.modelValue; return this.modelValue;
}, },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
set(value: any) { set(value: any) {
this.$emit('update:modelValue', value); this.$emit('update:modelValue', value);
}, },

View File

@ -28,9 +28,11 @@ export default defineComponent({
}, },
computed: { computed: {
model: { model: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get(): any { get(): any {
return this.modelValue; return this.modelValue;
}, },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
set(value: any) { set(value: any) {
this.$emit('update:modelValue', value); this.$emit('update:modelValue', value);
}, },

View File

@ -168,8 +168,8 @@ export default defineComponent({
}, },
isEaster() { isEaster() {
const easter = this.getEasterSunday(this.now.getFullYear()); const easter = this.getEasterSunday(this.now.getFullYear());
var easterStart = new Date(easter); const easterStart = new Date(easter);
var easterEnd = new Date(easter); const easterEnd = new Date(easter);
easterStart.setDate(easterStart.getDate() - 2); easterStart.setDate(easterStart.getDate() - 2);
easterEnd.setDate(easterEnd.getDate() + 1); easterEnd.setDate(easterEnd.getDate() + 1);
return this.now >= easterStart && this.now < easterEnd; return this.now >= easterStart && this.now < easterEnd;
@ -192,15 +192,15 @@ export default defineComponent({
this.$refs.navbarCollapse && (this.$refs.navbarCollapse as HTMLElement).classList.remove("show"); this.$refs.navbarCollapse && (this.$refs.navbarCollapse as HTMLElement).classList.remove("show");
}, },
getEasterSunday(year: number): Date { getEasterSunday(year: number): Date {
var f = Math.floor; const f = Math.floor;
var G = year % 19; const G = year % 19;
var C = f(year / 100); const C = f(year / 100);
var H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30; const 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)); const 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; const J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7;
var L = I - J; const L = I - J;
var month = 3 + f((L + 40) / 44); const month = 3 + f((L + 40) / 44);
var day = L + 28 - 31 * f(month / 4); const day = L + 28 - 31 * f(month / 4);
return new Date(year, month - 1, day); return new Date(year, month - 1, day);
} }

View File

@ -84,9 +84,11 @@ export default defineComponent({
let comCur = 999999; let comCur = 999999;
if (this.selectedPinAssignment && category in this.selectedPinAssignment) { if (this.selectedPinAssignment && category in this.selectedPinAssignment) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
comSel = (this.selectedPinAssignment as any)[category][prop]; comSel = (this.selectedPinAssignment as any)[category][prop];
} }
if (this.currentPinAssignment && category in this.currentPinAssignment) { if (this.currentPinAssignment && category in this.currentPinAssignment) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
comCur = (this.currentPinAssignment as any)[category][prop]; comCur = (this.currentPinAssignment as any)[category][prop];
} }

View File

@ -41,6 +41,9 @@
"Release": "Loslassen zum Aktualisieren", "Release": "Loslassen zum Aktualisieren",
"Close": "Schließen" "Close": "Schließen"
}, },
"Error": {
"Oops": "Oops!"
},
"localeswitcher": { "localeswitcher": {
"Dark": "Dunkel", "Dark": "Dunkel",
"Light": "Hell", "Light": "Hell",

View File

@ -41,6 +41,9 @@
"Release": "Release to refresh", "Release": "Release to refresh",
"Close": "Close" "Close": "Close"
}, },
"Error": {
"Oops": "Oops!"
},
"localeswitcher": { "localeswitcher": {
"Dark": "Dark", "Dark": "Dark",
"Light": "Light", "Light": "Light",

View File

@ -41,6 +41,9 @@
"Release": "Release to refresh", "Release": "Release to refresh",
"Close": "Fermer" "Close": "Fermer"
}, },
"Error": {
"Oops": "Oops!"
},
"localeswitcher": { "localeswitcher": {
"Dark": "Sombre", "Dark": "Sombre",
"Light": "Clair", "Light": "Clair",

View File

@ -5,6 +5,7 @@ import ConfigAdminView from '@/views/ConfigAdminView.vue';
import ConsoleInfoView from '@/views/ConsoleInfoView.vue'; import ConsoleInfoView from '@/views/ConsoleInfoView.vue';
import DeviceAdminView from '@/views/DeviceAdminView.vue' import DeviceAdminView from '@/views/DeviceAdminView.vue'
import DtuAdminView from '@/views/DtuAdminView.vue'; import DtuAdminView from '@/views/DtuAdminView.vue';
import ErrorView from '@/views/ErrorView.vue';
import FirmwareUpgradeView from '@/views/FirmwareUpgradeView.vue'; import FirmwareUpgradeView from '@/views/FirmwareUpgradeView.vue';
import HomeView from '@/views/HomeView.vue'; import HomeView from '@/views/HomeView.vue';
import VedirectAdminView from '@/views/VedirectAdminView.vue' import VedirectAdminView from '@/views/VedirectAdminView.vue'
@ -38,6 +39,11 @@ const router = createRouter({
name: 'Login', name: 'Login',
component: LoginView component: LoginView
}, },
{
path: '/error?status=:status&message=:message',
name: 'Error',
component: ErrorView
},
{ {
path: '/about', path: '/about',
name: 'About', name: 'About',

View File

@ -41,7 +41,7 @@ export function isLoggedIn(): boolean {
return (localStorage.getItem('user') != null); return (localStorage.getItem('user') != null);
} }
export function login(username: String, password: String) { export function login(username: string, password: string) {
const requestOptions = { const requestOptions = {
method: 'GET', method: 'GET',
headers: { headers: {
@ -74,9 +74,11 @@ export function handleResponse(response: Response, emitter: Emitter<Record<Event
logout(); logout();
emitter.emit("logged-out"); emitter.emit("logged-out");
router.push({ path: "/login", query: { returnUrl: router.currentRoute.value.fullPath } }); router.push({ path: "/login", query: { returnUrl: router.currentRoute.value.fullPath } });
return Promise.reject();
} }
const error = { message: (data && data.message) || response.statusText, status: response.status || 0 }; const error = { message: (data && data.message) || response.statusText, status: response.status || 0 };
router.push({ name: "Error", params: error });
return Promise.reject(error); return Promise.reject(error);
} }

View File

@ -188,8 +188,8 @@ export default defineComponent({
fetch("/api/config/get?file=" + this.backupFileSelect, { headers: authHeader() }) fetch("/api/config/get?file=" + this.backupFileSelect, { headers: authHeader() })
.then(res => res.blob()) .then(res => res.blob())
.then(blob => { .then(blob => {
var file = window.URL.createObjectURL(blob); const file = window.URL.createObjectURL(blob);
var a = document.createElement('a'); const a = document.createElement('a');
a.href = file; a.href = file;
a.download = this.backupFileSelect; a.download = this.backupFileSelect;
document.body.appendChild(a); document.body.appendChild(a);

View File

@ -56,7 +56,7 @@ export default defineComponent({
watch: { watch: {
consoleBuffer() { consoleBuffer() {
if (this.isAutoScroll) { if (this.isAutoScroll) {
let textarea = this.$el.querySelector("#console"); const textarea = this.$el.querySelector("#console");
setTimeout(() => { setTimeout(() => {
textarea.scrollTop = textarea.scrollHeight; textarea.scrollTop = textarea.scrollHeight;
}, 0); }, 0);
@ -121,7 +121,7 @@ export default defineComponent({
this.heartInterval && clearTimeout(this.heartInterval); this.heartInterval && clearTimeout(this.heartInterval);
}, },
getOutDate(): String { getOutDate(): string {
const u = new Date(); const u = new Date();
return ('0' + u.getHours()).slice(-2) + ':' + return ('0' + u.getHours()).slice(-2) + ':' +
('0' + u.getMinutes()).slice(-2) + ':' + ('0' + u.getMinutes()).slice(-2) + ':' +
@ -132,7 +132,7 @@ export default defineComponent({
this.consoleBuffer = ""; this.consoleBuffer = "";
}, },
copyConsole() { copyConsole() {
var input = document.createElement('textarea'); const input = document.createElement('textarea');
input.innerHTML = this.consoleBuffer; input.innerHTML = this.consoleBuffer;
document.body.appendChild(input); document.body.appendChild(input);
input.select(); input.select();

View File

@ -0,0 +1,18 @@
<template>
<BasePage :title="$t('Error.Oops')">
<div class="alert alert-danger" role="alert">
<h2>{{ $route.params.message }}</h2>
</div>
</BasePage>
</template>
<script lang="ts">
import BasePage from '@/components/BasePage.vue';
import { defineComponent } from 'vue';
export default defineComponent({
components: {
BasePage,
},
});
</script>

View File

@ -191,7 +191,7 @@ export default defineComponent({
const remoteHostUrl = "/api/system/status"; const remoteHostUrl = "/api/system/status";
// Use a simple fetch request to check if the remote host is reachable // Use a simple fetch request to check if the remote host is reachable
fetch(remoteHostUrl, { method: 'HEAD' }) fetch(remoteHostUrl, { method: 'GET' })
.then(response => { .then(response => {
// Check if the response status is OK (200-299 range) // Check if the response status is OK (200-299 range)
if (response.ok) { if (response.ok) {

View File

@ -493,17 +493,15 @@ export default defineComponent({
} }
}; };
var self = this; this.socket.onopen = (event) => {
this.socket.onopen = function (event) {
console.log(event); console.log(event);
console.log("Successfully connected to the echo websocket server..."); console.log("Successfully connected to the echo websocket server...");
self.isWebsocketConnected = true; this.isWebsocketConnected = true;
}; };
this.socket.onclose = function () { this.socket.onclose = () => {
console.log("Connection to websocket closed...") console.log("Connection to websocket closed...")
self.isWebsocketConnected = false; this.isWebsocketConnected = false;
} }
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect // Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect

View File

@ -99,7 +99,7 @@ export default defineComponent({
} }
}, },
watch: { watch: {
allowVersionInfo(allow: Boolean) { allowVersionInfo(allow: boolean) {
localStorage.setItem("allowVersionInfo", allow ? "1" : "0"); localStorage.setItem("allowVersionInfo", allow ? "1" : "0");
if (allow) { if (allow) {
this.getUpdateInfo(); this.getUpdateInfo();

View File

@ -12,6 +12,7 @@ import path from 'path'
// example 'vite.user.ts': export const proxy_target = '192.168.16.107' // example 'vite.user.ts': export const proxy_target = '192.168.16.107'
let proxy_target; let proxy_target;
try { try {
// eslint-disable-next-line
proxy_target = require('./vite.user.ts').proxy_target; proxy_target = require('./vite.user.ts').proxy_target;
} catch (error) { } catch (error) {
proxy_target = '192.168.20.110'; proxy_target = '192.168.20.110';

View File

@ -156,32 +156,32 @@
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8"
integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==
"@eslint/eslintrc@^2.1.4": "@eslint/eslintrc@^3.0.2":
version "2.1.4" version "3.0.2"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.0.2.tgz#36180f8e85bf34d2fe3ccc2261e8e204a411ab4e"
integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== integrity sha512-wV19ZEGEMAC1eHgrS7UQPqsdEiCIbTKTasEfcXAigzoXICcqZSjBZEHlZwNVvKg6UBCjSlos84XiLqsRJnIcIg==
dependencies: dependencies:
ajv "^6.12.4" ajv "^6.12.4"
debug "^4.3.2" debug "^4.3.2"
espree "^9.6.0" espree "^10.0.1"
globals "^13.19.0" globals "^14.0.0"
ignore "^5.2.0" ignore "^5.2.0"
import-fresh "^3.2.1" import-fresh "^3.2.1"
js-yaml "^4.1.0" js-yaml "^4.1.0"
minimatch "^3.1.2" minimatch "^3.1.2"
strip-json-comments "^3.1.1" strip-json-comments "^3.1.1"
"@eslint/js@8.57.0": "@eslint/js@9.0.0":
version "8.57.0" version "9.0.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.0.0.tgz#1a9e4b4c96d8c7886e0110ed310a0135144a1691"
integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== integrity sha512-RThY/MnKrhubF6+s1JflwUjPEsnCEmYCWwqa/aRISKWNXGZ9epUwft4bUMM35SdKF9xvBrLydAM1RDHd1Z//ZQ==
"@humanwhocodes/config-array@^0.11.14": "@humanwhocodes/config-array@^0.12.3":
version "0.11.14" version "0.12.3"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.12.3.tgz#a6216d90f81a30bedd1d4b5d799b47241f318072"
integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== integrity sha512-jsNnTBlMWuTpDkeE3on7+dWJi0D6fdDfeANj/w7MpS8ztROCoLvIO2nG0CcFj+E4k8j4QrSTh4Oryi3i2G669g==
dependencies: dependencies:
"@humanwhocodes/object-schema" "^2.0.2" "@humanwhocodes/object-schema" "^2.0.3"
debug "^4.3.1" debug "^4.3.1"
minimatch "^3.0.5" minimatch "^3.0.5"
@ -190,10 +190,10 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
"@humanwhocodes/object-schema@^2.0.2": "@humanwhocodes/object-schema@^2.0.3":
version "2.0.2" version "2.0.3"
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
"@intlify/bundle-utils@^8.0.0": "@intlify/bundle-utils@^8.0.0":
version "8.0.0" version "8.0.0"
@ -210,20 +210,20 @@
source-map-js "^1.0.1" source-map-js "^1.0.1"
yaml-eslint-parser "^1.2.2" yaml-eslint-parser "^1.2.2"
"@intlify/core-base@9.10.2": "@intlify/core-base@9.12.0":
version "9.10.2" version "9.12.0"
resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.10.2.tgz#e7f8857f8011184e4afbdcfae7dbd85c50ba5271" resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.12.0.tgz#79f43faa8eb1f3b2bfe569a9fbae9bc50908d311"
integrity sha512-HGStVnKobsJL0DoYIyRCGXBH63DMQqEZxDUGrkNI05FuTcruYUtOAxyL3zoAZu/uDGO6mcUvm3VXBaHG2GdZCg== integrity sha512-6EnWQXHnCh2bMiXT5N/IWwkcYQXjmF8nnEZ3YhTm23h1ZfOylz83D7pJYhcU8CsTiEdgbGiNdqyZPKwrHw03Ng==
dependencies: dependencies:
"@intlify/message-compiler" "9.10.2" "@intlify/message-compiler" "9.12.0"
"@intlify/shared" "9.10.2" "@intlify/shared" "9.12.0"
"@intlify/message-compiler@9.10.2": "@intlify/message-compiler@9.12.0":
version "9.10.2" version "9.12.0"
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.10.2.tgz#c44cbb915bdd0d62780a38595a84006c781f717a" resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.12.0.tgz#5e152344853c29369911bd5e541e061b09218333"
integrity sha512-ntY/kfBwQRtX5Zh6wL8cSATujPzWW2ZQd1QwKyWwAy5fMqJyyixHMeovN4fmEyCqSu+hFfYOE63nU94evsy4YA== integrity sha512-2c6VwhvVJ1nur+2cN2NjdrmrV6vXjvyxYVvtUYMXKsWSUwoNURHGds0xJVJmWxbF8qV9oGepcVV6xl9bvadEIg==
dependencies: dependencies:
"@intlify/shared" "9.10.2" "@intlify/shared" "9.12.0"
source-map-js "^1.0.2" source-map-js "^1.0.2"
"@intlify/message-compiler@^9.4.0": "@intlify/message-compiler@^9.4.0":
@ -234,10 +234,10 @@
"@intlify/shared" "9.4.0" "@intlify/shared" "9.4.0"
source-map-js "^1.0.2" source-map-js "^1.0.2"
"@intlify/shared@9.10.2": "@intlify/shared@9.12.0":
version "9.10.2" version "9.12.0"
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.10.2.tgz#693300ea033868cbe4086b832170612f002e24a9" resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.12.0.tgz#993383b6a98c8e37a1fa184a677eb39635a14a1c"
integrity sha512-ttHCAJkRy7R5W2S9RVnN9KYQYPIpV2+GiS79T4EE37nrPyH6/1SrOh3bmdCRC1T3ocL8qCDx7x2lBJ0xaITU7Q== integrity sha512-uBcH55x5CfZynnerWHQxrXbT6yD6j6T7Nt+R2+dHAOAneoMd6BoGvfEzfYscE94rgmjoDqdr+PdGDBLk5I5EjA==
"@intlify/shared@9.4.0", "@intlify/shared@^9.4.0": "@intlify/shared@9.4.0", "@intlify/shared@^9.4.0":
version "9.4.0" version "9.4.0"
@ -412,11 +412,6 @@
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz#6abd79db7ff8d01a58865ba20a63cfd23d9e2a10" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz#6abd79db7ff8d01a58865ba20a63cfd23d9e2a10"
integrity sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw== integrity sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==
"@rushstack/eslint-patch@^1.10.1":
version "1.10.1"
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.10.1.tgz#7ca168b6937818e9a74b47ac4e2112b2e1a024cf"
integrity sha512-S3Kq8e7LqxkA9s7HKLqXGTGck1uwis5vAXan3FnU5yw1Ec5hsSGnq4s/UCaSqABPOnOTg7zASLyst7+ohgWexg==
"@tsconfig/node18@^18.2.4": "@tsconfig/node18@^18.2.4":
version "18.2.4" version "18.2.4"
resolved "https://registry.yarnpkg.com/@tsconfig/node18/-/node18-18.2.4.tgz#094efbdd70f697d37c09f34067bf41bc4a828ae3" resolved "https://registry.yarnpkg.com/@tsconfig/node18/-/node18-18.2.4.tgz#094efbdd70f697d37c09f34067bf41bc4a828ae3"
@ -444,10 +439,10 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
"@types/node@^20.12.2": "@types/node@^20.12.7":
version "20.12.2" version "20.12.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.2.tgz#9facdd11102f38b21b4ebedd9d7999663343d72e" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.7.tgz#04080362fa3dd6c5822061aa3124f5c152cff384"
integrity sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ== integrity sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==
dependencies: dependencies:
undici-types "~5.26.4" undici-types "~5.26.4"
@ -557,36 +552,31 @@
"@typescript-eslint/types" "7.2.0" "@typescript-eslint/types" "7.2.0"
eslint-visitor-keys "^3.4.1" eslint-visitor-keys "^3.4.1"
"@ungap/structured-clone@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
"@vitejs/plugin-vue@^5.0.4": "@vitejs/plugin-vue@^5.0.4":
version "5.0.4" version "5.0.4"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz#508d6a0f2440f86945835d903fcc0d95d1bb8a37" resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz#508d6a0f2440f86945835d903fcc0d95d1bb8a37"
integrity sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ== integrity sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==
"@volar/language-core@2.1.3", "@volar/language-core@~2.1.3": "@volar/language-core@2.2.0-alpha.8":
version "2.1.3" version "2.2.0-alpha.8"
resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.1.3.tgz#ac6057ec73c5fcda1fc07677bf0d7be41e6c59b1" resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.2.0-alpha.8.tgz#74120a27ff2498ad297e86d17be95a9c7e1b46f5"
integrity sha512-F93KYZYqcYltG7NihfnLt/omMZOtrQtsh2+wj+cgx3xolopU+TZvmwlZWOjw3ObZGFj3SKBb4jJn6VSfSch6RA== integrity sha512-Ew1Iw7/RIRNuDLn60fWJdOLApAlfTVPxbPiSLzc434PReC9kleYtaa//Wo2WlN1oiRqneW0pWQQV0CwYqaimLQ==
dependencies: dependencies:
"@volar/source-map" "2.1.3" "@volar/source-map" "2.2.0-alpha.8"
"@volar/source-map@2.1.3": "@volar/source-map@2.2.0-alpha.8":
version "2.1.3" version "2.2.0-alpha.8"
resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.1.3.tgz#8f3cb110019c45fa4cd47ad2f5fe5469bd54b9e3" resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.2.0-alpha.8.tgz#ca090f828fbef7e09ea06a636c41a06aa2afe153"
integrity sha512-j+R+NG/OlDgdNMttADxNuSM9Z26StT/Bjw0NgSydI05Vihngn9zvaP/xXwfWs5qQrRzbKVFxJebS2ks5m/URuA== integrity sha512-E1ZVmXFJ5DU4fWDcWHzi8OLqqReqIDwhXvIMhVdk6+VipfMVv4SkryXu7/rs4GA/GsebcRyJdaSkKBB3OAkIcA==
dependencies: dependencies:
muggle-string "^0.4.0" muggle-string "^0.4.0"
"@volar/typescript@~2.1.3": "@volar/typescript@2.2.0-alpha.8":
version "2.1.3" version "2.2.0-alpha.8"
resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.1.3.tgz#bfdc901afd44c2d05697967211aa55d53fb8bf69" resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.2.0-alpha.8.tgz#83a056c52995b4142364be3dda41d955a96f7356"
integrity sha512-ZZqLMih4mvu2eJAW3UCFm84OM/ojYMoA/BU/W1TctT5F2nVzNJmW4jxMWmP3wQzxCbATfTa5gLb1+BSI9NBMBg== integrity sha512-RLbRDI+17CiayHZs9HhSzlH0FhLl/+XK6o2qoiw2o2GGKcyD1aDoY6AcMd44acYncTOrqoTNoY6LuCiRyiJiGg==
dependencies: dependencies:
"@volar/language-core" "2.1.3" "@volar/language-core" "2.2.0-alpha.8"
path-browserify "^1.0.1" path-browserify "^1.0.1"
"@vue/compiler-core@3.2.47": "@vue/compiler-core@3.2.47":
@ -692,12 +682,12 @@
"@typescript-eslint/parser" "^7.1.1" "@typescript-eslint/parser" "^7.1.1"
vue-eslint-parser "^9.3.1" vue-eslint-parser "^9.3.1"
"@vue/language-core@2.0.7": "@vue/language-core@2.0.13":
version "2.0.7" version "2.0.13"
resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.0.7.tgz#af12f752a93c4d2498626fca33f5d1ddc8c5ceb9" resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.0.13.tgz#2d1638b882011187b4b57115425d52b0901acab5"
integrity sha512-Vh1yZX3XmYjn9yYLkjU8DN6L0ceBtEcapqiyclHne8guG84IaTzqtvizZB1Yfxm3h6m7EIvjerLO5fvOZO6IIQ== integrity sha512-oQgM+BM66SU5GKtUMLQSQN0bxHFkFpLSSAiY87wVziPaiNQZuKVDt/3yA7GB9PiQw0y/bTNL0bOc0jM/siYjKg==
dependencies: dependencies:
"@volar/language-core" "~2.1.3" "@volar/language-core" "2.2.0-alpha.8"
"@vue/compiler-dom" "^3.4.0" "@vue/compiler-dom" "^3.4.0"
"@vue/shared" "^3.4.0" "@vue/shared" "^3.4.0"
computeds "^0.0.1" computeds "^0.0.1"
@ -768,6 +758,11 @@ acorn-jsx@^5.3.2:
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn@^8.11.3:
version "8.11.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
acorn@^8.5.0, acorn@^8.9.0: acorn@^8.5.0, acorn@^8.9.0:
version "8.10.0" version "8.10.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
@ -1028,13 +1023,6 @@ dir-glob@^3.0.1:
dependencies: dependencies:
path-type "^4.0.0" path-type "^4.0.0"
doctrine@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
dependencies:
esutils "^2.0.2"
entities@^4.5.0: entities@^4.5.0:
version "4.5.0" version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
@ -1136,10 +1124,10 @@ escodegen@^2.1.0:
optionalDependencies: optionalDependencies:
source-map "~0.6.1" source-map "~0.6.1"
eslint-plugin-vue@^9.24.0: eslint-plugin-vue@^9.24.1:
version "9.24.0" version "9.24.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.24.0.tgz#71209f4652ee767f18c0bf56f25991b7cdc5aa46" resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.24.1.tgz#0d90330c939f9dd2f4c759da5a2ad91dc1c8bac4"
integrity sha512-9SkJMvF8NGMT9aQCwFc5rj8Wo1XWSMSHk36i7ZwdI614BU7sIOR28ZjuFPKp8YGymZN12BSEbiSwa7qikp+PBw== integrity sha512-wk3SuwmS1pZdcuJlokGYEi/buDOwD6KltvhIZyOnpJ/378dcQ4zchu9PAMbbLAaydCz1iYc5AozszcOOgZIIOg==
dependencies: dependencies:
"@eslint-community/eslint-utils" "^4.4.0" "@eslint-community/eslint-utils" "^4.4.0"
globals "^13.24.0" globals "^13.24.0"
@ -1158,15 +1146,15 @@ eslint-scope@^7.1.1:
esrecurse "^4.3.0" esrecurse "^4.3.0"
estraverse "^5.2.0" estraverse "^5.2.0"
eslint-scope@^7.2.2: eslint-scope@^8.0.1:
version "7.2.2" version "8.0.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.0.1.tgz#a9601e4b81a0b9171657c343fb13111688963cfc"
integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== integrity sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==
dependencies: dependencies:
esrecurse "^4.3.0" esrecurse "^4.3.0"
estraverse "^5.2.0" estraverse "^5.2.0"
eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.4.3: eslint-visitor-keys@^3.0.0:
version "3.4.3" version "3.4.3"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
@ -1181,41 +1169,42 @@ eslint-visitor-keys@^3.4.1:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994"
integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
eslint@^8.57.0: eslint-visitor-keys@^4.0.0:
version "8.57.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb"
integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==
eslint@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.0.0.tgz#6270548758e390343f78c8afd030566d86927d40"
integrity sha512-IMryZ5SudxzQvuod6rUdIUz29qFItWx281VhtFVc2Psy/ZhlCeD/5DT6lBIJ4H3G+iamGJoTln1v+QSuPw0p7Q==
dependencies: dependencies:
"@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.6.1" "@eslint-community/regexpp" "^4.6.1"
"@eslint/eslintrc" "^2.1.4" "@eslint/eslintrc" "^3.0.2"
"@eslint/js" "8.57.0" "@eslint/js" "9.0.0"
"@humanwhocodes/config-array" "^0.11.14" "@humanwhocodes/config-array" "^0.12.3"
"@humanwhocodes/module-importer" "^1.0.1" "@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8" "@nodelib/fs.walk" "^1.2.8"
"@ungap/structured-clone" "^1.2.0"
ajv "^6.12.4" ajv "^6.12.4"
chalk "^4.0.0" chalk "^4.0.0"
cross-spawn "^7.0.2" cross-spawn "^7.0.2"
debug "^4.3.2" debug "^4.3.2"
doctrine "^3.0.0"
escape-string-regexp "^4.0.0" escape-string-regexp "^4.0.0"
eslint-scope "^7.2.2" eslint-scope "^8.0.1"
eslint-visitor-keys "^3.4.3" eslint-visitor-keys "^4.0.0"
espree "^9.6.1" espree "^10.0.1"
esquery "^1.4.2" esquery "^1.4.2"
esutils "^2.0.2" esutils "^2.0.2"
fast-deep-equal "^3.1.3" fast-deep-equal "^3.1.3"
file-entry-cache "^6.0.1" file-entry-cache "^8.0.0"
find-up "^5.0.0" find-up "^5.0.0"
glob-parent "^6.0.2" glob-parent "^6.0.2"
globals "^13.19.0"
graphemer "^1.4.0" graphemer "^1.4.0"
ignore "^5.2.0" ignore "^5.2.0"
imurmurhash "^0.1.4" imurmurhash "^0.1.4"
is-glob "^4.0.0" is-glob "^4.0.0"
is-path-inside "^3.0.3" is-path-inside "^3.0.3"
js-yaml "^4.1.0"
json-stable-stringify-without-jsonify "^1.0.1" json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.4.1" levn "^0.4.1"
lodash.merge "^4.6.2" lodash.merge "^4.6.2"
@ -1225,7 +1214,16 @@ eslint@^8.57.0:
strip-ansi "^6.0.1" strip-ansi "^6.0.1"
text-table "^0.2.0" text-table "^0.2.0"
espree@^9.0.0, espree@^9.6.1: espree@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-10.0.1.tgz#600e60404157412751ba4a6f3a2ee1a42433139f"
integrity sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==
dependencies:
acorn "^8.11.3"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^4.0.0"
espree@^9.0.0:
version "9.6.1" version "9.6.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
@ -1243,15 +1241,6 @@ espree@^9.3.1:
acorn-jsx "^5.3.2" acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.3.0" eslint-visitor-keys "^3.3.0"
espree@^9.6.0:
version "9.6.0"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.0.tgz#80869754b1c6560f32e3b6929194a3fe07c5b82f"
integrity sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==
dependencies:
acorn "^8.9.0"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.4.1"
esprima@^4.0.1: esprima@^4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
@ -1337,12 +1326,12 @@ fastq@^1.6.0:
dependencies: dependencies:
reusify "^1.0.4" reusify "^1.0.4"
file-entry-cache@^6.0.1: file-entry-cache@^8.0.0:
version "6.0.1" version "8.0.0"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f"
integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==
dependencies: dependencies:
flat-cache "^3.0.4" flat-cache "^4.0.0"
fill-range@^7.0.1: fill-range@^7.0.1:
version "7.0.1" version "7.0.1"
@ -1359,18 +1348,18 @@ find-up@^5.0.0:
locate-path "^6.0.0" locate-path "^6.0.0"
path-exists "^4.0.0" path-exists "^4.0.0"
flat-cache@^3.0.4: flat-cache@^4.0.0:
version "3.0.4" version "4.0.1"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c"
integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==
dependencies: dependencies:
flatted "^3.1.0" flatted "^3.2.9"
rimraf "^3.0.2" keyv "^4.5.4"
flatted@^3.1.0: flatted@^3.2.9:
version "3.2.5" version "3.3.1"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
fs-extra@^10.0.0: fs-extra@^10.0.0:
version "10.1.0" version "10.1.0"
@ -1381,11 +1370,6 @@ fs-extra@^10.0.0:
jsonfile "^6.0.1" jsonfile "^6.0.1"
universalify "^2.0.0" universalify "^2.0.0"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
fsevents@~2.3.2: fsevents@~2.3.2:
version "2.3.2" version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
@ -1447,25 +1431,6 @@ glob-parent@^6.0.2:
dependencies: dependencies:
is-glob "^4.0.3" is-glob "^4.0.3"
glob@^7.1.3:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.1.1"
once "^1.3.0"
path-is-absolute "^1.0.0"
globals@^13.19.0:
version "13.19.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-13.19.0.tgz#7a42de8e6ad4f7242fbcca27ea5b23aca367b5c8"
integrity sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==
dependencies:
type-fest "^0.20.2"
globals@^13.24.0: globals@^13.24.0:
version "13.24.0" version "13.24.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171"
@ -1473,6 +1438,11 @@ globals@^13.24.0:
dependencies: dependencies:
type-fest "^0.20.2" type-fest "^0.20.2"
globals@^14.0.0:
version "14.0.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e"
integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
globby@^11.1.0: globby@^11.1.0:
version "11.1.0" version "11.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
@ -1574,19 +1544,6 @@ imurmurhash@^0.1.4:
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
dependencies:
once "^1.3.0"
wrappy "1"
inherits@2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
internal-slot@^1.0.3: internal-slot@^1.0.3:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
@ -1724,6 +1681,11 @@ js-yaml@^4.1.0:
dependencies: dependencies:
argparse "^2.0.1" argparse "^2.0.1"
json-buffer@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
json-parse-better-errors@^1.0.1: json-parse-better-errors@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
@ -1768,6 +1730,13 @@ jsonfile@^6.0.1:
optionalDependencies: optionalDependencies:
graceful-fs "^4.1.6" graceful-fs "^4.1.6"
keyv@^4.5.4:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
dependencies:
json-buffer "3.0.1"
levn@^0.4.1: levn@^0.4.1:
version "0.4.1" version "0.4.1"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
@ -1849,7 +1818,7 @@ minimatch@9.0.3, minimatch@^9.0.3:
dependencies: dependencies:
brace-expansion "^2.0.1" brace-expansion "^2.0.1"
minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2:
version "3.1.2" version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
@ -1958,13 +1927,6 @@ object.assign@^4.1.4:
has-symbols "^1.0.3" has-symbols "^1.0.3"
object-keys "^1.1.1" object-keys "^1.1.1"
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
optionator@^0.9.3: optionator@^0.9.3:
version "0.9.3" version "0.9.3"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64"
@ -2016,11 +1978,6 @@ path-exists@^4.0.0:
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
path-key@^2.0.1: path-key@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
@ -2181,13 +2138,6 @@ reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
dependencies:
glob "^7.1.3"
rollup@^4.13.0: rollup@^4.13.0:
version "4.13.0" version "4.13.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.13.0.tgz#dd2ae144b4cdc2ea25420477f68d4937a721237a" resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.13.0.tgz#dd2ae144b4cdc2ea25420477f68d4937a721237a"
@ -2226,10 +2176,10 @@ safe-regex-test@^1.0.0:
get-intrinsic "^1.1.3" get-intrinsic "^1.1.3"
is-regex "^1.1.4" is-regex "^1.1.4"
sass@^1.72.0: sass@^1.75.0:
version "1.72.0" version "1.75.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.72.0.tgz#5b9978943fcfb32b25a6a5acb102fc9dabbbf41c" resolved "https://registry.yarnpkg.com/sass/-/sass-1.75.0.tgz#91bbe87fb02dfcc34e052ddd6ab80f60d392be6c"
integrity sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA== integrity sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==
dependencies: dependencies:
chokidar ">=3.0.0 <4.0.0" chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0" immutable "^4.0.0"
@ -2431,10 +2381,10 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
terser@^5.30.0: terser@^5.30.3:
version "5.30.0" version "5.30.3"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.30.0.tgz#64cb2af71e16ea3d32153f84d990f9be0cdc22bf" resolved "https://registry.yarnpkg.com/terser/-/terser-5.30.3.tgz#f1bb68ded42408c316b548e3ec2526d7dd03f4d2"
integrity sha512-Y/SblUl5kEyEFzhMAQdsxVHh+utAxd4IuRNJzKywY/4uzSogh3G219jqbDDxYu4MXO9CzY3tSEqmZvW6AoEDJw== integrity sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==
dependencies: dependencies:
"@jridgewell/source-map" "^0.3.3" "@jridgewell/source-map" "^0.3.3"
acorn "^8.8.2" acorn "^8.8.2"
@ -2470,10 +2420,10 @@ type-fest@^0.20.2:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
typescript@^5.4.3: typescript@^5.4.5:
version "5.4.3" version "5.4.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611"
integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==
ufo@^1.1.2: ufo@^1.1.2:
version "1.1.2" version "1.1.2"
@ -2544,10 +2494,10 @@ vite-plugin-css-injected-by-js@^3.5.0:
resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.5.0.tgz#784c0f42c2b42155eb4c726c6addfa24aba9f4fb" resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.5.0.tgz#784c0f42c2b42155eb4c726c6addfa24aba9f4fb"
integrity sha512-d0QaHH9kS93J25SwRqJNEfE29PSuQS5jn51y9N9i2Yoq0FRO7rjuTeLvjM5zwklZlRrIn6SUdtOEDKyHokgJZg== integrity sha512-d0QaHH9kS93J25SwRqJNEfE29PSuQS5jn51y9N9i2Yoq0FRO7rjuTeLvjM5zwklZlRrIn6SUdtOEDKyHokgJZg==
vite@^5.2.7: vite@^5.2.8:
version "5.2.7" version "5.2.8"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.7.tgz#e1b8a985eb54fcb9467d7f7f009d87485016df6e" resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.8.tgz#a99e09939f1a502992381395ce93efa40a2844aa"
integrity sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA== integrity sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==
dependencies: dependencies:
esbuild "^0.20.1" esbuild "^0.20.1"
postcss "^8.4.38" postcss "^8.4.38"
@ -2581,13 +2531,13 @@ vue-eslint-parser@^9.4.2:
lodash "^4.17.21" lodash "^4.17.21"
semver "^7.3.6" semver "^7.3.6"
vue-i18n@^9.10.2: vue-i18n@^9.12.0:
version "9.10.2" version "9.12.0"
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.10.2.tgz#6f4b5d76bce649f1e18bb9b7767b72962b3e30a3" resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.12.0.tgz#8d073b3d7b92e822dcc3268946af4ecf14b778b3"
integrity sha512-ECJ8RIFd+3c1d3m1pctQ6ywG5Yj8Efy1oYoAKQ9neRdkLbuKLVeW4gaY5HPkD/9ssf1pOnUrmIFjx2/gkGxmEw== integrity sha512-rUxCKTws8NH3XP98W71GA7btAQdAuO7j6BC5y5s1bTNQYo/CIgZQf+p7d1Zo5bo/3v8TIq9aSUMDjpfgKsC3Uw==
dependencies: dependencies:
"@intlify/core-base" "9.10.2" "@intlify/core-base" "9.12.0"
"@intlify/shared" "9.10.2" "@intlify/shared" "9.12.0"
"@vue/devtools-api" "^6.5.0" "@vue/devtools-api" "^6.5.0"
vue-router@^4.3.0: vue-router@^4.3.0:
@ -2605,13 +2555,13 @@ vue-template-compiler@^2.7.14:
de-indent "^1.0.2" de-indent "^1.0.2"
he "^1.2.0" he "^1.2.0"
vue-tsc@^2.0.7: vue-tsc@^2.0.13:
version "2.0.7" version "2.0.13"
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-2.0.7.tgz#3177a2fe720bfa7355d3717929ee8c8d132bc5d0" resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-2.0.13.tgz#6ee557705456442e0f43ec0d1774ebf5ffec54f1"
integrity sha512-LYa0nInkfcDBB7y8jQ9FQ4riJTRNTdh98zK/hzt4gEpBZQmf30dPhP+odzCa+cedGz6B/guvJEd0BavZaRptjg== integrity sha512-a3nL3FvguCWVJUQW/jFrUxdeUtiEkbZoQjidqvMeBK//tuE2w6NWQAbdrEpY2+6nSa4kZoKZp8TZUMtHpjt4mQ==
dependencies: dependencies:
"@volar/typescript" "~2.1.3" "@volar/typescript" "2.2.0-alpha.8"
"@vue/language-core" "2.0.7" "@vue/language-core" "2.0.13"
semver "^7.5.4" semver "^7.5.4"
vue@^3.4.21: vue@^3.4.21:
@ -2660,11 +2610,6 @@ which@^2.0.1:
dependencies: dependencies:
isexe "^2.0.0" isexe "^2.0.0"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
xml-name-validator@^4.0.0: xml-name-validator@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"

Binary file not shown.

Binary file not shown.

Binary file not shown.