Merge remote-tracking branch 'tbnobody/OpenDTU/master' into development

This commit is contained in:
helgeerbe 2024-01-25 23:36:32 +01:00
commit ebacc2f25f
109 changed files with 1171 additions and 1274 deletions

View File

@ -0,0 +1,47 @@
[
{
"name": "Olimex ESP32-Gateway",
"nrf24": {
"miso": 14,
"mosi": 13,
"clk": 12,
"irq": 15,
"en": 2,
"cs": 4
},
"eth": {
"enabled": true,
"phy_addr": 0,
"power": 12,
"mdc": 23,
"mdio": 18,
"type": 0,
"clk_mode": 3
}
},
{
"name": "Olimex ESP32-Gateway with SSH1106",
"nrf24": {
"miso": 14,
"mosi": 13,
"clk": 12,
"irq": 15,
"en": 2,
"cs": 4
},
"eth": {
"enabled": true,
"phy_addr": 0,
"power": 12,
"mdc": 23,
"mdio": 18,
"type": 0,
"clk_mode": 3
},
"display": {
"type": 3,
"data": 32,
"clk": 16
}
}
]

View File

@ -6,6 +6,7 @@
class DatastoreClass {
public:
DatastoreClass();
void init(Scheduler& scheduler);
// Sum of yield total of all enabled inverters, a inverter which is just disabled at night is also included

View File

@ -8,6 +8,7 @@
class InverterSettingsClass {
public:
InverterSettingsClass();
void init(Scheduler& scheduler);
private:

View File

@ -12,6 +12,7 @@
class MessageOutputClass : public Print {
public:
MessageOutputClass();
void init(Scheduler& scheduler);
size_t write(uint8_t c) override;
size_t write(const uint8_t* buffer, size_t size) override;

View File

@ -6,6 +6,7 @@
class MqttHandleDtuClass {
public:
MqttHandleDtuClass();
void init(Scheduler& scheduler);
private:

View File

@ -51,6 +51,7 @@ const byteAssign_fieldDeviceClass_t deviceFieldAssignment[] = {
class MqttHandleHassClass {
public:
MqttHandleHassClass();
void init(Scheduler& scheduler);
void publishConfig();
void forceUpdate();

View File

@ -8,6 +8,7 @@
class MqttHandleInverterClass {
public:
MqttHandleInverterClass();
void init(Scheduler& scheduler);
static String getTopic(std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId);

View File

@ -5,6 +5,7 @@
class MqttHandleInverterTotalClass {
public:
MqttHandleInverterTotalClass();
void init(Scheduler& scheduler);
private:

View File

@ -46,10 +46,6 @@ public:
static void writeConfig(JsonVariant& retMsg, const WebApiError code = WebApiError::GenericSuccess, const String& message = "Settings saved!");
private:
void loop();
Task _loopTask;
AsyncWebServer _server;
WebApiBatteryClass _webApiBattery;

View File

@ -2,12 +2,12 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#include <AsyncJson.h>
class WebApiHuaweiClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
void getJsonData(JsonVariant& root);
private:
void onStatus(AsyncWebServerRequest* request);

View File

@ -2,12 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiBatteryClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onStatus(AsyncWebServerRequest* request);

View File

@ -2,11 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiConfigClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onConfigGet(AsyncWebServerRequest* request);

View File

@ -2,11 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiDeviceClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onDeviceAdminGet(AsyncWebServerRequest* request);

View File

@ -2,11 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiDevInfoClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onDevInfoStatus(AsyncWebServerRequest* request);

View File

@ -2,16 +2,19 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiDtuClass {
public:
void init(AsyncWebServer& server);
void loop();
WebApiDtuClass();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onDtuAdminGet(AsyncWebServerRequest* request);
void onDtuAdminPost(AsyncWebServerRequest* request);
AsyncWebServer* _server;
bool _performReload = false;
Task _applyDataTask;
void applyDataTaskCb();
};

View File

@ -2,11 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiEventlogClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onEventlogStatus(AsyncWebServerRequest* request);

View File

@ -2,11 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiFirmwareClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onFirmwareUpdateFinish(AsyncWebServerRequest* request);

View File

@ -2,11 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiGridProfileClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onGridProfileStatus(AsyncWebServerRequest* request);

View File

@ -2,11 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiInverterClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onInverterList(AsyncWebServerRequest* request);

View File

@ -2,11 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiLimitClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onLimitStatus(AsyncWebServerRequest* request);

View File

@ -2,11 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiMaintenanceClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onRebootPost(AsyncWebServerRequest* request);

View File

@ -2,13 +2,13 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#define MQTT_JSON_DOC_SIZE 10240
class WebApiMqttClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onMqttStatus(AsyncWebServerRequest* request);

View File

@ -2,11 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiNetworkClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onNetworkStatus(AsyncWebServerRequest* request);

View File

@ -2,11 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiNtpClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onNtpStatus(AsyncWebServerRequest* request);

View File

@ -2,11 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiPowerClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onPowerStatus(AsyncWebServerRequest* request);

View File

@ -2,12 +2,12 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiPowerLimiterClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onStatus(AsyncWebServerRequest* request);

View File

@ -2,12 +2,12 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiPowerMeterClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onStatus(AsyncWebServerRequest* request);

View File

@ -3,12 +3,12 @@
#include <ESPAsyncWebServer.h>
#include <Hoymiles.h>
#include <TaskSchedulerDeclarations.h>
#include <map>
class WebApiPrometheusClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onPrometheusMetricsGet(AsyncWebServerRequest* request);

View File

@ -2,11 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiSecurityClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onSecurityGet(AsyncWebServerRequest* request);

View File

@ -2,11 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiSysstatusClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onSystemStatus(AsyncWebServerRequest* request);

View File

@ -2,12 +2,12 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiVedirectClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void onVedirectStatus(AsyncWebServerRequest* request);

View File

@ -2,11 +2,11 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiWebappClass {
public:
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
AsyncWebServer* _server;

View File

@ -3,13 +3,13 @@
#include "ArduinoJson.h"
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>
class WebApiWsHuaweiLiveClass {
public:
WebApiWsHuaweiLiveClass();
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void generateJsonResponse(JsonVariant& root);
@ -19,8 +19,11 @@ private:
AsyncWebServer* _server;
AsyncWebSocket _ws;
uint32_t _lastWsCleanup = 0;
uint32_t _lastUpdateCheck = 0;
std::mutex _mutex;
Task _wsCleanupTask;
void wsCleanupTaskCb();
Task _sendDataTask;
void sendDataTaskCb();
};

View File

@ -3,13 +3,13 @@
#include "ArduinoJson.h"
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#include <mutex>
class WebApiWsBatteryLiveClass {
public:
WebApiWsBatteryLiveClass();
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void generateJsonResponse(JsonVariant& root);
@ -19,9 +19,14 @@ private:
AsyncWebServer* _server;
AsyncWebSocket _ws;
uint32_t _lastWsCleanup = 0;
uint32_t _lastUpdateCheck = 0;
static constexpr uint16_t _responseSize = 1024 + 512;
std::mutex _mutex;
Task _wsCleanupTask;
void wsCleanupTaskCb();
Task _sendDataTask;
void sendDataTaskCb();
};

View File

@ -2,16 +2,17 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
class WebApiWsConsoleClass {
public:
WebApiWsConsoleClass();
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
AsyncWebServer* _server;
AsyncWebSocket _ws;
uint32_t _lastWsCleanup = 0;
Task _wsCleanupTask;
void wsCleanupTaskCb();
};

View File

@ -4,16 +4,16 @@
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <Hoymiles.h>
#include <TaskSchedulerDeclarations.h>
class WebApiWsLiveClass {
public:
WebApiWsLiveClass();
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void generateJsonResponse(JsonVariant& root);
void addField(JsonObject& root, uint8_t idx, std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, String topic = "");
void addField(JsonObject& root, std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, String topic = "");
void addTotalField(JsonObject& root, const String& name, const float value, const String& unit, const uint8_t digits);
void onLivedataStatus(AsyncWebServerRequest* request);
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
@ -22,9 +22,13 @@ private:
AsyncWebSocket _ws;
uint32_t _lastWsPublish = 0;
uint32_t _lastInvUpdateCheck = 0;
uint32_t _lastWsCleanup = 0;
uint32_t _newestInverterTimestamp = 0;
std::mutex _mutex;
Task _wsCleanupTask;
void wsCleanupTaskCb();
Task _sendDataTask;
void sendDataTaskCb();
};

View File

@ -3,14 +3,14 @@
#include "ArduinoJson.h"
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#include <VeDirectMpptController.h>
#include <mutex>
class WebApiWsVedirectLiveClass {
public:
WebApiWsVedirectLiveClass();
void init(AsyncWebServer& server);
void loop();
void init(AsyncWebServer& server, Scheduler& scheduler);
private:
void generateJsonResponse(JsonVariant& root);
@ -21,9 +21,14 @@ private:
AsyncWebSocket _ws;
uint32_t _lastWsPublish = 0;
uint32_t _lastWsCleanup = 0;
uint32_t _dataAgeMillis = 0;
static constexpr uint16_t _responseSize = 1024 + 128;
std::mutex _mutex;
Task _wsCleanupTask;
void wsCleanupTaskCb();
Task _sendDataTask;
void sendDataTaskCb();
};

View File

@ -137,6 +137,14 @@ time_t DevInfoParser::getFwBuildDateTime() const
return timegm(&timeinfo);
}
String DevInfoParser::getFwBuildDateTimeStr() const
{
char timebuffer[32];
const time_t t = getFwBuildDateTime();
std::strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", gmtime(&t));
return timebuffer;
}
uint16_t DevInfoParser::getFwBootloaderVersion() const
{
HOY_SEMAPHORE_TAKE();

View File

@ -21,6 +21,7 @@ public:
uint16_t getFwBuildVersion() const;
time_t getFwBuildDateTime() const;
String getFwBuildDateTimeStr() const;
uint16_t getFwBootloaderVersion() const;
uint32_t getHwPartNumber() const;

View File

@ -390,7 +390,7 @@ std::vector<uint8_t> GridProfileParser::getRawData() const
{
std::vector<uint8_t> ret;
HOY_SEMAPHORE_TAKE();
for (uint8_t i = 0; i < GRID_PROFILE_SIZE; i++) {
for (uint8_t i = 0; i < _gridProfileLength; i++) {
ret.push_back(_payloadGridProfile[i]);
}
HOY_SEMAPHORE_GIVE();

View File

@ -37,13 +37,12 @@ build_unflags =
lib_deps =
https://github.com/yubox-node-org/ESPAsyncWebServer
bblanchon/ArduinoJson @ ^6.21.4
bblanchon/ArduinoJson @ ^6.21.5
https://github.com/bertmelis/espMqttClient.git#v1.5.0
nrf24/RF24 @ ^1.4.8
olikraus/U8g2 @ ^2.35.9
buelowp/sunset @ ^1.1.7
https://github.com/arkhipenko/TaskScheduler#testing
https://github.com/arkhipenko/TaskScheduler#testing
https://github.com/coryjfowler/MCP_CAN_lib
plerup/EspSoftwareSerial @ ^8.0.1
https://github.com/dok-net/ghostl @ ^1.0.1

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
* Copyright (C) 2023-2024 Thomas Basler and others
*/
#include "Datastore.h"
#include "Configuration.h"
@ -8,12 +8,14 @@
DatastoreClass Datastore;
DatastoreClass::DatastoreClass()
: _loopTask(1 * TASK_SECOND, TASK_FOREVER, std::bind(&DatastoreClass::loop, this))
{
}
void DatastoreClass::init(Scheduler& scheduler)
{
scheduler.addTask(_loopTask);
_loopTask.setCallback(std::bind(&DatastoreClass::loop, this));
_loopTask.setIterations(TASK_FOREVER);
_loopTask.setInterval(1 * TASK_SECOND);
_loopTask.enable();
}

View File

@ -35,6 +35,7 @@ static const char* const i18n_yield_total_kwh[] = { "total: %.1f kWh", "Ges.: %.
static const char* const i18n_date_format[] = { "%m/%d/%Y %H:%M", "%d.%m.%Y %H:%M", "%d/%m/%Y %H:%M" };
DisplayGraphicClass::DisplayGraphicClass()
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&DisplayGraphicClass::loop, this))
{
}
@ -55,8 +56,6 @@ void DisplayGraphicClass::init(Scheduler& scheduler, const DisplayType_t type, c
_diagram.init(scheduler, _display);
scheduler.addTask(_loopTask);
_loopTask.setCallback(std::bind(&DisplayGraphicClass::loop, this));
_loopTask.setIterations(TASK_FOREVER);
_loopTask.setInterval(_period);
_loopTask.enable();
}

View File

@ -8,6 +8,8 @@
#include <algorithm>
DisplayGraphicDiagramClass::DisplayGraphicDiagramClass()
: _averageTask(1 * TASK_SECOND, TASK_FOREVER, std::bind(&DisplayGraphicDiagramClass::averageLoop, this))
, _dataPointTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&DisplayGraphicDiagramClass::dataPointLoop, this))
{
}
@ -16,14 +18,9 @@ void DisplayGraphicDiagramClass::init(Scheduler& scheduler, U8G2* display)
_display = display;
scheduler.addTask(_averageTask);
_averageTask.setCallback(std::bind(&DisplayGraphicDiagramClass::averageLoop, this));
_averageTask.setIterations(TASK_FOREVER);
_averageTask.setInterval(1 * TASK_SECOND);
_averageTask.enable();
scheduler.addTask(_dataPointTask);
_dataPointTask.setCallback(std::bind(&DisplayGraphicDiagramClass::dataPointLoop, this));
_dataPointTask.setIterations(TASK_FOREVER);
updatePeriod();
_dataPointTask.enable();
}
@ -110,7 +107,9 @@ void DisplayGraphicDiagramClass::redraw(uint8_t screenSaverOffsetX, uint8_t xPos
if (maxWatts > 0 && isFullscreen) {
// draw y axis ticks
const uint16_t yAxisWattPerTick = maxWatts <= 100 ? 10 : maxWatts <= 1000 ? 100 : maxWatts < 5000 ? 500 : 1000;
const uint16_t yAxisWattPerTick = maxWatts <= 100 ? 10 : maxWatts <= 1000 ? 100
: maxWatts < 5000 ? 500
: 1000;
const uint8_t yAxisTickSizePixel = height / (maxWatts / yAxisWattPerTick);
for (int16_t tickYPos = graphPosY + height; tickYPos > graphPosY - arrow_size; tickYPos -= yAxisTickSizePixel) {

View File

@ -25,6 +25,12 @@
InverterSettingsClass InverterSettings;
InverterSettingsClass::InverterSettingsClass()
: _settingsTask(INVERTER_UPDATE_SETTINGS_INTERVAL, TASK_FOREVER, std::bind(&InverterSettingsClass::settingsLoop, this))
, _hoyTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&InverterSettingsClass::hoyLoop, this))
{
}
void InverterSettingsClass::init(Scheduler& scheduler)
{
const CONFIG_T& config = Configuration.get();
@ -94,14 +100,9 @@ void InverterSettingsClass::init(Scheduler& scheduler)
}
scheduler.addTask(_hoyTask);
_hoyTask.setCallback(std::bind(&InverterSettingsClass::hoyLoop, this));
_hoyTask.setIterations(TASK_FOREVER);
_hoyTask.enable();
scheduler.addTask(_settingsTask);
_settingsTask.setCallback(std::bind(&InverterSettingsClass::settingsLoop, this));
_settingsTask.setIterations(TASK_FOREVER);
_settingsTask.setInterval(INVERTER_UPDATE_SETTINGS_INTERVAL);
_settingsTask.enable();
}

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
* Copyright (C) 2023-2024 Thomas Basler and others
*/
#include "Led_Single.h"
#include "Configuration.h"
@ -38,6 +38,8 @@ const uint8_t pwmTable[] = {
#define LED_OFF 0
LedSingleClass::LedSingleClass()
: _setTask(LEDSINGLE_UPDATE_INTERVAL * TASK_MILLISECOND, TASK_FOREVER, std::bind(&LedSingleClass::setLoop, this))
, _outputTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&LedSingleClass::outputLoop, this))
{
}
@ -62,14 +64,9 @@ void LedSingleClass::init(Scheduler& scheduler)
if (ledActive) {
scheduler.addTask(_outputTask);
_outputTask.setCallback(std::bind(&LedSingleClass::outputLoop, this));
_outputTask.setIterations(TASK_FOREVER);
_outputTask.enable();
scheduler.addTask(_setTask);
_setTask.setCallback(std::bind(&LedSingleClass::setLoop, this));
_setTask.setInterval(LEDSINGLE_UPDATE_INTERVAL * TASK_MILLISECOND);
_setTask.setIterations(TASK_FOREVER);
_setTask.enable();
}
}

View File

@ -1,17 +1,20 @@
// 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 <HardwareSerial.h>
#include "MessageOutput.h"
MessageOutputClass MessageOutput;
MessageOutputClass::MessageOutputClass()
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&MessageOutputClass::loop, this))
{
}
void MessageOutputClass::init(Scheduler& scheduler)
{
scheduler.addTask(_loopTask);
_loopTask.setCallback(std::bind(&MessageOutputClass::loop, this));
_loopTask.setIterations(TASK_FOREVER);
_loopTask.enable();
}

View File

@ -1,6 +1,6 @@
// 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 "MqttHandleDtu.h"
#include "Configuration.h"
@ -10,11 +10,14 @@
MqttHandleDtuClass MqttHandleDtu;
MqttHandleDtuClass::MqttHandleDtuClass()
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&MqttHandleDtuClass::loop, this))
{
}
void MqttHandleDtuClass::init(Scheduler& scheduler)
{
scheduler.addTask(_loopTask);
_loopTask.setCallback(std::bind(&MqttHandleDtuClass::loop, this));
_loopTask.setIterations(TASK_FOREVER);
_loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND);
_loopTask.enable();
}

