Merge branch 'tbnobody:master' into master
This commit is contained in:
commit
ac88d4f822
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -119,7 +119,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Changelog
|
- name: Build Changelog
|
||||||
id: github_release
|
id: github_release
|
||||||
uses: mikepenz/release-changelog-builder-action@v3
|
uses: mikepenz/release-changelog-builder-action@v4
|
||||||
with:
|
with:
|
||||||
failOnError: true
|
failOnError: true
|
||||||
commitMode: true
|
commitMode: true
|
||||||
@ -138,7 +138,7 @@ jobs:
|
|||||||
for i in */; do cp ${i}opendtu-*.bin ./; done
|
for i in */; do cp ${i}opendtu-*.bin ./; done
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
body: ${{steps.github_release.outputs.changelog}}
|
body: ${{steps.github_release.outputs.changelog}}
|
||||||
draft: False
|
draft: False
|
||||||
|
|||||||
54
.github/workflows/repo-maintenance.yml
vendored
Normal file
54
.github/workflows/repo-maintenance.yml
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
name: 'Repository Maintenance'
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 4 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
discussions: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: lock
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
name: 'Stale'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v9
|
||||||
|
with:
|
||||||
|
days-before-stale: 14
|
||||||
|
days-before-close: 60
|
||||||
|
any-of-labels: 'cant-reproduce,not a bug'
|
||||||
|
stale-issue-label: stale
|
||||||
|
stale-pr-label: stale
|
||||||
|
stale-issue-message: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions.
|
||||||
|
|
||||||
|
lock-threads:
|
||||||
|
name: 'Lock Old Threads'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: dessant/lock-threads@v5
|
||||||
|
with:
|
||||||
|
issue-inactive-days: '30'
|
||||||
|
pr-inactive-days: '30'
|
||||||
|
discussion-inactive-days: '30'
|
||||||
|
log-output: true
|
||||||
|
issue-comment: >
|
||||||
|
This issue has been automatically locked since there
|
||||||
|
has not been any recent activity after it was closed.
|
||||||
|
Please open a new discussion or issue for related concerns.
|
||||||
|
pr-comment: >
|
||||||
|
This pull request has been automatically locked since there
|
||||||
|
has not been any recent activity after it was closed.
|
||||||
|
Please open a new discussion or issue for related concerns.
|
||||||
|
discussion-comment: >
|
||||||
|
This discussion has been automatically locked since there
|
||||||
|
has not been any recent activity after it was closed.
|
||||||
|
Please open a new discussion for related concerns.
|
||||||
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@ -5,7 +5,6 @@
|
|||||||
"DavidAnson.vscode-markdownlint",
|
"DavidAnson.vscode-markdownlint",
|
||||||
"EditorConfig.EditorConfig",
|
"EditorConfig.EditorConfig",
|
||||||
"Vue.volar",
|
"Vue.volar",
|
||||||
"Vue.vscode-typescript-vue-plugin",
|
|
||||||
"platformio.platformio-ide"
|
"platformio.platformio-ide"
|
||||||
],
|
],
|
||||||
"unwantedRecommendations": [
|
"unwantedRecommendations": [
|
||||||
|
|||||||
@ -78,3 +78,6 @@ Generated using: `git log --date=short --pretty=format:"* %h%x09%ad%x09%s" | gre
|
|||||||
| TSUN TSOL-M350 | NRF24L01+ | 1 | 1 | 1 |
|
| TSUN TSOL-M350 | NRF24L01+ | 1 | 1 | 1 |
|
||||||
| TSUN TSOL-M800 | NRF24L01+ | 2 | 2 | 1 |
|
| TSUN TSOL-M800 | NRF24L01+ | 2 | 2 | 1 |
|
||||||
| TSUN TSOL-M1600 | NRF24L01+ | 4 | 2 | 1 |
|
| TSUN TSOL-M1600 | NRF24L01+ | 4 | 2 | 1 |
|
||||||
|
| E-Star HERF-800 | NRF24L01+ | 2 | 2 | 1 |
|
||||||
|
| E-Star HERF-1600 | NRF24L01+ | 4 | 2 | 1 |
|
||||||
|
| E-Star HERF-1800 | NRF24L01+ | 4 | 2 | 1 |
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#define CONFIG_FILENAME "/config.json"
|
#define CONFIG_FILENAME "/config.json"
|
||||||
#define CONFIG_VERSION 0x00011b00 // 0.1.27 // make sure to clean all after change
|
#define CONFIG_VERSION 0x00011c00 // 0.1.28 // make sure to clean all after change
|
||||||
|
|
||||||
#define WIFI_MAX_SSID_STRLEN 32
|
#define WIFI_MAX_SSID_STRLEN 32
|
||||||
#define WIFI_MAX_PASSWORD_STRLEN 64
|
#define WIFI_MAX_PASSWORD_STRLEN 64
|
||||||
@ -30,8 +30,6 @@
|
|||||||
|
|
||||||
#define DEV_MAX_MAPPING_NAME_STRLEN 63
|
#define DEV_MAX_MAPPING_NAME_STRLEN 63
|
||||||
|
|
||||||
#define JSON_BUFFER_SIZE 12288
|
|
||||||
|
|
||||||
struct CHANNEL_CONFIG_T {
|
struct CHANNEL_CONFIG_T {
|
||||||
uint16_t MaxChannelPower;
|
uint16_t MaxChannelPower;
|
||||||
char Name[CHAN_MAX_NAME_STRLEN];
|
char Name[CHAN_MAX_NAME_STRLEN];
|
||||||
@ -168,6 +166,7 @@ public:
|
|||||||
|
|
||||||
INVERTER_CONFIG_T* getFreeInverterSlot();
|
INVERTER_CONFIG_T* getFreeInverterSlot();
|
||||||
INVERTER_CONFIG_T* getInverterConfig(const uint64_t serial);
|
INVERTER_CONFIG_T* getInverterConfig(const uint64_t serial);
|
||||||
|
void deleteInverterById(const uint8_t id);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern ConfigurationClass Configuration;
|
extern ConfigurationClass Configuration;
|
||||||
|
|||||||
@ -66,10 +66,10 @@ private:
|
|||||||
void publishInverterNumber(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, const int16_t min = 1, const int16_t max = 100);
|
void publishInverterNumber(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, const int16_t min = 1, const int16_t max = 100);
|
||||||
void publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off);
|
void publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off);
|
||||||
|
|
||||||
static void createInverterInfo(DynamicJsonDocument& doc, std::shared_ptr<InverterAbstract> inv);
|
static void createInverterInfo(JsonDocument& doc, std::shared_ptr<InverterAbstract> inv);
|
||||||
static void createDtuInfo(DynamicJsonDocument& doc);
|
static void createDtuInfo(JsonDocument& doc);
|
||||||
|
|
||||||
static void createDeviceInfo(DynamicJsonDocument& doc, const String& name, const String& identifiers, const String& configuration_url, const String& manufacturer, const String& model, const String& sw_version, const String& via_device = "");
|
static void createDeviceInfo(JsonDocument& doc, const String& name, const String& identifiers, const String& configuration_url, const String& manufacturer, const String& model, const String& sw_version, const String& via_device = "");
|
||||||
|
|
||||||
static String getDtuUniqueId();
|
static String getDtuUniqueId();
|
||||||
static String getDtuUrl();
|
static String getDtuUrl();
|
||||||
|
|||||||
@ -10,6 +10,6 @@ public:
|
|||||||
static uint64_t generateDtuSerial();
|
static uint64_t generateDtuSerial();
|
||||||
static int getTimezoneOffset();
|
static int getTimezoneOffset();
|
||||||
static void restartDtu();
|
static void restartDtu();
|
||||||
static bool checkJsonAlloc(const DynamicJsonDocument& doc, const char* function, const uint16_t line);
|
static bool checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line);
|
||||||
static void removeAllFiles();
|
static void removeAllFiles();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -22,6 +22,7 @@
|
|||||||
#include "WebApi_webapp.h"
|
#include "WebApi_webapp.h"
|
||||||
#include "WebApi_ws_console.h"
|
#include "WebApi_ws_console.h"
|
||||||
#include "WebApi_ws_live.h"
|
#include "WebApi_ws_live.h"
|
||||||
|
#include <AsyncJson.h>
|
||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
|
|
||||||
@ -37,6 +38,10 @@ public:
|
|||||||
|
|
||||||
static void writeConfig(JsonVariant& retMsg, const WebApiError code = WebApiError::GenericSuccess, const String& message = "Settings saved!");
|
static void writeConfig(JsonVariant& retMsg, const WebApiError code = WebApiError::GenericSuccess, const String& message = "Settings saved!");
|
||||||
|
|
||||||
|
static bool parseRequestData(AsyncWebServerRequest* request, AsyncJsonResponse* response, JsonDocument& json_document);
|
||||||
|
static uint64_t parseSerialFromRequest(AsyncWebServerRequest* request, String param_name = "inv");
|
||||||
|
static bool sendJsonResponse(AsyncWebServerRequest* request, AsyncJsonResponse* response, const char* function, const uint16_t line);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AsyncWebServer _server;
|
AsyncWebServer _server;
|
||||||
|
|
||||||
|
|||||||
@ -5,10 +5,11 @@ enum WebApiError {
|
|||||||
GenericBase = 1000,
|
GenericBase = 1000,
|
||||||
GenericSuccess,
|
GenericSuccess,
|
||||||
GenericNoValueFound,
|
GenericNoValueFound,
|
||||||
GenericDataTooLarge,
|
GenericDataTooLarge, // not used anymore
|
||||||
GenericParseError,
|
GenericParseError,
|
||||||
GenericValueMissing,
|
GenericValueMissing,
|
||||||
GenericWriteFailed,
|
GenericWriteFailed,
|
||||||
|
GenericInternalServerError,
|
||||||
|
|
||||||
DtuBase = 2000,
|
DtuBase = 2000,
|
||||||
DtuSerialZero,
|
DtuSerialZero,
|
||||||
|
|||||||
@ -4,8 +4,6 @@
|
|||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
|
|
||||||
#define MQTT_JSON_DOC_SIZE 10240
|
|
||||||
|
|
||||||
class WebApiMqttClass {
|
class WebApiMqttClass {
|
||||||
public:
|
public:
|
||||||
void init(AsyncWebServer& server, Scheduler& scheduler);
|
void init(AsyncWebServer& server, Scheduler& scheduler);
|
||||||
|
|||||||
8
include/__compiled_constants.h
Normal file
8
include/__compiled_constants.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// The referenced values are generated by pio-scripts/auto_firmware_version.py
|
||||||
|
|
||||||
|
|
||||||
|
extern const char *__COMPILED_GIT_HASH__;
|
||||||
|
// extern const char *__COMPILED_DATE_TIME_UTC_STR__;
|
||||||
@ -22,7 +22,8 @@
|
|||||||
|
|
||||||
#define MDNS_ENABLED false
|
#define MDNS_ENABLED false
|
||||||
|
|
||||||
#define NTP_SERVER "pool.ntp.org"
|
#define NTP_SERVER_OLD "pool.ntp.org"
|
||||||
|
#define NTP_SERVER "opendtu.pool.ntp.org"
|
||||||
#define NTP_TIMEZONE "CET-1CEST,M3.5.0,M10.5.0/3"
|
#define NTP_TIMEZONE "CET-1CEST,M3.5.0,M10.5.0/3"
|
||||||
#define NTP_TIMEZONEDESCR "Europe/Berlin"
|
#define NTP_TIMEZONEDESCR "Europe/Berlin"
|
||||||
#define NTP_LONGITUDE 10.4515f
|
#define NTP_LONGITUDE 10.4515f
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022-2023 Thomas Basler and others
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "Hoymiles.h"
|
#include "Hoymiles.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
#include "inverters/HERF_2CH.h"
|
||||||
|
#include "inverters/HERF_4CH.h"
|
||||||
#include "inverters/HMS_1CH.h"
|
#include "inverters/HMS_1CH.h"
|
||||||
#include "inverters/HMS_1CHv2.h"
|
#include "inverters/HMS_1CHv2.h"
|
||||||
#include "inverters/HMS_2CH.h"
|
#include "inverters/HMS_2CH.h"
|
||||||
@ -112,7 +114,7 @@ void HoymilesClass::loop()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch grid profile
|
// Fetch grid profile
|
||||||
if (iv->Statistics()->getLastUpdate() > 0 && iv->GridProfile()->getLastUpdate() == 0) {
|
if (iv->Statistics()->getLastUpdate() > 0 && (iv->GridProfile()->getLastUpdate() == 0 || !iv->GridProfile()->containsValidData())) {
|
||||||
iv->sendGridOnProFileParaRequest();
|
iv->sendGridOnProFileParaRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,6 +170,10 @@ std::shared_ptr<InverterAbstract> HoymilesClass::addInverter(const char* name, c
|
|||||||
i = std::make_shared<HM_2CH>(_radioNrf.get(), serial);
|
i = std::make_shared<HM_2CH>(_radioNrf.get(), serial);
|
||||||
} else if (HM_1CH::isValidSerial(serial)) {
|
} else if (HM_1CH::isValidSerial(serial)) {
|
||||||
i = std::make_shared<HM_1CH>(_radioNrf.get(), serial);
|
i = std::make_shared<HM_1CH>(_radioNrf.get(), serial);
|
||||||
|
} else if (HERF_2CH::isValidSerial(serial)) {
|
||||||
|
i = std::make_shared<HERF_2CH>(_radioNrf.get(), serial);
|
||||||
|
} else if (HERF_4CH::isValidSerial(serial)) {
|
||||||
|
i = std::make_shared<HERF_4CH>(_radioNrf.get(), serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i) {
|
if (i) {
|
||||||
@ -271,4 +277,4 @@ void HoymilesClass::setMessageOutput(Print* output)
|
|||||||
Print* HoymilesClass::getMessageOutput()
|
Print* HoymilesClass::getMessageOutput()
|
||||||
{
|
{
|
||||||
return _messageOutput;
|
return _messageOutput;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "TimeoutHelper.h"
|
|
||||||
#include "commands/CommandAbstract.h"
|
#include "commands/CommandAbstract.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include <memory>
|
|
||||||
#include <ThreadSafeQueue.h>
|
#include <ThreadSafeQueue.h>
|
||||||
|
#include <TimeoutHelper.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class HoymilesRadio {
|
class HoymilesRadio {
|
||||||
public:
|
public:
|
||||||
@ -43,4 +43,4 @@ protected:
|
|||||||
bool _busyFlag = false;
|
bool _busyFlag = false;
|
||||||
|
|
||||||
TimeoutHelper _rxTimeout;
|
TimeoutHelper _rxTimeout;
|
||||||
};
|
};
|
||||||
|
|||||||
62
lib/Hoymiles/src/inverters/HERF_2CH.cpp
Normal file
62
lib/Hoymiles/src/inverters/HERF_2CH.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
|
*/
|
||||||
|
#include "HERF_2CH.h"
|
||||||
|
|
||||||
|
static const byteAssign_t byteAssignment[] = {
|
||||||
|
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
|
||||||
|
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
|
||||||
|
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
|
||||||
|
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
|
||||||
|
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 14, 4, 1000, false, 3 },
|
||||||
|
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_CH_IRR, CH0, CMD_CALC, false, 3 },
|
||||||
|
|
||||||
|
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, 4, 2, 10, false, 1 },
|
||||||
|
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 8, 2, 100, false, 2 },
|
||||||
|
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 12, 2, 10, false, 1 },
|
||||||
|
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 24, 2, 1, false, 0 },
|
||||||
|
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 18, 4, 1000, false, 3 },
|
||||||
|
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_CH_IRR, CH1, CMD_CALC, false, 3 },
|
||||||
|
|
||||||
|
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 26, 2, 10, false, 1 },
|
||||||
|
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 34, 2, 100, false, 2 },
|
||||||
|
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 30, 2, 10, false, 1 },
|
||||||
|
{ TYPE_AC, CH0, FLD_Q, UNIT_VAR, 32, 2, 10, false, 1 },
|
||||||
|
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 28, 2, 100, false, 2 },
|
||||||
|
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 36, 2, 1000, false, 3 },
|
||||||
|
|
||||||
|
{ TYPE_INV, CH0, FLD_T, UNIT_C, 38, 2, 10, true, 1 },
|
||||||
|
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 40, 2, 1, false, 0 },
|
||||||
|
|
||||||
|
{ TYPE_INV, CH0, FLD_YD, UNIT_WH, CALC_TOTAL_YD, 0, CMD_CALC, false, 0 },
|
||||||
|
{ TYPE_INV, CH0, FLD_YT, UNIT_KWH, CALC_TOTAL_YT, 0, CMD_CALC, false, 3 },
|
||||||
|
{ TYPE_INV, CH0, FLD_PDC, UNIT_W, CALC_TOTAL_PDC, 0, CMD_CALC, false, 1 },
|
||||||
|
{ TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 }
|
||||||
|
};
|
||||||
|
|
||||||
|
HERF_2CH::HERF_2CH(HoymilesRadio* radio, const uint64_t serial)
|
||||||
|
: HM_Abstract(radio, serial) {};
|
||||||
|
|
||||||
|
bool HERF_2CH::isValidSerial(const uint64_t serial)
|
||||||
|
{
|
||||||
|
// serial >= 0x282100000000 && serial <= 0x2821ffffffff
|
||||||
|
uint16_t preSerial = (serial >> 32) & 0xffff;
|
||||||
|
return preSerial == 0x2821;
|
||||||
|
}
|
||||||
|
|
||||||
|
String HERF_2CH::typeName() const
|
||||||
|
{
|
||||||
|
return "HERF-800-2T";
|
||||||
|
}
|
||||||
|
|
||||||
|
const byteAssign_t* HERF_2CH::getByteAssignment() const
|
||||||
|
{
|
||||||
|
return byteAssignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t HERF_2CH::getByteAssignmentSize() const
|
||||||
|
{
|
||||||
|
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
|
||||||
|
}
|
||||||
13
lib/Hoymiles/src/inverters/HERF_2CH.h
Normal file
13
lib/Hoymiles/src/inverters/HERF_2CH.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "HM_Abstract.h"
|
||||||
|
|
||||||
|
class HERF_2CH : public HM_Abstract {
|
||||||
|
public:
|
||||||
|
explicit HERF_2CH(HoymilesRadio* radio, const uint64_t serial);
|
||||||
|
static bool isValidSerial(const uint64_t serial);
|
||||||
|
String typeName() const;
|
||||||
|
const byteAssign_t* getByteAssignment() const;
|
||||||
|
uint8_t getByteAssignmentSize() const;
|
||||||
|
};
|
||||||
20
lib/Hoymiles/src/inverters/HERF_4CH.cpp
Normal file
20
lib/Hoymiles/src/inverters/HERF_4CH.cpp
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
|
*/
|
||||||
|
#include "HERF_4CH.h"
|
||||||
|
|
||||||
|
HERF_4CH::HERF_4CH(HoymilesRadio* radio, const uint64_t serial)
|
||||||
|
: HM_4CH(radio, serial) {};
|
||||||
|
|
||||||
|
bool HERF_4CH::isValidSerial(const uint64_t serial)
|
||||||
|
{
|
||||||
|
// serial >= 0x280100000000 && serial <= 0x2801ffffffff
|
||||||
|
uint16_t preSerial = (serial >> 32) & 0xffff;
|
||||||
|
return preSerial == 0x2801;
|
||||||
|
}
|
||||||
|
|
||||||
|
String HERF_4CH::typeName() const
|
||||||
|
{
|
||||||
|
return "HERF-1600/1800-4T";
|
||||||
|
}
|
||||||
11
lib/Hoymiles/src/inverters/HERF_4CH.h
Normal file
11
lib/Hoymiles/src/inverters/HERF_4CH.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "HM_4CH.h"
|
||||||
|
|
||||||
|
class HERF_4CH : public HM_4CH {
|
||||||
|
public:
|
||||||
|
explicit HERF_4CH(HoymilesRadio* radio, const uint64_t serial);
|
||||||
|
static bool isValidSerial(const uint64_t serial);
|
||||||
|
String typeName() const;
|
||||||
|
};
|
||||||
@ -33,7 +33,7 @@ HMS_1CH::HMS_1CH(HoymilesRadio* radio, const uint64_t serial)
|
|||||||
|
|
||||||
bool HMS_1CH::isValidSerial(const uint64_t serial)
|
bool HMS_1CH::isValidSerial(const uint64_t serial)
|
||||||
{
|
{
|
||||||
// serial >= 0x112400000000 && serial <= 0x112499999999
|
// serial >= 0x112400000000 && serial <= 0x1124ffffffff
|
||||||
uint16_t preSerial = (serial >> 32) & 0xffff;
|
uint16_t preSerial = (serial >> 32) & 0xffff;
|
||||||
return preSerial == 0x1124;
|
return preSerial == 0x1124;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,7 @@ HMS_1CHv2::HMS_1CHv2(HoymilesRadio* radio, const uint64_t serial)
|
|||||||
|
|
||||||
bool HMS_1CHv2::isValidSerial(const uint64_t serial)
|
bool HMS_1CHv2::isValidSerial(const uint64_t serial)
|
||||||
{
|
{
|
||||||
// serial >= 0x112500000000 && serial <= 0x112599999999
|
// serial >= 0x112500000000 && serial <= 0x1125ffffffff
|
||||||
uint16_t preSerial = (serial >> 32) & 0xffff;
|
uint16_t preSerial = (serial >> 32) & 0xffff;
|
||||||
return preSerial == 0x1125;
|
return preSerial == 0x1125;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,7 +40,7 @@ HMS_2CH::HMS_2CH(HoymilesRadio* radio, const uint64_t serial)
|
|||||||
|
|
||||||
bool HMS_2CH::isValidSerial(const uint64_t serial)
|
bool HMS_2CH::isValidSerial(const uint64_t serial)
|
||||||
{
|
{
|
||||||
// serial >= 0x114400000000 && serial <= 0x114499999999
|
// serial >= 0x114400000000 && serial <= 0x1144ffffffff
|
||||||
uint16_t preSerial = (serial >> 32) & 0xffff;
|
uint16_t preSerial = (serial >> 32) & 0xffff;
|
||||||
return preSerial == 0x1144;
|
return preSerial == 0x1144;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,7 +54,7 @@ HMS_4CH::HMS_4CH(HoymilesRadio* radio, const uint64_t serial)
|
|||||||
|
|
||||||
bool HMS_4CH::isValidSerial(const uint64_t serial)
|
bool HMS_4CH::isValidSerial(const uint64_t serial)
|
||||||
{
|
{
|
||||||
// serial >= 0x116400000000 && serial <= 0x116499999999
|
// serial >= 0x116400000000 && serial <= 0x1164ffffffff
|
||||||
uint16_t preSerial = (serial >> 32) & 0xffff;
|
uint16_t preSerial = (serial >> 32) & 0xffff;
|
||||||
return preSerial == 0x1164;
|
return preSerial == 0x1164;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,14 +63,14 @@ HMT_4CH::HMT_4CH(HoymilesRadio* radio, const uint64_t serial)
|
|||||||
|
|
||||||
bool HMT_4CH::isValidSerial(const uint64_t serial)
|
bool HMT_4CH::isValidSerial(const uint64_t serial)
|
||||||
{
|
{
|
||||||
// serial >= 0x136100000000 && serial <= 0x136199999999
|
// serial >= 0x136100000000 && serial <= 0x1361ffffffff
|
||||||
uint16_t preSerial = (serial >> 32) & 0xffff;
|
uint16_t preSerial = (serial >> 32) & 0xffff;
|
||||||
return preSerial == 0x1361;
|
return preSerial == 0x1361;
|
||||||
}
|
}
|
||||||
|
|
||||||
String HMT_4CH::typeName() const
|
String HMT_4CH::typeName() const
|
||||||
{
|
{
|
||||||
return F("HMT-1600/1800/2000-4T");
|
return "HMT-1600/1800/2000-4T";
|
||||||
}
|
}
|
||||||
|
|
||||||
const byteAssign_t* HMT_4CH::getByteAssignment() const
|
const byteAssign_t* HMT_4CH::getByteAssignment() const
|
||||||
|
|||||||
@ -77,14 +77,14 @@ HMT_6CH::HMT_6CH(HoymilesRadio* radio, const uint64_t serial)
|
|||||||
|
|
||||||
bool HMT_6CH::isValidSerial(const uint64_t serial)
|
bool HMT_6CH::isValidSerial(const uint64_t serial)
|
||||||
{
|
{
|
||||||
// serial >= 0x138200000000 && serial <= 0x138299999999
|
// serial >= 0x138200000000 && serial <= 0x1382ffffffff
|
||||||
uint16_t preSerial = (serial >> 32) & 0xffff;
|
uint16_t preSerial = (serial >> 32) & 0xffff;
|
||||||
return preSerial == 0x1382;
|
return preSerial == 0x1382;
|
||||||
}
|
}
|
||||||
|
|
||||||
String HMT_6CH::typeName() const
|
String HMT_6CH::typeName() const
|
||||||
{
|
{
|
||||||
return F("HMT-1800/2250-6T");
|
return "HMT-1800/2250-6T";
|
||||||
}
|
}
|
||||||
|
|
||||||
const byteAssign_t* HMT_6CH::getByteAssignment() const
|
const byteAssign_t* HMT_6CH::getByteAssignment() const
|
||||||
|
|||||||
@ -33,7 +33,7 @@ HM_1CH::HM_1CH(HoymilesRadio* radio, const uint64_t serial)
|
|||||||
|
|
||||||
bool HM_1CH::isValidSerial(const uint64_t serial)
|
bool HM_1CH::isValidSerial(const uint64_t serial)
|
||||||
{
|
{
|
||||||
// serial >= 0x112100000000 && serial <= 0x112199999999
|
// serial >= 0x112100000000 && serial <= 0x1121ffffffff
|
||||||
|
|
||||||
uint8_t preId[2];
|
uint8_t preId[2];
|
||||||
preId[0] = (uint8_t)(serial >> 40);
|
preId[0] = (uint8_t)(serial >> 40);
|
||||||
|
|||||||
@ -41,7 +41,7 @@ HM_2CH::HM_2CH(HoymilesRadio* radio, const uint64_t serial)
|
|||||||
|
|
||||||
bool HM_2CH::isValidSerial(const uint64_t serial)
|
bool HM_2CH::isValidSerial(const uint64_t serial)
|
||||||
{
|
{
|
||||||
// serial >= 0x114100000000 && serial <= 0x114199999999
|
// serial >= 0x114100000000 && serial <= 0x1141ffffffff
|
||||||
|
|
||||||
uint8_t preId[2];
|
uint8_t preId[2];
|
||||||
preId[0] = (uint8_t)(serial >> 40);
|
preId[0] = (uint8_t)(serial >> 40);
|
||||||
|
|||||||
@ -54,7 +54,7 @@ HM_4CH::HM_4CH(HoymilesRadio* radio, const uint64_t serial)
|
|||||||
|
|
||||||
bool HM_4CH::isValidSerial(const uint64_t serial)
|
bool HM_4CH::isValidSerial(const uint64_t serial)
|
||||||
{
|
{
|
||||||
// serial >= 0x116100000000 && serial <= 0x116199999999
|
// serial >= 0x116100000000 && serial <= 0x1161ffffffff
|
||||||
|
|
||||||
uint8_t preId[2];
|
uint8_t preId[2];
|
||||||
preId[0] = (uint8_t)(serial >> 40);
|
preId[0] = (uint8_t)(serial >> 40);
|
||||||
|
|||||||
@ -11,3 +11,5 @@
|
|||||||
| HMS_4CH | HMS-1600/1800/2000-4T | 1164 |
|
| HMS_4CH | HMS-1600/1800/2000-4T | 1164 |
|
||||||
| HMT_4CH | HMT-1600/1800/2000-4T | 1361 |
|
| HMT_4CH | HMT-1600/1800/2000-4T | 1361 |
|
||||||
| HMT_6CH | HMT-1800/2250-6T | 1382 |
|
| HMT_6CH | HMT-1800/2250-6T | 1382 |
|
||||||
|
| HERF_2CH | HERF 800 | 2821 |
|
||||||
|
| HERF_4CH | HERF 1800 | 2801 |
|
||||||
|
|||||||
@ -55,11 +55,12 @@ const std::array<const AlarmMessage_t, ALARM_MSG_COUNT> AlarmLogParser::_alarmMe
|
|||||||
{ AlarmMessageType_t::ALL, 144, "Grid: Grid overfrequency", "Netz: Netzüberfrequenz", "Réseau: Surfréquence du réseau" },
|
{ AlarmMessageType_t::ALL, 144, "Grid: Grid overfrequency", "Netz: Netzüberfrequenz", "Réseau: Surfréquence du réseau" },
|
||||||
{ AlarmMessageType_t::ALL, 145, "Grid: Grid underfrequency", "Netz: Netzunterfrequenz", "Réseau: Sous-fréquence du réseau" },
|
{ AlarmMessageType_t::ALL, 145, "Grid: Grid underfrequency", "Netz: Netzunterfrequenz", "Réseau: Sous-fréquence du réseau" },
|
||||||
{ AlarmMessageType_t::ALL, 146, "Grid: Rapid grid frequency change rate", "Netz: Schnelle Wechselrate der Netzfrequenz", "Réseau: Taux de fluctuation rapide de la fréquence du réseau" },
|
{ AlarmMessageType_t::ALL, 146, "Grid: Rapid grid frequency change rate", "Netz: Schnelle Wechselrate der Netzfrequenz", "Réseau: Taux de fluctuation rapide de la fréquence du réseau" },
|
||||||
{ AlarmMessageType_t::ALL, 147, "Grid: Power grid outage", "Netz: Eletrizitätsnetzausfall", "Réseau: Panne du réseau électrique" },
|
{ AlarmMessageType_t::ALL, 147, "Grid: Power grid outage", "Netz: Elektrizitätsnetzausfall", "Réseau: Panne du réseau électrique" },
|
||||||
{ AlarmMessageType_t::ALL, 148, "Grid: Grid disconnection", "Netz: Netztrennung", "Réseau: Déconnexion du réseau" },
|
{ AlarmMessageType_t::ALL, 148, "Grid: Grid disconnection", "Netz: Netztrennung", "Réseau: Déconnexion du réseau" },
|
||||||
{ AlarmMessageType_t::ALL, 149, "Grid: Island detected", "Netz: Inselbetrieb festgestellt", "Réseau: Détection d’îlots" },
|
{ AlarmMessageType_t::ALL, 149, "Grid: Island detected", "Netz: Inselbetrieb festgestellt", "Réseau: Détection d’îlots" },
|
||||||
|
|
||||||
{ AlarmMessageType_t::ALL, 150, "DCI exceeded", "", "" },
|
{ AlarmMessageType_t::ALL, 150, "DCI exceeded", "", "" },
|
||||||
|
{ AlarmMessageType_t::ALL, 152, "Grid: Phase angle difference between two phases exceeded 5° >10 times", "", "" },
|
||||||
{ AlarmMessageType_t::HMT, 171, "Grid: Abnormal phase difference between phase to phase", "", "" },
|
{ AlarmMessageType_t::HMT, 171, "Grid: Abnormal phase difference between phase to phase", "", "" },
|
||||||
{ AlarmMessageType_t::ALL, 181, "Abnormal insulation impedance", "", "" },
|
{ AlarmMessageType_t::ALL, 181, "Abnormal insulation impedance", "", "" },
|
||||||
{ AlarmMessageType_t::ALL, 182, "Abnormal grounding", "", "" },
|
{ AlarmMessageType_t::ALL, 182, "Abnormal grounding", "", "" },
|
||||||
@ -294,4 +295,4 @@ int AlarmLogParser::getTimezoneOffset()
|
|||||||
gmt = mktime(ptm);
|
gmt = mktime(ptm);
|
||||||
|
|
||||||
return static_cast<int>(difftime(rawtime, gmt));
|
return static_cast<int>(difftime(rawtime, gmt));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
#define ALARM_LOG_ENTRY_SIZE 12
|
#define ALARM_LOG_ENTRY_SIZE 12
|
||||||
#define ALARM_LOG_PAYLOAD_SIZE (ALARM_LOG_ENTRY_COUNT * ALARM_LOG_ENTRY_SIZE + 4)
|
#define ALARM_LOG_PAYLOAD_SIZE (ALARM_LOG_ENTRY_COUNT * ALARM_LOG_ENTRY_SIZE + 4)
|
||||||
|
|
||||||
#define ALARM_MSG_COUNT 130
|
#define ALARM_MSG_COUNT 131
|
||||||
|
|
||||||
struct AlarmLogEntry_t {
|
struct AlarmLogEntry_t {
|
||||||
uint16_t MessageId;
|
uint16_t MessageId;
|
||||||
@ -62,4 +62,4 @@ private:
|
|||||||
AlarmMessageType_t _messageType = AlarmMessageType_t::ALL;
|
AlarmMessageType_t _messageType = AlarmMessageType_t::ALL;
|
||||||
|
|
||||||
static const std::array<const AlarmMessage_t, ALARM_MSG_COUNT> _alarmMessages;
|
static const std::array<const AlarmMessage_t, ALARM_MSG_COUNT> _alarmMessages;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -52,7 +52,11 @@ const devInfo_t devInfo[] = {
|
|||||||
{ { 0x10, 0x32, 0x71, ALL }, 2000, "HMT-2000-4T" }, // 0
|
{ { 0x10, 0x32, 0x71, ALL }, 2000, "HMT-2000-4T" }, // 0
|
||||||
|
|
||||||
{ { 0x10, 0x33, 0x11, ALL }, 1800, "HMT-1800-6T" }, // 01
|
{ { 0x10, 0x33, 0x11, ALL }, 1800, "HMT-1800-6T" }, // 01
|
||||||
{ { 0x10, 0x33, 0x31, ALL }, 2250, "HMT-2250-6T" } // 01
|
{ { 0x10, 0x33, 0x31, ALL }, 2250, "HMT-2250-6T" }, // 01
|
||||||
|
|
||||||
|
{ { 0xF1, 0x01, 0x14, ALL }, 800, "HERF-800" }, // 00
|
||||||
|
{ { 0xF1, 0x01, 0x24, ALL }, 1600, "HERF-1600" }, // 00
|
||||||
|
{ { 0xF1, 0x01, 0x22, ALL }, 1800, "HERF-1800" }, // 00
|
||||||
};
|
};
|
||||||
|
|
||||||
DevInfoParser::DevInfoParser()
|
DevInfoParser::DevInfoParser()
|
||||||
@ -200,7 +204,7 @@ bool DevInfoParser::containsValidData() const
|
|||||||
struct tm info;
|
struct tm info;
|
||||||
localtime_r(&t, &info);
|
localtime_r(&t, &info);
|
||||||
|
|
||||||
return info.tm_year > (2016 - 1900);
|
return info.tm_year > (2016 - 1900) && getHwPartNumber() != 124097;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t DevInfoParser::getDevIdx() const
|
uint8_t DevInfoParser::getDevIdx() const
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Thomas Basler and others
|
* Copyright (C) 2023 - 2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "GridProfileParser.h"
|
#include "GridProfileParser.h"
|
||||||
#include "../Hoymiles.h"
|
#include "../Hoymiles.h"
|
||||||
@ -446,6 +446,11 @@ std::list<GridProfileSection_t> GridProfileParser::getProfile() const
|
|||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GridProfileParser::containsValidData() const
|
||||||
|
{
|
||||||
|
return _gridProfileLength > 6;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t GridProfileParser::getSectionSize(const uint8_t section_id, const uint8_t section_version)
|
uint8_t GridProfileParser::getSectionSize(const uint8_t section_id, const uint8_t section_version)
|
||||||
{
|
{
|
||||||
uint8_t count = 0;
|
uint8_t count = 0;
|
||||||
|
|||||||
@ -43,6 +43,8 @@ public:
|
|||||||
|
|
||||||
std::list<GridProfileSection_t> getProfile() const;
|
std::list<GridProfileSection_t> getProfile() const;
|
||||||
|
|
||||||
|
bool containsValidData() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static uint8_t getSectionSize(const uint8_t section_id, const uint8_t section_version);
|
static uint8_t getSectionSize(const uint8_t section_id, const uint8_t section_version);
|
||||||
static int16_t getSectionStart(const uint8_t section_id, const uint8_t section_version);
|
static int16_t getSectionStart(const uint8_t section_id, const uint8_t section_version);
|
||||||
@ -52,4 +54,4 @@ private:
|
|||||||
|
|
||||||
static const std::array<const ProfileType_t, PROFILE_TYPE_COUNT> _profileTypes;
|
static const std::array<const ProfileType_t, PROFILE_TYPE_COUNT> _profileTypes;
|
||||||
static const std::array<const GridProfileValue_t, SECTION_VALUE_COUNT> _profileValues;
|
static const std::array<const GridProfileValue_t, SECTION_VALUE_COUNT> _profileValues;
|
||||||
};
|
};
|
||||||
|
|||||||
0
lib/ThreadSafeQueue/README.md
Normal file
0
lib/ThreadSafeQueue/README.md
Normal file
13
lib/ThreadSafeQueue/library.json
Normal file
13
lib/ThreadSafeQueue/library.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "ThreadSafeQueue",
|
||||||
|
"keywords": "queue, threadsafe",
|
||||||
|
"description": "An Arduino for ESP32 thread safe queue implementation",
|
||||||
|
"authors": {
|
||||||
|
"name": "Thomas Basler"
|
||||||
|
},
|
||||||
|
"version": "0.0.1",
|
||||||
|
"frameworks": "arduino",
|
||||||
|
"platforms": [
|
||||||
|
"espressif32"
|
||||||
|
]
|
||||||
|
}
|
||||||
0
lib/TimeoutHelper/README.md
Normal file
0
lib/TimeoutHelper/README.md
Normal file
13
lib/TimeoutHelper/library.json
Normal file
13
lib/TimeoutHelper/library.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "TimeoutHelper",
|
||||||
|
"keywords": "timeout",
|
||||||
|
"description": "An Arduino for ESP32 timeout helper",
|
||||||
|
"authors": {
|
||||||
|
"name": "Thomas Basler"
|
||||||
|
},
|
||||||
|
"version": "0.0.1",
|
||||||
|
"frameworks": "arduino",
|
||||||
|
"platforms": [
|
||||||
|
"espressif32"
|
||||||
|
]
|
||||||
|
}
|
||||||
26
patches/async_tcp/event_queue_size.patch
Normal file
26
patches/async_tcp/event_queue_size.patch
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
diff --color -ruN a/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.cpp b/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.cpp
|
||||||
|
--- a/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.cpp
|
||||||
|
+++ b/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.cpp
|
||||||
|
@@ -97,7 +97,7 @@
|
||||||
|
|
||||||
|
static inline bool _init_async_event_queue(){
|
||||||
|
if(!_async_queue){
|
||||||
|
- _async_queue = xQueueCreate(32, sizeof(lwip_event_packet_t *));
|
||||||
|
+ _async_queue = xQueueCreate(CONFIG_ASYNC_TCP_EVENT_QUEUE_SIZE, sizeof(lwip_event_packet_t *));
|
||||||
|
if(!_async_queue){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
diff --color -ruN a/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.h b/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.h
|
||||||
|
--- a/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.h
|
||||||
|
+++ b/.pio/libdeps/$$$env$$$/AsyncTCP-esphome/src/AsyncTCP.h
|
||||||
|
@@ -53,6 +53,10 @@
|
||||||
|
#define CONFIG_ASYNC_TCP_STACK_SIZE 8192 * 2
|
||||||
|
#endif
|
||||||
|
|
||||||
|
+#ifndef CONFIG_ASYNC_TCP_EVENT_QUEUE_SIZE
|
||||||
|
+#define CONFIG_ASYNC_TCP_EVENT_QUEUE_SIZE 32
|
||||||
|
+#endif
|
||||||
|
+
|
||||||
|
class AsyncClient;
|
||||||
|
|
||||||
|
#define ASYNC_MAX_ACK_TIME 5000
|
||||||
@ -1,13 +0,0 @@
|
|||||||
diff --git a/.pio/libdeps/$$$env$$$/ESP Async WebServer/src/AsyncWebSocket.cpp b/.pio/libdeps/$$$env$$$/ESP Async WebServer/src/AsyncWebSocket.cpp
|
|
||||||
index 12be5f8..8505f73 100644
|
|
||||||
--- a/.pio/libdeps/$$$env$$$/ESP Async WebServer/src/AsyncWebSocket.cpp
|
|
||||||
+++ b/.pio/libdeps/$$$env$$$/ESP Async WebServer/src/AsyncWebSocket.cpp
|
|
||||||
@@ -737,7 +737,7 @@ void AsyncWebSocketClient::binary(const __FlashStringHelper *data, size_t len)
|
|
||||||
IPAddress AsyncWebSocketClient::remoteIP() const
|
|
||||||
{
|
|
||||||
if (!_client)
|
|
||||||
- return IPAddress(0U);
|
|
||||||
+ return IPAddress((uint32_t)0);
|
|
||||||
|
|
||||||
return _client->remoteIP();
|
|
||||||
}
|
|
||||||
@ -2,6 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Copyright (C) 2022 Thomas Basler and others
|
# Copyright (C) 2022 Thomas Basler and others
|
||||||
#
|
#
|
||||||
|
import os
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
Import("env")
|
Import("env")
|
||||||
@ -15,15 +16,64 @@ if missing_pkgs:
|
|||||||
|
|
||||||
from dulwich import porcelain
|
from dulwich import porcelain
|
||||||
|
|
||||||
def get_firmware_specifier_build_flag():
|
|
||||||
|
def updateFileIfChanged(filename, content):
|
||||||
|
mustUpdate = True
|
||||||
|
try:
|
||||||
|
fp = open(filename, "rb")
|
||||||
|
if fp.read() == content:
|
||||||
|
mustUpdate = False
|
||||||
|
fp.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if mustUpdate:
|
||||||
|
fp = open(filename, "wb")
|
||||||
|
fp.write(content)
|
||||||
|
fp.close()
|
||||||
|
return mustUpdate
|
||||||
|
|
||||||
|
|
||||||
|
def get_build_version():
|
||||||
try:
|
try:
|
||||||
build_version = porcelain.describe('.') # '.' refers to the repository root dir
|
build_version = porcelain.describe('.') # '.' refers to the repository root dir
|
||||||
except:
|
except:
|
||||||
build_version = "g0000000"
|
build_version = "g0000000"
|
||||||
build_flag = "-D AUTO_GIT_HASH=\\\"" + build_version + "\\\""
|
|
||||||
print ("Firmware Revision: " + build_version)
|
print ("Firmware Revision: " + build_version)
|
||||||
|
return build_version
|
||||||
|
|
||||||
|
|
||||||
|
def get_firmware_specifier_build_flag():
|
||||||
|
build_version = get_build_version()
|
||||||
|
build_flag = "-D AUTO_GIT_HASH=\\\"" + build_version + "\\\""
|
||||||
return (build_flag)
|
return (build_flag)
|
||||||
|
|
||||||
env.Append(
|
|
||||||
BUILD_FLAGS=[get_firmware_specifier_build_flag()]
|
def do_main():
|
||||||
)
|
if 0:
|
||||||
|
# this results in a full recompilation of the whole project after each commit
|
||||||
|
env.Append(
|
||||||
|
BUILD_FLAGS=[get_firmware_specifier_build_flag()]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# we just create a .c file containing the needed datas
|
||||||
|
targetfile = os.path.join(env.subst("$BUILD_DIR"), "__compiled_constants.c")
|
||||||
|
lines = ""
|
||||||
|
lines += "/* Generated file within build process - Do NOT edit */\n"
|
||||||
|
|
||||||
|
if 0:
|
||||||
|
# Add the current date and time as string in UTC timezone
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
now = datetime.now(tz=timezone.utc)
|
||||||
|
COMPILED_DATE_TIME_UTC_STR = now.strftime("%Y/%m/%d %H:%M:%S")
|
||||||
|
lines += 'const char *__COMPILED_DATE_TIME_UTC_STR__ = "%s";\n' % (COMPILED_DATE_TIME_UTC_STR)
|
||||||
|
|
||||||
|
if 1:
|
||||||
|
# Add the description of the current git revision
|
||||||
|
lines += 'const char *__COMPILED_GIT_HASH__ = "%s";\n' % (get_build_version())
|
||||||
|
|
||||||
|
updateFileIfChanged(targetfile, bytes(lines, "utf-8"))
|
||||||
|
|
||||||
|
# Add the created file to the buildfiles - platformio knows how to handle *.c files
|
||||||
|
env.AppendUnique(PIOBUILDFILES=[targetfile])
|
||||||
|
|
||||||
|
do_main()
|
||||||
|
|||||||
@ -19,12 +19,14 @@ extra_configs =
|
|||||||
custom_ci_action = generic,generic_esp32,generic_esp32s3,generic_esp32s3_usb
|
custom_ci_action = generic,generic_esp32,generic_esp32s3,generic_esp32s3_usb
|
||||||
|
|
||||||
framework = arduino
|
framework = arduino
|
||||||
platform = espressif32@6.5.0
|
platform = espressif32@6.6.0
|
||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
-DPIOENV=\"$PIOENV\"
|
-DPIOENV=\"$PIOENV\"
|
||||||
-D_TASK_STD_FUNCTION=1
|
-D_TASK_STD_FUNCTION=1
|
||||||
-D_TASK_THREAD_SAFE=1
|
-D_TASK_THREAD_SAFE=1
|
||||||
|
-DCONFIG_ASYNC_TCP_EVENT_QUEUE_SIZE=128
|
||||||
|
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=128
|
||||||
-Wall -Wextra -Wunused -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference
|
-Wall -Wextra -Wunused -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference
|
||||||
; Have to remove -Werror because of
|
; Have to remove -Werror because of
|
||||||
; https://github.com/espressif/arduino-esp32/issues/9044 and
|
; https://github.com/espressif/arduino-esp32/issues/9044 and
|
||||||
@ -36,12 +38,12 @@ build_unflags =
|
|||||||
-std=gnu++11
|
-std=gnu++11
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
mathieucarbou/ESP Async WebServer @ 2.7.0
|
mathieucarbou/ESP Async WebServer @ 2.9.5
|
||||||
bblanchon/ArduinoJson @ ^6.21.5
|
bblanchon/ArduinoJson @ 7.0.4
|
||||||
https://github.com/bertmelis/espMqttClient.git#v1.6.0
|
https://github.com/bertmelis/espMqttClient.git#v1.6.0
|
||||||
nrf24/RF24 @ ^1.4.8
|
nrf24/RF24 @ 1.4.8
|
||||||
olikraus/U8g2 @ ^2.35.9
|
olikraus/U8g2 @ 2.35.19
|
||||||
buelowp/sunset @ ^1.1.7
|
buelowp/sunset @ 1.1.7
|
||||||
https://github.com/arkhipenko/TaskScheduler#testing
|
https://github.com/arkhipenko/TaskScheduler#testing
|
||||||
|
|
||||||
extra_scripts =
|
extra_scripts =
|
||||||
@ -59,7 +61,7 @@ board_build.embed_files =
|
|||||||
webapp_dist/js/app.js.gz
|
webapp_dist/js/app.js.gz
|
||||||
webapp_dist/site.webmanifest
|
webapp_dist/site.webmanifest
|
||||||
|
|
||||||
custom_patches =
|
custom_patches = async_tcp
|
||||||
|
|
||||||
monitor_filters = esp32_exception_decoder, time, log2file, colorize
|
monitor_filters = esp32_exception_decoder, time, log2file, colorize
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
@ -87,13 +89,13 @@ build_flags = ${env.build_flags}
|
|||||||
|
|
||||||
[env:generic_esp32c3]
|
[env:generic_esp32c3]
|
||||||
board = esp32-c3-devkitc-02
|
board = esp32-c3-devkitc-02
|
||||||
custom_patches = ${env.custom_patches},esp32c3
|
custom_patches = ${env.custom_patches}
|
||||||
build_flags = ${env.build_flags}
|
build_flags = ${env.build_flags}
|
||||||
|
|
||||||
|
|
||||||
[env:generic_esp32c3_usb]
|
[env:generic_esp32c3_usb]
|
||||||
board = esp32-c3-devkitc-02
|
board = esp32-c3-devkitc-02
|
||||||
custom_patches = ${env.custom_patches},esp32c3
|
custom_patches = ${env.custom_patches}
|
||||||
build_flags = ${env.build_flags}
|
build_flags = ${env.build_flags}
|
||||||
-DARDUINO_USB_MODE=1
|
-DARDUINO_USB_MODE=1
|
||||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
|||||||
@ -25,17 +25,13 @@ bool ConfigurationClass::write()
|
|||||||
}
|
}
|
||||||
config.Cfg.SaveCount++;
|
config.Cfg.SaveCount++;
|
||||||
|
|
||||||
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
|
JsonDocument doc;
|
||||||
|
|
||||||
if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
|
JsonObject cfg = doc["cfg"].to<JsonObject>();
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonObject cfg = doc.createNestedObject("cfg");
|
|
||||||
cfg["version"] = config.Cfg.Version;
|
cfg["version"] = config.Cfg.Version;
|
||||||
cfg["save_count"] = config.Cfg.SaveCount;
|
cfg["save_count"] = config.Cfg.SaveCount;
|
||||||
|
|
||||||
JsonObject wifi = doc.createNestedObject("wifi");
|
JsonObject wifi = doc["wifi"].to<JsonObject>();
|
||||||
wifi["ssid"] = config.WiFi.Ssid;
|
wifi["ssid"] = config.WiFi.Ssid;
|
||||||
wifi["password"] = config.WiFi.Password;
|
wifi["password"] = config.WiFi.Password;
|
||||||
wifi["ip"] = IPAddress(config.WiFi.Ip).toString();
|
wifi["ip"] = IPAddress(config.WiFi.Ip).toString();
|
||||||
@ -47,10 +43,10 @@ bool ConfigurationClass::write()
|
|||||||
wifi["hostname"] = config.WiFi.Hostname;
|
wifi["hostname"] = config.WiFi.Hostname;
|
||||||
wifi["aptimeout"] = config.WiFi.ApTimeout;
|
wifi["aptimeout"] = config.WiFi.ApTimeout;
|
||||||
|
|
||||||
JsonObject mdns = doc.createNestedObject("mdns");
|
JsonObject mdns = doc["mdns"].to<JsonObject>();
|
||||||
mdns["enabled"] = config.Mdns.Enabled;
|
mdns["enabled"] = config.Mdns.Enabled;
|
||||||
|
|
||||||
JsonObject ntp = doc.createNestedObject("ntp");
|
JsonObject ntp = doc["ntp"].to<JsonObject>();
|
||||||
ntp["server"] = config.Ntp.Server;
|
ntp["server"] = config.Ntp.Server;
|
||||||
ntp["timezone"] = config.Ntp.Timezone;
|
ntp["timezone"] = config.Ntp.Timezone;
|
||||||
ntp["timezone_descr"] = config.Ntp.TimezoneDescr;
|
ntp["timezone_descr"] = config.Ntp.TimezoneDescr;
|
||||||
@ -58,7 +54,7 @@ bool ConfigurationClass::write()
|
|||||||
ntp["longitude"] = config.Ntp.Longitude;
|
ntp["longitude"] = config.Ntp.Longitude;
|
||||||
ntp["sunsettype"] = config.Ntp.SunsetType;
|
ntp["sunsettype"] = config.Ntp.SunsetType;
|
||||||
|
|
||||||
JsonObject mqtt = doc.createNestedObject("mqtt");
|
JsonObject mqtt = doc["mqtt"].to<JsonObject>();
|
||||||
mqtt["enabled"] = config.Mqtt.Enabled;
|
mqtt["enabled"] = config.Mqtt.Enabled;
|
||||||
mqtt["hostname"] = config.Mqtt.Hostname;
|
mqtt["hostname"] = config.Mqtt.Hostname;
|
||||||
mqtt["port"] = config.Mqtt.Port;
|
mqtt["port"] = config.Mqtt.Port;
|
||||||
@ -69,27 +65,27 @@ bool ConfigurationClass::write()
|
|||||||
mqtt["publish_interval"] = config.Mqtt.PublishInterval;
|
mqtt["publish_interval"] = config.Mqtt.PublishInterval;
|
||||||
mqtt["clean_session"] = config.Mqtt.CleanSession;
|
mqtt["clean_session"] = config.Mqtt.CleanSession;
|
||||||
|
|
||||||
JsonObject mqtt_lwt = mqtt.createNestedObject("lwt");
|
JsonObject mqtt_lwt = mqtt["lwt"].to<JsonObject>();
|
||||||
mqtt_lwt["topic"] = config.Mqtt.Lwt.Topic;
|
mqtt_lwt["topic"] = config.Mqtt.Lwt.Topic;
|
||||||
mqtt_lwt["value_online"] = config.Mqtt.Lwt.Value_Online;
|
mqtt_lwt["value_online"] = config.Mqtt.Lwt.Value_Online;
|
||||||
mqtt_lwt["value_offline"] = config.Mqtt.Lwt.Value_Offline;
|
mqtt_lwt["value_offline"] = config.Mqtt.Lwt.Value_Offline;
|
||||||
mqtt_lwt["qos"] = config.Mqtt.Lwt.Qos;
|
mqtt_lwt["qos"] = config.Mqtt.Lwt.Qos;
|
||||||
|
|
||||||
JsonObject mqtt_tls = mqtt.createNestedObject("tls");
|
JsonObject mqtt_tls = mqtt["tls"].to<JsonObject>();
|
||||||
mqtt_tls["enabled"] = config.Mqtt.Tls.Enabled;
|
mqtt_tls["enabled"] = config.Mqtt.Tls.Enabled;
|
||||||
mqtt_tls["root_ca_cert"] = config.Mqtt.Tls.RootCaCert;
|
mqtt_tls["root_ca_cert"] = config.Mqtt.Tls.RootCaCert;
|
||||||
mqtt_tls["certlogin"] = config.Mqtt.Tls.CertLogin;
|
mqtt_tls["certlogin"] = config.Mqtt.Tls.CertLogin;
|
||||||
mqtt_tls["client_cert"] = config.Mqtt.Tls.ClientCert;
|
mqtt_tls["client_cert"] = config.Mqtt.Tls.ClientCert;
|
||||||
mqtt_tls["client_key"] = config.Mqtt.Tls.ClientKey;
|
mqtt_tls["client_key"] = config.Mqtt.Tls.ClientKey;
|
||||||
|
|
||||||
JsonObject mqtt_hass = mqtt.createNestedObject("hass");
|
JsonObject mqtt_hass = mqtt["hass"].to<JsonObject>();
|
||||||
mqtt_hass["enabled"] = config.Mqtt.Hass.Enabled;
|
mqtt_hass["enabled"] = config.Mqtt.Hass.Enabled;
|
||||||
mqtt_hass["retain"] = config.Mqtt.Hass.Retain;
|
mqtt_hass["retain"] = config.Mqtt.Hass.Retain;
|
||||||
mqtt_hass["topic"] = config.Mqtt.Hass.Topic;
|
mqtt_hass["topic"] = config.Mqtt.Hass.Topic;
|
||||||
mqtt_hass["individual_panels"] = config.Mqtt.Hass.IndividualPanels;
|
mqtt_hass["individual_panels"] = config.Mqtt.Hass.IndividualPanels;
|
||||||
mqtt_hass["expire"] = config.Mqtt.Hass.Expire;
|
mqtt_hass["expire"] = config.Mqtt.Hass.Expire;
|
||||||
|
|
||||||
JsonObject dtu = doc.createNestedObject("dtu");
|
JsonObject dtu = doc["dtu"].to<JsonObject>();
|
||||||
dtu["serial"] = config.Dtu.Serial;
|
dtu["serial"] = config.Dtu.Serial;
|
||||||
dtu["poll_interval"] = config.Dtu.PollInterval;
|
dtu["poll_interval"] = config.Dtu.PollInterval;
|
||||||
dtu["nrf_pa_level"] = config.Dtu.Nrf.PaLevel;
|
dtu["nrf_pa_level"] = config.Dtu.Nrf.PaLevel;
|
||||||
@ -97,14 +93,14 @@ bool ConfigurationClass::write()
|
|||||||
dtu["cmt_frequency"] = config.Dtu.Cmt.Frequency;
|
dtu["cmt_frequency"] = config.Dtu.Cmt.Frequency;
|
||||||
dtu["cmt_country_mode"] = config.Dtu.Cmt.CountryMode;
|
dtu["cmt_country_mode"] = config.Dtu.Cmt.CountryMode;
|
||||||
|
|
||||||
JsonObject security = doc.createNestedObject("security");
|
JsonObject security = doc["security"].to<JsonObject>();
|
||||||
security["password"] = config.Security.Password;
|
security["password"] = config.Security.Password;
|
||||||
security["allow_readonly"] = config.Security.AllowReadonly;
|
security["allow_readonly"] = config.Security.AllowReadonly;
|
||||||
|
|
||||||
JsonObject device = doc.createNestedObject("device");
|
JsonObject device = doc["device"].to<JsonObject>();
|
||||||
device["pinmapping"] = config.Dev_PinMapping;
|
device["pinmapping"] = config.Dev_PinMapping;
|
||||||
|
|
||||||
JsonObject display = device.createNestedObject("display");
|
JsonObject display = device["display"].to<JsonObject>();
|
||||||
display["powersafe"] = config.Display.PowerSafe;
|
display["powersafe"] = config.Display.PowerSafe;
|
||||||
display["screensaver"] = config.Display.ScreenSaver;
|
display["screensaver"] = config.Display.ScreenSaver;
|
||||||
display["rotation"] = config.Display.Rotation;
|
display["rotation"] = config.Display.Rotation;
|
||||||
@ -113,15 +109,15 @@ bool ConfigurationClass::write()
|
|||||||
display["diagram_duration"] = config.Display.Diagram.Duration;
|
display["diagram_duration"] = config.Display.Diagram.Duration;
|
||||||
display["diagram_mode"] = config.Display.Diagram.Mode;
|
display["diagram_mode"] = config.Display.Diagram.Mode;
|
||||||
|
|
||||||
JsonArray leds = device.createNestedArray("led");
|
JsonArray leds = device["led"].to<JsonArray>();
|
||||||
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
||||||
JsonObject led = leds.createNestedObject();
|
JsonObject led = leds.add<JsonObject>();
|
||||||
led["brightness"] = config.Led_Single[i].Brightness;
|
led["brightness"] = config.Led_Single[i].Brightness;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonArray inverters = doc.createNestedArray("inverters");
|
JsonArray inverters = doc["inverters"].to<JsonArray>();
|
||||||
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
||||||
JsonObject inv = inverters.createNestedObject();
|
JsonObject inv = inverters.add<JsonObject>();
|
||||||
inv["serial"] = config.Inverter[i].Serial;
|
inv["serial"] = config.Inverter[i].Serial;
|
||||||
inv["name"] = config.Inverter[i].Name;
|
inv["name"] = config.Inverter[i].Name;
|
||||||
inv["order"] = config.Inverter[i].Order;
|
inv["order"] = config.Inverter[i].Order;
|
||||||
@ -134,15 +130,19 @@ bool ConfigurationClass::write()
|
|||||||
inv["zero_day"] = config.Inverter[i].ZeroYieldDayOnMidnight;
|
inv["zero_day"] = config.Inverter[i].ZeroYieldDayOnMidnight;
|
||||||
inv["yieldday_correction"] = config.Inverter[i].YieldDayCorrection;
|
inv["yieldday_correction"] = config.Inverter[i].YieldDayCorrection;
|
||||||
|
|
||||||
JsonArray channel = inv.createNestedArray("channel");
|
JsonArray channel = inv["channel"].to<JsonArray>();
|
||||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||||
JsonObject chanData = channel.createNestedObject();
|
JsonObject chanData = channel.add<JsonObject>();
|
||||||
chanData["name"] = config.Inverter[i].channel[c].Name;
|
chanData["name"] = config.Inverter[i].channel[c].Name;
|
||||||
chanData["max_power"] = config.Inverter[i].channel[c].MaxChannelPower;
|
chanData["max_power"] = config.Inverter[i].channel[c].MaxChannelPower;
|
||||||
chanData["yield_total_offset"] = config.Inverter[i].channel[c].YieldTotalOffset;
|
chanData["yield_total_offset"] = config.Inverter[i].channel[c].YieldTotalOffset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Serialize JSON to file
|
// Serialize JSON to file
|
||||||
if (serializeJson(doc, f) == 0) {
|
if (serializeJson(doc, f) == 0) {
|
||||||
MessageOutput.println("Failed to write file");
|
MessageOutput.println("Failed to write file");
|
||||||
@ -157,11 +157,7 @@ bool ConfigurationClass::read()
|
|||||||
{
|
{
|
||||||
File f = LittleFS.open(CONFIG_FILENAME, "r", false);
|
File f = LittleFS.open(CONFIG_FILENAME, "r", false);
|
||||||
|
|
||||||
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
|
JsonDocument doc;
|
||||||
|
|
||||||
if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deserialize the JSON document
|
// Deserialize the JSON document
|
||||||
const DeserializationError error = deserializeJson(doc, f);
|
const DeserializationError error = deserializeJson(doc, f);
|
||||||
@ -169,6 +165,10 @@ bool ConfigurationClass::read()
|
|||||||
MessageOutput.println("Failed to read file, using default configuration");
|
MessageOutput.println("Failed to read file, using default configuration");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
JsonObject cfg = doc["cfg"];
|
JsonObject cfg = doc["cfg"];
|
||||||
config.Cfg.Version = cfg["version"] | CONFIG_VERSION;
|
config.Cfg.Version = cfg["version"] | CONFIG_VERSION;
|
||||||
config.Cfg.SaveCount = cfg["save_count"] | 0;
|
config.Cfg.SaveCount = cfg["save_count"] | 0;
|
||||||
@ -324,11 +324,7 @@ void ConfigurationClass::migrate()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
|
JsonDocument doc;
|
||||||
|
|
||||||
if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deserialize the JSON document
|
// Deserialize the JSON document
|
||||||
const DeserializationError error = deserializeJson(doc, f);
|
const DeserializationError error = deserializeJson(doc, f);
|
||||||
@ -337,6 +333,10 @@ void ConfigurationClass::migrate()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (config.Cfg.Version < 0x00011700) {
|
if (config.Cfg.Version < 0x00011700) {
|
||||||
JsonArray inverters = doc["inverters"];
|
JsonArray inverters = doc["inverters"];
|
||||||
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
||||||
@ -372,6 +372,12 @@ void ConfigurationClass::migrate()
|
|||||||
config.Dtu.Cmt.Frequency *= 1000;
|
config.Dtu.Cmt.Frequency *= 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.Cfg.Version < 0x00011c00) {
|
||||||
|
if (!strcmp(config.Ntp.Server, NTP_SERVER_OLD)) {
|
||||||
|
strlcpy(config.Ntp.Server, NTP_SERVER, sizeof(config.Ntp.Server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
f.close();
|
f.close();
|
||||||
|
|
||||||
config.Cfg.Version = CONFIG_VERSION;
|
config.Cfg.Version = CONFIG_VERSION;
|
||||||
@ -406,4 +412,26 @@ INVERTER_CONFIG_T* ConfigurationClass::getInverterConfig(const uint64_t serial)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConfigurationClass::deleteInverterById(const uint8_t id)
|
||||||
|
{
|
||||||
|
config.Inverter[id].Serial = 0ULL;
|
||||||
|
strlcpy(config.Inverter[id].Name, "", sizeof(config.Inverter[id].Name));
|
||||||
|
config.Inverter[id].Order = 0;
|
||||||
|
|
||||||
|
config.Inverter[id].Poll_Enable = true;
|
||||||
|
config.Inverter[id].Poll_Enable_Night = true;
|
||||||
|
config.Inverter[id].Command_Enable = true;
|
||||||
|
config.Inverter[id].Command_Enable_Night = true;
|
||||||
|
config.Inverter[id].ReachableThreshold = REACHABLE_THRESHOLD;
|
||||||
|
config.Inverter[id].ZeroRuntimeDataIfUnrechable = false;
|
||||||
|
config.Inverter[id].ZeroYieldDayOnMidnight = false;
|
||||||
|
config.Inverter[id].YieldDayCorrection = false;
|
||||||
|
|
||||||
|
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||||
|
config.Inverter[id].channel[c].MaxChannelPower = 0;
|
||||||
|
config.Inverter[id].channel[c].YieldTotalOffset = 0.0f;
|
||||||
|
strlcpy(config.Inverter[id].channel[c].Name, "", sizeof(config.Inverter[id].channel[c].Name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ConfigurationClass Configuration;
|
ConfigurationClass Configuration;
|
||||||
|
|||||||
@ -29,11 +29,16 @@ const uint8_t languages[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const char* const i18n_offline[] = { "Offline", "Offline", "Offline" };
|
static const char* const i18n_offline[] = { "Offline", "Offline", "Offline" };
|
||||||
|
|
||||||
static const char* const i18n_current_power_w[] = { "%.0f W", "%.0f W", "%.0f W" };
|
static const char* const i18n_current_power_w[] = { "%.0f W", "%.0f W", "%.0f W" };
|
||||||
static const char* const i18n_current_power_kw[] = { "%.1f kW", "%.1f kW", "%.1f kW" };
|
static const char* const i18n_current_power_kw[] = { "%.1f kW", "%.1f kW", "%.1f kW" };
|
||||||
|
|
||||||
static const char* const i18n_yield_today_wh[] = { "today: %4.0f Wh", "Heute: %4.0f Wh", "auj.: %4.0f Wh" };
|
static const char* const i18n_yield_today_wh[] = { "today: %4.0f Wh", "Heute: %4.0f Wh", "auj.: %4.0f Wh" };
|
||||||
|
static const char* const i18n_yield_today_kwh[] = { "today: %.1f kWh", "Heute: %.1f kWh", "auj.: %.1f kWh" };
|
||||||
|
|
||||||
static const char* const i18n_yield_total_kwh[] = { "total: %.1f kWh", "Ges.: %.1f kWh", "total: %.1f kWh" };
|
static const char* const i18n_yield_total_kwh[] = { "total: %.1f kWh", "Ges.: %.1f kWh", "total: %.1f kWh" };
|
||||||
static const char* const i18n_yield_total_mwh[] = { "total: %.0f kWh", "Ges.: %.0f kWh", "total: %.0f kWh" };
|
static const char* const i18n_yield_total_mwh[] = { "total: %.0f kWh", "Ges.: %.0f kWh", "total: %.0f kWh" };
|
||||||
|
|
||||||
static const char* const i18n_date_format[] = { "%m/%d/%Y %H:%M", "%d.%m.%Y %H:%M", "%d/%m/%Y %H:%M" };
|
static const char* const i18n_date_format[] = { "%m/%d/%Y %H:%M", "%d.%m.%Y %H:%M", "%d/%m/%Y %H:%M" };
|
||||||
|
|
||||||
DisplayGraphicClass::DisplayGraphicClass()
|
DisplayGraphicClass::DisplayGraphicClass()
|
||||||
@ -129,6 +134,10 @@ void DisplayGraphicClass::printText(const char* text, const uint8_t line)
|
|||||||
offset -= (_isLarge ? 5 : 0); // oscillate around center on large screens
|
offset -= (_isLarge ? 5 : 0); // oscillate around center on large screens
|
||||||
dispX += offset;
|
dispX += offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dispX > _display->getDisplayWidth()) {
|
||||||
|
dispX = 0;
|
||||||
|
}
|
||||||
_display->drawStr(dispX, _lineOffsets[line], text);
|
_display->drawStr(dispX, _lineOffsets[line], text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,15 +246,20 @@ void DisplayGraphicClass::loop()
|
|||||||
//<=======================
|
//<=======================
|
||||||
|
|
||||||
if (showText) {
|
if (showText) {
|
||||||
//=====> Today & Total Production =======
|
// Daily production
|
||||||
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_wh[_display_language], Datastore.getTotalAcYieldDayEnabled());
|
float wattsToday = Datastore.getTotalAcYieldDayEnabled();
|
||||||
|
if (wattsToday >= 10000) {
|
||||||
|
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_kwh[_display_language], wattsToday / 1000);
|
||||||
|
} else {
|
||||||
|
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_wh[_display_language], wattsToday);
|
||||||
|
}
|
||||||
printText(_fmtText, 1);
|
printText(_fmtText, 1);
|
||||||
|
|
||||||
const float watts = Datastore.getTotalAcYieldTotalEnabled();
|
// Total production
|
||||||
auto const format = (watts >= 1000) ? i18n_yield_total_mwh : i18n_yield_total_kwh;
|
const float wattsTotal = Datastore.getTotalAcYieldTotalEnabled();
|
||||||
snprintf(_fmtText, sizeof(_fmtText), format[_display_language], watts);
|
auto const format = (wattsTotal >= 1000) ? i18n_yield_total_mwh : i18n_yield_total_kwh;
|
||||||
|
snprintf(_fmtText, sizeof(_fmtText), format[_display_language], wattsTotal);
|
||||||
printText(_fmtText, 2);
|
printText(_fmtText, 2);
|
||||||
//<=======================
|
|
||||||
|
|
||||||
//=====> IP or Date-Time ========
|
//=====> IP or Date-Time ========
|
||||||
// Change every 3 seconds
|
// Change every 3 seconds
|
||||||
|
|||||||
@ -51,9 +51,9 @@ void InverterSettingsClass::init(Scheduler& scheduler)
|
|||||||
|
|
||||||
if (PinMapping.isValidCmt2300Config()) {
|
if (PinMapping.isValidCmt2300Config()) {
|
||||||
Hoymiles.initCMT(pin.cmt_sdio, pin.cmt_clk, pin.cmt_cs, pin.cmt_fcs, pin.cmt_gpio2, pin.cmt_gpio3);
|
Hoymiles.initCMT(pin.cmt_sdio, pin.cmt_clk, pin.cmt_cs, pin.cmt_fcs, pin.cmt_gpio2, pin.cmt_gpio3);
|
||||||
MessageOutput.println(F(" Setting country mode... "));
|
MessageOutput.println(" Setting country mode... ");
|
||||||
Hoymiles.getRadioCmt()->setCountryMode(static_cast<CountryModeId_t>(config.Dtu.Cmt.CountryMode));
|
Hoymiles.getRadioCmt()->setCountryMode(static_cast<CountryModeId_t>(config.Dtu.Cmt.CountryMode));
|
||||||
MessageOutput.println(F(" Setting CMT target frequency... "));
|
MessageOutput.println(" Setting CMT target frequency... ");
|
||||||
Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency);
|
Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
#include "NetworkSettings.h"
|
#include "NetworkSettings.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "defaults.h"
|
#include "defaults.h"
|
||||||
|
#include "__compiled_constants.h"
|
||||||
|
|
||||||
MqttHandleHassClass MqttHandleHass;
|
MqttHandleHassClass MqttHandleHass;
|
||||||
|
|
||||||
@ -137,10 +138,7 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr<InverterAbstract>
|
|||||||
name = "CH" + chanNum + " " + fieldName;
|
name = "CH" + chanNum + " " + fieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
JsonDocument root;
|
||||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
root["name"] = name;
|
root["name"] = name;
|
||||||
root["stat_t"] = stateTopic;
|
root["stat_t"] = stateTopic;
|
||||||
@ -163,6 +161,10 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr<InverterAbstract>
|
|||||||
root["stat_cla"] = stateCls;
|
root["stat_cla"] = stateCls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String buffer;
|
String buffer;
|
||||||
serializeJson(root, buffer);
|
serializeJson(root, buffer);
|
||||||
publish(configTopic, buffer);
|
publish(configTopic, buffer);
|
||||||
@ -185,10 +187,7 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptr<InverterAbstract
|
|||||||
|
|
||||||
const String cmdTopic = MqttSettings.getPrefix() + serial + "/" + subTopic;
|
const String cmdTopic = MqttSettings.getPrefix() + serial + "/" + subTopic;
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
JsonDocument root;
|
||||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
root["name"] = caption;
|
root["name"] = caption;
|
||||||
root["uniq_id"] = serial + "_" + buttonId;
|
root["uniq_id"] = serial + "_" + buttonId;
|
||||||
@ -204,6 +203,10 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptr<InverterAbstract
|
|||||||
|
|
||||||
createInverterInfo(root, inv);
|
createInverterInfo(root, inv);
|
||||||
|
|
||||||
|
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String buffer;
|
String buffer;
|
||||||
serializeJson(root, buffer);
|
serializeJson(root, buffer);
|
||||||
publish(configTopic, buffer);
|
publish(configTopic, buffer);
|
||||||
@ -227,10 +230,7 @@ void MqttHandleHassClass::publishInverterNumber(
|
|||||||
const String cmdTopic = MqttSettings.getPrefix() + serial + "/" + commandTopic;
|
const String cmdTopic = MqttSettings.getPrefix() + serial + "/" + commandTopic;
|
||||||
const String statTopic = MqttSettings.getPrefix() + serial + "/" + stateTopic;
|
const String statTopic = MqttSettings.getPrefix() + serial + "/" + stateTopic;
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
JsonDocument root;
|
||||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
root["name"] = caption;
|
root["name"] = caption;
|
||||||
root["uniq_id"] = serial + "_" + buttonId;
|
root["uniq_id"] = serial + "_" + buttonId;
|
||||||
@ -246,6 +246,10 @@ void MqttHandleHassClass::publishInverterNumber(
|
|||||||
|
|
||||||
createInverterInfo(root, inv);
|
createInverterInfo(root, inv);
|
||||||
|
|
||||||
|
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String buffer;
|
String buffer;
|
||||||
serializeJson(root, buffer);
|
serializeJson(root, buffer);
|
||||||
publish(configTopic, buffer);
|
publish(configTopic, buffer);
|
||||||
@ -265,10 +269,7 @@ void MqttHandleHassClass::publishInverterBinarySensor(std::shared_ptr<InverterAb
|
|||||||
|
|
||||||
const String statTopic = MqttSettings.getPrefix() + serial + "/" + subTopic;
|
const String statTopic = MqttSettings.getPrefix() + serial + "/" + subTopic;
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
JsonDocument root;
|
||||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
root["name"] = caption;
|
root["name"] = caption;
|
||||||
root["uniq_id"] = serial + "_" + sensorId;
|
root["uniq_id"] = serial + "_" + sensorId;
|
||||||
@ -278,6 +279,10 @@ void MqttHandleHassClass::publishInverterBinarySensor(std::shared_ptr<InverterAb
|
|||||||
|
|
||||||
createInverterInfo(root, inv);
|
createInverterInfo(root, inv);
|
||||||
|
|
||||||
|
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String buffer;
|
String buffer;
|
||||||
serializeJson(root, buffer);
|
serializeJson(root, buffer);
|
||||||
publish(configTopic, buffer);
|
publish(configTopic, buffer);
|
||||||
@ -293,10 +298,7 @@ void MqttHandleHassClass::publishDtuSensor(const char* name, const char* device_
|
|||||||
topic = id;
|
topic = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
JsonDocument root;
|
||||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
root["name"] = name;
|
root["name"] = name;
|
||||||
root["uniq_id"] = getDtuUniqueId() + "_" + id;
|
root["uniq_id"] = getDtuUniqueId() + "_" + id;
|
||||||
@ -322,6 +324,10 @@ void MqttHandleHassClass::publishDtuSensor(const char* name, const char* device_
|
|||||||
|
|
||||||
createDtuInfo(root);
|
createDtuInfo(root);
|
||||||
|
|
||||||
|
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String buffer;
|
String buffer;
|
||||||
const String configTopic = "sensor/" + getDtuUniqueId() + "/" + id + "/config";
|
const String configTopic = "sensor/" + getDtuUniqueId() + "/" + id + "/config";
|
||||||
serializeJson(root, buffer);
|
serializeJson(root, buffer);
|
||||||
@ -339,10 +345,7 @@ void MqttHandleHassClass::publishDtuBinarySensor(const char* name, const char* d
|
|||||||
topic = String("dtu/") + "/" + id;
|
topic = String("dtu/") + "/" + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
JsonDocument root;
|
||||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
root["name"] = name;
|
root["name"] = name;
|
||||||
root["uniq_id"] = getDtuUniqueId() + "_" + id;
|
root["uniq_id"] = getDtuUniqueId() + "_" + id;
|
||||||
@ -359,13 +362,17 @@ void MqttHandleHassClass::publishDtuBinarySensor(const char* name, const char* d
|
|||||||
|
|
||||||
createDtuInfo(root);
|
createDtuInfo(root);
|
||||||
|
|
||||||
|
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String buffer;
|
String buffer;
|
||||||
const String configTopic = "binary_sensor/" + getDtuUniqueId() + "/" + id + "/config";
|
const String configTopic = "binary_sensor/" + getDtuUniqueId() + "/" + id + "/config";
|
||||||
serializeJson(root, buffer);
|
serializeJson(root, buffer);
|
||||||
publish(configTopic, buffer);
|
publish(configTopic, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttHandleHassClass::createInverterInfo(DynamicJsonDocument& root, std::shared_ptr<InverterAbstract> inv)
|
void MqttHandleHassClass::createInverterInfo(JsonDocument& root, std::shared_ptr<InverterAbstract> inv)
|
||||||
{
|
{
|
||||||
createDeviceInfo(
|
createDeviceInfo(
|
||||||
root,
|
root,
|
||||||
@ -374,11 +381,11 @@ void MqttHandleHassClass::createInverterInfo(DynamicJsonDocument& root, std::sha
|
|||||||
getDtuUrl(),
|
getDtuUrl(),
|
||||||
"OpenDTU",
|
"OpenDTU",
|
||||||
inv->typeName(),
|
inv->typeName(),
|
||||||
AUTO_GIT_HASH,
|
__COMPILED_GIT_HASH__,
|
||||||
getDtuUniqueId());
|
getDtuUniqueId());
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttHandleHassClass::createDtuInfo(DynamicJsonDocument& root)
|
void MqttHandleHassClass::createDtuInfo(JsonDocument& root)
|
||||||
{
|
{
|
||||||
createDeviceInfo(
|
createDeviceInfo(
|
||||||
root,
|
root,
|
||||||
@ -387,16 +394,16 @@ void MqttHandleHassClass::createDtuInfo(DynamicJsonDocument& root)
|
|||||||
getDtuUrl(),
|
getDtuUrl(),
|
||||||
"OpenDTU",
|
"OpenDTU",
|
||||||
"OpenDTU",
|
"OpenDTU",
|
||||||
AUTO_GIT_HASH);
|
__COMPILED_GIT_HASH__);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttHandleHassClass::createDeviceInfo(
|
void MqttHandleHassClass::createDeviceInfo(
|
||||||
DynamicJsonDocument& root,
|
JsonDocument& root,
|
||||||
const String& name, const String& identifiers, const String& configuration_url,
|
const String& name, const String& identifiers, const String& configuration_url,
|
||||||
const String& manufacturer, const String& model, const String& sw_version,
|
const String& manufacturer, const String& model, const String& sw_version,
|
||||||
const String& via_device)
|
const String& via_device)
|
||||||
{
|
{
|
||||||
auto object = root.createNestedObject("dev");
|
auto object = root["dev"].to<JsonObject>();
|
||||||
|
|
||||||
object["name"] = name;
|
object["name"] = name;
|
||||||
object["ids"] = identifiers;
|
object["ids"] = identifiers;
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
#include "defaults.h"
|
#include "defaults.h"
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
#include <ETH.h>
|
#include <ETH.h>
|
||||||
|
#include "__compiled_constants.h"
|
||||||
|
|
||||||
NetworkSettingsClass::NetworkSettingsClass()
|
NetworkSettingsClass::NetworkSettingsClass()
|
||||||
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&NetworkSettingsClass::loop, this))
|
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&NetworkSettingsClass::loop, this))
|
||||||
@ -136,7 +137,7 @@ void NetworkSettingsClass::handleMDNS()
|
|||||||
|
|
||||||
MDNS.addService("http", "tcp", 80);
|
MDNS.addService("http", "tcp", 80);
|
||||||
MDNS.addService("opendtu", "tcp", 80);
|
MDNS.addService("opendtu", "tcp", 80);
|
||||||
MDNS.addServiceTxt("opendtu", "tcp", "git_hash", AUTO_GIT_HASH);
|
MDNS.addServiceTxt("opendtu", "tcp", "git_hash", __COMPILED_GIT_HASH__);
|
||||||
|
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -8,8 +8,6 @@
|
|||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define JSON_BUFFER_SIZE 6144
|
|
||||||
|
|
||||||
#ifndef DISPLAY_TYPE
|
#ifndef DISPLAY_TYPE
|
||||||
#define DISPLAY_TYPE 0U
|
#define DISPLAY_TYPE 0U
|
||||||
#endif
|
#endif
|
||||||
@ -141,7 +139,7 @@ bool PinMappingClass::init(const String& deviceMapping)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
|
JsonDocument doc;
|
||||||
// Deserialize the JSON document
|
// Deserialize the JSON document
|
||||||
DeserializationError error = deserializeJson(doc, f);
|
DeserializationError error = deserializeJson(doc, f);
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -216,4 +214,4 @@ bool PinMappingClass::isValidCmt2300Config() const
|
|||||||
bool PinMappingClass::isValidEthConfig() const
|
bool PinMappingClass::isValidEthConfig() const
|
||||||
{
|
{
|
||||||
return _pinMapping.eth_enabled;
|
return _pinMapping.eth_enabled;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,9 +69,9 @@ void Utils::restartDtu()
|
|||||||
ESP.restart();
|
ESP.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Utils::checkJsonAlloc(const DynamicJsonDocument& doc, const char* function, const uint16_t line)
|
bool Utils::checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line)
|
||||||
{
|
{
|
||||||
if (doc.capacity() == 0) {
|
if (doc.overflowed()) {
|
||||||
MessageOutput.printf("Alloc failed: %s, %d\r\n", function, line);
|
MessageOutput.printf("Alloc failed: %s, %d\r\n", function, line);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
|
#include "MessageOutput.h"
|
||||||
#include "defaults.h"
|
#include "defaults.h"
|
||||||
#include <AsyncJson.h>
|
#include <AsyncJson.h>
|
||||||
|
|
||||||
@ -85,4 +86,58 @@ void WebApiClass::writeConfig(JsonVariant& retMsg, const WebApiError code, const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WebApiClass::parseRequestData(AsyncWebServerRequest* request, AsyncJsonResponse* response, JsonDocument& json_document)
|
||||||
|
{
|
||||||
|
auto& retMsg = response->getRoot();
|
||||||
|
retMsg["type"] = "warning";
|
||||||
|
|
||||||
|
if (!request->hasParam("data", true)) {
|
||||||
|
retMsg["message"] = "No values found!";
|
||||||
|
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||||
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String json = request->getParam("data", true)->value();
|
||||||
|
const DeserializationError error = deserializeJson(json_document, json);
|
||||||
|
if (error) {
|
||||||
|
retMsg["message"] = "Failed to parse data!";
|
||||||
|
retMsg["code"] = WebApiError::GenericParseError;
|
||||||
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t WebApiClass::parseSerialFromRequest(AsyncWebServerRequest* request, String param_name)
|
||||||
|
{
|
||||||
|
if (request->hasParam(param_name)) {
|
||||||
|
String s = request->getParam(param_name)->value();
|
||||||
|
return strtoll(s.c_str(), NULL, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebApiClass::sendJsonResponse(AsyncWebServerRequest* request, AsyncJsonResponse* response, const char* function, const uint16_t line)
|
||||||
|
{
|
||||||
|
bool ret_val = true;
|
||||||
|
if (response->overflowed()) {
|
||||||
|
auto& root = response->getRoot();
|
||||||
|
|
||||||
|
root.clear();
|
||||||
|
root["message"] = String("500 Internal Server Error: ") + function + ", " + line;
|
||||||
|
root["code"] = WebApiError::GenericInternalServerError;
|
||||||
|
root["type"] = "danger";
|
||||||
|
response->setCode(500);
|
||||||
|
MessageOutput.printf("WebResponse failed: %s, %d\r\n", function, line);
|
||||||
|
ret_val = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
|
||||||
WebApiClass WebApi;
|
WebApiClass WebApi;
|
||||||
|
|||||||
@ -40,6 +40,7 @@ void WebApiConfigClass::onConfigGet(AsyncWebServerRequest* request)
|
|||||||
requestFile = name;
|
requestFile = name;
|
||||||
} else {
|
} else {
|
||||||
request->send(404);
|
request->send(404);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,51 +54,24 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonDocument root;
|
||||||
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
retMsg["type"] = "warning";
|
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
|
||||||
retMsg["message"] = "No values found!";
|
|
||||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String json = request->getParam("data", true)->value();
|
|
||||||
|
|
||||||
if (json.length() > 1024) {
|
|
||||||
retMsg["message"] = "Data too large!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
|
||||||
const DeserializationError error = deserializeJson(root, json);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
retMsg["message"] = "Failed to parse data!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(root.containsKey("delete"))) {
|
if (!(root.containsKey("delete"))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["delete"].as<bool>() == false) {
|
if (root["delete"].as<bool>() == false) {
|
||||||
retMsg["message"] = "Not deleted anything!";
|
retMsg["message"] = "Not deleted anything!";
|
||||||
retMsg["code"] = WebApiError::ConfigNotDeleted;
|
retMsg["code"] = WebApiError::ConfigNotDeleted;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,8 +79,7 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Configuration resettet. Rebooting now...";
|
retMsg["message"] = "Configuration resettet. Rebooting now...";
|
||||||
retMsg["code"] = WebApiError::ConfigSuccess;
|
retMsg["code"] = WebApiError::ConfigSuccess;
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
|
|
||||||
Utils::removeAllFiles();
|
Utils::removeAllFiles();
|
||||||
Utils::restartDtu();
|
Utils::restartDtu();
|
||||||
@ -120,7 +93,7 @@ void WebApiConfigClass::onConfigListGet(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
auto& root = response->getRoot();
|
auto& root = response->getRoot();
|
||||||
auto data = root.createNestedArray("configs");
|
auto data = root["configs"].to<JsonArray>();
|
||||||
|
|
||||||
File rootfs = LittleFS.open("/");
|
File rootfs = LittleFS.open("/");
|
||||||
File file = rootfs.openNextFile();
|
File file = rootfs.openNextFile();
|
||||||
@ -128,15 +101,14 @@ void WebApiConfigClass::onConfigListGet(AsyncWebServerRequest* request)
|
|||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
JsonObject obj = data.createNestedObject();
|
JsonObject obj = data.add<JsonObject>();
|
||||||
obj["name"] = String(file.name());
|
obj["name"] = String(file.name());
|
||||||
|
|
||||||
file = rootfs.openNextFile();
|
file = rootfs.openNextFile();
|
||||||
}
|
}
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiConfigClass::onConfigUploadFinish(AsyncWebServerRequest* request)
|
void WebApiConfigClass::onConfigUploadFinish(AsyncWebServerRequest* request)
|
||||||
|
|||||||
@ -26,15 +26,15 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
auto& root = response->getRoot();
|
auto& root = response->getRoot();
|
||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
const PinMapping_t& pin = PinMapping.get();
|
const PinMapping_t& pin = PinMapping.get();
|
||||||
|
|
||||||
auto curPin = root.createNestedObject("curPin");
|
auto curPin = root["curPin"].to<JsonObject>();
|
||||||
curPin["name"] = config.Dev_PinMapping;
|
curPin["name"] = config.Dev_PinMapping;
|
||||||
|
|
||||||
auto nrfPinObj = curPin.createNestedObject("nrf24");
|
auto nrfPinObj = curPin["nrf24"].to<JsonObject>();
|
||||||
nrfPinObj["clk"] = pin.nrf24_clk;
|
nrfPinObj["clk"] = pin.nrf24_clk;
|
||||||
nrfPinObj["cs"] = pin.nrf24_cs;
|
nrfPinObj["cs"] = pin.nrf24_cs;
|
||||||
nrfPinObj["en"] = pin.nrf24_en;
|
nrfPinObj["en"] = pin.nrf24_en;
|
||||||
@ -42,7 +42,7 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
|
|||||||
nrfPinObj["miso"] = pin.nrf24_miso;
|
nrfPinObj["miso"] = pin.nrf24_miso;
|
||||||
nrfPinObj["mosi"] = pin.nrf24_mosi;
|
nrfPinObj["mosi"] = pin.nrf24_mosi;
|
||||||
|
|
||||||
auto cmtPinObj = curPin.createNestedObject("cmt");
|
auto cmtPinObj = curPin["cmt"].to<JsonObject>();
|
||||||
cmtPinObj["clk"] = pin.cmt_clk;
|
cmtPinObj["clk"] = pin.cmt_clk;
|
||||||
cmtPinObj["cs"] = pin.cmt_cs;
|
cmtPinObj["cs"] = pin.cmt_cs;
|
||||||
cmtPinObj["fcs"] = pin.cmt_fcs;
|
cmtPinObj["fcs"] = pin.cmt_fcs;
|
||||||
@ -50,7 +50,7 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
|
|||||||
cmtPinObj["gpio2"] = pin.cmt_gpio2;
|
cmtPinObj["gpio2"] = pin.cmt_gpio2;
|
||||||
cmtPinObj["gpio3"] = pin.cmt_gpio3;
|
cmtPinObj["gpio3"] = pin.cmt_gpio3;
|
||||||
|
|
||||||
auto ethPinObj = curPin.createNestedObject("eth");
|
auto ethPinObj = curPin["eth"].to<JsonObject>();
|
||||||
ethPinObj["enabled"] = pin.eth_enabled;
|
ethPinObj["enabled"] = pin.eth_enabled;
|
||||||
ethPinObj["phy_addr"] = pin.eth_phy_addr;
|
ethPinObj["phy_addr"] = pin.eth_phy_addr;
|
||||||
ethPinObj["power"] = pin.eth_power;
|
ethPinObj["power"] = pin.eth_power;
|
||||||
@ -59,19 +59,19 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
|
|||||||
ethPinObj["type"] = pin.eth_type;
|
ethPinObj["type"] = pin.eth_type;
|
||||||
ethPinObj["clk_mode"] = pin.eth_clk_mode;
|
ethPinObj["clk_mode"] = pin.eth_clk_mode;
|
||||||
|
|
||||||
auto displayPinObj = curPin.createNestedObject("display");
|
auto displayPinObj = curPin["display"].to<JsonObject>();
|
||||||
displayPinObj["type"] = pin.display_type;
|
displayPinObj["type"] = pin.display_type;
|
||||||
displayPinObj["data"] = pin.display_data;
|
displayPinObj["data"] = pin.display_data;
|
||||||
displayPinObj["clk"] = pin.display_clk;
|
displayPinObj["clk"] = pin.display_clk;
|
||||||
displayPinObj["cs"] = pin.display_cs;
|
displayPinObj["cs"] = pin.display_cs;
|
||||||
displayPinObj["reset"] = pin.display_reset;
|
displayPinObj["reset"] = pin.display_reset;
|
||||||
|
|
||||||
auto ledPinObj = curPin.createNestedObject("led");
|
auto ledPinObj = curPin["led"].to<JsonObject>();
|
||||||
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
||||||
ledPinObj["led" + String(i)] = pin.led[i];
|
ledPinObj["led" + String(i)] = pin.led[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
auto display = root.createNestedObject("display");
|
auto display = root["display"].to<JsonObject>();
|
||||||
display["rotation"] = config.Display.Rotation;
|
display["rotation"] = config.Display.Rotation;
|
||||||
display["power_safe"] = config.Display.PowerSafe;
|
display["power_safe"] = config.Display.PowerSafe;
|
||||||
display["screensaver"] = config.Display.ScreenSaver;
|
display["screensaver"] = config.Display.ScreenSaver;
|
||||||
@ -80,14 +80,13 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
|
|||||||
display["diagramduration"] = config.Display.Diagram.Duration;
|
display["diagramduration"] = config.Display.Diagram.Duration;
|
||||||
display["diagrammode"] = config.Display.Diagram.Mode;
|
display["diagrammode"] = config.Display.Diagram.Mode;
|
||||||
|
|
||||||
auto leds = root.createNestedArray("led");
|
auto leds = root["led"].to<JsonArray>();
|
||||||
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
||||||
auto led = leds.createNestedObject();
|
auto led = leds.add<JsonObject>();
|
||||||
led["brightness"] = config.Led_Single[i].Brightness;
|
led["brightness"] = config.Led_Single[i].Brightness;
|
||||||
}
|
}
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
||||||
@ -96,45 +95,19 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonDocument root;
|
||||||
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
retMsg["type"] = "warning";
|
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
|
||||||
retMsg["message"] = "No values found!";
|
|
||||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String json = request->getParam("data", true)->value();
|
|
||||||
|
|
||||||
if (json.length() > MQTT_JSON_DOC_SIZE) {
|
|
||||||
retMsg["message"] = "Data too large!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicJsonDocument root(MQTT_JSON_DOC_SIZE);
|
|
||||||
const DeserializationError error = deserializeJson(root, json);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
retMsg["message"] = "Failed to parse data!";
|
|
||||||
retMsg["code"] = WebApiError::GenericParseError;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(root.containsKey("curPin")
|
if (!(root.containsKey("curPin")
|
||||||
|| root.containsKey("display"))) {
|
|| root.containsKey("display"))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,8 +115,7 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Pin mapping must between 1 and " STR(DEV_MAX_MAPPING_NAME_STRLEN) " characters long!";
|
retMsg["message"] = "Pin mapping must between 1 and " STR(DEV_MAX_MAPPING_NAME_STRLEN) " characters long!";
|
||||||
retMsg["code"] = WebApiError::HardwarePinMappingLength;
|
retMsg["code"] = WebApiError::HardwarePinMappingLength;
|
||||||
retMsg["param"]["max"] = DEV_MAX_MAPPING_NAME_STRLEN;
|
retMsg["param"]["max"] = DEV_MAX_MAPPING_NAME_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,8 +146,7 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
WebApi.writeConfig(retMsg);
|
WebApi.writeConfig(retMsg);
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
|
|
||||||
if (performRestart) {
|
if (performRestart) {
|
||||||
Utils::restartDtu();
|
Utils::restartDtu();
|
||||||
|
|||||||
@ -23,13 +23,7 @@ void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
auto& root = response->getRoot();
|
auto& root = response->getRoot();
|
||||||
|
auto serial = WebApi.parseSerialFromRequest(request);
|
||||||
uint64_t serial = 0;
|
|
||||||
if (request->hasParam("inv")) {
|
|
||||||
String s = request->getParam("inv")->value();
|
|
||||||
serial = strtoll(s.c_str(), NULL, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto inv = Hoymiles.getInverterBySerial(serial);
|
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||||
|
|
||||||
if (inv != nullptr) {
|
if (inv != nullptr) {
|
||||||
@ -43,6 +37,5 @@ void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request)
|
|||||||
root["fw_build_datetime"] = inv->DevInfo()->getFwBuildDateTimeStr();
|
root["fw_build_datetime"] = inv->DevInfo()->getFwBuildDateTimeStr();
|
||||||
}
|
}
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,10 +62,10 @@ void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request)
|
|||||||
root["cmt_country"] = config.Dtu.Cmt.CountryMode;
|
root["cmt_country"] = config.Dtu.Cmt.CountryMode;
|
||||||
root["cmt_chan_width"] = Hoymiles.getRadioCmt()->getChannelWidth();
|
root["cmt_chan_width"] = Hoymiles.getRadioCmt()->getChannelWidth();
|
||||||
|
|
||||||
auto data = root.createNestedArray("country_def");
|
auto data = root["country_def"].to<JsonArray>();
|
||||||
auto countryDefs = Hoymiles.getRadioCmt()->getCountryFrequencyList();
|
auto countryDefs = Hoymiles.getRadioCmt()->getCountryFrequencyList();
|
||||||
for (const auto& definition : countryDefs) {
|
for (const auto& definition : countryDefs) {
|
||||||
auto obj = data.createNestedObject();
|
auto obj = data.add<JsonObject>();
|
||||||
obj["freq_default"] = definition.definition.Freq_Default;
|
obj["freq_default"] = definition.definition.Freq_Default;
|
||||||
obj["freq_min"] = definition.definition.Freq_Min;
|
obj["freq_min"] = definition.definition.Freq_Min;
|
||||||
obj["freq_max"] = definition.definition.Freq_Max;
|
obj["freq_max"] = definition.definition.Freq_Max;
|
||||||
@ -73,8 +73,7 @@ void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request)
|
|||||||
obj["freq_legal_max"] = definition.definition.Freq_Legal_Max;
|
obj["freq_legal_max"] = definition.definition.Freq_Legal_Max;
|
||||||
}
|
}
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
||||||
@ -84,37 +83,12 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonDocument root;
|
||||||
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
retMsg["type"] = "warning";
|
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
|
||||||
retMsg["message"] = "No values found!";
|
|
||||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String json = request->getParam("data", true)->value();
|
|
||||||
|
|
||||||
if (json.length() > 1024) {
|
|
||||||
retMsg["message"] = "Data too large!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
|
||||||
const DeserializationError error = deserializeJson(root, json);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
retMsg["message"] = "Failed to parse data!";
|
|
||||||
retMsg["code"] = WebApiError::GenericParseError;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(root.containsKey("serial")
|
if (!(root.containsKey("serial")
|
||||||
&& root.containsKey("pollinterval")
|
&& root.containsKey("pollinterval")
|
||||||
@ -124,48 +98,45 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
|||||||
&& root.containsKey("cmt_country"))) {
|
&& root.containsKey("cmt_country"))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["serial"].as<uint64_t>() == 0) {
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
|
const uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
||||||
|
|
||||||
|
if (serial == 0) {
|
||||||
retMsg["message"] = "Serial cannot be zero!";
|
retMsg["message"] = "Serial cannot be zero!";
|
||||||
retMsg["code"] = WebApiError::DtuSerialZero;
|
retMsg["code"] = WebApiError::DtuSerialZero;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["pollinterval"].as<uint32_t>() == 0) {
|
if (root["pollinterval"].as<uint32_t>() == 0) {
|
||||||
retMsg["message"] = "Poll interval must be greater zero!";
|
retMsg["message"] = "Poll interval must be greater zero!";
|
||||||
retMsg["code"] = WebApiError::DtuPollZero;
|
retMsg["code"] = WebApiError::DtuPollZero;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["nrf_palevel"].as<uint8_t>() > 3) {
|
if (root["nrf_palevel"].as<uint8_t>() > 3) {
|
||||||
retMsg["message"] = "Invalid power level setting!";
|
retMsg["message"] = "Invalid power level setting!";
|
||||||
retMsg["code"] = WebApiError::DtuInvalidPowerLevel;
|
retMsg["code"] = WebApiError::DtuInvalidPowerLevel;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["cmt_palevel"].as<int8_t>() < -10 || root["cmt_palevel"].as<int8_t>() > 20) {
|
if (root["cmt_palevel"].as<int8_t>() < -10 || root["cmt_palevel"].as<int8_t>() > 20) {
|
||||||
retMsg["message"] = "Invalid power level setting!";
|
retMsg["message"] = "Invalid power level setting!";
|
||||||
retMsg["code"] = WebApiError::DtuInvalidPowerLevel;
|
retMsg["code"] = WebApiError::DtuInvalidPowerLevel;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["cmt_country"].as<uint8_t>() >= CountryModeId_t::CountryModeId_Max) {
|
if (root["cmt_country"].as<uint8_t>() >= CountryModeId_t::CountryModeId_Max) {
|
||||||
retMsg["message"] = "Invalid country setting!";
|
retMsg["message"] = "Invalid country setting!";
|
||||||
retMsg["code"] = WebApiError::DtuInvalidCmtCountry;
|
retMsg["code"] = WebApiError::DtuInvalidCmtCountry;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,15 +149,13 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["code"] = WebApiError::DtuInvalidCmtFrequency;
|
retMsg["code"] = WebApiError::DtuInvalidCmtFrequency;
|
||||||
retMsg["param"]["min"] = FrequencyDefinition.Freq_Min;
|
retMsg["param"]["min"] = FrequencyDefinition.Freq_Min;
|
||||||
retMsg["param"]["max"] = FrequencyDefinition.Freq_Max;
|
retMsg["param"]["max"] = FrequencyDefinition.Freq_Max;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
// Interpret the string as a hex value and convert it to uint64_t
|
config.Dtu.Serial = serial;
|
||||||
config.Dtu.Serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
|
||||||
config.Dtu.PollInterval = root["pollinterval"].as<uint32_t>();
|
config.Dtu.PollInterval = root["pollinterval"].as<uint32_t>();
|
||||||
config.Dtu.Nrf.PaLevel = root["nrf_palevel"].as<uint8_t>();
|
config.Dtu.Nrf.PaLevel = root["nrf_palevel"].as<uint8_t>();
|
||||||
config.Dtu.Cmt.PaLevel = root["cmt_palevel"].as<int8_t>();
|
config.Dtu.Cmt.PaLevel = root["cmt_palevel"].as<int8_t>();
|
||||||
@ -195,8 +164,8 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
WebApi.writeConfig(retMsg);
|
WebApi.writeConfig(retMsg);
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
|
|
||||||
_applyDataTask.enable();
|
_applyDataTask.enable();
|
||||||
|
_applyDataTask.restart();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,14 +20,9 @@ void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 2048);
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
auto& root = response->getRoot();
|
auto& root = response->getRoot();
|
||||||
|
auto serial = WebApi.parseSerialFromRequest(request);
|
||||||
uint64_t serial = 0;
|
|
||||||
if (request->hasParam("inv")) {
|
|
||||||
String s = request->getParam("inv")->value();
|
|
||||||
serial = strtoll(s.c_str(), NULL, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
AlarmMessageLocale_t locale = AlarmMessageLocale_t::EN;
|
AlarmMessageLocale_t locale = AlarmMessageLocale_t::EN;
|
||||||
if (request->hasParam("locale")) {
|
if (request->hasParam("locale")) {
|
||||||
@ -47,10 +42,10 @@ void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request)
|
|||||||
uint8_t logEntryCount = inv->EventLog()->getEntryCount();
|
uint8_t logEntryCount = inv->EventLog()->getEntryCount();
|
||||||
|
|
||||||
root["count"] = logEntryCount;
|
root["count"] = logEntryCount;
|
||||||
JsonArray eventsArray = root.createNestedArray("events");
|
JsonArray eventsArray = root["events"].to<JsonArray>();
|
||||||
|
|
||||||
for (uint8_t logEntry = 0; logEntry < logEntryCount; logEntry++) {
|
for (uint8_t logEntry = 0; logEntry < logEntryCount; logEntry++) {
|
||||||
JsonObject eventsObject = eventsArray.createNestedObject();
|
JsonObject eventsObject = eventsArray.add<JsonObject>();
|
||||||
|
|
||||||
AlarmLogEntry_t entry;
|
AlarmLogEntry_t entry;
|
||||||
inv->EventLog()->getLogEntry(logEntry, entry, locale);
|
inv->EventLog()->getLogEntry(logEntry, entry, locale);
|
||||||
@ -62,6 +57,5 @@ void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,32 +21,26 @@ void WebApiGridProfileClass::onGridProfileStatus(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192);
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
auto& root = response->getRoot();
|
auto& root = response->getRoot();
|
||||||
|
auto serial = WebApi.parseSerialFromRequest(request);
|
||||||
uint64_t serial = 0;
|
|
||||||
if (request->hasParam("inv")) {
|
|
||||||
String s = request->getParam("inv")->value();
|
|
||||||
serial = strtoll(s.c_str(), NULL, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto inv = Hoymiles.getInverterBySerial(serial);
|
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||||
|
|
||||||
if (inv != nullptr) {
|
if (inv != nullptr) {
|
||||||
root["name"] = inv->GridProfile()->getProfileName();
|
root["name"] = inv->GridProfile()->getProfileName();
|
||||||
root["version"] = inv->GridProfile()->getProfileVersion();
|
root["version"] = inv->GridProfile()->getProfileVersion();
|
||||||
|
|
||||||
auto jsonSections = root.createNestedArray("sections");
|
auto jsonSections = root["sections"].to<JsonArray>();
|
||||||
auto profSections = inv->GridProfile()->getProfile();
|
auto profSections = inv->GridProfile()->getProfile();
|
||||||
|
|
||||||
for (auto &profSection : profSections) {
|
for (auto &profSection : profSections) {
|
||||||
auto jsonSection = jsonSections.createNestedObject();
|
auto jsonSection = jsonSections.add<JsonObject>();
|
||||||
jsonSection["name"] = profSection.SectionName;
|
jsonSection["name"] = profSection.SectionName;
|
||||||
|
|
||||||
auto jsonItems = jsonSection.createNestedArray("items");
|
auto jsonItems = jsonSection["items"].to<JsonArray>();
|
||||||
|
|
||||||
for (auto &profItem : profSection.items) {
|
for (auto &profItem : profSection.items) {
|
||||||
auto jsonItem = jsonItems.createNestedObject();
|
auto jsonItem = jsonItems.add<JsonObject>();
|
||||||
|
|
||||||
jsonItem["n"] = profItem.Name;
|
jsonItem["n"] = profItem.Name;
|
||||||
jsonItem["u"] = profItem.Unit;
|
jsonItem["u"] = profItem.Unit;
|
||||||
@ -55,8 +49,7 @@ void WebApiGridProfileClass::onGridProfileStatus(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiGridProfileClass::onGridProfileRawdata(AsyncWebServerRequest* request)
|
void WebApiGridProfileClass::onGridProfileRawdata(AsyncWebServerRequest* request)
|
||||||
@ -65,24 +58,17 @@ void WebApiGridProfileClass::onGridProfileRawdata(AsyncWebServerRequest* request
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096);
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
auto& root = response->getRoot();
|
auto& root = response->getRoot();
|
||||||
|
auto serial = WebApi.parseSerialFromRequest(request);
|
||||||
uint64_t serial = 0;
|
|
||||||
if (request->hasParam("inv")) {
|
|
||||||
String s = request->getParam("inv")->value();
|
|
||||||
serial = strtoll(s.c_str(), NULL, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto inv = Hoymiles.getInverterBySerial(serial);
|
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||||
|
|
||||||
if (inv != nullptr) {
|
if (inv != nullptr) {
|
||||||
auto raw = root.createNestedArray("raw");
|
auto raw = root["raw"].to<JsonArray>();
|
||||||
auto data = inv->GridProfile()->getRawData();
|
auto data = inv->GridProfile()->getRawData();
|
||||||
|
|
||||||
copyArray(&data[0], data.size(), raw);
|
copyArray(&data[0], data.size(), raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,15 +29,15 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 768 * INV_MAX_COUNT);
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
auto& root = response->getRoot();
|
auto& root = response->getRoot();
|
||||||
JsonArray data = root.createNestedArray("inverter");
|
JsonArray data = root["inverter"].to<JsonArray>();
|
||||||
|
|
||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
||||||
if (config.Inverter[i].Serial > 0) {
|
if (config.Inverter[i].Serial > 0) {
|
||||||
JsonObject obj = data.createNestedObject();
|
JsonObject obj = data.add<JsonObject>();
|
||||||
obj["id"] = i;
|
obj["id"] = i;
|
||||||
obj["name"] = String(config.Inverter[i].Name);
|
obj["name"] = String(config.Inverter[i].Name);
|
||||||
obj["order"] = config.Inverter[i].Order;
|
obj["order"] = config.Inverter[i].Order;
|
||||||
@ -67,9 +67,9 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
|
|||||||
max_channels = inv->Statistics()->getChannelsByType(TYPE_DC).size();
|
max_channels = inv->Statistics()->getChannelsByType(TYPE_DC).size();
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonArray channel = obj.createNestedArray("channel");
|
JsonArray channel = obj["channel"].to<JsonArray>();
|
||||||
for (uint8_t c = 0; c < max_channels; c++) {
|
for (uint8_t c = 0; c < max_channels; c++) {
|
||||||
JsonObject chanData = channel.createNestedObject();
|
JsonObject chanData = channel.add<JsonObject>();
|
||||||
chanData["name"] = config.Inverter[i].channel[c].Name;
|
chanData["name"] = config.Inverter[i].channel[c].Name;
|
||||||
chanData["max_power"] = config.Inverter[i].channel[c].MaxChannelPower;
|
chanData["max_power"] = config.Inverter[i].channel[c].MaxChannelPower;
|
||||||
chanData["yield_total_offset"] = config.Inverter[i].channel[c].YieldTotalOffset;
|
chanData["yield_total_offset"] = config.Inverter[i].channel[c].YieldTotalOffset;
|
||||||
@ -77,8 +77,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
||||||
@ -88,52 +87,28 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonDocument root;
|
||||||
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
retMsg["type"] = "warning";
|
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
|
||||||
retMsg["message"] = "No values found!";
|
|
||||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String json = request->getParam("data", true)->value();
|
|
||||||
|
|
||||||
if (json.length() > 1024) {
|
|
||||||
retMsg["message"] = "Data too large!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
|
||||||
const DeserializationError error = deserializeJson(root, json);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
retMsg["message"] = "Failed to parse data!";
|
|
||||||
retMsg["code"] = WebApiError::GenericParseError;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(root.containsKey("serial")
|
if (!(root.containsKey("serial")
|
||||||
&& root.containsKey("name"))) {
|
&& root.containsKey("name"))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["serial"].as<uint64_t>() == 0) {
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
|
const uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
||||||
|
|
||||||
|
if (serial == 0) {
|
||||||
retMsg["message"] = "Serial must be a number > 0!";
|
retMsg["message"] = "Serial must be a number > 0!";
|
||||||
retMsg["code"] = WebApiError::InverterSerialZero;
|
retMsg["code"] = WebApiError::InverterSerialZero;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,8 +116,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Name must between 1 and " STR(INV_MAX_NAME_STRLEN) " characters long!";
|
retMsg["message"] = "Name must between 1 and " STR(INV_MAX_NAME_STRLEN) " characters long!";
|
||||||
retMsg["code"] = WebApiError::InverterNameLength;
|
retMsg["code"] = WebApiError::InverterNameLength;
|
||||||
retMsg["param"]["max"] = INV_MAX_NAME_STRLEN;
|
retMsg["param"]["max"] = INV_MAX_NAME_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,20 +126,18 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Only " STR(INV_MAX_COUNT) " inverters are supported!";
|
retMsg["message"] = "Only " STR(INV_MAX_COUNT) " inverters are supported!";
|
||||||
retMsg["code"] = WebApiError::InverterCount;
|
retMsg["code"] = WebApiError::InverterCount;
|
||||||
retMsg["param"]["max"] = INV_MAX_COUNT;
|
retMsg["param"]["max"] = INV_MAX_COUNT;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpret the string as a hex value and convert it to uint64_t
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
inverter->Serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
inverter->Serial = serial;
|
||||||
|
|
||||||
strncpy(inverter->Name, root["name"].as<String>().c_str(), INV_MAX_NAME_STRLEN);
|
strncpy(inverter->Name, root["name"].as<String>().c_str(), INV_MAX_NAME_STRLEN);
|
||||||
|
|
||||||
WebApi.writeConfig(retMsg, WebApiError::InverterAdded, "Inverter created!");
|
WebApi.writeConfig(retMsg, WebApiError::InverterAdded, "Inverter created!");
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
|
|
||||||
auto inv = Hoymiles.addInverter(inverter->Name, inverter->Serial);
|
auto inv = Hoymiles.addInverter(inverter->Name, inverter->Serial);
|
||||||
|
|
||||||
@ -185,59 +157,34 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonDocument root;
|
||||||
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
retMsg["type"] = "warning";
|
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
|
||||||
retMsg["message"] = "No values found!";
|
|
||||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String json = request->getParam("data", true)->value();
|
|
||||||
|
|
||||||
if (json.length() > 1024) {
|
|
||||||
retMsg["message"] = "Data too large!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
|
||||||
const DeserializationError error = deserializeJson(root, json);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
retMsg["message"] = "Failed to parse data!";
|
|
||||||
retMsg["code"] = WebApiError::GenericParseError;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(root.containsKey("id") && root.containsKey("serial") && root.containsKey("name") && root.containsKey("channel"))) {
|
if (!(root.containsKey("id") && root.containsKey("serial") && root.containsKey("name") && root.containsKey("channel"))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["id"].as<uint8_t>() > INV_MAX_COUNT - 1) {
|
if (root["id"].as<uint8_t>() > INV_MAX_COUNT - 1) {
|
||||||
retMsg["message"] = "Invalid ID specified!";
|
retMsg["message"] = "Invalid ID specified!";
|
||||||
retMsg["code"] = WebApiError::InverterInvalidId;
|
retMsg["code"] = WebApiError::InverterInvalidId;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["serial"].as<uint64_t>() == 0) {
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
|
const uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
||||||
|
|
||||||
|
if (serial == 0) {
|
||||||
retMsg["message"] = "Serial must be a number > 0!";
|
retMsg["message"] = "Serial must be a number > 0!";
|
||||||
retMsg["code"] = WebApiError::InverterSerialZero;
|
retMsg["code"] = WebApiError::InverterSerialZero;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,8 +192,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Name must between 1 and " STR(INV_MAX_NAME_STRLEN) " characters long!";
|
retMsg["message"] = "Name must between 1 and " STR(INV_MAX_NAME_STRLEN) " characters long!";
|
||||||
retMsg["code"] = WebApiError::InverterNameLength;
|
retMsg["code"] = WebApiError::InverterNameLength;
|
||||||
retMsg["param"]["max"] = INV_MAX_NAME_STRLEN;
|
retMsg["param"]["max"] = INV_MAX_NAME_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,14 +200,13 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
|||||||
if (channelArray.size() == 0 || channelArray.size() > INV_MAX_CHAN_COUNT) {
|
if (channelArray.size() == 0 || channelArray.size() > INV_MAX_CHAN_COUNT) {
|
||||||
retMsg["message"] = "Invalid amount of max channel setting given!";
|
retMsg["message"] = "Invalid amount of max channel setting given!";
|
||||||
retMsg["code"] = WebApiError::InverterInvalidMaxChannel;
|
retMsg["code"] = WebApiError::InverterInvalidMaxChannel;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[root["id"].as<uint8_t>()];
|
INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[root["id"].as<uint8_t>()];
|
||||||
|
|
||||||
uint64_t new_serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
uint64_t new_serial = serial;
|
||||||
uint64_t old_serial = inverter.Serial;
|
uint64_t old_serial = inverter.Serial;
|
||||||
|
|
||||||
// Interpret the string as a hex value and convert it to uint64_t
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
@ -287,8 +232,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
WebApi.writeConfig(retMsg, WebApiError::InverterChanged, "Inverter changed!");
|
WebApi.writeConfig(retMsg, WebApiError::InverterChanged, "Inverter changed!");
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
|
|
||||||
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterBySerial(old_serial);
|
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterBySerial(old_serial);
|
||||||
|
|
||||||
@ -327,51 +271,24 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonDocument root;
|
||||||
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
retMsg["type"] = "warning";
|
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
|
||||||
retMsg["message"] = "No values found!";
|
|
||||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String json = request->getParam("data", true)->value();
|
|
||||||
|
|
||||||
if (json.length() > 1024) {
|
|
||||||
retMsg["message"] = "Data too large!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
|
||||||
const DeserializationError error = deserializeJson(root, json);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
retMsg["message"] = "Failed to parse data!";
|
|
||||||
retMsg["code"] = WebApiError::GenericParseError;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(root.containsKey("id"))) {
|
if (!(root.containsKey("id"))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["id"].as<uint8_t>() > INV_MAX_COUNT - 1) {
|
if (root["id"].as<uint8_t>() > INV_MAX_COUNT - 1) {
|
||||||
retMsg["message"] = "Invalid ID specified!";
|
retMsg["message"] = "Invalid ID specified!";
|
||||||
retMsg["code"] = WebApiError::InverterInvalidId;
|
retMsg["code"] = WebApiError::InverterInvalidId;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,13 +297,11 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
Hoymiles.removeInverterBySerial(inverter.Serial);
|
Hoymiles.removeInverterBySerial(inverter.Serial);
|
||||||
|
|
||||||
inverter.Serial = 0;
|
Configuration.deleteInverterById(inverter_id);
|
||||||
strncpy(inverter.Name, "", sizeof(inverter.Name));
|
|
||||||
|
|
||||||
WebApi.writeConfig(retMsg, WebApiError::InverterDeleted, "Inverter deleted!");
|
WebApi.writeConfig(retMsg, WebApiError::InverterDeleted, "Inverter deleted!");
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
|
|
||||||
MqttHandleHass.forceUpdate();
|
MqttHandleHass.forceUpdate();
|
||||||
}
|
}
|
||||||
@ -398,43 +313,17 @@ void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonDocument root;
|
||||||
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
retMsg["type"] = "warning";
|
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
|
||||||
retMsg["message"] = "No values found!";
|
|
||||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String json = request->getParam("data", true)->value();
|
|
||||||
|
|
||||||
if (json.length() > 1024) {
|
|
||||||
retMsg["message"] = "Data too large!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
|
||||||
const DeserializationError error = deserializeJson(root, json);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
retMsg["message"] = "Failed to parse data!";
|
|
||||||
retMsg["code"] = WebApiError::GenericParseError;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(root.containsKey("order"))) {
|
if (!(root.containsKey("order"))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -452,6 +341,5 @@ void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
WebApi.writeConfig(retMsg, WebApiError::InverterOrdered, "Inverter order saved!");
|
WebApi.writeConfig(retMsg, WebApiError::InverterOrdered, "Inverter order saved!");
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,8 +47,7 @@ void WebApiLimitClass::onLimitStatus(AsyncWebServerRequest* request)
|
|||||||
root[serial]["limit_set_status"] = limitStatus;
|
root[serial]["limit_set_status"] = limitStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
||||||
@ -58,53 +57,29 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonDocument root;
|
||||||
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
retMsg["type"] = "warning";
|
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
|
||||||
retMsg["message"] = "No values found!";
|
|
||||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String json = request->getParam("data", true)->value();
|
|
||||||
|
|
||||||
if (json.length() > 1024) {
|
|
||||||
retMsg["message"] = "Data too large!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
|
||||||
const DeserializationError error = deserializeJson(root, json);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
retMsg["message"] = "Failed to parse data!";
|
|
||||||
retMsg["code"] = WebApiError::GenericParseError;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(root.containsKey("serial")
|
if (!(root.containsKey("serial")
|
||||||
&& root.containsKey("limit_value")
|
&& root.containsKey("limit_value")
|
||||||
&& root.containsKey("limit_type"))) {
|
&& root.containsKey("limit_type"))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["serial"].as<uint64_t>() == 0) {
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
|
const uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
||||||
|
|
||||||
|
if (serial == 0) {
|
||||||
retMsg["message"] = "Serial must be a number > 0!";
|
retMsg["message"] = "Serial must be a number > 0!";
|
||||||
retMsg["code"] = WebApiError::LimitSerialZero;
|
retMsg["code"] = WebApiError::LimitSerialZero;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,8 +87,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Limit must between 0 and " STR(MAX_INVERTER_LIMIT) "!";
|
retMsg["message"] = "Limit must between 0 and " STR(MAX_INVERTER_LIMIT) "!";
|
||||||
retMsg["code"] = WebApiError::LimitInvalidLimit;
|
retMsg["code"] = WebApiError::LimitInvalidLimit;
|
||||||
retMsg["param"]["max"] = MAX_INVERTER_LIMIT;
|
retMsg["param"]["max"] = MAX_INVERTER_LIMIT;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,12 +98,10 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
retMsg["message"] = "Invalid type specified!";
|
retMsg["message"] = "Invalid type specified!";
|
||||||
retMsg["code"] = WebApiError::LimitInvalidType;
|
retMsg["code"] = WebApiError::LimitInvalidType;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
|
||||||
uint16_t limit = root["limit_value"].as<uint16_t>();
|
uint16_t limit = root["limit_value"].as<uint16_t>();
|
||||||
PowerLimitControlType type = root["limit_type"].as<PowerLimitControlType>();
|
PowerLimitControlType type = root["limit_type"].as<PowerLimitControlType>();
|
||||||
|
|
||||||
@ -137,8 +109,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
|||||||
if (inv == nullptr) {
|
if (inv == nullptr) {
|
||||||
retMsg["message"] = "Invalid inverter specified!";
|
retMsg["message"] = "Invalid inverter specified!";
|
||||||
retMsg["code"] = WebApiError::LimitInvalidInverter;
|
retMsg["code"] = WebApiError::LimitInvalidInverter;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,6 +119,5 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Settings saved!";
|
retMsg["message"] = "Settings saved!";
|
||||||
retMsg["code"] = WebApiError::GenericSuccess;
|
retMsg["code"] = WebApiError::GenericSuccess;
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,44 +22,18 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonDocument root;
|
||||||
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
retMsg["type"] = "warning";
|
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
|
||||||
retMsg["message"] = "No values found!";
|
|
||||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String json = request->getParam("data", true)->value();
|
|
||||||
|
|
||||||
if (json.length() > MQTT_JSON_DOC_SIZE) {
|
|
||||||
retMsg["message"] = "Data too large!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicJsonDocument root(MQTT_JSON_DOC_SIZE);
|
|
||||||
const DeserializationError error = deserializeJson(root, json);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
retMsg["message"] = "Failed to parse data!";
|
|
||||||
retMsg["code"] = WebApiError::GenericParseError;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(root.containsKey("reboot"))) {
|
if (!(root.containsKey("reboot"))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,14 +42,12 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Reboot triggered!";
|
retMsg["message"] = "Reboot triggered!";
|
||||||
retMsg["code"] = WebApiError::MaintenanceRebootTriggered;
|
retMsg["code"] = WebApiError::MaintenanceRebootTriggered;
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
Utils::restartDtu();
|
Utils::restartDtu();
|
||||||
} else {
|
} else {
|
||||||
retMsg["message"] = "Reboot cancled!";
|
retMsg["message"] = "Reboot cancled!";
|
||||||
retMsg["code"] = WebApiError::MaintenanceRebootCancled;
|
retMsg["code"] = WebApiError::MaintenanceRebootCancled;
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
auto& root = response->getRoot();
|
auto& root = response->getRoot();
|
||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
@ -50,8 +50,7 @@ void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request)
|
|||||||
root["mqtt_hass_topic"] = config.Mqtt.Hass.Topic;
|
root["mqtt_hass_topic"] = config.Mqtt.Hass.Topic;
|
||||||
root["mqtt_hass_individualpanels"] = config.Mqtt.Hass.IndividualPanels;
|
root["mqtt_hass_individualpanels"] = config.Mqtt.Hass.IndividualPanels;
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request)
|
void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request)
|
||||||
@ -60,7 +59,7 @@ void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
auto& root = response->getRoot();
|
auto& root = response->getRoot();
|
||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
@ -88,8 +87,7 @@ void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request)
|
|||||||
root["mqtt_hass_topic"] = config.Mqtt.Hass.Topic;
|
root["mqtt_hass_topic"] = config.Mqtt.Hass.Topic;
|
||||||
root["mqtt_hass_individualpanels"] = config.Mqtt.Hass.IndividualPanels;
|
root["mqtt_hass_individualpanels"] = config.Mqtt.Hass.IndividualPanels;
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||||
@ -98,38 +96,13 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonDocument root;
|
||||||
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
retMsg["type"] = "warning";
|
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
|
||||||
retMsg["message"] = "No values found!";
|
|
||||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String json = request->getParam("data", true)->value();
|
|
||||||
|
|
||||||
if (json.length() > MQTT_JSON_DOC_SIZE) {
|
|
||||||
retMsg["message"] = "Data too large!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicJsonDocument root(MQTT_JSON_DOC_SIZE);
|
|
||||||
const DeserializationError error = deserializeJson(root, json);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
retMsg["message"] = "Failed to parse data!";
|
|
||||||
retMsg["code"] = WebApiError::GenericParseError;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(root.containsKey("mqtt_enabled")
|
if (!(root.containsKey("mqtt_enabled")
|
||||||
&& root.containsKey("mqtt_hostname")
|
&& root.containsKey("mqtt_hostname")
|
||||||
@ -155,8 +128,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
|||||||
&& root.containsKey("mqtt_hass_individualpanels"))) {
|
&& root.containsKey("mqtt_hass_individualpanels"))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,8 +137,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "MqTT Server must between 1 and " STR(MQTT_MAX_HOSTNAME_STRLEN) " characters long!";
|
retMsg["message"] = "MqTT Server must between 1 and " STR(MQTT_MAX_HOSTNAME_STRLEN) " characters long!";
|
||||||
retMsg["code"] = WebApiError::MqttHostnameLength;
|
retMsg["code"] = WebApiError::MqttHostnameLength;
|
||||||
retMsg["param"]["max"] = MQTT_MAX_HOSTNAME_STRLEN;
|
retMsg["param"]["max"] = MQTT_MAX_HOSTNAME_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,48 +145,42 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Username must not be longer than " STR(MQTT_MAX_USERNAME_STRLEN) " characters!";
|
retMsg["message"] = "Username must not be longer than " STR(MQTT_MAX_USERNAME_STRLEN) " characters!";
|
||||||
retMsg["code"] = WebApiError::MqttUsernameLength;
|
retMsg["code"] = WebApiError::MqttUsernameLength;
|
||||||
retMsg["param"]["max"] = MQTT_MAX_USERNAME_STRLEN;
|
retMsg["param"]["max"] = MQTT_MAX_USERNAME_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (root["mqtt_password"].as<String>().length() > MQTT_MAX_PASSWORD_STRLEN) {
|
if (root["mqtt_password"].as<String>().length() > MQTT_MAX_PASSWORD_STRLEN) {
|
||||||
retMsg["message"] = "Password must not be longer than " STR(MQTT_MAX_PASSWORD_STRLEN) " characters!";
|
retMsg["message"] = "Password must not be longer than " STR(MQTT_MAX_PASSWORD_STRLEN) " characters!";
|
||||||
retMsg["code"] = WebApiError::MqttPasswordLength;
|
retMsg["code"] = WebApiError::MqttPasswordLength;
|
||||||
retMsg["param"]["max"] = MQTT_MAX_PASSWORD_STRLEN;
|
retMsg["param"]["max"] = MQTT_MAX_PASSWORD_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (root["mqtt_topic"].as<String>().length() > MQTT_MAX_TOPIC_STRLEN) {
|
if (root["mqtt_topic"].as<String>().length() > MQTT_MAX_TOPIC_STRLEN) {
|
||||||
retMsg["message"] = "Topic must not be longer than " STR(MQTT_MAX_TOPIC_STRLEN) " characters!";
|
retMsg["message"] = "Topic must not be longer than " STR(MQTT_MAX_TOPIC_STRLEN) " characters!";
|
||||||
retMsg["code"] = WebApiError::MqttTopicLength;
|
retMsg["code"] = WebApiError::MqttTopicLength;
|
||||||
retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN;
|
retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["mqtt_topic"].as<String>().indexOf(' ') != -1) {
|
if (root["mqtt_topic"].as<String>().indexOf(' ') != -1) {
|
||||||
retMsg["message"] = "Topic must not contain space characters!";
|
retMsg["message"] = "Topic must not contain space characters!";
|
||||||
retMsg["code"] = WebApiError::MqttTopicCharacter;
|
retMsg["code"] = WebApiError::MqttTopicCharacter;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!root["mqtt_topic"].as<String>().endsWith("/")) {
|
if (!root["mqtt_topic"].as<String>().endsWith("/")) {
|
||||||
retMsg["message"] = "Topic must end with a slash (/)!";
|
retMsg["message"] = "Topic must end with a slash (/)!";
|
||||||
retMsg["code"] = WebApiError::MqttTopicTrailingSlash;
|
retMsg["code"] = WebApiError::MqttTopicTrailingSlash;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["mqtt_port"].as<uint>() == 0 || root["mqtt_port"].as<uint>() > 65535) {
|
if (root["mqtt_port"].as<uint>() == 0 || root["mqtt_port"].as<uint>() > 65535) {
|
||||||
retMsg["message"] = "Port must be a number between 1 and 65535!";
|
retMsg["message"] = "Port must be a number between 1 and 65535!";
|
||||||
retMsg["code"] = WebApiError::MqttPort;
|
retMsg["code"] = WebApiError::MqttPort;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,8 +190,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Certificates must not be longer than " STR(MQTT_MAX_CERT_STRLEN) " characters!";
|
retMsg["message"] = "Certificates must not be longer than " STR(MQTT_MAX_CERT_STRLEN) " characters!";
|
||||||
retMsg["code"] = WebApiError::MqttCertificateLength;
|
retMsg["code"] = WebApiError::MqttCertificateLength;
|
||||||
retMsg["param"]["max"] = MQTT_MAX_CERT_STRLEN;
|
retMsg["param"]["max"] = MQTT_MAX_CERT_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,16 +198,14 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "LWT topic must not be longer than " STR(MQTT_MAX_TOPIC_STRLEN) " characters!";
|
retMsg["message"] = "LWT topic must not be longer than " STR(MQTT_MAX_TOPIC_STRLEN) " characters!";
|
||||||
retMsg["code"] = WebApiError::MqttLwtTopicLength;
|
retMsg["code"] = WebApiError::MqttLwtTopicLength;
|
||||||
retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN;
|
retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["mqtt_lwt_topic"].as<String>().indexOf(' ') != -1) {
|
if (root["mqtt_lwt_topic"].as<String>().indexOf(' ') != -1) {
|
||||||
retMsg["message"] = "LWT topic must not contain space characters!";
|
retMsg["message"] = "LWT topic must not contain space characters!";
|
||||||
retMsg["code"] = WebApiError::MqttLwtTopicCharacter;
|
retMsg["code"] = WebApiError::MqttLwtTopicCharacter;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,8 +213,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "LWT online value must not be longer than " STR(MQTT_MAX_LWTVALUE_STRLEN) " characters!";
|
retMsg["message"] = "LWT online value must not be longer than " STR(MQTT_MAX_LWTVALUE_STRLEN) " characters!";
|
||||||
retMsg["code"] = WebApiError::MqttLwtOnlineLength;
|
retMsg["code"] = WebApiError::MqttLwtOnlineLength;
|
||||||
retMsg["param"]["max"] = MQTT_MAX_LWTVALUE_STRLEN;
|
retMsg["param"]["max"] = MQTT_MAX_LWTVALUE_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,8 +221,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "LWT offline value must not be longer than " STR(MQTT_MAX_LWTVALUE_STRLEN) " characters!";
|
retMsg["message"] = "LWT offline value must not be longer than " STR(MQTT_MAX_LWTVALUE_STRLEN) " characters!";
|
||||||
retMsg["code"] = WebApiError::MqttLwtOfflineLength;
|
retMsg["code"] = WebApiError::MqttLwtOfflineLength;
|
||||||
retMsg["param"]["max"] = MQTT_MAX_LWTVALUE_STRLEN;
|
retMsg["param"]["max"] = MQTT_MAX_LWTVALUE_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,8 +229,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "LWT QoS must not be greater than " STR(2) "!";
|
retMsg["message"] = "LWT QoS must not be greater than " STR(2) "!";
|
||||||
retMsg["code"] = WebApiError::MqttLwtQos;
|
retMsg["code"] = WebApiError::MqttLwtQos;
|
||||||
retMsg["param"]["max"] = 2;
|
retMsg["param"]["max"] = 2;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,8 +238,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["code"] = WebApiError::MqttPublishInterval;
|
retMsg["code"] = WebApiError::MqttPublishInterval;
|
||||||
retMsg["param"]["min"] = 5;
|
retMsg["param"]["min"] = 5;
|
||||||
retMsg["param"]["max"] = 65535;
|
retMsg["param"]["max"] = 65535;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,16 +247,14 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Hass topic must not be longer than " STR(MQTT_MAX_TOPIC_STRLEN) " characters!";
|
retMsg["message"] = "Hass topic must not be longer than " STR(MQTT_MAX_TOPIC_STRLEN) " characters!";
|
||||||
retMsg["code"] = WebApiError::MqttHassTopicLength;
|
retMsg["code"] = WebApiError::MqttHassTopicLength;
|
||||||
retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN;
|
retMsg["param"]["max"] = MQTT_MAX_TOPIC_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["mqtt_hass_topic"].as<String>().indexOf(' ') != -1) {
|
if (root["mqtt_hass_topic"].as<String>().indexOf(' ') != -1) {
|
||||||
retMsg["message"] = "Hass topic must not contain space characters!";
|
retMsg["message"] = "Hass topic must not contain space characters!";
|
||||||
retMsg["code"] = WebApiError::MqttHassTopicCharacter;
|
retMsg["code"] = WebApiError::MqttHassTopicCharacter;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -331,8 +287,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
WebApi.writeConfig(retMsg);
|
WebApi.writeConfig(retMsg);
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
|
|
||||||
MqttSettings.performReconnect();
|
MqttSettings.performReconnect();
|
||||||
MqttHandleHass.forceUpdate();
|
MqttHandleHass.forceUpdate();
|
||||||
|
|||||||
@ -46,8 +46,7 @@ void WebApiNetworkClass::onNetworkStatus(AsyncWebServerRequest* request)
|
|||||||
root["ap_mac"] = WiFi.softAPmacAddress();
|
root["ap_mac"] = WiFi.softAPmacAddress();
|
||||||
root["ap_stationnum"] = WiFi.softAPgetStationNum();
|
root["ap_stationnum"] = WiFi.softAPgetStationNum();
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request)
|
void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request)
|
||||||
@ -72,8 +71,7 @@ void WebApiNetworkClass::onNetworkAdminGet(AsyncWebServerRequest* request)
|
|||||||
root["aptimeout"] = config.WiFi.ApTimeout;
|
root["aptimeout"] = config.WiFi.ApTimeout;
|
||||||
root["mdnsenabled"] = config.Mdns.Enabled;
|
root["mdnsenabled"] = config.Mdns.Enabled;
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
||||||
@ -83,37 +81,12 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonDocument root;
|
||||||
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
retMsg["type"] = "warning";
|
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
|
||||||
retMsg["message"] = "No values found!";
|
|
||||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String json = request->getParam("data", true)->value();
|
|
||||||
|
|
||||||
if (json.length() > 1024) {
|
|
||||||
retMsg["message"] = "Data too large!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
|
||||||
const DeserializationError error = deserializeJson(root, json);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
retMsg["message"] = "Failed to parse data!";
|
|
||||||
retMsg["code"] = WebApiError::GenericParseError;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(root.containsKey("ssid")
|
if (!(root.containsKey("ssid")
|
||||||
&& root.containsKey("password")
|
&& root.containsKey("password")
|
||||||
@ -127,8 +100,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
|||||||
&& root.containsKey("aptimeout"))) {
|
&& root.containsKey("aptimeout"))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,68 +108,59 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
|||||||
if (!ipaddress.fromString(root["ipaddress"].as<String>())) {
|
if (!ipaddress.fromString(root["ipaddress"].as<String>())) {
|
||||||
retMsg["message"] = "IP address is invalid!";
|
retMsg["message"] = "IP address is invalid!";
|
||||||
retMsg["code"] = WebApiError::NetworkIpInvalid;
|
retMsg["code"] = WebApiError::NetworkIpInvalid;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
IPAddress netmask;
|
IPAddress netmask;
|
||||||
if (!netmask.fromString(root["netmask"].as<String>())) {
|
if (!netmask.fromString(root["netmask"].as<String>())) {
|
||||||
retMsg["message"] = "Netmask is invalid!";
|
retMsg["message"] = "Netmask is invalid!";
|
||||||
retMsg["code"] = WebApiError::NetworkNetmaskInvalid;
|
retMsg["code"] = WebApiError::NetworkNetmaskInvalid;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
IPAddress gateway;
|
IPAddress gateway;
|
||||||
if (!gateway.fromString(root["gateway"].as<String>())) {
|
if (!gateway.fromString(root["gateway"].as<String>())) {
|
||||||
retMsg["message"] = "Gateway is invalid!";
|
retMsg["message"] = "Gateway is invalid!";
|
||||||
retMsg["code"] = WebApiError::NetworkGatewayInvalid;
|
retMsg["code"] = WebApiError::NetworkGatewayInvalid;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
IPAddress dns1;
|
IPAddress dns1;
|
||||||
if (!dns1.fromString(root["dns1"].as<String>())) {
|
if (!dns1.fromString(root["dns1"].as<String>())) {
|
||||||
retMsg["message"] = "DNS Server IP 1 is invalid!";
|
retMsg["message"] = "DNS Server IP 1 is invalid!";
|
||||||
retMsg["code"] = WebApiError::NetworkDns1Invalid;
|
retMsg["code"] = WebApiError::NetworkDns1Invalid;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
IPAddress dns2;
|
IPAddress dns2;
|
||||||
if (!dns2.fromString(root["dns2"].as<String>())) {
|
if (!dns2.fromString(root["dns2"].as<String>())) {
|
||||||
retMsg["message"] = "DNS Server IP 2 is invalid!";
|
retMsg["message"] = "DNS Server IP 2 is invalid!";
|
||||||
retMsg["code"] = WebApiError::NetworkDns2Invalid;
|
retMsg["code"] = WebApiError::NetworkDns2Invalid;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["hostname"].as<String>().length() == 0 || root["hostname"].as<String>().length() > WIFI_MAX_HOSTNAME_STRLEN) {
|
if (root["hostname"].as<String>().length() == 0 || root["hostname"].as<String>().length() > WIFI_MAX_HOSTNAME_STRLEN) {
|
||||||
retMsg["message"] = "Hostname must between 1 and " STR(WIFI_MAX_HOSTNAME_STRLEN) " characters long!";
|
retMsg["message"] = "Hostname must between 1 and " STR(WIFI_MAX_HOSTNAME_STRLEN) " characters long!";
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (NetworkSettings.NetworkMode() == network_mode::WiFi) {
|
if (NetworkSettings.NetworkMode() == network_mode::WiFi) {
|
||||||
if (root["ssid"].as<String>().length() == 0 || root["ssid"].as<String>().length() > WIFI_MAX_SSID_STRLEN) {
|
if (root["ssid"].as<String>().length() == 0 || root["ssid"].as<String>().length() > WIFI_MAX_SSID_STRLEN) {
|
||||||
retMsg["message"] = "SSID must between 1 and " STR(WIFI_MAX_SSID_STRLEN) " characters long!";
|
retMsg["message"] = "SSID must between 1 and " STR(WIFI_MAX_SSID_STRLEN) " characters long!";
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (root["password"].as<String>().length() > WIFI_MAX_PASSWORD_STRLEN - 1) {
|
if (root["password"].as<String>().length() > WIFI_MAX_PASSWORD_STRLEN - 1) {
|
||||||
retMsg["message"] = "Password must not be longer than " STR(WIFI_MAX_PASSWORD_STRLEN) " characters long!";
|
retMsg["message"] = "Password must not be longer than " STR(WIFI_MAX_PASSWORD_STRLEN) " characters long!";
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (root["aptimeout"].as<uint>() > 99999) {
|
if (root["aptimeout"].as<uint>() > 99999) {
|
||||||
retMsg["message"] = "ApTimeout must be a number between 0 and 99999!";
|
retMsg["message"] = "ApTimeout must be a number between 0 and 99999!";
|
||||||
retMsg["code"] = WebApiError::NetworkApTimeoutInvalid;
|
retMsg["code"] = WebApiError::NetworkApTimeoutInvalid;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,8 +198,7 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
WebApi.writeConfig(retMsg);
|
WebApi.writeConfig(retMsg);
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
|
|
||||||
NetworkSettings.enableAdminMode();
|
NetworkSettings.enableAdminMode();
|
||||||
NetworkSettings.applyConfig();
|
NetworkSettings.applyConfig();
|
||||||
|
|||||||
@ -63,8 +63,7 @@ void WebApiNtpClass::onNtpStatus(AsyncWebServerRequest* request)
|
|||||||
root["sun_isSunsetAvailable"] = SunPosition.isSunsetAvailable();
|
root["sun_isSunsetAvailable"] = SunPosition.isSunsetAvailable();
|
||||||
root["sun_isDayPeriod"] = SunPosition.isDayPeriod();
|
root["sun_isDayPeriod"] = SunPosition.isDayPeriod();
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiNtpClass::onNtpAdminGet(AsyncWebServerRequest* request)
|
void WebApiNtpClass::onNtpAdminGet(AsyncWebServerRequest* request)
|
||||||
@ -84,8 +83,7 @@ void WebApiNtpClass::onNtpAdminGet(AsyncWebServerRequest* request)
|
|||||||
root["latitude"] = config.Ntp.Latitude;
|
root["latitude"] = config.Ntp.Latitude;
|
||||||
root["sunsettype"] = config.Ntp.SunsetType;
|
root["sunsettype"] = config.Ntp.SunsetType;
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
||||||
@ -95,37 +93,12 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonDocument root;
|
||||||
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
retMsg["type"] = "warning";
|
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
|
||||||
retMsg["message"] = "No values found!";
|
|
||||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String json = request->getParam("data", true)->value();
|
|
||||||
|
|
||||||
if (json.length() > 1024) {
|
|
||||||
retMsg["message"] = "Data too large!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
|
||||||
const DeserializationError error = deserializeJson(root, json);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
retMsg["message"] = "Failed to parse data!";
|
|
||||||
retMsg["code"] = WebApiError::GenericParseError;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(root.containsKey("ntp_server")
|
if (!(root.containsKey("ntp_server")
|
||||||
&& root.containsKey("ntp_timezone")
|
&& root.containsKey("ntp_timezone")
|
||||||
@ -134,8 +107,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
|||||||
&& root.containsKey("sunsettype"))) {
|
&& root.containsKey("sunsettype"))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,8 +115,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "NTP Server must between 1 and " STR(NTP_MAX_SERVER_STRLEN) " characters long!";
|
retMsg["message"] = "NTP Server must between 1 and " STR(NTP_MAX_SERVER_STRLEN) " characters long!";
|
||||||
retMsg["code"] = WebApiError::NtpServerLength;
|
retMsg["code"] = WebApiError::NtpServerLength;
|
||||||
retMsg["param"]["max"] = NTP_MAX_SERVER_STRLEN;
|
retMsg["param"]["max"] = NTP_MAX_SERVER_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,8 +123,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Timezone must between 1 and " STR(NTP_MAX_TIMEZONE_STRLEN) " characters long!";
|
retMsg["message"] = "Timezone must between 1 and " STR(NTP_MAX_TIMEZONE_STRLEN) " characters long!";
|
||||||
retMsg["code"] = WebApiError::NtpTimezoneLength;
|
retMsg["code"] = WebApiError::NtpTimezoneLength;
|
||||||
retMsg["param"]["max"] = NTP_MAX_TIMEZONE_STRLEN;
|
retMsg["param"]["max"] = NTP_MAX_TIMEZONE_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,8 +131,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Timezone description must between 1 and " STR(NTP_MAX_TIMEZONEDESCR_STRLEN) " characters long!";
|
retMsg["message"] = "Timezone description must between 1 and " STR(NTP_MAX_TIMEZONEDESCR_STRLEN) " characters long!";
|
||||||
retMsg["code"] = WebApiError::NtpTimezoneDescriptionLength;
|
retMsg["code"] = WebApiError::NtpTimezoneDescriptionLength;
|
||||||
retMsg["param"]["max"] = NTP_MAX_TIMEZONEDESCR_STRLEN;
|
retMsg["param"]["max"] = NTP_MAX_TIMEZONEDESCR_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,8 +145,7 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
WebApi.writeConfig(retMsg);
|
WebApi.writeConfig(retMsg);
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
|
|
||||||
NtpSettings.setServer();
|
NtpSettings.setServer();
|
||||||
NtpSettings.setTimezone();
|
NtpSettings.setTimezone();
|
||||||
@ -208,8 +176,7 @@ void WebApiNtpClass::onNtpTimeGet(AsyncWebServerRequest* request)
|
|||||||
root["minute"] = timeinfo.tm_min;
|
root["minute"] = timeinfo.tm_min;
|
||||||
root["second"] = timeinfo.tm_sec;
|
root["second"] = timeinfo.tm_sec;
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
||||||
@ -219,37 +186,12 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonDocument root;
|
||||||
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
retMsg["type"] = "warning";
|
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
|
||||||
retMsg["message"] = "No values found!";
|
|
||||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String json = request->getParam("data", true)->value();
|
|
||||||
|
|
||||||
if (json.length() > 1024) {
|
|
||||||
retMsg["message"] = "Data too large!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
|
||||||
const DeserializationError error = deserializeJson(root, json);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
retMsg["message"] = "Failed to parse data!";
|
|
||||||
retMsg["code"] = WebApiError::GenericParseError;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(root.containsKey("year")
|
if (!(root.containsKey("year")
|
||||||
&& root.containsKey("month")
|
&& root.containsKey("month")
|
||||||
@ -259,8 +201,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
|||||||
&& root.containsKey("second"))) {
|
&& root.containsKey("second"))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,8 +210,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
|||||||
retMsg["code"] = WebApiError::NtpYearInvalid;
|
retMsg["code"] = WebApiError::NtpYearInvalid;
|
||||||
retMsg["param"]["min"] = 2022;
|
retMsg["param"]["min"] = 2022;
|
||||||
retMsg["param"]["max"] = 2100;
|
retMsg["param"]["max"] = 2100;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,8 +219,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
|||||||
retMsg["code"] = WebApiError::NtpMonthInvalid;
|
retMsg["code"] = WebApiError::NtpMonthInvalid;
|
||||||
retMsg["param"]["min"] = 1;
|
retMsg["param"]["min"] = 1;
|
||||||
retMsg["param"]["max"] = 12;
|
retMsg["param"]["max"] = 12;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,8 +228,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
|||||||
retMsg["code"] = WebApiError::NtpDayInvalid;
|
retMsg["code"] = WebApiError::NtpDayInvalid;
|
||||||
retMsg["param"]["min"] = 1;
|
retMsg["param"]["min"] = 1;
|
||||||
retMsg["param"]["max"] = 31;
|
retMsg["param"]["max"] = 31;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,8 +237,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
|||||||
retMsg["code"] = WebApiError::NtpHourInvalid;
|
retMsg["code"] = WebApiError::NtpHourInvalid;
|
||||||
retMsg["param"]["min"] = 0;
|
retMsg["param"]["min"] = 0;
|
||||||
retMsg["param"]["max"] = 23;
|
retMsg["param"]["max"] = 23;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,8 +246,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
|||||||
retMsg["code"] = WebApiError::NtpMinuteInvalid;
|
retMsg["code"] = WebApiError::NtpMinuteInvalid;
|
||||||
retMsg["param"]["min"] = 0;
|
retMsg["param"]["min"] = 0;
|
||||||
retMsg["param"]["max"] = 59;
|
retMsg["param"]["max"] = 59;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,8 +255,7 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
|||||||
retMsg["code"] = WebApiError::NtpSecondInvalid;
|
retMsg["code"] = WebApiError::NtpSecondInvalid;
|
||||||
retMsg["param"]["min"] = 0;
|
retMsg["param"]["min"] = 0;
|
||||||
retMsg["param"]["max"] = 59;
|
retMsg["param"]["max"] = 59;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,6 +276,5 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Time updated!";
|
retMsg["message"] = "Time updated!";
|
||||||
retMsg["code"] = WebApiError::NtpTimeUpdated;
|
retMsg["code"] = WebApiError::NtpTimeUpdated;
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,8 +40,7 @@ void WebApiPowerClass::onPowerStatus(AsyncWebServerRequest* request)
|
|||||||
root[inv->serialString()]["power_set_status"] = limitStatus;
|
root[inv->serialString()]["power_set_status"] = limitStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
|
void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
|
||||||
@ -51,63 +50,37 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonDocument root;
|
||||||
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
retMsg["type"] = "warning";
|
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
|
||||||
retMsg["message"] = "No values found!";
|
|
||||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String json = request->getParam("data", true)->value();
|
|
||||||
|
|
||||||
if (json.length() > 1024) {
|
|
||||||
retMsg["message"] = "Data too large!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
|
||||||
const DeserializationError error = deserializeJson(root, json);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
retMsg["message"] = "Failed to parse data!";
|
|
||||||
retMsg["code"] = WebApiError::GenericParseError;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(root.containsKey("serial")
|
if (!(root.containsKey("serial")
|
||||||
&& (root.containsKey("power")
|
&& (root.containsKey("power")
|
||||||
|| root.containsKey("restart")))) {
|
|| root.containsKey("restart")))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["serial"].as<uint64_t>() == 0) {
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
|
const uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
||||||
|
|
||||||
|
if (serial == 0) {
|
||||||
retMsg["message"] = "Serial must be a number > 0!";
|
retMsg["message"] = "Serial must be a number > 0!";
|
||||||
retMsg["code"] = WebApiError::PowerSerialZero;
|
retMsg["code"] = WebApiError::PowerSerialZero;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
|
||||||
auto inv = Hoymiles.getInverterBySerial(serial);
|
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||||
if (inv == nullptr) {
|
if (inv == nullptr) {
|
||||||
retMsg["message"] = "Invalid inverter specified!";
|
retMsg["message"] = "Invalid inverter specified!";
|
||||||
retMsg["code"] = WebApiError::PowerInvalidInverter;
|
retMsg["code"] = WebApiError::PowerInvalidInverter;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,6 +97,5 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Settings saved!";
|
retMsg["message"] = "Settings saved!";
|
||||||
retMsg["code"] = WebApiError::GenericSuccess;
|
retMsg["code"] = WebApiError::GenericSuccess;
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#include "NetworkSettings.h"
|
#include "NetworkSettings.h"
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
#include <Hoymiles.h>
|
#include <Hoymiles.h>
|
||||||
|
#include "__compiled_constants.h"
|
||||||
|
|
||||||
void WebApiPrometheusClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
void WebApiPrometheusClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
||||||
{
|
{
|
||||||
@ -29,7 +30,7 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques
|
|||||||
stream->print("# HELP opendtu_build Build info\n");
|
stream->print("# HELP opendtu_build Build info\n");
|
||||||
stream->print("# TYPE opendtu_build gauge\n");
|
stream->print("# TYPE opendtu_build gauge\n");
|
||||||
stream->printf("opendtu_build{name=\"%s\",id=\"%s\",version=\"%d.%d.%d\"} 1\n",
|
stream->printf("opendtu_build{name=\"%s\",id=\"%s\",version=\"%d.%d.%d\"} 1\n",
|
||||||
NetworkSettings.getHostname().c_str(), AUTO_GIT_HASH, CONFIG_VERSION >> 24 & 0xff, CONFIG_VERSION >> 16 & 0xff, CONFIG_VERSION >> 8 & 0xff);
|
NetworkSettings.getHostname().c_str(), __COMPILED_GIT_HASH__, CONFIG_VERSION >> 24 & 0xff, CONFIG_VERSION >> 16 & 0xff, CONFIG_VERSION >> 8 & 0xff);
|
||||||
|
|
||||||
stream->print("# HELP opendtu_platform Platform info\n");
|
stream->print("# HELP opendtu_platform Platform info\n");
|
||||||
stream->print("# TYPE opendtu_platform gauge\n");
|
stream->print("# TYPE opendtu_platform gauge\n");
|
||||||
@ -142,7 +143,7 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, const Stri
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CONFIG_T& config = Configuration.get();
|
const auto& config = Configuration.getInverterConfig(inv->serial());
|
||||||
|
|
||||||
const bool printHelp = (idx == 0 && channel == 0);
|
const bool printHelp = (idx == 0 && channel == 0);
|
||||||
if (printHelp) {
|
if (printHelp) {
|
||||||
@ -154,7 +155,7 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, const Stri
|
|||||||
idx,
|
idx,
|
||||||
inv->name(),
|
inv->name(),
|
||||||
channel,
|
channel,
|
||||||
config.Inverter[idx].channel[channel].Name);
|
config->channel[channel].Name);
|
||||||
|
|
||||||
if (printHelp) {
|
if (printHelp) {
|
||||||
stream->print("# HELP opendtu_MaxPower panel maximum output power\n");
|
stream->print("# HELP opendtu_MaxPower panel maximum output power\n");
|
||||||
@ -165,7 +166,7 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, const Stri
|
|||||||
idx,
|
idx,
|
||||||
inv->name(),
|
inv->name(),
|
||||||
channel,
|
channel,
|
||||||
config.Inverter[idx].channel[channel].MaxChannelPower);
|
config->channel[channel].MaxChannelPower);
|
||||||
|
|
||||||
if (printHelp) {
|
if (printHelp) {
|
||||||
stream->print("# HELP opendtu_YieldTotalOffset panel yield offset (for used inverters)\n");
|
stream->print("# HELP opendtu_YieldTotalOffset panel yield offset (for used inverters)\n");
|
||||||
@ -176,5 +177,5 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, const Stri
|
|||||||
idx,
|
idx,
|
||||||
inv->name(),
|
inv->name(),
|
||||||
channel,
|
channel,
|
||||||
config.Inverter[idx].channel[channel].YieldTotalOffset);
|
config->channel[channel].YieldTotalOffset);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,8 +31,7 @@ void WebApiSecurityClass::onSecurityGet(AsyncWebServerRequest* request)
|
|||||||
root["password"] = config.Security.Password;
|
root["password"] = config.Security.Password;
|
||||||
root["allow_readonly"] = config.Security.AllowReadonly;
|
root["allow_readonly"] = config.Security.AllowReadonly;
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
|
void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
|
||||||
@ -42,44 +41,18 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonDocument root;
|
||||||
|
if (!WebApi.parseRequestData(request, response, root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
retMsg["type"] = "warning";
|
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
|
||||||
retMsg["message"] = "No values found!";
|
|
||||||
retMsg["code"] = WebApiError::GenericNoValueFound;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String json = request->getParam("data", true)->value();
|
|
||||||
|
|
||||||
if (json.length() > 1024) {
|
|
||||||
retMsg["message"] = "Data too large!";
|
|
||||||
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
|
||||||
const DeserializationError error = deserializeJson(root, json);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
retMsg["message"] = "Failed to parse data!";
|
|
||||||
retMsg["code"] = WebApiError::GenericParseError;
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!root.containsKey("password")
|
if (!root.containsKey("password")
|
||||||
&& root.containsKey("allow_readonly")) {
|
&& root.containsKey("allow_readonly")) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,8 +60,7 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Password must between 8 and " STR(WIFI_MAX_PASSWORD_STRLEN) " characters long!";
|
retMsg["message"] = "Password must between 8 and " STR(WIFI_MAX_PASSWORD_STRLEN) " characters long!";
|
||||||
retMsg["code"] = WebApiError::SecurityPasswordLength;
|
retMsg["code"] = WebApiError::SecurityPasswordLength;
|
||||||
retMsg["param"]["max"] = WIFI_MAX_PASSWORD_STRLEN;
|
retMsg["param"]["max"] = WIFI_MAX_PASSWORD_STRLEN;
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,8 +70,7 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
WebApi.writeConfig(retMsg);
|
WebApi.writeConfig(retMsg);
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request)
|
void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request)
|
||||||
@ -114,6 +85,5 @@ void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request)
|
|||||||
retMsg["message"] = "Authentication successful!";
|
retMsg["message"] = "Authentication successful!";
|
||||||
retMsg["code"] = WebApiError::SecurityAuthSuccess;
|
retMsg["code"] = WebApiError::SecurityAuthSuccess;
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,10 +11,7 @@
|
|||||||
#include <Hoymiles.h>
|
#include <Hoymiles.h>
|
||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
#include <ResetReason.h>
|
#include <ResetReason.h>
|
||||||
|
#include "__compiled_constants.h"
|
||||||
#ifndef AUTO_GIT_HASH
|
|
||||||
#define AUTO_GIT_HASH ""
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void WebApiSysstatusClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
void WebApiSysstatusClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
||||||
{
|
{
|
||||||
@ -64,7 +61,7 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request)
|
|||||||
char version[16];
|
char version[16];
|
||||||
snprintf(version, sizeof(version), "%d.%d.%d", CONFIG_VERSION >> 24 & 0xff, CONFIG_VERSION >> 16 & 0xff, CONFIG_VERSION >> 8 & 0xff);
|
snprintf(version, sizeof(version), "%d.%d.%d", CONFIG_VERSION >> 24 & 0xff, CONFIG_VERSION >> 16 & 0xff, CONFIG_VERSION >> 8 & 0xff);
|
||||||
root["config_version"] = version;
|
root["config_version"] = version;
|
||||||
root["git_hash"] = AUTO_GIT_HASH;
|
root["git_hash"] = __COMPILED_GIT_HASH__;
|
||||||
root["pioenv"] = PIOENV;
|
root["pioenv"] = PIOENV;
|
||||||
|
|
||||||
root["uptime"] = esp_timer_get_time() / 1000000;
|
root["uptime"] = esp_timer_get_time() / 1000000;
|
||||||
@ -76,6 +73,5 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request)
|
|||||||
root["cmt_configured"] = PinMapping.isValidCmt2300Config();
|
root["cmt_configured"] = PinMapping.isValidCmt2300Config();
|
||||||
root["cmt_connected"] = Hoymiles.getRadioCmt()->isConnected();
|
root["cmt_connected"] = Hoymiles.getRadioCmt()->isConnected();
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,19 +73,20 @@ void WebApiWsLiveClass::sendDataTaskCb()
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
DynamicJsonDocument root(4096);
|
JsonDocument root;
|
||||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
JsonVariant var = root;
|
JsonVariant var = root;
|
||||||
|
|
||||||
auto invArray = var.createNestedArray("inverters");
|
auto invArray = var["inverters"].to<JsonArray>();
|
||||||
auto invObject = invArray.createNestedObject();
|
auto invObject = invArray.add<JsonObject>();
|
||||||
|
|
||||||
generateCommonJsonResponse(var);
|
generateCommonJsonResponse(var);
|
||||||
generateInverterCommonJsonResponse(invObject, inv);
|
generateInverterCommonJsonResponse(invObject, inv);
|
||||||
generateInverterChannelJsonResponse(invObject, inv);
|
generateInverterChannelJsonResponse(invObject, inv);
|
||||||
|
|
||||||
|
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
String buffer;
|
String buffer;
|
||||||
serializeJson(root, buffer);
|
serializeJson(root, buffer);
|
||||||
|
|
||||||
@ -101,12 +102,12 @@ void WebApiWsLiveClass::sendDataTaskCb()
|
|||||||
|
|
||||||
void WebApiWsLiveClass::generateCommonJsonResponse(JsonVariant& root)
|
void WebApiWsLiveClass::generateCommonJsonResponse(JsonVariant& root)
|
||||||
{
|
{
|
||||||
JsonObject totalObj = root.createNestedObject("total");
|
auto totalObj = root["total"].to<JsonObject>();
|
||||||
addTotalField(totalObj, "Power", Datastore.getTotalAcPowerEnabled(), "W", Datastore.getTotalAcPowerDigits());
|
addTotalField(totalObj, "Power", Datastore.getTotalAcPowerEnabled(), "W", Datastore.getTotalAcPowerDigits());
|
||||||
addTotalField(totalObj, "YieldDay", Datastore.getTotalAcYieldDayEnabled(), "Wh", Datastore.getTotalAcYieldDayDigits());
|
addTotalField(totalObj, "YieldDay", Datastore.getTotalAcYieldDayEnabled(), "Wh", Datastore.getTotalAcYieldDayDigits());
|
||||||
addTotalField(totalObj, "YieldTotal", Datastore.getTotalAcYieldTotalEnabled(), "kWh", Datastore.getTotalAcYieldTotalDigits());
|
addTotalField(totalObj, "YieldTotal", Datastore.getTotalAcYieldTotalEnabled(), "kWh", Datastore.getTotalAcYieldTotalDigits());
|
||||||
|
|
||||||
JsonObject hintObj = root.createNestedObject("hints");
|
JsonObject hintObj = root["hints"].to<JsonObject>();
|
||||||
struct tm timeinfo;
|
struct tm timeinfo;
|
||||||
hintObj["time_sync"] = !getLocalTime(&timeinfo, 5);
|
hintObj["time_sync"] = !getLocalTime(&timeinfo, 5);
|
||||||
hintObj["radio_problem"] = (Hoymiles.getRadioNrf()->isInitialized() && (!Hoymiles.getRadioNrf()->isConnected() || !Hoymiles.getRadioNrf()->isPVariant())) || (Hoymiles.getRadioCmt()->isInitialized() && (!Hoymiles.getRadioCmt()->isConnected()));
|
hintObj["radio_problem"] = (Hoymiles.getRadioNrf()->isInitialized() && (!Hoymiles.getRadioNrf()->isConnected() || !Hoymiles.getRadioNrf()->isPVariant())) || (Hoymiles.getRadioCmt()->isInitialized() && (!Hoymiles.getRadioCmt()->isConnected()));
|
||||||
@ -144,7 +145,7 @@ void WebApiWsLiveClass::generateInverterChannelJsonResponse(JsonObject& root, st
|
|||||||
|
|
||||||
// Loop all channels
|
// Loop all channels
|
||||||
for (auto& t : inv->Statistics()->getChannelTypes()) {
|
for (auto& t : inv->Statistics()->getChannelTypes()) {
|
||||||
JsonObject chanTypeObj = root.createNestedObject(inv->Statistics()->getChannelTypeName(t));
|
auto chanTypeObj = root[inv->Statistics()->getChannelTypeName(t)].to<JsonObject>();
|
||||||
for (auto& c : inv->Statistics()->getChannelsByType(t)) {
|
for (auto& c : inv->Statistics()->getChannelsByType(t)) {
|
||||||
if (t == TYPE_DC) {
|
if (t == TYPE_DC) {
|
||||||
chanTypeObj[String(static_cast<uint8_t>(c))]["name"]["u"] = inv_cfg->channel[c].Name;
|
chanTypeObj[String(static_cast<uint8_t>(c))]["name"]["u"] = inv_cfg->channel[c].Name;
|
||||||
@ -221,21 +222,15 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096);
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
auto& root = response->getRoot();
|
auto& root = response->getRoot();
|
||||||
|
auto invArray = root["inverters"].to<JsonArray>();
|
||||||
JsonArray invArray = root.createNestedArray("inverters");
|
auto serial = WebApi.parseSerialFromRequest(request);
|
||||||
|
|
||||||
uint64_t serial = 0;
|
|
||||||
if (request->hasParam("inv")) {
|
|
||||||
String s = request->getParam("inv")->value();
|
|
||||||
serial = strtoll(s.c_str(), NULL, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serial > 0) {
|
if (serial > 0) {
|
||||||
auto inv = Hoymiles.getInverterBySerial(serial);
|
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||||
if (inv != nullptr) {
|
if (inv != nullptr) {
|
||||||
JsonObject invObject = invArray.createNestedObject();
|
JsonObject invObject = invArray.add<JsonObject>();
|
||||||
generateInverterCommonJsonResponse(invObject, inv);
|
generateInverterCommonJsonResponse(invObject, inv);
|
||||||
generateInverterChannelJsonResponse(invObject, inv);
|
generateInverterChannelJsonResponse(invObject, inv);
|
||||||
}
|
}
|
||||||
@ -247,15 +242,14 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonObject invObject = invArray.createNestedObject();
|
JsonObject invObject = invArray.add<JsonObject>();
|
||||||
generateInverterCommonJsonResponse(invObject, inv);
|
generateInverterCommonJsonResponse(invObject, inv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generateCommonJsonResponse(root);
|
generateCommonJsonResponse(root);
|
||||||
|
|
||||||
response->setLength();
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
request->send(response);
|
|
||||||
|
|
||||||
} catch (const std::bad_alloc& bad_alloc) {
|
} catch (const std::bad_alloc& bad_alloc) {
|
||||||
MessageOutput.printf("Call to /api/livedata/status temporarely out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
MessageOutput.printf("Call to /api/livedata/status temporarely out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
/* eslint-env node */
|
|
||||||
require('@rushstack/eslint-patch/modern-module-resolution')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
'extends': [
|
|
||||||
'plugin:vue/vue3-essential',
|
|
||||||
'eslint:recommended',
|
|
||||||
'@vue/eslint-config-typescript'
|
|
||||||
],
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 'latest'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
36
webapp/eslint.config.js
Normal file
36
webapp/eslint.config.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
import js from "@eslint/js";
|
||||||
|
import pluginVue from 'eslint-plugin-vue'
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default [
|
||||||
|
js.configs.recommended,
|
||||||
|
...pluginVue.configs['flat/essential'],
|
||||||
|
...compat.extends("@vue/eslint-config-typescript/recommended"),
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"**/*.vue",
|
||||||
|
"**/*.js",
|
||||||
|
"**/*.jsx",
|
||||||
|
"**/*.cjs",
|
||||||
|
"**/*.mjs",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
"**/*.cts",
|
||||||
|
"**/*.mts",
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 'latest'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -9,41 +9,40 @@
|
|||||||
"preview": "vite preview --port 4173",
|
"preview": "vite preview --port 4173",
|
||||||
"build-only": "vite build",
|
"build-only": "vite build",
|
||||||
"type-check": "vue-tsc --noEmit",
|
"type-check": "vue-tsc --noEmit",
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"bootstrap": "^5.3.2",
|
"bootstrap": "^5.3.3",
|
||||||
"bootstrap-icons-vue": "^1.11.3",
|
"bootstrap-icons-vue": "^1.11.3",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"sortablejs": "^1.15.2",
|
"sortablejs": "^1.15.2",
|
||||||
"spark-md5": "^3.0.2",
|
"spark-md5": "^3.0.2",
|
||||||
"vue": "^3.4.19",
|
"vue": "^3.4.26",
|
||||||
"vue-i18n": "^9.9.1",
|
"vue-i18n": "^9.13.1",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@intlify/unplugin-vue-i18n": "^2.0.0",
|
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||||
"@rushstack/eslint-patch": "^1.7.2",
|
"@tsconfig/node18": "^18.2.4",
|
||||||
"@tsconfig/node18": "^18.2.2",
|
|
||||||
"@types/bootstrap": "^5.2.10",
|
"@types/bootstrap": "^5.2.10",
|
||||||
"@types/node": "^20.11.19",
|
"@types/node": "^20.12.10",
|
||||||
"@types/pulltorefreshjs": "^0.1.7",
|
"@types/pulltorefreshjs": "^0.1.7",
|
||||||
"@types/sortablejs": "^1.15.7",
|
"@types/sortablejs": "^1.15.8",
|
||||||
"@types/spark-md5": "^3.0.4",
|
"@types/spark-md5": "^3.0.4",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@vue/eslint-config-typescript": "^12.0.0",
|
"@vue/eslint-config-typescript": "^13.0.0",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^9.2.0",
|
||||||
"eslint-plugin-vue": "^9.21.1",
|
"eslint-plugin-vue": "^9.25.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"pulltorefreshjs": "^0.1.22",
|
"pulltorefreshjs": "^0.1.22",
|
||||||
"sass": "^1.71.0",
|
"sass": "^1.76.0",
|
||||||
"terser": "^5.27.1",
|
"terser": "^5.31.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.4.5",
|
||||||
"vite": "^5.1.3",
|
"vite": "^5.2.11",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-css-injected-by-js": "^3.4.0",
|
"vite-plugin-css-injected-by-js": "^3.5.1",
|
||||||
"vue-tsc": "^1.8.27"
|
"vue-tsc": "^2.0.16"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -180,7 +180,7 @@
|
|||||||
"America/Santiago":"<-04>4<-03>,M9.1.6/24,M4.1.6/24",
|
"America/Santiago":"<-04>4<-03>,M9.1.6/24,M4.1.6/24",
|
||||||
"America/Santo_Domingo":"AST4",
|
"America/Santo_Domingo":"AST4",
|
||||||
"America/Sao_Paulo":"<-03>3",
|
"America/Sao_Paulo":"<-03>3",
|
||||||
"America/Scoresbysund":"<-01>1<+00>,M3.5.0/0,M10.5.0/1",
|
"America/Scoresbysund":"<-02>2<-01>,M3.5.0/-1,M10.5.0/0",
|
||||||
"America/Sitka":"AKST9AKDT,M3.2.0,M11.1.0",
|
"America/Sitka":"AKST9AKDT,M3.2.0,M11.1.0",
|
||||||
"America/St_Barthelemy":"AST4",
|
"America/St_Barthelemy":"AST4",
|
||||||
"America/St_Johns":"NST3:30NDT,M3.2.0,M11.1.0",
|
"America/St_Johns":"NST3:30NDT,M3.2.0,M11.1.0",
|
||||||
@ -200,7 +200,7 @@
|
|||||||
"America/Winnipeg":"CST6CDT,M3.2.0,M11.1.0",
|
"America/Winnipeg":"CST6CDT,M3.2.0,M11.1.0",
|
||||||
"America/Yakutat":"AKST9AKDT,M3.2.0,M11.1.0",
|
"America/Yakutat":"AKST9AKDT,M3.2.0,M11.1.0",
|
||||||
"America/Yellowknife":"MST7MDT,M3.2.0,M11.1.0",
|
"America/Yellowknife":"MST7MDT,M3.2.0,M11.1.0",
|
||||||
"Antarctica/Casey":"<+11>-11",
|
"Antarctica/Casey":"<+08>-8",
|
||||||
"Antarctica/Davis":"<+07>-7",
|
"Antarctica/Davis":"<+07>-7",
|
||||||
"Antarctica/DumontDUrville":"<+10>-10",
|
"Antarctica/DumontDUrville":"<+10>-10",
|
||||||
"Antarctica/Macquarie":"AEST-10AEDT,M10.1.0,M4.1.0/3",
|
"Antarctica/Macquarie":"AEST-10AEDT,M10.1.0,M4.1.0/3",
|
||||||
@ -210,10 +210,10 @@
|
|||||||
"Antarctica/Rothera":"<-03>3",
|
"Antarctica/Rothera":"<-03>3",
|
||||||
"Antarctica/Syowa":"<+03>-3",
|
"Antarctica/Syowa":"<+03>-3",
|
||||||
"Antarctica/Troll":"<+00>0<+02>-2,M3.5.0/1,M10.5.0/3",
|
"Antarctica/Troll":"<+00>0<+02>-2,M3.5.0/1,M10.5.0/3",
|
||||||
"Antarctica/Vostok":"<+06>-6",
|
"Antarctica/Vostok":"<+05>-5",
|
||||||
"Arctic/Longyearbyen":"CET-1CEST,M3.5.0,M10.5.0/3",
|
"Arctic/Longyearbyen":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||||
"Asia/Aden":"<+03>-3",
|
"Asia/Aden":"<+03>-3",
|
||||||
"Asia/Almaty":"<+06>-6",
|
"Asia/Almaty":"<+05>-5",
|
||||||
"Asia/Amman":"<+03>-3",
|
"Asia/Amman":"<+03>-3",
|
||||||
"Asia/Anadyr":"<+12>-12",
|
"Asia/Anadyr":"<+12>-12",
|
||||||
"Asia/Aqtau":"<+05>-5",
|
"Asia/Aqtau":"<+05>-5",
|
||||||
|
|||||||
@ -48,15 +48,14 @@ export default defineComponent({
|
|||||||
showReload: { type: Boolean, required: false, default: false },
|
showReload: { type: Boolean, required: false, default: false },
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
var self = this;
|
|
||||||
console.log("init");
|
console.log("init");
|
||||||
PullToRefresh.init({
|
PullToRefresh.init({
|
||||||
mainElement: 'body', // above which element?
|
mainElement: 'body', // above which element?
|
||||||
instructionsPullToRefresh: this.$t('base.Pull'),
|
instructionsPullToRefresh: this.$t('base.Pull'),
|
||||||
instructionsReleaseToRefresh: this.$t('base.Release'),
|
instructionsReleaseToRefresh: this.$t('base.Release'),
|
||||||
instructionsRefreshing: this.$t('base.Refreshing'),
|
instructionsRefreshing: this.$t('base.Refreshing'),
|
||||||
onRefresh: function() {
|
onRefresh: () => {
|
||||||
self.$emit('reload');
|
this.$emit('reload');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export default defineComponent({
|
|||||||
_countDownTimeout = undefined;
|
_countDownTimeout = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
var countDown = ref();
|
const countDown = ref();
|
||||||
watch(() => props.modelValue, () => {
|
watch(() => props.modelValue, () => {
|
||||||
countDown.value = parseCountDown(props.modelValue);
|
countDown.value = parseCountDown(props.modelValue);
|
||||||
});
|
});
|
||||||
@ -116,4 +116,4 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -76,14 +76,14 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
productionYear() {
|
productionYear() {
|
||||||
return() => {
|
return() => {
|
||||||
return ((parseInt(this.devInfoList.serial.toString(), 16) >> (7 * 4)) & 0xF) + 2014;
|
return ((parseInt(this.devInfoList.serial, 16) >> (7 * 4)) & 0xF) + 2014;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
productionWeek() {
|
productionWeek() {
|
||||||
return() => {
|
return() => {
|
||||||
return ((parseInt(this.devInfoList.serial.toString(), 16) >> (5 * 4)) & 0xFF).toString(16);
|
return ((parseInt(this.devInfoList.serial, 16) >> (5 * 4)) & 0xFF).toString(16);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -28,17 +28,20 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('firmwareinfo.FirmwareUpdate') }}</th>
|
<th>{{ $t('firmwareinfo.FirmwareUpdate') }}</th>
|
||||||
<td v-if="modelAllowVersionInfo">
|
<td>
|
||||||
<a :href="systemStatus.update_url" target="_blank" v-tooltip
|
<div class="form-check form-check-inline form-switch">
|
||||||
:title="$t('firmwareinfo.FirmwareUpdateHint')">
|
|
||||||
<span class="badge" :class="systemStatus.update_status">
|
|
||||||
{{ systemStatus.update_text }}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td v-else>
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="modelAllowVersionInfo" class="form-check-input" type="checkbox" role="switch" v-tooltip :title="$t('firmwareinfo.FrmwareUpdateAllow')" />
|
<input v-model="modelAllowVersionInfo" class="form-check-input" type="checkbox" role="switch" v-tooltip :title="$t('firmwareinfo.FrmwareUpdateAllow')" />
|
||||||
|
<label class="form-check-label">
|
||||||
|
<a v-if="modelAllowVersionInfo && systemStatus.update_url !== undefined" :href="systemStatus.update_url" target="_blank" v-tooltip
|
||||||
|
:title="$t('firmwareinfo.FirmwareUpdateHint')">
|
||||||
|
<span class="badge" :class="systemStatus.update_status">
|
||||||
|
{{ systemStatus.update_text }}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<span v-else-if="modelAllowVersionInfo" class="badge" :class="systemStatus.update_status">
|
||||||
|
{{ systemStatus.update_text }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -80,10 +83,10 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
modelAllowVersionInfo: {
|
modelAllowVersionInfo: {
|
||||||
get(): any {
|
get(): boolean {
|
||||||
return !!this.allowVersionInfo;
|
return !!this.allowVersionInfo;
|
||||||
},
|
},
|
||||||
set(value: any) {
|
set(value: boolean) {
|
||||||
this.$emit('update:allowVersionInfo', value);
|
this.$emit('update:allowVersionInfo', value);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -83,10 +83,12 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
model: {
|
model: {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
get(): any {
|
get(): any {
|
||||||
if (this.type === 'checkbox') return !!this.modelValue;
|
if (this.type === 'checkbox') return !!this.modelValue;
|
||||||
return this.modelValue;
|
return this.modelValue;
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
set(value: any) {
|
set(value: any) {
|
||||||
this.$emit('update:modelValue', value);
|
this.$emit('update:modelValue', value);
|
||||||
},
|
},
|
||||||
@ -112,4 +114,4 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
116
webapp/src/components/InputSerial.vue
Normal file
116
webapp/src/components/InputSerial.vue
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<input v-model="inputSerial" type="text" :id="id" :required="required" class="form-control" :class="inputClass" />
|
||||||
|
<BootstrapAlert show :variant="formatShow" v-if="formatHint">{{ formatHint }}</BootstrapAlert>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import BootstrapAlert from './BootstrapAlert.vue';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
const chars32 = '0123456789ABCDEFGHJKLMNPRSTUVWXY';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
BootstrapAlert,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
'modelValue': { type: [String, Number], required: true },
|
||||||
|
'id': String,
|
||||||
|
'inputClass': String,
|
||||||
|
'required': Boolean,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
inputSerial: "",
|
||||||
|
formatHint: "",
|
||||||
|
formatShow: "info",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
model: {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
get(): any {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
set(value: any) {
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue: function (val) {
|
||||||
|
this.inputSerial = val;
|
||||||
|
},
|
||||||
|
inputSerial: function (val) {
|
||||||
|
const serial = val.toString().toUpperCase(); // Convert to lowercase for case-insensitivity
|
||||||
|
|
||||||
|
if (serial == "") {
|
||||||
|
this.formatHint = "";
|
||||||
|
this.model = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.formatShow = "info";
|
||||||
|
|
||||||
|
// Contains only numbers
|
||||||
|
if (/^1{1}[\dA-F]{11}$/.test(serial)) {
|
||||||
|
this.model = serial;
|
||||||
|
this.formatHint = this.$t('inputserial.format_hoymiles');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains numbers and hex characters but at least one number
|
||||||
|
else if (/^(?=.*\d)[\dA-F]{12}$/.test(serial)) {
|
||||||
|
this.model = serial;
|
||||||
|
this.formatHint = this.$t('inputserial.format_converted');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has format: xxxxxxxxx-xxx
|
||||||
|
else if (/^((A01)|(A11)|(A21))[\dA-HJ-NR-YP]{6}-[\dA-HJ-NP-Z]{3}$/.test(serial)) {
|
||||||
|
if (this.checkHerfChecksum(serial)) {
|
||||||
|
this.model = this.convertHerfToHoy(serial);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.formatHint = this.$t('inputserial.format_herf_valid', { serial: this.model });
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.formatHint = this.$t('inputserial.format_herf_invalid');
|
||||||
|
this.formatShow = "danger";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any other format
|
||||||
|
} else {
|
||||||
|
this.formatHint = this.$t('inputserial.format_unknown');
|
||||||
|
this.formatShow = "danger";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
checkHerfChecksum(sn: string) {
|
||||||
|
const chars64 = 'HMFLGW5XC301234567899Z67YRT2S8ABCDEFGHJKDVEJ4KQPUALMNPRSTUVWXYNB';
|
||||||
|
|
||||||
|
const checksum = sn.substring(sn.indexOf("-") + 1);
|
||||||
|
const serial = sn.substring(0, sn.indexOf("-"));
|
||||||
|
|
||||||
|
const first_char = '1';
|
||||||
|
const i = chars32.indexOf(first_char)
|
||||||
|
const sum1: number = Array.from(serial).reduce((sum, c) => sum + c.charCodeAt(0), 0) & 31;
|
||||||
|
const sum2: number = Array.from(serial).reduce((sum, c) => sum + chars32.indexOf(c), 0) & 31;
|
||||||
|
const ext = first_char + chars64[sum1 + i] + chars64[sum2 + i];
|
||||||
|
|
||||||
|
return checksum == ext;
|
||||||
|
},
|
||||||
|
convertHerfToHoy(sn: string) {
|
||||||
|
let sn_int: bigint = 0n;
|
||||||
|
|
||||||
|
for (let i = 0; i < 9; i++) {
|
||||||
|
const pos: bigint = BigInt(chars32.indexOf(sn[i].toUpperCase()));
|
||||||
|
const shift: bigint = BigInt(42 - 5 * i - (i <= 2 ? 0 : 2));
|
||||||
|
sn_int |= (pos << shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sn_int.toString(16);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -146,8 +146,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
isEaster() {
|
isEaster() {
|
||||||
const easter = this.getEasterSunday(this.now.getFullYear());
|
const easter = this.getEasterSunday(this.now.getFullYear());
|
||||||
var easterStart = new Date(easter);
|
const easterStart = new Date(easter);
|
||||||
var easterEnd = new Date(easter);
|
const easterEnd = new Date(easter);
|
||||||
easterStart.setDate(easterStart.getDate() - 2);
|
easterStart.setDate(easterStart.getDate() - 2);
|
||||||
easterEnd.setDate(easterEnd.getDate() + 1);
|
easterEnd.setDate(easterEnd.getDate() + 1);
|
||||||
return this.now >= easterStart && this.now < easterEnd;
|
return this.now >= easterStart && this.now < easterEnd;
|
||||||
@ -170,18 +170,18 @@ export default defineComponent({
|
|||||||
this.$refs.navbarCollapse && (this.$refs.navbarCollapse as HTMLElement).classList.remove("show");
|
this.$refs.navbarCollapse && (this.$refs.navbarCollapse as HTMLElement).classList.remove("show");
|
||||||
},
|
},
|
||||||
getEasterSunday(year: number): Date {
|
getEasterSunday(year: number): Date {
|
||||||
var f = Math.floor;
|
const f = Math.floor;
|
||||||
var G = year % 19;
|
const G = year % 19;
|
||||||
var C = f(year / 100);
|
const C = f(year / 100);
|
||||||
var H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30;
|
const H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30;
|
||||||
var I = H - f(H / 28) * (1 - f(29 / (H + 1)) * f((21 - G) / 11));
|
const I = H - f(H / 28) * (1 - f(29 / (H + 1)) * f((21 - G) / 11));
|
||||||
var J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7;
|
const J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7;
|
||||||
var L = I - J;
|
const L = I - J;
|
||||||
var month = 3 + f((L + 40) / 44);
|
const month = 3 + f((L + 40) / 44);
|
||||||
var day = L + 28 - 31 * f(month / 4);
|
const day = L + 28 - 31 * f(month / 4);
|
||||||
|
|
||||||
return new Date(year, month - 1, day);
|
return new Date(year, month - 1, day);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -84,9 +84,11 @@ export default defineComponent({
|
|||||||
let comCur = 999999;
|
let comCur = 999999;
|
||||||
|
|
||||||
if (this.selectedPinAssignment && category in this.selectedPinAssignment) {
|
if (this.selectedPinAssignment && category in this.selectedPinAssignment) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
comSel = (this.selectedPinAssignment as any)[category][prop];
|
comSel = (this.selectedPinAssignment as any)[category][prop];
|
||||||
}
|
}
|
||||||
if (this.currentPinAssignment && category in this.currentPinAssignment) {
|
if (this.currentPinAssignment && category in this.currentPinAssignment) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
comCur = (this.currentPinAssignment as any)[category][prop];
|
comCur = (this.currentPinAssignment as any)[category][prop];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,6 +32,9 @@
|
|||||||
"Release": "Loslassen zum Aktualisieren",
|
"Release": "Loslassen zum Aktualisieren",
|
||||||
"Close": "Schließen"
|
"Close": "Schließen"
|
||||||
},
|
},
|
||||||
|
"Error": {
|
||||||
|
"Oops": "Oops!"
|
||||||
|
},
|
||||||
"localeswitcher": {
|
"localeswitcher": {
|
||||||
"Dark": "Dunkel",
|
"Dark": "Dunkel",
|
||||||
"Light": "Hell",
|
"Light": "Hell",
|
||||||
@ -618,5 +621,12 @@
|
|||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"ValueSelected": "Ausgewählt",
|
"ValueSelected": "Ausgewählt",
|
||||||
"ValueActive": "Aktiv"
|
"ValueActive": "Aktiv"
|
||||||
|
},
|
||||||
|
"inputserial": {
|
||||||
|
"format_hoymiles": "Hoymiles Seriennummerformat",
|
||||||
|
"format_converted": "Bereits konvertierte Seriennummer",
|
||||||
|
"format_herf_valid": "E-Star HERF Format (wird konvertiert gespeichert): {serial}",
|
||||||
|
"format_herf_invalid": "E-Star HERF Format: Ungültige Prüfsumme",
|
||||||
|
"format_unknown": "Unbekanntes Format"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,9 @@
|
|||||||
"Release": "Release to refresh",
|
"Release": "Release to refresh",
|
||||||
"Close": "Close"
|
"Close": "Close"
|
||||||
},
|
},
|
||||||
|
"Error": {
|
||||||
|
"Oops": "Oops!"
|
||||||
|
},
|
||||||
"localeswitcher": {
|
"localeswitcher": {
|
||||||
"Dark": "Dark",
|
"Dark": "Dark",
|
||||||
"Light": "Light",
|
"Light": "Light",
|
||||||
@ -619,5 +622,12 @@
|
|||||||
"Number": "Number",
|
"Number": "Number",
|
||||||
"ValueSelected": "Selected",
|
"ValueSelected": "Selected",
|
||||||
"ValueActive": "Active"
|
"ValueActive": "Active"
|
||||||
|
},
|
||||||
|
"inputserial": {
|
||||||
|
"format_hoymiles": "Hoymiles serial number format",
|
||||||
|
"format_converted": "Already converted serial number",
|
||||||
|
"format_herf_valid": "E-Star HERF format (will be saved converted): {serial}",
|
||||||
|
"format_herf_invalid": "E-Star HERF format: Invalid checksum",
|
||||||
|
"format_unknown": "Unknown format"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,9 @@
|
|||||||
"Release": "Release to refresh",
|
"Release": "Release to refresh",
|
||||||
"Close": "Fermer"
|
"Close": "Fermer"
|
||||||
},
|
},
|
||||||
|
"Error": {
|
||||||
|
"Oops": "Oops!"
|
||||||
|
},
|
||||||
"localeswitcher": {
|
"localeswitcher": {
|
||||||
"Dark": "Sombre",
|
"Dark": "Sombre",
|
||||||
"Light": "Clair",
|
"Light": "Clair",
|
||||||
@ -618,5 +621,12 @@
|
|||||||
"Name": "Nom",
|
"Name": "Nom",
|
||||||
"ValueSelected": "Sélectionné",
|
"ValueSelected": "Sélectionné",
|
||||||
"ValueActive": "Activé"
|
"ValueActive": "Activé"
|
||||||
|
},
|
||||||
|
"inputserial": {
|
||||||
|
"format_hoymiles": "Hoymiles serial number format",
|
||||||
|
"format_converted": "Already converted serial number",
|
||||||
|
"format_herf_valid": "E-Star HERF format (will be saved converted): {serial}",
|
||||||
|
"format_herf_invalid": "E-Star HERF format: Invalid checksum",
|
||||||
|
"format_unknown": "Unknown format"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import ConfigAdminView from '@/views/ConfigAdminView.vue';
|
|||||||
import ConsoleInfoView from '@/views/ConsoleInfoView.vue';
|
import ConsoleInfoView from '@/views/ConsoleInfoView.vue';
|
||||||
import DeviceAdminView from '@/views/DeviceAdminView.vue'
|
import DeviceAdminView from '@/views/DeviceAdminView.vue'
|
||||||
import DtuAdminView from '@/views/DtuAdminView.vue';
|
import DtuAdminView from '@/views/DtuAdminView.vue';
|
||||||
|
import ErrorView from '@/views/ErrorView.vue';
|
||||||
import FirmwareUpgradeView from '@/views/FirmwareUpgradeView.vue';
|
import FirmwareUpgradeView from '@/views/FirmwareUpgradeView.vue';
|
||||||
import HomeView from '@/views/HomeView.vue';
|
import HomeView from '@/views/HomeView.vue';
|
||||||
import InverterAdminView from '@/views/InverterAdminView.vue';
|
import InverterAdminView from '@/views/InverterAdminView.vue';
|
||||||
@ -32,6 +33,11 @@ const router = createRouter({
|
|||||||
name: 'Login',
|
name: 'Login',
|
||||||
component: LoginView
|
component: LoginView
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/error?status=:status&message=:message',
|
||||||
|
name: 'Error',
|
||||||
|
component: ErrorView
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: '/about',
|
||||||
name: 'About',
|
name: 'About',
|
||||||
@ -115,4 +121,4 @@ const router = createRouter({
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
export interface DevInfoStatus {
|
export interface DevInfoStatus {
|
||||||
serial: number;
|
serial: string;
|
||||||
valid_data: boolean;
|
valid_data: boolean;
|
||||||
fw_bootloader_version: number;
|
fw_bootloader_version: number;
|
||||||
fw_build_version: number;
|
fw_build_version: number;
|
||||||
@ -8,4 +8,4 @@ export interface DevInfoStatus {
|
|||||||
hw_version: number;
|
hw_version: number;
|
||||||
hw_model_name: string;
|
hw_model_name: string;
|
||||||
max_power: number;
|
max_power: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ export interface InverterChannel {
|
|||||||
|
|
||||||
export interface Inverter {
|
export interface Inverter {
|
||||||
id: string;
|
id: string;
|
||||||
serial: number;
|
serial: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
order: number;
|
order: number;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
export interface LimitConfig {
|
export interface LimitConfig {
|
||||||
serial: number;
|
serial: string;
|
||||||
limit_value: number;
|
limit_value: number;
|
||||||
limit_type: number;
|
limit_type: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export interface InverterStatistics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Inverter {
|
export interface Inverter {
|
||||||
serial: number;
|
serial: string;
|
||||||
name: string;
|
name: string;
|
||||||
order: number;
|
order: number;
|
||||||
data_age: number;
|
data_age: number;
|
||||||
@ -53,4 +53,4 @@ export interface LiveData {
|
|||||||
inverters: Inverter[];
|
inverters: Inverter[];
|
||||||
total: Total;
|
total: Total;
|
||||||
hints: Hints;
|
hints: Hints;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,7 +41,7 @@ export function isLoggedIn(): boolean {
|
|||||||
return (localStorage.getItem('user') != null);
|
return (localStorage.getItem('user') != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function login(username: String, password: String) {
|
export function login(username: string, password: string) {
|
||||||
const requestOptions = {
|
const requestOptions = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
@ -65,7 +65,7 @@ export function login(username: String, password: String) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleResponse(response: Response, emitter: Emitter<Record<EventType, unknown>>, router: Router) {
|
export function handleResponse(response: Response, emitter: Emitter<Record<EventType, unknown>>, router: Router, ignore_error: boolean = false) {
|
||||||
return response.text().then(text => {
|
return response.text().then(text => {
|
||||||
const data = text && JSON.parse(text);
|
const data = text && JSON.parse(text);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@ -74,9 +74,13 @@ export function handleResponse(response: Response, emitter: Emitter<Record<Event
|
|||||||
logout();
|
logout();
|
||||||
emitter.emit("logged-out");
|
emitter.emit("logged-out");
|
||||||
router.push({ path: "/login", query: { returnUrl: router.currentRoute.value.fullPath } });
|
router.push({ path: "/login", query: { returnUrl: router.currentRoute.value.fullPath } });
|
||||||
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
const error = { message: (data && data.message) || response.statusText, status: response.status || 0 };
|
const error = { message: (data && data.message) || response.statusText, status: response.status || 0 };
|
||||||
|
if (!ignore_error) {
|
||||||
|
router.push({ name: "Error", params: error });
|
||||||
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,4 +103,4 @@ function handleAuthResponse(response: Response) {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -188,8 +188,8 @@ export default defineComponent({
|
|||||||
fetch("/api/config/get?file=" + this.backupFileSelect, { headers: authHeader() })
|
fetch("/api/config/get?file=" + this.backupFileSelect, { headers: authHeader() })
|
||||||
.then(res => res.blob())
|
.then(res => res.blob())
|
||||||
.then(blob => {
|
.then(blob => {
|
||||||
var file = window.URL.createObjectURL(blob);
|
const file = window.URL.createObjectURL(blob);
|
||||||
var a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = file;
|
a.href = file;
|
||||||
a.download = this.backupFileSelect;
|
a.download = this.backupFileSelect;
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
|
|||||||
@ -1,154 +1,154 @@
|
|||||||
<template>
|
<template>
|
||||||
<BasePage :title="$t('console.Console')" :isLoading="dataLoading">
|
<BasePage :title="$t('console.Console')" :isLoading="dataLoading">
|
||||||
<CardElement :text="$t('console.VirtualDebugConsole')" textVariant="text-bg-primary">
|
<CardElement :text="$t('console.VirtualDebugConsole')" textVariant="text-bg-primary">
|
||||||
<div class="row g-3 align-items-center">
|
<div class="row g-3 align-items-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="autoScroll"
|
<input class="form-check-input" type="checkbox" role="switch" id="autoScroll"
|
||||||
v-model="isAutoScroll">
|
v-model="isAutoScroll">
|
||||||
<label class="form-check-label" for="autoScroll">
|
<label class="form-check-label" for="autoScroll">
|
||||||
{{ $t('console.EnableAutoScroll') }}
|
{{ $t('console.EnableAutoScroll') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col text-end">
|
<div class="col text-end">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<button type="button" class="btn btn-primary" :onClick="clearConsole">
|
<button type="button" class="btn btn-primary" :onClick="clearConsole">
|
||||||
{{ $t('console.ClearConsole') }}</button>
|
{{ $t('console.ClearConsole') }}</button>
|
||||||
<button type="button" class="btn btn-secondary" :onClick="copyConsole">
|
<button type="button" class="btn btn-secondary" :onClick="copyConsole">
|
||||||
{{ $t('console.CopyToClipboard') }}</button>
|
{{ $t('console.CopyToClipboard') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<textarea id="console" class="form-control" rows="24" v-model="consoleBuffer" readonly></textarea>
|
<textarea id="console" class="form-control" rows="24" v-model="consoleBuffer" readonly></textarea>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
</BasePage>
|
</BasePage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import { authUrl } from '@/utils/authentication';
|
import { authUrl } from '@/utils/authentication';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
BasePage,
|
BasePage,
|
||||||
CardElement,
|
CardElement,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
socket: {} as WebSocket,
|
socket: {} as WebSocket,
|
||||||
heartInterval: 0,
|
heartInterval: 0,
|
||||||
dataLoading: true,
|
dataLoading: true,
|
||||||
consoleBuffer: "",
|
consoleBuffer: "",
|
||||||
isAutoScroll: true,
|
isAutoScroll: true,
|
||||||
endWithNewline: false,
|
endWithNewline: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.initSocket();
|
this.initSocket();
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.closeSocket();
|
this.closeSocket();
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
consoleBuffer() {
|
consoleBuffer() {
|
||||||
if (this.isAutoScroll) {
|
if (this.isAutoScroll) {
|
||||||
let textarea = this.$el.querySelector("#console");
|
const textarea = this.$el.querySelector("#console");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
textarea.scrollTop = textarea.scrollHeight;
|
textarea.scrollTop = textarea.scrollHeight;
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initSocket() {
|
initSocket() {
|
||||||
console.log("Starting connection to WebSocket Server");
|
console.log("Starting connection to WebSocket Server");
|
||||||
|
|
||||||
const { protocol, host } = location;
|
const { protocol, host } = location;
|
||||||
const authString = authUrl();
|
const authString = authUrl();
|
||||||
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
|
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
|
||||||
}://${authString}${host}/console`;
|
}://${authString}${host}/console`;
|
||||||
|
|
||||||
this.closeSocket();
|
this.closeSocket();
|
||||||
this.socket = new WebSocket(webSocketUrl);
|
this.socket = new WebSocket(webSocketUrl);
|
||||||
|
|
||||||
this.socket.onmessage = (event) => {
|
this.socket.onmessage = (event) => {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
|
|
||||||
let outstr = new String(event.data);
|
let outstr = new String(event.data);
|
||||||
let removedNewline = false;
|
let removedNewline = false;
|
||||||
if (outstr.endsWith('\n')) {
|
if (outstr.endsWith('\n')) {
|
||||||
outstr = outstr.substring(0, outstr.length - 1);
|
outstr = outstr.substring(0, outstr.length - 1);
|
||||||
removedNewline = true;
|
removedNewline = true;
|
||||||
}
|
}
|
||||||
this.consoleBuffer += (this.endWithNewline ? this.getOutDate() : '') + outstr.replaceAll("\n", "\n" + this.getOutDate());
|
this.consoleBuffer += (this.endWithNewline ? this.getOutDate() : '') + outstr.replaceAll("\n", "\n" + this.getOutDate());
|
||||||
this.endWithNewline = removedNewline;
|
this.endWithNewline = removedNewline;
|
||||||
this.heartCheck(); // Reset heartbeat detection
|
this.heartCheck(); // Reset heartbeat detection
|
||||||
};
|
};
|
||||||
|
|
||||||
this.socket.onopen = function (event) {
|
this.socket.onopen = function (event) {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
console.log("Successfully connected to the echo websocket server...");
|
console.log("Successfully connected to the echo websocket server...");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
||||||
window.onbeforeunload = () => {
|
window.onbeforeunload = () => {
|
||||||
this.closeSocket();
|
this.closeSocket();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
// Send heartbeat packets regularly * 59s Send a heartbeat
|
// Send heartbeat packets regularly * 59s Send a heartbeat
|
||||||
heartCheck() {
|
heartCheck() {
|
||||||
this.heartInterval && clearTimeout(this.heartInterval);
|
this.heartInterval && clearTimeout(this.heartInterval);
|
||||||
this.heartInterval = setInterval(() => {
|
this.heartInterval = setInterval(() => {
|
||||||
if (this.socket.readyState === 1) {
|
if (this.socket.readyState === 1) {
|
||||||
// Connection status
|
// Connection status
|
||||||
this.socket.send("ping");
|
this.socket.send("ping");
|
||||||
} else {
|
} else {
|
||||||
this.initSocket(); // Breakpoint reconnection 5 Time
|
this.initSocket(); // Breakpoint reconnection 5 Time
|
||||||
}
|
}
|
||||||
}, 5 * 1000);
|
}, 5 * 1000);
|
||||||
},
|
},
|
||||||
/** To break off websocket Connect */
|
/** To break off websocket Connect */
|
||||||
closeSocket() {
|
closeSocket() {
|
||||||
try {
|
try {
|
||||||
this.socket.close();
|
this.socket.close();
|
||||||
} catch {
|
} catch {
|
||||||
// continue regardless of error
|
// continue regardless of error
|
||||||
}
|
}
|
||||||
|
|
||||||
this.heartInterval && clearTimeout(this.heartInterval);
|
this.heartInterval && clearTimeout(this.heartInterval);
|
||||||
},
|
},
|
||||||
getOutDate(): String {
|
getOutDate(): string {
|
||||||
const u = new Date();
|
const u = new Date();
|
||||||
return ('0' + u.getHours()).slice(-2) + ':' +
|
return ('0' + u.getHours()).slice(-2) + ':' +
|
||||||
('0' + u.getMinutes()).slice(-2) + ':' +
|
('0' + u.getMinutes()).slice(-2) + ':' +
|
||||||
('0' + u.getSeconds()).slice(-2) + '.' +
|
('0' + u.getSeconds()).slice(-2) + '.' +
|
||||||
(u.getMilliseconds() / 1000).toFixed(3).slice(2, 5) + ' > ';
|
(u.getMilliseconds() / 1000).toFixed(3).slice(2, 5) + ' > ';
|
||||||
},
|
},
|
||||||
clearConsole() {
|
clearConsole() {
|
||||||
this.consoleBuffer = "";
|
this.consoleBuffer = "";
|
||||||
},
|
},
|
||||||
copyConsole() {
|
copyConsole() {
|
||||||
var input = document.createElement('textarea');
|
const input = document.createElement('textarea');
|
||||||
input.innerHTML = this.consoleBuffer;
|
input.innerHTML = this.consoleBuffer;
|
||||||
document.body.appendChild(input);
|
document.body.appendChild(input);
|
||||||
input.select();
|
input.select();
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
document.body.removeChild(input);
|
document.body.removeChild(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#console {
|
#console {
|
||||||
background-color: #0C0C0C;
|
background-color: #0C0C0C;
|
||||||
color: #CCCCCC;
|
color: #CCCCCC;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
font-family: courier new;
|
font-family: courier new;
|
||||||
font-size: .875em;
|
font-size: .875em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -219,7 +219,7 @@ export default defineComponent({
|
|||||||
getPinMappingList() {
|
getPinMappingList() {
|
||||||
this.pinMappingLoading = true;
|
this.pinMappingLoading = true;
|
||||||
fetch("/api/config/get?file=pin_mapping.json", { headers: authHeader() })
|
fetch("/api/config/get?file=pin_mapping.json", { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router, true))
|
||||||
.then(
|
.then(
|
||||||
(data) => {
|
(data) => {
|
||||||
this.pinMappingList = data;
|
this.pinMappingList = data;
|
||||||
@ -246,6 +246,9 @@ export default defineComponent({
|
|||||||
.then(
|
.then(
|
||||||
(data) => {
|
(data) => {
|
||||||
this.deviceConfigList = data;
|
this.deviceConfigList = data;
|
||||||
|
if (this.deviceConfigList.curPin.name === "") {
|
||||||
|
this.deviceConfigList.curPin.name = "Default";
|
||||||
|
}
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
18
webapp/src/views/ErrorView.vue
Normal file
18
webapp/src/views/ErrorView.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<BasePage :title="$t('Error.Oops')">
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<h2>{{ $route.params.message }}</h2>
|
||||||
|
</div>
|
||||||
|
</BasePage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import BasePage from '@/components/BasePage.vue';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
BasePage,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -191,7 +191,7 @@ export default defineComponent({
|
|||||||
const remoteHostUrl = "/api/system/status";
|
const remoteHostUrl = "/api/system/status";
|
||||||
|
|
||||||
// Use a simple fetch request to check if the remote host is reachable
|
// Use a simple fetch request to check if the remote host is reachable
|
||||||
fetch(remoteHostUrl, { method: 'HEAD' })
|
fetch(remoteHostUrl, { method: 'GET' })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
// Check if the response status is OK (200-299 range)
|
// Check if the response status is OK (200-299 range)
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
|||||||
@ -5,14 +5,20 @@
|
|||||||
<div class="row gy-3">
|
<div class="row gy-3">
|
||||||
<div class="col-sm-3 col-md-2" :style="[inverterData.length == 1 ? { 'display': 'none' } : {}]">
|
<div class="col-sm-3 col-md-2" :style="[inverterData.length == 1 ? { 'display': 'none' } : {}]">
|
||||||
<div class="nav nav-pills row-cols-sm-1" id="v-pills-tab" role="tablist" aria-orientation="vertical">
|
<div class="nav nav-pills row-cols-sm-1" id="v-pills-tab" role="tablist" aria-orientation="vertical">
|
||||||
<button v-for="inverter in inverterData" :key="inverter.serial" class="nav-link"
|
<button v-for="inverter in inverterData" :key="inverter.serial" class="nav-link border border-primary text-break"
|
||||||
:id="'v-pills-' + inverter.serial + '-tab'" data-bs-toggle="pill"
|
:id="'v-pills-' + inverter.serial + '-tab'" data-bs-toggle="pill"
|
||||||
:data-bs-target="'#v-pills-' + inverter.serial" type="button" role="tab"
|
:data-bs-target="'#v-pills-' + inverter.serial" type="button" role="tab"
|
||||||
aria-controls="'v-pills-' + inverter.serial" aria-selected="true">
|
aria-controls="'v-pills-' + inverter.serial" aria-selected="true">
|
||||||
<BIconXCircleFill class="fs-4" v-if="!inverter.reachable" />
|
<div class="row">
|
||||||
<BIconExclamationCircleFill class="fs-4" v-if="inverter.reachable && !inverter.producing" />
|
<div class="col-auto col-sm-2">
|
||||||
<BIconCheckCircleFill class="fs-4" v-if="inverter.reachable && inverter.producing" />
|
<BIconXCircleFill class="fs-4" v-if="!inverter.reachable" />
|
||||||
{{ inverter.name }}
|
<BIconExclamationCircleFill class="fs-4" v-if="inverter.reachable && !inverter.producing" />
|
||||||
|
<BIconCheckCircleFill class="fs-4" v-if="inverter.reachable && inverter.producing" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
{{ inverter.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -346,7 +352,7 @@ export default defineComponent({
|
|||||||
showAlertLimit: false,
|
showAlertLimit: false,
|
||||||
|
|
||||||
powerSettingView: {} as bootstrap.Modal,
|
powerSettingView: {} as bootstrap.Modal,
|
||||||
powerSettingSerial: 0,
|
powerSettingSerial: "",
|
||||||
powerSettingLoading: true,
|
powerSettingLoading: true,
|
||||||
alertMessagePower: "",
|
alertMessagePower: "",
|
||||||
alertTypePower: "info",
|
alertTypePower: "info",
|
||||||
@ -470,17 +476,15 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var self = this;
|
this.socket.onopen = (event) => {
|
||||||
|
|
||||||
this.socket.onopen = function (event) {
|
|
||||||
console.log(event);
|
console.log(event);
|
||||||
console.log("Successfully connected to the echo websocket server...");
|
console.log("Successfully connected to the echo websocket server...");
|
||||||
self.isWebsocketConnected = true;
|
this.isWebsocketConnected = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.socket.onclose = function () {
|
this.socket.onclose = () => {
|
||||||
console.log("Connection to websocket closed...")
|
console.log("Connection to websocket closed...")
|
||||||
self.isWebsocketConnected = false;
|
this.isWebsocketConnected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
||||||
@ -515,7 +519,7 @@ export default defineComponent({
|
|||||||
this.heartInterval && clearTimeout(this.heartInterval);
|
this.heartInterval && clearTimeout(this.heartInterval);
|
||||||
this.isFirstFetchAfterConnect = true;
|
this.isFirstFetchAfterConnect = true;
|
||||||
},
|
},
|
||||||
onShowEventlog(serial: number) {
|
onShowEventlog(serial: string) {
|
||||||
this.eventLogLoading = true;
|
this.eventLogLoading = true;
|
||||||
fetch("/api/eventlog/status?inv=" + serial + "&locale=" + this.$i18n.locale, { headers: authHeader() })
|
fetch("/api/eventlog/status?inv=" + serial + "&locale=" + this.$i18n.locale, { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
@ -526,7 +530,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
this.eventLogView.show();
|
this.eventLogView.show();
|
||||||
},
|
},
|
||||||
onShowDevInfo(serial: number) {
|
onShowDevInfo(serial: string) {
|
||||||
this.devInfoLoading = true;
|
this.devInfoLoading = true;
|
||||||
fetch("/api/devinfo/status?inv=" + serial, { headers: authHeader() })
|
fetch("/api/devinfo/status?inv=" + serial, { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
@ -538,7 +542,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
this.devInfoView.show();
|
this.devInfoView.show();
|
||||||
},
|
},
|
||||||
onShowGridProfile(serial: number) {
|
onShowGridProfile(serial: string) {
|
||||||
this.gridProfileLoading = true;
|
this.gridProfileLoading = true;
|
||||||
fetch("/api/gridprofile/status?inv=" + serial, { headers: authHeader() })
|
fetch("/api/gridprofile/status?inv=" + serial, { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
@ -555,9 +559,9 @@ export default defineComponent({
|
|||||||
|
|
||||||
this.gridProfileView.show();
|
this.gridProfileView.show();
|
||||||
},
|
},
|
||||||
onShowLimitSettings(serial: number) {
|
onShowLimitSettings(serial: string) {
|
||||||
this.showAlertLimit = false;
|
this.showAlertLimit = false;
|
||||||
this.targetLimitList.serial = 0;
|
this.targetLimitList.serial = "";
|
||||||
this.targetLimitList.limit_value = 0;
|
this.targetLimitList.limit_value = 0;
|
||||||
this.targetLimitType = 1;
|
this.targetLimitType = 1;
|
||||||
this.targetLimitTypeText = this.$t('home.Relative');
|
this.targetLimitTypeText = this.$t('home.Relative');
|
||||||
@ -611,9 +615,9 @@ export default defineComponent({
|
|||||||
this.targetLimitType = type;
|
this.targetLimitType = type;
|
||||||
},
|
},
|
||||||
|
|
||||||
onShowPowerSettings(serial: number) {
|
onShowPowerSettings(serial: string) {
|
||||||
this.showAlertPower = false;
|
this.showAlertPower = false;
|
||||||
this.powerSettingSerial = 0;
|
this.powerSettingSerial = "";
|
||||||
this.powerSettingLoading = true;
|
this.powerSettingLoading = true;
|
||||||
fetch("/api/power/status", { headers: authHeader() })
|
fetch("/api/power/status", { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
|
|||||||
@ -8,8 +8,7 @@
|
|||||||
<form class="form-inline" v-on:submit.prevent="onSubmit">
|
<form class="form-inline" v-on:submit.prevent="onSubmit">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('inverteradmin.Serial') }}</label>
|
<label>{{ $t('inverteradmin.Serial') }}</label>
|
||||||
<input v-model="newInverterData.serial" type="number" class="form-control ml-sm-2 mr-sm-4 my-2"
|
<InputSerial v-model="newInverterData.serial" inputClass="ml-sm-2 mr-sm-4 my-2" required />
|
||||||
required />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('inverteradmin.Name') }}</label>
|
<label>{{ $t('inverteradmin.Name') }}</label>
|
||||||
@ -91,7 +90,7 @@
|
|||||||
<label for="inverter-serial" class="col-form-label">
|
<label for="inverter-serial" class="col-form-label">
|
||||||
{{ $t('inverteradmin.InverterSerial') }}
|
{{ $t('inverteradmin.InverterSerial') }}
|
||||||
</label>
|
</label>
|
||||||
<input v-model="selectedInverterData.serial" type="number" id="inverter-serial" class="form-control" />
|
<InputSerial v-model="selectedInverterData.serial" id="inverter-serial" />
|
||||||
<label for="inverter-name" class="col-form-label">{{ $t('inverteradmin.InverterName') }}
|
<label for="inverter-name" class="col-form-label">{{ $t('inverteradmin.InverterName') }}
|
||||||
<BIconInfoCircle v-tooltip :title="$t('inverteradmin.InverterNameHint')" />
|
<BIconInfoCircle v-tooltip :title="$t('inverteradmin.InverterNameHint')" />
|
||||||
</label>
|
</label>
|
||||||
@ -207,6 +206,7 @@ import BasePage from '@/components/BasePage.vue';
|
|||||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import InputElement from '@/components/InputElement.vue';
|
import InputElement from '@/components/InputElement.vue';
|
||||||
|
import InputSerial from '@/components/InputSerial.vue';
|
||||||
import ModalDialog from '@/components/ModalDialog.vue';
|
import ModalDialog from '@/components/ModalDialog.vue';
|
||||||
import type { Inverter } from '@/types/InverterConfig';
|
import type { Inverter } from '@/types/InverterConfig';
|
||||||
import { authHeader, handleResponse } from '@/utils/authentication';
|
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||||
@ -235,6 +235,7 @@ export default defineComponent({
|
|||||||
BootstrapAlert,
|
BootstrapAlert,
|
||||||
CardElement,
|
CardElement,
|
||||||
InputElement,
|
InputElement,
|
||||||
|
InputSerial,
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
BIconInfoCircle,
|
BIconInfoCircle,
|
||||||
BIconPencil,
|
BIconPencil,
|
||||||
|
|||||||
@ -58,12 +58,16 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
getUpdateInfo() {
|
getUpdateInfo() {
|
||||||
|
if (this.systemDataList.git_hash === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If the left char is a "g" the value is the git hash (remove the "g")
|
// If the left char is a "g" the value is the git hash (remove the "g")
|
||||||
this.systemDataList.git_is_hash = this.systemDataList.git_hash?.substring(0, 1) == 'g';
|
this.systemDataList.git_is_hash = this.systemDataList.git_hash?.substring(0, 1) == 'g';
|
||||||
this.systemDataList.git_hash = this.systemDataList.git_is_hash ? this.systemDataList.git_hash?.substring(1) : this.systemDataList.git_hash;
|
this.systemDataList.git_hash = this.systemDataList.git_is_hash ? this.systemDataList.git_hash?.substring(1) : this.systemDataList.git_hash;
|
||||||
|
|
||||||
// Handle format "v0.1-5-gabcdefh"
|
// Handle format "v0.1-5-gabcdefh"
|
||||||
if (this.systemDataList.git_hash.lastIndexOf("-") >= 0) {
|
if (this.systemDataList.git_hash?.lastIndexOf("-") >= 0) {
|
||||||
this.systemDataList.git_hash = this.systemDataList.git_hash.substring(this.systemDataList.git_hash.lastIndexOf("-") + 2)
|
this.systemDataList.git_hash = this.systemDataList.git_hash.substring(this.systemDataList.git_hash.lastIndexOf("-") + 2)
|
||||||
this.systemDataList.git_is_hash = true;
|
this.systemDataList.git_is_hash = true;
|
||||||
}
|
}
|
||||||
@ -95,9 +99,9 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
allowVersionInfo(allow: Boolean) {
|
allowVersionInfo(allow: boolean) {
|
||||||
|
localStorage.setItem("allowVersionInfo", allow ? "1" : "0");
|
||||||
if (allow) {
|
if (allow) {
|
||||||
localStorage.setItem("allowVersionInfo", this.allowVersionInfo ? "1" : "0");
|
|
||||||
this.getUpdateInfo();
|
this.getUpdateInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import path from 'path'
|
|||||||
// example 'vite.user.ts': export const proxy_target = '192.168.16.107'
|
// example 'vite.user.ts': export const proxy_target = '192.168.16.107'
|
||||||
let proxy_target;
|
let proxy_target;
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line
|
||||||
proxy_target = require('./vite.user.ts').proxy_target;
|
proxy_target = require('./vite.user.ts').proxy_target;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
proxy_target = '192.168.20.110';
|
proxy_target = '192.168.20.110';
|
||||||
@ -29,6 +30,7 @@ export default defineConfig({
|
|||||||
fullInstall: false,
|
fullInstall: false,
|
||||||
forceStringify: true,
|
forceStringify: true,
|
||||||
strictMessage: false,
|
strictMessage: false,
|
||||||
|
jitCompilation: false,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
|
|||||||
1275
webapp/yarn.lock
1275
webapp/yarn.lock
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user