// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2022 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 "HttpPowerMeter.h" #include "WebApi.h" #include "helper.h" void WebApiPowerMeterClass::init(AsyncWebServer& server) { 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/testhttprequest", HTTP_POST, std::bind(&WebApiPowerMeterClass::onTestHttpRequest, this, _1)); } void WebApiPowerMeterClass::loop() { } void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request) { AsyncJsonResponse* response = new AsyncJsonResponse(false, 2048); 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; root["interval"] = config.PowerMeter.Interval; root["mqtt_topic_powermeter_1"] = config.PowerMeter.MqttTopicPowerMeter1; root["mqtt_topic_powermeter_2"] = config.PowerMeter.MqttTopicPowerMeter2; root["mqtt_topic_powermeter_3"] = config.PowerMeter.MqttTopicPowerMeter3; root["sdmbaudrate"] = config.PowerMeter.SdmBaudrate; root["sdmaddress"] = config.PowerMeter.SdmAddress; root["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests; JsonArray httpPhases = root.createNestedArray("http_phases"); for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) { JsonObject phaseObject = httpPhases.createNestedObject(); phaseObject["index"] = i + 1; phaseObject["enabled"] = config.PowerMeter.Http_Phase[i].Enabled; phaseObject["url"] = String(config.PowerMeter.Http_Phase[i].Url); phaseObject["auth_type"]= config.PowerMeter.Http_Phase[i].AuthType; phaseObject["username"] = String(config.PowerMeter.Http_Phase[i].Username); phaseObject["password"] = String(config.PowerMeter.Http_Phase[i].Password); phaseObject["header_key"] = String(config.PowerMeter.Http_Phase[i].HeaderKey); phaseObject["header_value"] = String(config.PowerMeter.Http_Phase[i].HeaderValue); phaseObject["json_path"] = String(config.PowerMeter.Http_Phase[i].JsonPath); phaseObject["timeout"] = config.PowerMeter.Http_Phase[i].Timeout; } response->setLength(); request->send(response); } 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(); auto& retMsg = response->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { retMsg["message"] = "No values found!"; response->setLength(); request->send(response); return; } String json = request->getParam("data", true)->value(); if (json.length() > 4096) { retMsg["message"] = "Data too large!"; response->setLength(); request->send(response); return; } DynamicJsonDocument root(4096); DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; response->setLength(); request->send(response); return; } if (!(root.containsKey("enabled") && root.containsKey("source"))) { retMsg["message"] = "Values are missing!"; response->setLength(); request->send(response); return; } if (root["source"].as() == PowerMeter.SOURCE_HTTP) { JsonArray http_phases = root["http_phases"]; for (uint8_t i = 0; i < http_phases.size(); i++) { JsonObject phase = http_phases[i].as(); if (i > 0 && !phase["enabled"].as()) { continue; } if (i == 0 || phase["http_individual_requests"].as()) { if (!phase.containsKey("url") || (!phase["url"].as().startsWith("http://") && !phase["url"].as().startsWith("https://"))) { retMsg["message"] = "URL must either start with http:// or https://!"; response->setLength(); request->send(response); return; } if ((phase["auth_type"].as() != Auth::none) && ( phase["username"].as().length() == 0 || phase["password"].as().length() == 0)) { retMsg["message"] = "Username or password must not be empty!"; response->setLength(); request->send(response); return; } if (!phase.containsKey("timeout") || phase["timeout"].as() <= 0) { retMsg["message"] = "Timeout must be greater than 0 ms!"; response->setLength(); request->send(response); return; } } if (!phase.containsKey("json_path") || phase["json_path"].as().length() == 0) { retMsg["message"] = "Json path must not be empty!"; response->setLength(); request->send(response); 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(); config.PowerMeter.Interval = root["interval"].as(); strlcpy(config.PowerMeter.MqttTopicPowerMeter1, root["mqtt_topic_powermeter_1"].as().c_str(), sizeof(config.PowerMeter.MqttTopicPowerMeter1)); strlcpy(config.PowerMeter.MqttTopicPowerMeter2, root["mqtt_topic_powermeter_2"].as().c_str(), sizeof(config.PowerMeter.MqttTopicPowerMeter2)); strlcpy(config.PowerMeter.MqttTopicPowerMeter3, root["mqtt_topic_powermeter_3"].as().c_str(), sizeof(config.PowerMeter.MqttTopicPowerMeter3)); config.PowerMeter.SdmBaudrate = root["sdmbaudrate"].as(); config.PowerMeter.SdmAddress = root["sdmaddress"].as(); config.PowerMeter.HttpIndividualRequests = root["http_individual_requests"].as(); JsonArray http_phases = root["http_phases"]; for (uint8_t i = 0; i < http_phases.size(); i++) { JsonObject phase = http_phases[i].as(); config.PowerMeter.Http_Phase[i].Enabled = (i == 0 ? true : phase["enabled"].as()); strlcpy(config.PowerMeter.Http_Phase[i].Url, phase["url"].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Url)); config.PowerMeter.Http_Phase[i].AuthType = phase["auth_type"].as(); strlcpy(config.PowerMeter.Http_Phase[i].Username, phase["username"].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Username)); strlcpy(config.PowerMeter.Http_Phase[i].Password, phase["password"].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Password)); strlcpy(config.PowerMeter.Http_Phase[i].HeaderKey, phase["header_key"].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].HeaderKey)); strlcpy(config.PowerMeter.Http_Phase[i].HeaderValue, phase["header_value"].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].HeaderValue)); config.PowerMeter.Http_Phase[i].Timeout = phase["timeout"].as(); strlcpy(config.PowerMeter.Http_Phase[i].JsonPath, phase["json_path"].as().c_str(), sizeof(config.PowerMeter.Http_Phase[i].JsonPath)); } WebApi.writeConfig(retMsg); response->setLength(); request->send(response); yield(); delay(1000); yield(); ESP.restart(); } void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request) { if (!WebApi.checkCredentials(request)) { return; } AsyncJsonResponse* asyncJsonResponse = new AsyncJsonResponse(); auto& retMsg = asyncJsonResponse->getRoot(); retMsg["type"] = "warning"; if (!request->hasParam("data", true)) { retMsg["message"] = "No values found!"; asyncJsonResponse->setLength(); request->send(asyncJsonResponse); return; } String json = request->getParam("data", true)->value(); if (json.length() > 2048) { retMsg["message"] = "Data too large!"; asyncJsonResponse->setLength(); request->send(asyncJsonResponse); return; } DynamicJsonDocument root(2048); DeserializationError error = deserializeJson(root, json); if (error) { retMsg["message"] = "Failed to parse data!"; asyncJsonResponse->setLength(); request->send(asyncJsonResponse); return; } if (!root.containsKey("url") || !root.containsKey("auth_type") || !root.containsKey("username") || !root.containsKey("password") || !root.containsKey("header_key") || !root.containsKey("header_value") || !root.containsKey("timeout") || !root.containsKey("json_path")) { retMsg["message"] = "Missing fields!"; asyncJsonResponse->setLength(); request->send(asyncJsonResponse); return; } char powerMeterResponse[2000], errorMessage[256]; char response[200]; if (HttpPowerMeter.httpRequest(root["url"].as().c_str(), root["auth_type"].as(), root["username"].as().c_str(), root["password"].as().c_str(), root["header_key"].as().c_str(), root["header_value"].as().c_str(), root["timeout"].as(), powerMeterResponse, sizeof(powerMeterResponse), errorMessage, sizeof(errorMessage))) { float power; if (HttpPowerMeter.getFloatValueByJsonPath(powerMeterResponse, root["json_path"].as().c_str(), power)) { retMsg["type"] = "success"; snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", power); } else { snprintf_P(response, sizeof(response), "Error: Could not find value for JSON path!"); } } else { snprintf_P(response, sizeof(response), errorMessage); } retMsg["message"] = response; asyncJsonResponse->setLength(); request->send(asyncJsonResponse); }