View File

@ -1,6 +1,6 @@
// 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 "MqttHandleHass.h"
#include "MqttHandleInverter.h"
@ -11,11 +11,14 @@
MqttHandleHassClass MqttHandleHass;
MqttHandleHassClass::MqttHandleHassClass()
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&MqttHandleHassClass::loop, this))
{
}
void MqttHandleHassClass::init(Scheduler& scheduler)
{
scheduler.addTask(_loopTask);
_loopTask.setCallback(std::bind(&MqttHandleHassClass::loop, this));
_loopTask.setIterations(TASK_FOREVER);
_loopTask.enable();
}
@ -421,5 +424,5 @@ void MqttHandleHassClass::publish(const String& subtopic, const String& payload)
{
String topic = Configuration.get().Mqtt.Hass.Topic;
topic += subtopic;
MqttSettings.publishGeneric(topic.c_str(), payload.c_str(), Configuration.get().Mqtt.Hass.Retain);
MqttSettings.publishGeneric(topic, payload, Configuration.get().Mqtt.Hass.Retain);
}

View File

@ -1,6 +1,6 @@
// 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 "MqttHandleInverter.h"
#include "MessageOutput.h"
@ -18,6 +18,11 @@
MqttHandleInverterClass MqttHandleInverter;
MqttHandleInverterClass::MqttHandleInverterClass()
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&MqttHandleInverterClass::loop, this))
{
}
void MqttHandleInverterClass::init(Scheduler& scheduler)
{
using std::placeholders::_1;
@ -28,16 +33,14 @@ void MqttHandleInverterClass::init(Scheduler& scheduler)
using std::placeholders::_6;
const String topic = MqttSettings.getPrefix();
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_POWER).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_RESTART).c_str(), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_POWER), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_RESTART), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
scheduler.addTask(_loopTask);
_loopTask.setCallback(std::bind(&MqttHandleInverterClass::loop, this));
_loopTask.setIterations(TASK_FOREVER);
_loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND);
_loopTask.enable();
}
@ -68,10 +71,7 @@ void MqttHandleInverterClass::loop()
MqttSettings.publish(subtopic + "/device/fwbuildversion", String(inv->DevInfo()->getFwBuildVersion()));
// Firmware Build DateTime
char timebuffer[32];
const time_t t = inv->DevInfo()->getFwBuildDateTime();
std::strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", gmtime(&t));
MqttSettings.publish(subtopic + "/device/fwbuilddatetime", String(timebuffer));
MqttSettings.publish(subtopic + "/device/fwbuilddatetime", inv->DevInfo()->getFwBuildDateTimeStr());
// Hardware part number
MqttSettings.publish(subtopic + "/device/hwpartnumber", String(inv->DevInfo()->getHwPartNumber()));

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
* Copyright (C) 2023-2024 Thomas Basler and others
*/
#include "MqttHandleInverterTotal.h"
#include "Configuration.h"
@ -10,11 +10,14 @@
MqttHandleInverterTotalClass MqttHandleInverterTotal;
MqttHandleInverterTotalClass::MqttHandleInverterTotalClass()
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&MqttHandleInverterTotalClass::loop, this))
{
}
void MqttHandleInverterTotalClass::init(Scheduler& scheduler)
{
scheduler.addTask(_loopTask);
_loopTask.setCallback(std::bind(&MqttHandleInverterTotalClass::loop, this));
_loopTask.setIterations(TASK_FOREVER);
_loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND);
_loopTask.enable();
}

