OpenDTU-old/src/WebApi_ws_vedirect_live.cpp
helgeerbe d494810975
merge V23.12.16 (#556)
* Optimize Sun data calculation

* Remove not required enum

* Split config struct into different sub structs

* Feature: Allow configuration of LWT QoS

* Made resetreason methods static

* Feature: Implement offset cache for "YieldDay"

Thanks to @broth-itk for the idea!
Fix: #1258 #1397

* Add Esp32-Stick-PoE-A

* remove broken LilyGO_T_ETH_POE config, use device profile instead

* Feature: High resolution Icon and PWA (Progressive Web App) functionality

Fix: #1289

* webapp: Update dependencies

* Initialize TaskScheduler

* Migrate SunPosition to TaskScheduler

* Migrate Datastore to TaskScheduler

* Migrate MqttHandleInverterTotal to TaskSchedule

* Migrate MqttHandleHass to TaskScheduler

* Migrate MqttHandleDtu to TaskScheduler

* Migrate MqttHandleInverter to TaskScheduler

* Migrate LedSingle to TaskScheduler

* Migrate NetworkSettings to TaskScheduler

* Migrate InverterSettings to TaskScheduler

* Migrate MessageOutput to TaskScheduler

* Migrate Display_Graphic to TaskScheduler

* Migrate WebApi to TaskScheduler

* Split InverterSettings into multiple tasks

* Calculate SunPosition only every 5 seconds

* Split LedSingle into multiple tasks

* Upgrade espMqttClient from 1.4.5 to 1.5.0

* Doc: Correct amount of MPP-Tracker

* Added HMT-1600-4T and HMT-1800-4T to DevInfoParser

Fix #1524

* Adjusted inverter names for HMS-1600/1800/2000-4T

* Add channel count to description of detected inverter type (DevInfoParser)

* Adjust device web api endpoint for dynamic led count

* Feature: Added ability to change the brightness of the LEDs

Based on the idea of @moritzlerch with several modifications like pwmTable and structure

* webapp: Update dependencies

* Update olikraus/U8g2 from 2.35.7 to 2.35.8

* Remove not required onWebsocketEvent

* Remove code nesting

* Introduce several const statements

* Remove not required AsyncEventSource

* Doc: Added byte specification to each command

* Feature: Added basic Grid Profile parser which shows the used profile and version

Other values are still outstanding.

* Optimize AlarmLogParser to save memory

* Add libfrozen to project to create constexpr maps

* Feature: First version of GridProfile Parser which shows all values contained in the profile.

* webapp: Update dependencies

* Apply better variable names

* Remove not required casts

* Add additional compiler flags to prevent errors

* Add const statement to several variables

* Replace NULL by nullptr

* Update bblanchon/ArduinoJson from 6.21.3 to 6.21.4

* Add const keyword to method parameters

* Add const keyword to methods

* Use references instead of pointers whenver possible

* Adjust member variable names in MqttSettings

* Adjust member variable names in NetworkSettings

* webapp: Update timezone database to latest version

* webapp: Beautify and unify form footers

* Feature: Allow setting of an inverter limit of 0% and 0W

Thanks to @madmartin in #1270

* Feature: Allow links in device profiles

These links will be shown on the hardware settings page.

* Doc: Added hint regarding HMS-xxxx-xT-NA inverters

* Feature: Added DeviceProfile for CASmo-DTU

Based on #1565

* Upgrade actions/upload-artifact from v3 to v4

* Upgrade actions/download-artifact from v3 to v4

* webapp: add app.js.gz

* Gridprofileparser: Added latest known values

Thanks to @stefan123t and @noone2k

* webapp: Fix lint errors

* Feature: Add DTU to Home Assistant Auto Discovery

This is based on PR 1365 from @CFenner with several fixes and optimizations

* Fix: Remove debug output as it floods the console

* Fix: Gridprofileparser: Add additional error handling if profile is unknown

* webapp: add app.js.gz

* Fix: Offset cache for "YieldDay" did not work correctly

* webapp: update dependencies

* webapp: add app.js.gz

* Fix: yarn.lock was outdated

* Fix: yarn build error

* Fix: Reset Yield day correction in combination with Zero Yield Day on Midnight lead to wrong values.

* Fix: Allow negative values in GridProfileParser

* Correct variable name

* Fix #1579: Static IP in Ethernet mode did not work correctly

* Feature: Added diagram to display

This is based on the idea of @Henrik-Ingenieur and was discussed in #1504

* webapp: update dependencies

* webapp: add app.js.gz

---------

Co-authored-by: Thomas Basler <thomas@familie-basler.net>
Co-authored-by: Pierre Kancir <pierre.kancir.emn@gmail.com>
2023-12-27 11:49:57 +01:00

184 lines
6.5 KiB
C++

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Thomas Basler and others
*/
#include "WebApi_ws_vedirect_live.h"
#include "AsyncJson.h"
#include "Configuration.h"
#include "MessageOutput.h"
#include "WebApi.h"
#include "defaults.h"
#include "PowerLimiter.h"
#include "VictronMppt.h"
WebApiWsVedirectLiveClass::WebApiWsVedirectLiveClass()
: _ws("/vedirectlivedata")
{
}
void WebApiWsVedirectLiveClass::init(AsyncWebServer& server)
{
using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
using std::placeholders::_4;
using std::placeholders::_5;
using std::placeholders::_6;
_server = &server;
_server->on("/api/vedirectlivedata/status", HTTP_GET, std::bind(&WebApiWsVedirectLiveClass::onLivedataStatus, this, _1));
_server->addHandler(&_ws);
_ws.onEvent(std::bind(&WebApiWsVedirectLiveClass::onWebsocketEvent, this, _1, _2, _3, _4, _5, _6));
}
void WebApiWsVedirectLiveClass::loop()
{
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
if (millis() - _lastWsCleanup > 1000) {
_ws.cleanupClients();
_lastWsCleanup = millis();
}
// do nothing if no WS client is connected
if (_ws.count() == 0) {
return;
}
// we assume this loop to be running at least twice for every
// update from a VE.Direct MPPT data producer, so _dataAgeMillis
// acutally grows in between updates.
auto lastDataAgeMillis = _dataAgeMillis;
_dataAgeMillis = VictronMppt.getDataAgeMillis();
// Update on ve.direct change or at least after 10 seconds
if (millis() - _lastWsPublish > (10 * 1000) || lastDataAgeMillis > _dataAgeMillis) {
try {
String buffer;
// free JsonDocument as soon as possible
{
DynamicJsonDocument root(_responseSize);
JsonVariant var = root;
generateJsonResponse(var);
serializeJson(root, buffer);
}
if (buffer) {
if (Configuration.get().Security.AllowReadonly) {
_ws.setAuthentication("", "");
} else {
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password);
}
_ws.textAll(buffer);
}
} catch (std::bad_alloc& bad_alloc) {
MessageOutput.printf("Calling /api/vedirectlivedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
}
_lastWsPublish = millis();
}
}
void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root)
{
auto spMpptData = VictronMppt.getData();
// device info
root["device"]["data_age"] = VictronMppt.getDataAgeMillis() / 1000;
root["device"]["age_critical"] = !VictronMppt.isDataValid();
root["device"]["PID"] = spMpptData->getPidAsString();
root["device"]["SER"] = spMpptData->SER;
root["device"]["FW"] = spMpptData->FW;
root["device"]["LOAD"] = spMpptData->LOAD == true ? "ON" : "OFF";
root["device"]["CS"] = spMpptData->getCsAsString();
root["device"]["ERR"] = spMpptData->getErrAsString();
root["device"]["OR"] = spMpptData->getOrAsString();
root["device"]["MPPT"] = spMpptData->getMpptAsString();
root["device"]["HSDS"]["v"] = spMpptData->HSDS;
root["device"]["HSDS"]["u"] = "d";
// battery info
root["output"]["P"]["v"] = spMpptData->P;
root["output"]["P"]["u"] = "W";
root["output"]["P"]["d"] = 0;
root["output"]["V"]["v"] = spMpptData->V;
root["output"]["V"]["u"] = "V";
root["output"]["V"]["d"] = 2;
root["output"]["I"]["v"] = spMpptData->I;
root["output"]["I"]["u"] = "A";
root["output"]["I"]["d"] = 2;
root["output"]["E"]["v"] = spMpptData->E;
root["output"]["E"]["u"] = "%";
root["output"]["E"]["d"] = 1;
// panel info
root["input"]["PPV"]["v"] = spMpptData->PPV;
root["input"]["PPV"]["u"] = "W";
root["input"]["PPV"]["d"] = 0;
root["input"]["VPV"]["v"] = spMpptData->VPV;
root["input"]["VPV"]["u"] = "V";
root["input"]["VPV"]["d"] = 2;
root["input"]["IPV"]["v"] = spMpptData->IPV;
root["input"]["IPV"]["u"] = "A";
root["input"]["IPV"]["d"] = 2;
root["input"]["YieldToday"]["v"] = spMpptData->H20;
root["input"]["YieldToday"]["u"] = "kWh";
root["input"]["YieldToday"]["d"] = 3;
root["input"]["YieldYesterday"]["v"] = spMpptData->H22;
root["input"]["YieldYesterday"]["u"] = "kWh";
root["input"]["YieldYesterday"]["d"] = 3;
root["input"]["YieldTotal"]["v"] = spMpptData->H19;
root["input"]["YieldTotal"]["u"] = "kWh";
root["input"]["YieldTotal"]["d"] = 3;
root["input"]["MaximumPowerToday"]["v"] = spMpptData->H21;
root["input"]["MaximumPowerToday"]["u"] = "W";
root["input"]["MaximumPowerToday"]["d"] = 0;
root["input"]["MaximumPowerYesterday"]["v"] = spMpptData->H23;
root["input"]["MaximumPowerYesterday"]["u"] = "W";
root["input"]["MaximumPowerYesterday"]["d"] = 0;
// power limiter state
root["dpl"]["PLSTATE"] = -1;
if (Configuration.get().PowerLimiter.Enabled)
root["dpl"]["PLSTATE"] = PowerLimiter.getPowerLimiterState();
root["dpl"]["PLLIMIT"] = PowerLimiter.getLastRequestedPowerLimit();
}
void WebApiWsVedirectLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
{
if (type == WS_EVT_CONNECT) {
char str[64];
snprintf(str, sizeof(str), "Websocket: [%s][%u] connect", server->url(), client->id());
Serial.println(str);
MessageOutput.println(str);
} else if (type == WS_EVT_DISCONNECT) {
char str[64];
snprintf(str, sizeof(str), "Websocket: [%s][%u] disconnect", server->url(), client->id());
Serial.println(str);
MessageOutput.println(str);
}
}
void WebApiWsVedirectLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentialsReadonly(request)) {
return;
}
try {
AsyncJsonResponse* response = new AsyncJsonResponse(false, _responseSize);
JsonVariant root = response->getRoot();
generateJsonResponse(root);
response->setLength();
request->send(response);
} catch (std::bad_alloc& bad_alloc) {
MessageOutput.printf("Calling /api/livedata/status has temporarily run out of resources. Reason: \"%s\".\r\n", bad_alloc.what());
WebApi.sendTooManyRequests(request);
}
}