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