View File

@ -1,6 +1,6 @@
// 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 "NetworkSettings.h"
#include "Configuration.h"
@ -12,7 +12,8 @@
#include <ETH.h>
NetworkSettingsClass::NetworkSettingsClass()
: _apIp(192, 168, 4, 1)
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&NetworkSettingsClass::loop, this))
, _apIp(192, 168, 4, 1)
, _apNetmask(255, 255, 255, 0)
{
_dnsServer.reset(new DNSServer());
@ -29,8 +30,6 @@ void NetworkSettingsClass::init(Scheduler& scheduler)
setupMode();
scheduler.addTask(_loopTask);
_loopTask.setCallback(std::bind(&NetworkSettingsClass::loop, this));
_loopTask.setIterations(TASK_FOREVER);
_loopTask.enable();
}

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Thomas Basler and others
* Copyright (C) 2023-2024 Thomas Basler and others
*/
#include "SunPosition.h"
#include "Configuration.h"
@ -10,15 +10,13 @@
SunPositionClass SunPosition;
SunPositionClass::SunPositionClass()
: _loopTask(5 * TASK_SECOND, TASK_FOREVER, std::bind(&SunPositionClass::loop, this))
{
}
void SunPositionClass::init(Scheduler& scheduler)
{
scheduler.addTask(_loopTask);
_loopTask.setCallback(std::bind(&SunPositionClass::loop, this));
_loopTask.setIterations(TASK_FOREVER);
_loopTask.setInterval(5 * TASK_SECOND);
_loopTask.enable();
}

View File

@ -1,6 +1,6 @@
// 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 "WebApi.h"
#include "Configuration.h"
@ -14,72 +14,36 @@ WebApiClass::WebApiClass()
void WebApiClass::init(Scheduler& scheduler)
{
_webApiConfig.init(_server);
_webApiDevice.init(_server);
_webApiDevInfo.init(_server);
_webApiDtu.init(_server);
_webApiEventlog.init(_server);
_webApiFirmware.init(_server);
_webApiGridprofile.init(_server);
_webApiInverter.init(_server);
_webApiLimit.init(_server);
_webApiMaintenance.init(_server);
_webApiMqtt.init(_server);
_webApiNetwork.init(_server);
_webApiNtp.init(_server);
_webApiPower.init(_server);
_webApiPrometheus.init(_server);
_webApiSecurity.init(_server);
_webApiSysstatus.init(_server);
_webApiWebapp.init(_server);
_webApiWsConsole.init(_server);
_webApiWsLive.init(_server);
_webApiBattery.init(_server);
_webApiPowerMeter.init(_server);
_webApiPowerLimiter.init(_server);
_webApiWsVedirectLive.init(_server);
_webApiVedirect.init(_server);
_webApiWsHuaweiLive.init(_server);
_webApiHuaweiClass.init(_server);
_webApiWsBatteryLive.init(_server);
_webApiConfig.init(_server, scheduler);
_webApiDevice.init(_server, scheduler);
_webApiDevInfo.init(_server, scheduler);
_webApiDtu.init(_server, scheduler);
_webApiEventlog.init(_server, scheduler);
_webApiFirmware.init(_server, scheduler);
_webApiGridprofile.init(_server, scheduler);
_webApiInverter.init(_server, scheduler);
_webApiLimit.init(_server, scheduler);
_webApiMaintenance.init(_server, scheduler);
_webApiMqtt.init(_server, scheduler);
_webApiNetwork.init(_server, scheduler);
_webApiNtp.init(_server, scheduler);
_webApiPower.init(_server, scheduler);
_webApiPrometheus.init(_server, scheduler);
_webApiSecurity.init(_server, scheduler);
_webApiSysstatus.init(_server, scheduler);
_webApiWebapp.init(_server, scheduler);
_webApiWsConsole.init(_server, scheduler);
_webApiWsLive.init(_server, scheduler);
_webApiBattery.init(_server, scheduler);
_webApiPowerMeter.init(_server, scheduler);
_webApiPowerLimiter.init(_server, scheduler);
_webApiWsVedirectLive.init(_server, scheduler);
_webApiVedirect.init(_server, scheduler);
_webApiWsHuaweiLive.init(_server, scheduler);
_webApiHuaweiClass.init(_server, scheduler);
_webApiWsBatteryLive.init(_server, scheduler);
_server.begin();
scheduler.addTask(_loopTask);
_loopTask.setCallback(std::bind(&WebApiClass::loop, this));
_loopTask.setIterations(TASK_FOREVER);
_loopTask.enable();
}
void WebApiClass::loop()
{
_webApiBattery.loop();
_webApiConfig.loop();
_webApiDevice.loop();
_webApiDevInfo.loop();
_webApiDtu.loop();
_webApiEventlog.loop();
_webApiFirmware.loop();
_webApiGridprofile.loop();
_webApiInverter.loop();
_webApiLimit.loop();
_webApiMaintenance.loop();
_webApiMqtt.loop();
_webApiNetwork.loop();
_webApiNtp.loop();
_webApiPower.loop();
_webApiPowerMeter.loop();
_webApiPowerLimiter.loop();
_webApiSecurity.loop();
_webApiSysstatus.loop();
_webApiWebapp.loop();
_webApiWsConsole.loop();
_webApiWsLive.loop();
_webApiWsVedirectLive.loop();
_webApiVedirect.loop();
_webApiWsHuaweiLive.loop();
_webApiHuaweiClass.loop();
_webApiWsBatteryLive.loop();
}
bool WebApiClass::checkCredentials(AsyncWebServerRequest* request)

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "WebApi_Huawei.h"
#include "Huawei_can.h"
@ -12,7 +12,7 @@
#include <AsyncJson.h>
#include <Hoymiles.h>
void WebApiHuaweiClass::init(AsyncWebServer& server)
void WebApiHuaweiClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -24,10 +24,6 @@ void WebApiHuaweiClass::init(AsyncWebServer& server)
_server->on("/api/huawei/limit/config", HTTP_POST, std::bind(&WebApiHuaweiClass::onPost, this, _1));
}
void WebApiHuaweiClass::loop()
{
}
void WebApiHuaweiClass::getJsonData(JsonVariant& root) {
const RectifierParameters_t * rp = HuaweiCan.get();

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "ArduinoJson.h"
@ -13,7 +13,7 @@
#include "WebApi_errors.h"
#include "helper.h"
void WebApiBatteryClass::init(AsyncWebServer& server)
void WebApiBatteryClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -24,10 +24,6 @@ void WebApiBatteryClass::init(AsyncWebServer& server)
_server->on("/api/battery/config", HTTP_POST, std::bind(&WebApiBatteryClass::onAdminPost, this, _1));
}
void WebApiBatteryClass::loop()
{
}
void WebApiBatteryClass::onStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {

View File

@ -1,6 +1,6 @@
// 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 "WebApi_config.h"
#include "Configuration.h"
@ -10,7 +10,7 @@
#include <AsyncJson.h>
#include <LittleFS.h>
void WebApiConfigClass::init(AsyncWebServer& server)
void WebApiConfigClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
using std::placeholders::_2;
@ -29,10 +29,6 @@ void WebApiConfigClass::init(AsyncWebServer& server)
std::bind(&WebApiConfigClass::onConfigUpload, this, _1, _2, _3, _4, _5, _6));
}
void WebApiConfigClass::loop()
{
}
void WebApiConfigClass::onConfigGet(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentials(request)) {

View File

@ -1,6 +1,6 @@
// 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 "WebApi_device.h"
#include "Configuration.h"
@ -12,7 +12,7 @@
#include "helper.h"
#include <AsyncJson.h>
void WebApiDeviceClass::init(AsyncWebServer& server)
void WebApiDeviceClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -22,10 +22,6 @@ void WebApiDeviceClass::init(AsyncWebServer& server)
_server->on("/api/device/config", HTTP_POST, std::bind(&WebApiDeviceClass::onDeviceAdminPost, this, _1));
}
void WebApiDeviceClass::loop()
{
}
void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentials(request)) {

View File

@ -1,6 +1,6 @@
// 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 "WebApi_devinfo.h"
#include "WebApi.h"
@ -8,7 +8,7 @@
#include <Hoymiles.h>
#include <ctime>
void WebApiDevInfoClass::init(AsyncWebServer& server)
void WebApiDevInfoClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -17,10 +17,6 @@ void WebApiDevInfoClass::init(AsyncWebServer& server)
_server->on("/api/devinfo/status", HTTP_GET, std::bind(&WebApiDevInfoClass::onDevInfoStatus, this, _1));
}
void WebApiDevInfoClass::loop()
{
}
void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {
@ -46,11 +42,7 @@ void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request)
root["hw_version"] = inv->DevInfo()->getHwVersion();
root["hw_model_name"] = inv->DevInfo()->getHwModelName();
root["max_power"] = inv->DevInfo()->getMaxPower();
char timebuffer[32];
const time_t t = inv->DevInfo()->getFwBuildDateTime();
std::strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", gmtime(&t));
root["fw_build_datetime"] = String(timebuffer);
root["fw_build_datetime"] = inv->DevInfo()->getFwBuildDateTimeStr();
}
response->setLength();

View File

