Merge branch 'development'

This commit is contained in:
helgeerbe 2024-01-07 19:09:26 +01:00
commit ffd189c1f5
68 changed files with 805 additions and 649 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,6 +8,7 @@ enum WebApiError {
GenericDataTooLarge,
GenericParseError,
GenericValueMissing,
GenericWriteFailed,
DtuBase = 2000,
DtuSerialZero,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,8 @@
#include "MqttSettings.h"
#include "NetworkSettings.h"
#include "MessageOutput.h"
#include "VictronMppt.h"
#include "VictronMppt.h"
#include "Utils.h"
MqttHandleVedirectHassClass MqttHandleVedirectHass;
@ -109,29 +110,32 @@ void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char*
statTopic.concat(subTopic);
DynamicJsonDocument root(1024);
root[F("name")] = caption;
root[F("stat_t")] = statTopic;
root[F("uniq_id")] = serial + "_" + sensorId;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
root["name"] = caption;
root["stat_t"] = statTopic;
root["uniq_id"] = serial + "_" + sensorId;
if (icon != NULL) {
root[F("icon")] = icon;
root["icon"] = icon;
}
if (unitOfMeasurement != NULL) {
root[F("unit_of_meas")] = unitOfMeasurement;
root["unit_of_meas"] = unitOfMeasurement;
}
JsonObject deviceObj = root.createNestedObject("dev");
createDeviceInfo(deviceObj);
if (Configuration.get().Mqtt.Hass.Expire) {
root[F("exp_aft")] = Configuration.get().Mqtt.PublishInterval * 3;
root["exp_aft"] = Configuration.get().Mqtt.PublishInterval * 3;
}
if (deviceClass != NULL) {
root[F("dev_cla")] = deviceClass;
root["dev_cla"] = deviceClass;
}
if (stateClass != NULL) {
root[F("stat_cla")] = stateClass;
root["stat_cla"] = stateClass;
}
char buffer[512];
@ -160,14 +164,17 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const
statTopic.concat(subTopic);
DynamicJsonDocument root(1024);
root[F("name")] = caption;
root[F("uniq_id")] = serial + "_" + sensorId;
root[F("stat_t")] = statTopic;
root[F("pl_on")] = payload_on;
root[F("pl_off")] = payload_off;
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
return;
}
root["name"] = caption;
root["uniq_id"] = serial + "_" + sensorId;
root["stat_t"] = statTopic;
root["pl_on"] = payload_on;
root["pl_off"] = payload_off;
if (icon != NULL) {
root[F("icon")] = icon;
root["icon"] = icon;
}
JsonObject deviceObj = root.createNestedObject("dev");
@ -182,12 +189,12 @@ void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject& object)
{
auto spMpptData = VictronMppt.getData();
String serial = spMpptData->SER;
object[F("name")] = "Victron(" + serial + ")";
object[F("ids")] = serial;
object[F("cu")] = String(F("http://")) + NetworkSettings.localIP().toString();
object[F("mf")] = F("OpenDTU");
object[F("mdl")] = spMpptData->getPidAsString();
object[F("sw")] = AUTO_GIT_HASH;
object["name"] = "Victron(" + serial + ")";
object["ids"] = serial;
object["cu"] = String("http://") + NetworkSettings.localIP().toString();
object["mf"] = "OpenDTU";
object["mdl"] = spMpptData->getPidAsString();
object["sw"] = AUTO_GIT_HASH;
}
void MqttHandleVedirectHassClass::publish(const String& subtopic, const String& payload)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
retMsg[F("type")] = F("success");
retMsg[F("message")] = F("Settings saved!");
retMsg[F("code")] = WebApiError::GenericSuccess;
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));
WebApi.writeConfig(retMsg);
response->setLength();
request->send(response);

View File

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

View File

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

View File

@ -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")) {

View File

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

View File

@ -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")) {

View File

@ -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")) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
String buffer;
serializeJson(root, buffer);
}
if (buffer) {
if (Configuration.get().Security.AllowReadonly) {
_ws.setAuthentication("", "");
} else {
@ -76,6 +76,8 @@ void WebApiWsVedirectLiveClass::loop()
} catch (std::bad_alloc& bad_alloc) {
MessageOutput.printf("Calling /api/vedirectlivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
} catch (const std::exception& exc) {
MessageOutput.printf("Unknown exception in /api/vedirectlivedata/status. Reason: \"%s\".\r\n", exc.what());
}
_lastWsPublish = millis();
@ -168,8 +170,9 @@ void WebApiWsVedirectLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
return;
}
try {
std::lock_guard<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);
}
}

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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