// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2022-2024 Thomas Basler and others */ #include "WebApi_powermeter.h" #include "VeDirectFrameHandler.h" #include "ArduinoJson.h" #include "AsyncJson.h" #include "Configuration.h" #include "MqttHandleVedirectHass.h" #include "MqttHandleHass.h" #include "MqttSettings.h" #include "PowerLimiter.h" #include "PowerMeter.h" #include "PowerMeterHttpJson.h" #include "PowerMeterHttpSml.h" #include "WebApi.h" #include "helper.h" void WebApiPowerMeterClass::init(AsyncWebServer& server, Scheduler& scheduler) { using std::placeholders::_1; _server = &server; _server->on("/api/powermeter/status", HTTP_GET, std::bind(&WebApiPowerMeterClass::onStatus, this, _1)); _server->on("/api/powermeter/config", HTTP_GET, std::bind(&WebApiPowerMeterClass::onAdminGet, this, _1)); _server->on("/api/powermeter/config", HTTP_POST, std::bind(&WebApiPowerMeterClass::onAdminPost, this, _1)); _server->on("/api/powermeter/testhttpjsonrequest", HTTP_POST, std::bind(&WebApiPowerMeterClass::onTestHttpJsonRequest, this, _1)); _server->on("/api/powermeter/testhttpsmlrequest", HTTP_POST, std::bind(&WebApiPowerMeterClass::onTestHttpSmlRequest, this, _1)); } void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request) { if (!WebApi.checkCredentialsReadonly(request)) { return; } AsyncJsonResponse* response = new AsyncJsonResponse(); auto& root = response->getRoot(); const CONFIG_T& config = Configuration.get(); root["enabled"] = config.PowerMeter.Enabled; root["verbose_logging"] = config.PowerMeter.VerboseLogging; root["source"] = config.PowerMeter.Source; auto mqtt = root["mqtt"].to(); Configuration.serializePowerMeterMqttConfig(config.PowerMeter.Mqtt, mqtt); auto serialSdm = root["serial_sdm"].to(); Configuration.serializePowerMeterSerialSdmConfig(config.PowerMeter.SerialSdm, serialSdm); auto httpJson = root["http_json"].to(); Configuration.serializePowerMeterHttpJsonConfig(config.PowerMeter.HttpJson, httpJson); auto httpSml = root["http_sml"].to(); Configuration.serializePowerMeterHttpSmlConfig(config.PowerMeter.HttpSml, httpSml); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } void WebApiPowerMeterClass::onAdminGet(AsyncWebServerRequest* request) { if (!WebApi.checkCredentials(request)) { return; } this->onStatus(request); } void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request) { if (!WebApi.checkCredentials(request)) { return; } AsyncJsonResponse* response = new AsyncJsonResponse(); JsonDocument root; if (!WebApi.parseRequestData(request, response, root)) { return; } auto& retMsg = response->getRoot(); if (!(root["enabled"].is() && root["source"].is())) { retMsg["message"] = "Values are missing!"; response->setLength(); request->send(response); return; } auto checkHttpConfig = [&](JsonObject const& cfg) -> bool { if (!cfg["url"].is() || (!cfg["url"].as().startsWith("http://") && !cfg["url"].as().startsWith("https://"))) { retMsg["message"] = "URL must either start with http:// or https://!"; response->setLength(); request->send(response); return false; } if ((cfg["auth_type"].as() != HttpRequestConfig::Auth::None) && (cfg["username"].as().length() == 0 || cfg["password"].as().length() == 0)) { retMsg["message"] = "Username or password must not be empty!"; response->setLength(); request->send(response); return false; } if (!cfg["timeout"].is() || cfg["timeout"].as() <= 0) { retMsg["message"] = "Timeout must be greater than 0 ms!"; response->setLength(); request->send(response); return false; } return true; }; if (static_cast(root["source"].as()) == PowerMeterProvider::Type::HTTP_JSON) { JsonObject httpJson = root["http_json"]; JsonArray valueConfigs = httpJson["values"]; for (uint8_t i = 0; i < valueConfigs.size(); i++) { JsonObject valueConfig = valueConfigs[i].as(); if (i > 0 && !valueConfig["enabled"].as()) { continue; } if (i == 0 || httpJson["individual_requests"].as()) { if (!checkHttpConfig(valueConfig["http_request"].as())) { return; } } if (!valueConfig["json_path"].is() || valueConfig["json_path"].as().length() == 0) { retMsg["message"] = "Json path must not be empty!"; response->setLength(); request->send(response); return; } } } if (static_cast(root["source"].as()) == PowerMeterProvider::Type::HTTP_SML) { JsonObject httpSml = root["http_sml"]; if (!checkHttpConfig(httpSml["http_request"].as())) { return; } } CONFIG_T& config = Configuration.get(); config.PowerMeter.Enabled = root["enabled"].as(); config.PowerMeter.VerboseLogging = root["verbose_logging"].as(); config.PowerMeter.Source = root["source"].as(); Configuration.deserializePowerMeterMqttConfig(root["mqtt"].as(), config.PowerMeter.Mqtt); Configuration.deserializePowerMeterSerialSdmConfig(root["serial_sdm"].as(), config.PowerMeter.SerialSdm); Configuration.deserializePowerMeterHttpJsonConfig(root["http_json"].as(), config.PowerMeter.HttpJson); Configuration.deserializePowerMeterHttpSmlConfig(root["http_sml"].as(), config.PowerMeter.HttpSml); WebApi.writeConfig(retMsg); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); PowerMeter.updateSettings(); } void WebApiPowerMeterClass::onTestHttpJsonRequest(AsyncWebServerRequest* request) { if (!WebApi.checkCredentials(request)) { return; } AsyncJsonResponse* asyncJsonResponse = new AsyncJsonResponse(); JsonDocument root; if (!WebApi.parseRequestData(request, asyncJsonResponse, root)) { return; } auto& retMsg = asyncJsonResponse->getRoot(); char response[256]; auto powerMeterConfig = std::make_unique(); Configuration.deserializePowerMeterHttpJsonConfig(root["http_json"].as(), *powerMeterConfig); auto upMeter = std::make_unique(*powerMeterConfig); upMeter->init(); auto res = upMeter->poll(); using values_t = PowerMeterHttpJson::power_values_t; if (std::holds_alternative(res)) { retMsg["type"] = "success"; auto vals = std::get(res); auto pos = snprintf(response, sizeof(response), "Result: %5.2fW", vals[0]); for (size_t i = 1; i < vals.size(); ++i) { if (!powerMeterConfig->Values[i].Enabled) { continue; } pos += snprintf(response + pos, sizeof(response) - pos, ", %5.2fW", vals[i]); } snprintf(response + pos, sizeof(response) - pos, ", Total: %5.2f", upMeter->getPowerTotal()); } else { snprintf(response, sizeof(response), "%s", std::get(res).c_str()); } retMsg["message"] = response; asyncJsonResponse->setLength(); request->send(asyncJsonResponse); } void WebApiPowerMeterClass::onTestHttpSmlRequest(AsyncWebServerRequest* request) { if (!WebApi.checkCredentials(request)) { return; } AsyncJsonResponse* asyncJsonResponse = new AsyncJsonResponse(); JsonDocument root; if (!WebApi.parseRequestData(request, asyncJsonResponse, root)) { return; } auto& retMsg = asyncJsonResponse->getRoot(); char response[256]; auto powerMeterConfig = std::make_unique(); Configuration.deserializePowerMeterHttpSmlConfig(root["http_sml"].as(), *powerMeterConfig); auto upMeter = std::make_unique(*powerMeterConfig); upMeter->init(); auto res = upMeter->poll(); if (res.isEmpty()) { retMsg["type"] = "success"; snprintf(response, sizeof(response), "Result: %5.2fW", upMeter->getPowerTotal()); } else { snprintf(response, sizeof(response), "%s", res.c_str()); } retMsg["message"] = response; asyncJsonResponse->setLength(); request->send(asyncJsonResponse); }