@ -9,7 +9,12 @@
#include <AsyncJson.h>
#include <Hoymiles.h>
void WebApiDtuClass::init(AsyncWebServer& server)
WebApiDtuClass::WebApiDtuClass()
: _applyDataTask(TASK_IMMEDIATE, TASK_ONCE, std::bind(&WebApiDtuClass::applyDataTaskCb, this))
{
}
void WebApiDtuClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -17,11 +22,12 @@ void WebApiDtuClass::init(AsyncWebServer& server)
_server->on("/api/dtu/config", HTTP_GET, std::bind(&WebApiDtuClass::onDtuAdminGet, this, _1));
_server->on("/api/dtu/config", HTTP_POST, std::bind(&WebApiDtuClass::onDtuAdminPost, this, _1));
scheduler.addTask(_applyDataTask);
}
void WebApiDtuClass::loop()
void WebApiDtuClass::applyDataTaskCb()
{
if (_performReload) {
// Execute stuff in main thread to avoid busy SPI bus
CONFIG_T& config = Configuration.get();
Hoymiles.getRadioNrf()->setPALevel((rf24_pa_dbm_e)config.Dtu.Nrf.PaLevel);
@ -31,8 +37,6 @@ void WebApiDtuClass::loop()
Hoymiles.getRadioCmt()->setCountryMode(static_cast<CountryModeId_t>(config.Dtu.Cmt.CountryMode));
Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency);
Hoymiles.setPollInterval(config.Dtu.PollInterval);
_performReload = false;
}
}
void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request)
@ -199,5 +203,5 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
response->setLength();
request->send(response);
_performReload = true;
_applyDataTask.enable();
}

View File

@ -1,13 +1,13 @@
// 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 "WebApi_eventlog.h"
#include "WebApi.h"
#include <AsyncJson.h>
#include <Hoymiles.h>
void WebApiEventlogClass::init(AsyncWebServer& server)
void WebApiEventlogClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -16,10 +16,6 @@ void WebApiEventlogClass::init(AsyncWebServer& server)
_server->on("/api/eventlog/status", HTTP_GET, std::bind(&WebApiEventlogClass::onEventlogStatus, this, _1));
}
void WebApiEventlogClass::loop()
{
}
void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {

View File

@ -1,6 +1,6 @@
// 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 "WebApi_firmware.h"
#include "Configuration.h"
@ -10,7 +10,7 @@
#include "helper.h"
#include <AsyncJson.h>
void WebApiFirmwareClass::init(AsyncWebServer& server)
void WebApiFirmwareClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
using std::placeholders::_2;
@ -26,10 +26,6 @@ void WebApiFirmwareClass::init(AsyncWebServer& server)
std::bind(&WebApiFirmwareClass::onFirmwareUpdateUpload, this, _1, _2, _3, _4, _5, _6));
}
void WebApiFirmwareClass::loop()
{
}
void WebApiFirmwareClass::onFirmwareUpdateFinish(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentials(request)) {

View File

@ -1,13 +1,13 @@
// 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 "WebApi_gridprofile.h"
#include "WebApi.h"
#include <AsyncJson.h>
#include <Hoymiles.h>
void WebApiGridProfileClass::init(AsyncWebServer& server)
void WebApiGridProfileClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -17,10 +17,6 @@ void WebApiGridProfileClass::init(AsyncWebServer& server)
_server->on("/api/gridprofile/rawdata", HTTP_GET, std::bind(&WebApiGridProfileClass::onGridProfileRawdata, this, _1));
}
void WebApiGridProfileClass::loop()
{
}
void WebApiGridProfileClass::onGridProfileStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {

View File

@ -1,6 +1,6 @@
// 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 "WebApi_inverter.h"
#include "Configuration.h"
@ -12,7 +12,7 @@
#include <AsyncJson.h>
#include <Hoymiles.h>
void WebApiInverterClass::init(AsyncWebServer& server)
void WebApiInverterClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -25,10 +25,6 @@ void WebApiInverterClass::init(AsyncWebServer& server)
_server->on("/api/inverter/order", HTTP_POST, std::bind(&WebApiInverterClass::onInverterOrder, this, _1));
}
void WebApiInverterClass::loop()
{
}
void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentials(request)) {

View File

@ -1,6 +1,6 @@
// 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 "WebApi_limit.h"
#include "WebApi.h"
@ -10,7 +10,7 @@
#include <AsyncJson.h>
#include <Hoymiles.h>
void WebApiLimitClass::init(AsyncWebServer& server)
void WebApiLimitClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -20,10 +20,6 @@ void WebApiLimitClass::init(AsyncWebServer& server)
_server->on("/api/limit/config", HTTP_POST, std::bind(&WebApiLimitClass::onLimitPost, this, _1));
}
void WebApiLimitClass::loop()
{
}
void WebApiLimitClass::onLimitStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {

View File

@ -1,6 +1,6 @@
// 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 "WebApi_maintenance.h"
@ -9,7 +9,7 @@
#include "WebApi_errors.h"
#include <AsyncJson.h>
void WebApiMaintenanceClass::init(AsyncWebServer& server)
void WebApiMaintenanceClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -18,10 +18,6 @@ void WebApiMaintenanceClass::init(AsyncWebServer& server)
_server->on("/api/maintenance/reboot", HTTP_POST, std::bind(&WebApiMaintenanceClass::onRebootPost, this, _1));
}
void WebApiMaintenanceClass::loop()
{
}
void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentials(request)) {

View File

@ -1,6 +1,6 @@
// 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 "WebApi_mqtt.h"
#include "Configuration.h"
@ -15,7 +15,7 @@
#include "PowerMeter.h"
#include <AsyncJson.h>
void WebApiMqttClass::init(AsyncWebServer& server)
void WebApiMqttClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -26,10 +26,6 @@ void WebApiMqttClass::init(AsyncWebServer& server)
_server->on("/api/mqtt/config", HTTP_POST, std::bind(&WebApiMqttClass::onMqttAdminPost, this, _1));
}
void WebApiMqttClass::loop()
{
}
void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {

View File

@ -1,6 +1,6 @@
// 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 "WebApi_network.h"
#include "Configuration.h"
@ -10,7 +10,7 @@
#include "helper.h"
#include <AsyncJson.h>
void WebApiNetworkClass::init(AsyncWebServer& server)
void WebApiNetworkClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -21,10 +21,6 @@ void WebApiNetworkClass::init(AsyncWebServer& server)
_server->on("/api/network/config", HTTP_POST, std::bind(&WebApiNetworkClass::onNetworkAdminPost, this, _1));
}
void WebApiNetworkClass::loop()
{
}
void WebApiNetworkClass::onNetworkStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {

View File

@ -1,6 +1,6 @@
// 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 "WebApi_ntp.h"
#include "Configuration.h"
@ -11,7 +11,7 @@
#include "helper.h"
#include <AsyncJson.h>
void WebApiNtpClass::init(AsyncWebServer& server)
void WebApiNtpClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -24,10 +24,6 @@ void WebApiNtpClass::init(AsyncWebServer& server)
_server->on("/api/ntp/time", HTTP_POST, std::bind(&WebApiNtpClass::onNtpTimePost, this, _1));
}
void WebApiNtpClass::loop()
{
}
void WebApiNtpClass::onNtpStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {

View File

@ -1,6 +1,6 @@
// 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 "WebApi_power.h"
#include "WebApi.h"
@ -8,7 +8,7 @@
#include <AsyncJson.h>
#include <Hoymiles.h>
void WebApiPowerClass::init(AsyncWebServer& server)
void WebApiPowerClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -18,10 +18,6 @@ void WebApiPowerClass::init(AsyncWebServer& server)
_server->on("/api/power/config", HTTP_POST, std::bind(&WebApiPowerClass::onPowerPost, this, _1));
}
void WebApiPowerClass::loop()
{
}
void WebApiPowerClass::onPowerStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "WebApi_powerlimiter.h"
#include "VeDirectFrameHandler.h"
@ -16,7 +16,7 @@
#include "helper.h"
#include "WebApi_errors.h"
void WebApiPowerLimiterClass::init(AsyncWebServer& server)
void WebApiPowerLimiterClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -27,10 +27,6 @@ void WebApiPowerLimiterClass::init(AsyncWebServer& server)
_server->on("/api/powerlimiter/config", HTTP_POST, std::bind(&WebApiPowerLimiterClass::onAdminPost, this, _1));
}
void WebApiPowerLimiterClass::loop()
{
}
void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request)
{
AsyncJsonResponse* response = new AsyncJsonResponse();

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "WebApi_powermeter.h"
#include "VeDirectFrameHandler.h"
@ -16,7 +16,7 @@
#include "WebApi.h"
#include "helper.h"
void WebApiPowerMeterClass::init(AsyncWebServer& server)
void WebApiPowerMeterClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -28,10 +28,6 @@ void WebApiPowerMeterClass::init(AsyncWebServer& server)
_server->on("/api/powermeter/testhttprequest", HTTP_POST, std::bind(&WebApiPowerMeterClass::onTestHttpRequest, this, _1));
}
void WebApiPowerMeterClass::loop()
{
}
void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request)
{
AsyncJsonResponse* response = new AsyncJsonResponse(false, 2048);

View File

@ -1,7 +1,7 @@
// 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 "WebApi_prometheus.h"
#include "Configuration.h"
@ -11,7 +11,7 @@
#include <Hoymiles.h>
#include "MessageOutput.h"
void WebApiPrometheusClass::init(AsyncWebServer& server)
void WebApiPrometheusClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -20,10 +20,6 @@ void WebApiPrometheusClass::init(AsyncWebServer& server)
_server->on("/api/prometheus/metrics", HTTP_GET, std::bind(&WebApiPrometheusClass::onPrometheusMetricsGet, this, _1));
}
void WebApiPrometheusClass::loop()
{
}
void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {

View File

@ -1,6 +1,6 @@
// 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 "WebApi_security.h"
#include "Configuration.h"
@ -9,7 +9,7 @@
#include "helper.h"
#include <AsyncJson.h>
void WebApiSecurityClass::init(AsyncWebServer& server)
void WebApiSecurityClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -20,10 +20,6 @@ void WebApiSecurityClass::init(AsyncWebServer& server)
_server->on("/api/security/authenticate", HTTP_GET, std::bind(&WebApiSecurityClass::onAuthenticateGet, this, _1));
}
void WebApiSecurityClass::loop()
{
}
void WebApiSecurityClass::onSecurityGet(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentials(request)) {

View File

@ -1,6 +1,6 @@
// 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 "WebApi_sysstatus.h"
#include "Configuration.h"
@ -20,7 +20,7 @@
#define AUTO_GIT_BRANCH ""
#endif
void WebApiSysstatusClass::init(AsyncWebServer& server)
void WebApiSysstatusClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -29,10 +29,6 @@ void WebApiSysstatusClass::init(AsyncWebServer& server)
_server->on("/api/system/status", HTTP_GET, std::bind(&WebApiSysstatusClass::onSystemStatus, this, _1));
}
void WebApiSysstatusClass::loop()
{
}
void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "WebApi_vedirect.h"
#include "VictronMppt.h"
@ -11,7 +11,7 @@
#include "WebApi_errors.h"
#include "helper.h"
void WebApiVedirectClass::init(AsyncWebServer& server)
void WebApiVedirectClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
@ -22,10 +22,6 @@ void WebApiVedirectClass::init(AsyncWebServer& server)
_server->on("/api/vedirect/config", HTTP_POST, std::bind(&WebApiVedirectClass::onVedirectAdminPost, this, _1));
}
void WebApiVedirectClass::loop()
{
}
void WebApiVedirectClass::onVedirectStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {

View File

@ -1,6 +1,6 @@
// 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 "WebApi_webapp.h"
@ -22,7 +22,7 @@ extern const uint8_t file_site_webmanifest_end[] asm("_binary_webapp_dist_site_w
#define ETAG_HTTP_HEADER_VAL "\"" AUTO_GIT_HASH "\"" // ETag value must be between quotes
#endif
void WebApiWebappClass::init(AsyncWebServer& server)
void WebApiWebappClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
_server = &server;
@ -94,7 +94,3 @@ void WebApiWebappClass::init(AsyncWebServer& server)
request->send(response);
});
}
void WebApiWebappClass::loop()
{
}

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "WebApi_ws_Huawei.h"
#include "AsyncJson.h"
@ -16,7 +16,7 @@ WebApiWsHuaweiLiveClass::WebApiWsHuaweiLiveClass()
{
}
void WebApiWsHuaweiLiveClass::init(AsyncWebServer& server)
void WebApiWsHuaweiLiveClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
using std::placeholders::_2;
@ -30,26 +30,33 @@ void WebApiWsHuaweiLiveClass::init(AsyncWebServer& server)
_server->addHandler(&_ws);
_ws.onEvent(std::bind(&WebApiWsHuaweiLiveClass::onWebsocketEvent, this, _1, _2, _3, _4, _5, _6));
scheduler.addTask(_wsCleanupTask);
_wsCleanupTask.setCallback(std::bind(&WebApiWsHuaweiLiveClass::wsCleanupTaskCb, this));
_wsCleanupTask.setIterations(TASK_FOREVER);
_wsCleanupTask.setInterval(1 * TASK_SECOND);
_wsCleanupTask.enable();
scheduler.addTask(_sendDataTask);
_sendDataTask.setCallback(std::bind(&WebApiWsHuaweiLiveClass::sendDataTaskCb, this));
_sendDataTask.setIterations(TASK_FOREVER);
_sendDataTask.setInterval(1 * TASK_SECOND);
_sendDataTask.enable();
}
void WebApiWsHuaweiLiveClass::loop()
void WebApiWsHuaweiLiveClass::wsCleanupTaskCb()
{
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
if (millis() - _lastWsCleanup > 1000) {
_ws.cleanupClients();
_lastWsCleanup = millis();
}
void WebApiWsHuaweiLiveClass::sendDataTaskCb()
{
// do nothing if no WS client is connected
if (_ws.count() == 0) {
return;
}
if (millis() - _lastUpdateCheck < 1000) {
return;
}
_lastUpdateCheck = millis();
try {
std::lock_guard<std::mutex> lock(_mutex);
DynamicJsonDocument root(1024);

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "WebApi_ws_battery.h"
#include "AsyncJson.h"
@ -16,7 +16,7 @@ WebApiWsBatteryLiveClass::WebApiWsBatteryLiveClass()
{
}
void WebApiWsBatteryLiveClass::init(AsyncWebServer& server)
void WebApiWsBatteryLiveClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
using std::placeholders::_2;
@ -30,16 +30,28 @@ void WebApiWsBatteryLiveClass::init(AsyncWebServer& server)
_server->addHandler(&_ws);
_ws.onEvent(std::bind(&WebApiWsBatteryLiveClass::onWebsocketEvent, this, _1, _2, _3, _4, _5, _6));
scheduler.addTask(_wsCleanupTask);
_wsCleanupTask.setCallback(std::bind(&WebApiWsBatteryLiveClass::wsCleanupTaskCb, this));
_wsCleanupTask.setIterations(TASK_FOREVER);
_wsCleanupTask.setInterval(1 * TASK_SECOND);
_wsCleanupTask.enable();
scheduler.addTask(_sendDataTask);
_sendDataTask.setCallback(std::bind(&WebApiWsBatteryLiveClass::sendDataTaskCb, this));
_sendDataTask.setIterations(TASK_FOREVER);
_sendDataTask.setInterval(1 * TASK_SECOND);
_sendDataTask.enable();
}
void WebApiWsBatteryLiveClass::loop()
void WebApiWsBatteryLiveClass::wsCleanupTaskCb()
{
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
if (millis() - _lastWsCleanup > 1000) {
_ws.cleanupClients();
_lastWsCleanup = millis();
}
void WebApiWsBatteryLiveClass::sendDataTaskCb()
{
// do nothing if no WS client is connected
if (_ws.count() == 0) {
return;

View File

@ -1,6 +1,6 @@
// 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 "WebApi_ws_console.h"
#include "Configuration.h"
@ -10,20 +10,23 @@
WebApiWsConsoleClass::WebApiWsConsoleClass()
: _ws("/console")
, _wsCleanupTask(1 * TASK_SECOND, TASK_FOREVER, std::bind(&WebApiWsConsoleClass::wsCleanupTaskCb, this))
{
}
void WebApiWsConsoleClass::init(AsyncWebServer& server)
void WebApiWsConsoleClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
_server = &server;
_server->addHandler(&_ws);
MessageOutput.register_ws_output(&_ws);
scheduler.addTask(_wsCleanupTask);
_wsCleanupTask.enable();
}
void WebApiWsConsoleClass::loop()
void WebApiWsConsoleClass::wsCleanupTaskCb()
{
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
if (millis() - _lastWsCleanup > 1000) {
_ws.cleanupClients();
if (Configuration.get().Security.AllowReadonly) {
@ -31,7 +34,4 @@ void WebApiWsConsoleClass::loop()
} else {
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password);
}
_lastWsCleanup = millis();
}
}

View File

@ -1,6 +1,6 @@
// 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 "WebApi_ws_live.h"
#include "Configuration.h"
@ -17,10 +17,12 @@
WebApiWsLiveClass::WebApiWsLiveClass()
: _ws("/livedata")
, _wsCleanupTask(1 * TASK_SECOND, TASK_FOREVER, std::bind(&WebApiWsLiveClass::wsCleanupTaskCb, this))
, _sendDataTask(1 * TASK_SECOND, TASK_FOREVER, std::bind(&WebApiWsLiveClass::sendDataTaskCb, this))
{
}
void WebApiWsLiveClass::init(AsyncWebServer& server)
void WebApiWsLiveClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
using std::placeholders::_2;
@ -34,33 +36,37 @@ void WebApiWsLiveClass::init(AsyncWebServer& server)
_server->addHandler(&_ws);
_ws.onEvent(std::bind(&WebApiWsLiveClass::onWebsocketEvent, this, _1, _2, _3, _4, _5, _6));
scheduler.addTask(_wsCleanupTask);
_wsCleanupTask.enable();
scheduler.addTask(_sendDataTask);
_sendDataTask.enable();
}
void WebApiWsLiveClass::loop()
void WebApiWsLiveClass::wsCleanupTaskCb()
{
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
if (millis() - _lastWsCleanup > 1000) {
_ws.cleanupClients();
_lastWsCleanup = millis();
if (Configuration.get().Security.AllowReadonly) {
_ws.setAuthentication("", "");
} else {
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password);
}
}
void WebApiWsLiveClass::sendDataTaskCb()
{
// do nothing if no WS client is connected
if (_ws.count() == 0) {
return;
}
if (millis() - _lastInvUpdateCheck < 1000) {
return;
}
_lastInvUpdateCheck = millis();
uint32_t maxTimeStamp = 0;
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
auto inv = Hoymiles.getInverterByPos(i);
if (inv->Statistics()->getLastUpdate() > maxTimeStamp) {
maxTimeStamp = inv->Statistics()->getLastUpdate();
}
maxTimeStamp = std::max<uint32_t>(maxTimeStamp, inv->Statistics()->getLastUpdate());
}
// Update on every inverter change or at least after 10 seconds
@ -76,13 +82,8 @@ void WebApiWsLiveClass::loop()
String buffer;
serializeJson(root, buffer);
if (Configuration.get().Security.AllowReadonly) {
_ws.setAuthentication("", "");
} else {
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password);
}
_ws.textAll(buffer);
_newestInverterTimestamp = maxTimeStamp;
}
} catch (const std::bad_alloc& bad_alloc) {
@ -133,25 +134,25 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
if (t == TYPE_DC) {
chanTypeObj[String(static_cast<uint8_t>(c))]["name"]["u"] = inv_cfg->channel[c].Name;
}
addField(chanTypeObj, i, inv, t, c, FLD_PAC);
addField(chanTypeObj, i, inv, t, c, FLD_UAC);
addField(chanTypeObj, i, inv, t, c, FLD_IAC);
addField(chanTypeObj, inv, t, c, FLD_PAC);
addField(chanTypeObj, inv, t, c, FLD_UAC);
addField(chanTypeObj, inv, t, c, FLD_IAC);
if (t == TYPE_AC) {
addField(chanTypeObj, i, inv, t, c, FLD_PDC, "Power DC");
addField(chanTypeObj, inv, t, c, FLD_PDC, "Power DC");
} else {
addField(chanTypeObj, i, inv, t, c, FLD_PDC);
addField(chanTypeObj, inv, t, c, FLD_PDC);
}
addField(chanTypeObj, i, inv, t, c, FLD_UDC);
addField(chanTypeObj, i, inv, t, c, FLD_IDC);
addField(chanTypeObj, i, inv, t, c, FLD_YD);
addField(chanTypeObj, i, inv, t, c, FLD_YT);
addField(chanTypeObj, i, inv, t, c, FLD_F);
addField(chanTypeObj, i, inv, t, c, FLD_T);
addField(chanTypeObj, i, inv, t, c, FLD_PF);
addField(chanTypeObj, i, inv, t, c, FLD_Q);
addField(chanTypeObj, i, inv, t, c, FLD_EFF);
addField(chanTypeObj, inv, t, c, FLD_UDC);
addField(chanTypeObj, inv, t, c, FLD_IDC);
addField(chanTypeObj, inv, t, c, FLD_YD);
addField(chanTypeObj, inv, t, c, FLD_YT);
addField(chanTypeObj, inv, t, c, FLD_F);
addField(chanTypeObj, inv, t, c, FLD_T);
addField(chanTypeObj, inv, t, c, FLD_PF);
addField(chanTypeObj, inv, t, c, FLD_Q);
addField(chanTypeObj, inv, t, c, FLD_EFF);
if (t == TYPE_DC && inv->Statistics()->getStringMaxPower(c) > 0) {
addField(chanTypeObj, i, inv, t, c, FLD_IRR);
addField(chanTypeObj, inv, t, c, FLD_IRR);
chanTypeObj[String(c)][inv->Statistics()->getChannelFieldName(t, c, FLD_IRR)]["max"] = inv->Statistics()->getStringMaxPower(c);
}
}
@ -162,10 +163,6 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
} else {
invObject["events"] = -1;
}
if (inv->Statistics()->getLastUpdate() > _newestInverterTimestamp) {
_newestInverterTimestamp = inv->Statistics()->getLastUpdate();
}
}
JsonObject totalObj = root.createNestedObject("total");
@ -206,7 +203,7 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
}
void WebApiWsLiveClass::addField(JsonObject& root, uint8_t idx, std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, String topic)
void WebApiWsLiveClass::addField(JsonObject& root, std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, String topic)
{
if (inv->Statistics()->hasChannelFieldValue(type, channel, fieldId)) {
String chanName;

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "WebApi_ws_vedirect_live.h"
#include "AsyncJson.h"
@ -17,7 +17,7 @@ WebApiWsVedirectLiveClass::WebApiWsVedirectLiveClass()
{
}
void WebApiWsVedirectLiveClass::init(AsyncWebServer& server)
void WebApiWsVedirectLiveClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;
using std::placeholders::_2;
@ -31,16 +31,29 @@ void WebApiWsVedirectLiveClass::init(AsyncWebServer& server)
_server->addHandler(&_ws);
_ws.onEvent(std::bind(&WebApiWsVedirectLiveClass::onWebsocketEvent, this, _1, _2, _3, _4, _5, _6));
scheduler.addTask(_wsCleanupTask);
_wsCleanupTask.setCallback(std::bind(&WebApiWsVedirectLiveClass::wsCleanupTaskCb, this));
_wsCleanupTask.setIterations(TASK_FOREVER);
_wsCleanupTask.setInterval(1 * TASK_SECOND);
_wsCleanupTask.enable();
scheduler.addTask(_sendDataTask);
_sendDataTask.setCallback(std::bind(&WebApiWsVedirectLiveClass::sendDataTaskCb, this));
_sendDataTask.setIterations(TASK_FOREVER);
_sendDataTask.setInterval(1 * TASK_SECOND);
_sendDataTask.enable();
}
void WebApiWsVedirectLiveClass::loop()
void WebApiWsVedirectLiveClass::wsCleanupTaskCb()
{
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
if (millis() - _lastWsCleanup > 1000) {
_ws.cleanupClients();
_lastWsCleanup = millis();
}
void WebApiWsVedirectLiveClass::sendDataTaskCb()
{
// do nothing if no WS client is connected
if (_ws.count() == 0) {
return;

View File

@ -16,18 +16,18 @@
"bootstrap": "^5.3.2",
"bootstrap-icons-vue": "^1.11.1",
"mitt": "^3.0.1",
"sortablejs": "^1.15.1",
"sortablejs": "^1.15.2",
"spark-md5": "^3.0.2",
"vue": "^3.4.13",
"vue": "^3.4.15",
"vue-i18n": "^9.9.0",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@intlify/unplugin-vue-i18n": "^2.0.0",
"@rushstack/eslint-patch": "^1.6.1",
"@rushstack/eslint-patch": "^1.7.0",
"@tsconfig/node18": "^18.2.2",
"@types/bootstrap": "^5.2.10",
"@types/node": "^20.11.0",
"@types/node": "^20.11.5",
"@types/pulltorefreshjs": "^0.1.7",
"@types/sortablejs": "^1.15.7",
"@types/spark-md5": "^3.0.4",
@ -38,10 +38,10 @@
"eslint-plugin-vue": "^9.20.1",
"npm-run-all": "^4.1.5",
"pulltorefreshjs": "^0.1.22",
"sass": "^1.69.7",
"terser": "^5.26.0",
"sass": "^1.70.0",
"terser": "^5.27.0",
"typescript": "^5.3.3",
"vite": "^5.0.11",
"vite": "^5.0.12",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-css-injected-by-js": "^3.3.1",
"vue-tsc": "^1.8.27"

View File

@ -1,8 +1,6 @@
<template>
<NavBar />
<main class="container-fluid">
<router-view />
</main>
</template>
<script lang="ts">

View File

@ -51,7 +51,7 @@ export default defineComponent({
var self = this;
console.log("init");
PullToRefresh.init({
mainElement: 'main', // above which element?
mainElement: 'body', // above which element?
instructionsPullToRefresh: this.$t('base.Pull'),
instructionsReleaseToRefresh: this.$t('base.Release'),
instructionsRefreshing: this.$t('base.Refreshing'),

View File

@ -1,7 +1,7 @@
<template>
<div :class="['card', addSpace ? 'mt-5' : '' ]">
<div :class="['card-header', textVariant]">{{ text }}</div>
<div :class="['card-body', centerContent ? 'text-center' : '']">
<div :class="['card-body', 'card-text', centerContent ? 'text-center' : '']">
<slot />
</div>
</div>

View File

@ -31,7 +31,7 @@ export default defineComponent({
computed: {
timeInHours() {
return (value: number) => {
return timestampToString(value);
return timestampToString(this.$i18n.locale, value)[0];
};
},
},

View File

@ -32,12 +32,19 @@
</tr>
<tr>
<th>{{ $t('firmwareinfo.FirmwareUpdate') }}</th>
<td><a :href="systemStatus.update_url" target="_blank" v-tooltip
<td v-if="modelAllowVersionInfo">
<a :href="systemStatus.update_url" target="_blank" v-tooltip
:title="$t('firmwareinfo.FirmwareUpdateHint')">
<span class="badge" :class="systemStatus.update_status">
{{ systemStatus.update_text }}
</span>
</a></td>
</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')" />
</div>
</td>
</tr>
<tr>
<th>{{ $t('firmwareinfo.ResetReason0') }}</th>
@ -53,7 +60,7 @@
</tr>
<tr>
<th>{{ $t('firmwareinfo.Uptime') }}</th>
<td>{{ timeInHours(systemStatus.uptime) }}</td>
<td>{{ $t('firmwareinfo.UptimeValue', timeInHours(systemStatus.uptime)) }}</td>
</tr>
</tbody>
</table>
@ -73,11 +80,21 @@ export default defineComponent({
},
props: {
systemStatus: { type: Object as PropType<SystemStatus>, required: true },
allowVersionInfo: Boolean,
},
computed: {
modelAllowVersionInfo: {
get(): any {
return !!this.allowVersionInfo;
},
set(value: any) {
this.$emit('update:allowVersionInfo', value);
},
},
timeInHours() {
return (value: number) => {
return timestampToString(value, true);
const [count, time] = timestampToString(this.$i18n.locale, value, true);
return {count, time};
};
},
versionInfoUrl(): string {

View File

@ -33,9 +33,9 @@
<tr v-for="value in section.items" :key="value.n">
<th>{{ value.n }}</th>
<td>
<tempplate v-if="value.u!='bool'">
<template v-if="value.u!='bool'">
{{ $n(value.v, 'decimal') }} {{ value.u }}
</tempplate>
</template>
<template v-else>
<StatusBadge :status="value.v==1" true_text="gridprofile.Enabled" false_text="gridprofile.Disabled"/>
</template>

View File

@ -2,7 +2,7 @@
<div class="card" :class="{
'border-info': channelType == 'AC',
'border-secondary': channelType == 'INV'
}">
}" style="overflow: hidden">
<div v-if="channelType == 'INV'" class="card-header text-bg-secondary">
{{ $t('inverterchannelinfo.General') }}
</div>
@ -16,24 +16,17 @@
{{ $t('inverterchannelinfo.Phase', { num: channelNumber + 1 }) }}
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{ $t('inverterchannelinfo.Property') }}</th>
<th style="text-align: right" scope="col">{{ $t('inverterchannelinfo.Value') }}</th>
<th scope="col">{{ $t('inverterchannelinfo.Unit') }}</th>
</tr>
</thead>
<table class="table table-striped table-hover" style="margin: 0">
<tbody>
<tr v-for="(property, key) in channelData" :key="`prop-${key}`">
<template v-if="key != 'name' && property">
<th scope="row">{{ $t('inverterchannelproperty.' + key) }}</th>
<td style="text-align: right">
<td style="text-align: right; padding-right: 0;">
{{ $n(property.v, 'decimal', {
minimumFractionDigits: property.d,
maximumFractionDigits: property.d})
maximumFractionDigits: property.d
})
}}
</td>
<td>{{ property.u }}</td>
@ -43,7 +36,6 @@
</table>
</div>
</div>
</div>
</template>
<script lang="ts">

View File

@ -2,9 +2,7 @@
<div v-show="totalVeData.enabled">
<div class="row row-cols-1 row-cols-md-3 g-3">
<div class="col">
<div class="card">
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.MpptTotalYieldTotal') }}</div>
<div class="card-body card-text text-center">
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.MpptTotalYieldTotal')">
<h2>
{{ $n(totalVeData.total.YieldTotal.v, 'decimal', {
minimumFractionDigits: totalVeData.total.YieldTotal.d,
@ -12,13 +10,10 @@
}) }}
<small class="text-muted">{{ totalVeData.total.YieldTotal.u }}</small>
</h2>
</div>
</div>
</CardElement>
</div>
<div class="col">
<div class="card">
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.MpptTotalYieldDay') }}</div>
<div class="card-body card-text text-center">
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.MpptTotalYieldDay')">
<h2>
{{ $n(totalVeData.total.YieldDay.v, 'decimal', {
minimumFractionDigits: totalVeData.total.YieldDay.d,
@ -26,13 +21,10 @@
}) }}
<small class="text-muted">{{ totalVeData.total.YieldDay.u }}</small>
</h2>
</div>
</div>
</CardElement>
</div>
<div class="col">
<div class="card">
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.MpptTotalPower') }}</div>
<div class="card-body card-text text-center">
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.MpptTotalPower')">
<h2>
{{ $n(totalVeData.total.Power.v, 'decimal', {
minimumFractionDigits: totalVeData.total.Power.d,
@ -40,16 +32,13 @@
}) }}
<small class="text-muted">{{ totalVeData.total.Power.u }}</small>
</h2>
</div>
</div>
</CardElement>
</div>
</div>
</div>
<div class="row row-cols-1 row-cols-md-3 g-3">
<div class="col">
<div class="card">
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.InverterTotalYieldTotal') }}</div>
<div class="card-body card-text text-center">
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.TotalYieldTotal')">
<h2>
{{ $n(totalData.YieldTotal.v, 'decimal', {
minimumFractionDigits: totalData.YieldTotal.d,
@ -57,13 +46,10 @@
}) }}
<small class="text-muted">{{ totalData.YieldTotal.u }}</small>
</h2>
</div>
</div>
</CardElement>
</div>
<div class="col">
<div class="card">
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.InverterTotalYieldDay') }}</div>
<div class="card-body card-text text-center">
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.TotalYieldDay')">
<h2>
{{ $n(totalData.YieldDay.v, 'decimal', {
minimumFractionDigits: totalData.YieldDay.d,
@ -71,13 +57,10 @@
}) }}
<small class="text-muted">{{ totalData.YieldDay.u }}</small>
</h2>
</div>
</div>
</CardElement>
</div>
<div class="col">
<div class="card">
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.InverterTotalPower') }}</div>
<div class="card-body card-text text-center">
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.TotalPower')">
<h2>
{{ $n(totalData.Power.v, 'decimal', {
minimumFractionDigits: totalData.Power.d,
@ -85,16 +68,13 @@
}) }}
<small class="text-muted">{{ totalData.Power.u }}</small>
</h2>
</div>
</div>
</CardElement>
</div>
</div>
<div v-show="totalBattData.enabled || powerMeterData.enabled || huaweiData.enabled">
<div class="row row-cols-1 row-cols-md-3 g-3">
<div class="col" v-show="totalBattData.enabled">
<div class="card">
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.BatterySoc') }}</div>
<div class="card-body card-text text-center">
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.BatterySoc')">
<h2>
{{ $n(totalBattData.soc.v, 'decimal', {
minimumFractionDigits: totalBattData.soc.d,
@ -102,13 +82,10 @@
}) }}
<small class="text-muted">{{ totalBattData.soc.u }}</small>
</h2>
</div>
</div>
</CardElement>
</div>
<div class="col" v-show="powerMeterData.enabled">
<div class="card">
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.HomePower') }}</div>
<div class="card-body card-text text-center">
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.HomePower')">
<h2>
{{ $n(powerMeterData.Power.v, 'decimal', {
minimumFractionDigits: powerMeterData.Power.d,
@ -116,13 +93,10 @@
}) }}
<small class="text-muted">{{powerMeterData.Power.u }}</small>
</h2>
</div>
</div>
</CardElement>
</div>
<div class="col" v-show="huaweiData.enabled">
<div class="card">
<div class="card-header text-bg-success">{{ $t('invertertotalinfo.HuaweiPower') }}</div>
<div class="card-body card-text text-center">
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.HuaweiPower')">
<h2>
{{ $n(huaweiData.Power.v, 'decimal', {
minimumFractionDigits: huaweiData.Power.d,
@ -130,8 +104,7 @@
}) }}
<small class="text-muted">{{huaweiData.Power.u }}</small>
</h2>
</div>
</div>
</CardElement>
</div>
</div>
</div>
@ -139,9 +112,13 @@
<script lang="ts">
import type { Battery, Total, Vedirect, Huawei, PowerMeter } from '@/types/LiveDataStatus';
import CardElement from './CardElement.vue';
import { defineComponent, type PropType } from 'vue';
export default defineComponent({
components: {
CardElement,
},
props: {
totalData: { type: Object as PropType<Total>, required: true },
totalVeData: { type: Object as PropType<Vedirect>, required: true },

View File

@ -0,0 +1,52 @@
<template>
<div class="modal" :id="modalId" tabindex="-1">
<div class="modal-dialog" :class="[small ? '' : 'modal-lg']">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ title }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="getCloseText"
@click="close"></button>
</div>
<div class="modal-body">
<div class="text-center" v-if="loading">
<div class="spinner-border" role="status">
<span class="visually-hidden">{{ $t('home.Loading') }}</span>
</div>
</div>
<slot v-else>
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
</slot>
<button type="button" class="btn btn-secondary" @click="close" data-bs-dismiss="modal">{{
getCloseText }}</button>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
'modalId': { type: String, required: true },
'title': { type: String, required: true },
'closeText': { type: String, required: false, default: '' },
'small': Boolean,
'loading': Boolean,
},
computed: {
getCloseText() {
return this.closeText == '' ? this.$t('base.Close') : this.closeText;
}
},
methods: {
close() {
this.$emit('close');
},
},
});
</script>

View File

@ -1,17 +1,16 @@
<template>
<nav class="navbar navbar-expand-md fixed-top bg-body-tertiary" data-bs-theme="dark">
<div class="container-fluid">
<router-link @click="onClick" class="navbar-brand" to="/">
<span v-if="isXmas" class="text-success">
<BIconTree width="30" height="30" class="d-inline-block align-text-top" />
</span>
<span v-else-if="isEaster" class="text-info">
<BIconEgg width="30" height="30" class="d-inline-block align-text-top" />
</span>
<span v-else class="text-warning">
<BIconSun width="30" height="30" class="d-inline-block align-text-top" />
</span>
<router-link @click="onClick" class="navbar-brand" to="/" style="display: flex; height: 30px; padding: 0;">
<BIconTree v-if="isXmas" width="30" height="30" class="d-inline-block align-text-top text-success" />
<BIconEgg v-else-if="isEaster" width="30" height="30" class="d-inline-block align-text-top text-info" />
<BIconSun v-else width="30" height="30" class="d-inline-block align-text-top text-warning" />
<span style="margin-left: .5rem">
OpenDTU-onBattery
</span>
<span class="text-info">
<BIconBatteryCharging width="20" height="20" class="d-inline-block align-text-center" />
</span>

View File

@ -38,7 +38,8 @@
"Save": "Speichern",
"Refreshing": "Aktualisieren",
"Pull": "Zum Aktualisieren nach unten ziehen",
"Release": "Loslassen zum Aktualisieren"
"Release": "Loslassen zum Aktualisieren",
"Close": "Schließen"
},
"localeswitcher": {
"Dark": "Dunkel",
@ -125,7 +126,6 @@
"UnreadMessages": "Ungelesene Meldungen",
"Loading": "@:base.Loading",
"EventLog": "Ereignisanzeige",
"Close": "Schließen",
"InverterInfo": "Wechselrichter-Informationen",
"LimitSettings": "Limit-Einstellungen",
"LastLimitSetStatus": "Letzter Übertragungsstatus:",
@ -229,10 +229,12 @@
"FirmwareVersionHint": "Klicken Sie hier, um Informationen über Ihre aktuelle Version anzuzeigen",
"FirmwareUpdate": "Firmware-Aktualisierung",
"FirmwareUpdateHint": "Klicken Sie hier, um die Änderungen zwischen Ihrer Version und der neuesten Version anzuzeigen",
"FrmwareUpdateAllow": "Durch Aktivieren der Update Prüfung wird bei jedem Seitenaufruf eine Anfrage an GitHub.com gesendet um die aktuell verfügbare Version abzurufen. Wenn du damit nicht einverstanden bist, lasse diese Funktion deaktiviert.",
"ResetReason0": "Reset Grund CPU 0",
"ResetReason1": "Reset Grund CPU 1",
"ConfigSaveCount": "Anzahl der Konfigurationsspeicherungen",
"Uptime": "Betriebszeit"
"Uptime": "Betriebszeit",
"UptimeValue": "0 Tage {time} | 1 Tag {time} | {count} Tage {time}"
},
"hardwareinfo": {
"HardwareInformation": "Hardwareinformationen",
@ -371,10 +373,7 @@
"inverterchannelinfo": {
"String": "String {num}",
"Phase": "Phase {num}",
"General": "Allgemein",
"Property": "Eigenschaft",
"Value": "Wert",
"Unit": "Einheit"
"General": "Allgemein"
},
"invertertotalinfo": {
"InverterTotalYieldTotal": "Inverter Gesamtertrag Insgesamt",
@ -422,7 +421,7 @@
"CmtPaLevel": "CMT2300A Sendeleistung:",
"NrfPaLevelHint": "Verwendet für HM-Wechselrichter. Stellen Sie sicher, dass Ihre Stromversorgung stabil genug ist, bevor Sie die Sendeleistung erhöhen.",
"CmtPaLevelHint": "Verwendet für HMS/HMT-Wechselrichter. Stellen Sie sicher, dass Ihre Stromversorgung stabil genug ist, bevor Sie die Sendeleistung erhöhen.",
"CmtCountry": "Region/Land:",
"CmtCountry": "CMT2300A Region/Land:",
"CmtCountryHint": "Jedes Land hat unterschiedliche Frequenzzuteilungen.",
"country_0": "Europa ({min}MHz - {max}MHz)",
"country_1": "Nordamerika ({min}MHz - {max}MHz)",
@ -716,7 +715,7 @@
"Back": "Zurück",
"Retry": "Wiederholen",
"OtaStatus": "OTA-Status",
"OtaSuccess": "OTA erfolgreich. Das Gerät wurde automatisch neu gestartet und wird in wenigen Augenblicken wieder zur Verfügung stehen. Bitte nicht vergessen, die Weboberfläche neu zu laden!",
"OtaSuccess": "Das Hochladen der Firmware war erfolgreich. Das Gerät wurde automatisch neu gestartet. Wenn das Gerät wieder erreichbar ist wird die automatisch Oberfläche neu geladen.",
"FirmwareUpload": "Firmware hochladen",
"UploadProgress": "Hochlade-Fortschritt"
},

View File

@ -38,7 +38,8 @@
"Save": "Save",
"Refreshing": "Refreshing",
"Pull": "Pull down to refresh",
"Release": "Release to refresh"
"Release": "Release to refresh",
"Close": "Close"
},
"localeswitcher": {
"Dark": "Dark",
@ -125,7 +126,6 @@
"UnreadMessages": "unread messages",
"Loading": "@:base.Loading",
"EventLog": "Event Log",
"Close": "Close",
"InverterInfo": "Inverter Info",
"LimitSettings": "Limit Settings",
"LastLimitSetStatus": "Last Limit Set Status:",
@ -230,10 +230,12 @@
"FirmwareVersionHint": "Click here to show information about your current version",
"FirmwareUpdate": "Firmware Update",
"FirmwareUpdateHint": "Click here to view the changes between your version and the latest version",
"FrmwareUpdateAllow": "By activating the update check, a request is sent to GitHub.com each time the page is called up to retrieve the currently available version. If you do not agree with this, leave this function deactivated.",
"ResetReason0": "Reset Reason CPU 0",
"ResetReason1": "Reset Reason CPU 1",
"ConfigSaveCount": "Config save count",
"Uptime": "Uptime"
"Uptime": "Uptime",
"UptimeValue": "0 days {time} | 1 day {time} | {count} days {time}"
},
"hardwareinfo": {
"HardwareInformation": "Hardware Information",
@ -373,10 +375,7 @@
"inverterchannelinfo": {
"String": "String {num}",
"Phase": "Phase {num}",
"General": "General",
"Property": "Property",
"Value": "Value",
"Unit": "Unit"
"General": "General"
},
"invertertotalinfo": {
"InverterTotalYieldTotal": "Inverter Total Yield Total",
@ -424,7 +423,7 @@
"CmtPaLevel": "CMT2300A Transmitting power:",
"NrfPaLevelHint": "Used for HM-Inverters. Make sure your power supply is stable enough before increasing the transmit power.",
"CmtPaLevelHint": "Used for HMS/HMT-Inverters. Make sure your power supply is stable enough before increasing the transmit power.",
"CmtCountry": "Region/Country:",
"CmtCountry": "CMT2300A Region/Country:",
"CmtCountryHint": "Each country has different frequency allocations.",
"country_0": "Europe ({min}MHz - {max}MHz)",
"country_1": "North America ({min}MHz - {max}MHz)",
@ -725,7 +724,7 @@
"Back": "Back",
"Retry": "Retry",
"OtaStatus": "OTA Status",
"OtaSuccess": "OTA Success. The unit has been automatically restarted and will be available again in a few moments. Please do not forget to reload the web interface!",
"OtaSuccess": "The firmware upload was successful. The device was restarted automatically. When the device is accessible again, the interface is automatically reloaded.",
"FirmwareUpload": "Firmware Upload",
"UploadProgress": "Upload Progress"
},

View File

@ -38,7 +38,8 @@
"Save": "Sauvegarder",
"Refreshing": "Refreshing",
"Pull": "Pull down to refresh",
"Release": "Release to refresh"
"Release": "Release to refresh",
"Close": "Fermer"
},
"localeswitcher": {
"Dark": "Sombre",
@ -125,7 +126,6 @@
"UnreadMessages": "messages non lus",
"Loading": "@:base.Loading",
"EventLog": "Journal des événements",
"Close": "Fermer",
"InverterInfo": "Informations sur l'onduleur",
"LimitSettings": "Paramètres de la limite",
"LastLimitSetStatus": "Statut de la dernière limite fixée",
@ -229,10 +229,12 @@
"FirmwareVersionHint": "Cliquez ici pour afficher des informations sur votre version actuelle",
"FirmwareUpdate": "Mise à jour du firmware",
"FirmwareUpdateHint": "Cliquez ici pour voir les changements entre votre version et la dernière version",
"FrmwareUpdateAllow": "En activant le contrôle de mise à jour, une demande est envoyée à GitHub.com à chaque fois que la page est consultée afin de récupérer la dernière version disponible. Si tu n'es pas d'accord, laisse cette fonction désactivée.",
"ResetReason0": "Raison de la réinitialisation CPU 0",
"ResetReason1": "Raison de la réinitialisation CPU 1",
"ConfigSaveCount": "Nombre d'enregistrements de la configuration",
"Uptime": "Temps de fonctionnement"
"Uptime": "Durée de fonctionnement",
"UptimeValue": "0 jour {time} | 1 jour {time} | {count} jours {time}"
},
"hardwareinfo": {
"HardwareInformation": "Informations sur le matériel",
@ -371,10 +373,7 @@
"inverterchannelinfo": {
"String": "Ligne {num}",
"Phase": "Phase {num}",
"General": "General",
"Property": "Propriété",
"Value": "Valeur",
"Unit": "Unité"
"General": "General"
},
"invertertotalinfo": {
"InverterTotalYieldTotal": "Onduleurs rendement total",
@ -422,7 +421,7 @@
"CmtPaLevel": "CMT2300A Niveau de puissance d'émission",
"NrfPaLevelHint": "Used for HM-Inverters. Assurez-vous que votre alimentation est suffisamment stable avant d'augmenter la puissance d'émission.",
"CmtPaLevelHint": "Used for HMS/HMT-Inverters. Assurez-vous que votre alimentation est suffisamment stable avant d'augmenter la puissance d'émission.",
"CmtCountry": "Region/Country:",
"CmtCountry": "CMT2300A Region/Country:",
"CmtCountryHint": "Each country has different frequency allocations.",
"country_0": "Europe ({min}MHz - {max}MHz)",
"country_1": "North America ({min}MHz - {max}MHz)",
@ -683,7 +682,7 @@
"Back": "Retour",
"Retry": "Réessayer",
"OtaStatus": "Statut OTA",
"OtaSuccess": "Succès de l'OTA. L'unité a été automatiquement redémarrée et sera à nouveau disponible dans quelques instants. N'oubliez pas de recharger l'interface web !",
"OtaSuccess": "Le téléchargement du firmware a réussi. L'appareil a été redémarré automatiquement. Lorsque l'appareil est à nouveau accessible, l'interface est automatiquement rechargée.",
"FirmwareUpload": "Téléversement du firmware",
"UploadProgress": "Progression du téléversement"
},

View File

@ -1,6 +1,6 @@
// Import all of Bootstrap's CSS
@import "~bootstrap/scss/bootstrap";
main {
.container-fluid .row {
font-feature-settings: "tnum";
}

View File

@ -0,0 +1,22 @@
export interface InverterChannel {
name: string;
max_power: number;
yield_total_offset: number;
}
export interface Inverter {
id: string;
serial: number;
name: string;
type: string;
order: number;
poll_enable: boolean;
poll_enable_night: boolean;
command_enable: boolean;
command_enable_night: boolean;
reachable_threshold: number;
zero_runtime: boolean;
zero_day: boolean;
yieldday_correction: boolean;
channel: Array<InverterChannel>;
}

View File

@ -1,8 +1,10 @@
export const timestampToString = (timestampSeconds: number, includeDays = false): string => {
const timeString = new Date(timestampSeconds * 1000).toLocaleTimeString([], { timeZone: "UTC" });
if (!includeDays) return timeString;
export function timestampToString(locale: string, timestampSeconds: number, includeDays: true): [number, string];
export function timestampToString(locale: string, timestampSeconds: number, includeDays?: false): [string];
export function timestampToString(locale: string, timestampSeconds: number, includeDays = false): [number, string] | [string] {
const timeString = new Date(timestampSeconds * 1000).toLocaleTimeString(locale, { timeZone: "UTC", hour12: false });
if (!includeDays) return [timeString];
const secondsPerDay = 60 * 60 * 24;
const days = Math.floor(timestampSeconds / secondsPerDay);
return new Intl.RelativeTimeFormat().format(-days, "day") + " " + timeString;
return [days, timeString];
}

View File

@ -88,32 +88,21 @@
</CardElement>
</BasePage>
<div class="modal" id="factoryReset" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ $t('configadmin.FactoryReset') }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<ModalDialog modalId="factoryReset" small :title="$t('configadmin.FactoryReset')" :closeText="$t('configadmin.Cancel')">
{{ $t('configadmin.ResetMsg') }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="onFactoryResetCancel"
data-bs-dismiss="modal">{{ $t('configadmin.Cancel') }}</button>
<template #footer>
<button type="button" class="btn btn-danger" @click="onFactoryResetPerform">
{{ $t('configadmin.ResetConfirm') }}
</button>
</div>
</div>
</div>
</div>
</template>
</ModalDialog>
</template>
<script lang="ts">
import BasePage from '@/components/BasePage.vue';
import BootstrapAlert from "@/components/BootstrapAlert.vue";
import CardElement from '@/components/CardElement.vue';
import ModalDialog from '@/components/ModalDialog.vue';
import type { ConfigFileList } from '@/types/Config';
import { authHeader, handleResponse } from '@/utils/authentication';
import * as bootstrap from 'bootstrap';
@ -129,6 +118,7 @@ export default defineComponent({
BasePage,
BootstrapAlert,
CardElement,
ModalDialog,
BIconArrowLeft,
BIconCheckCircle,
BIconExclamationCircleFill,

Some files were not shown because too many files have changed in this diff Show More