From ede1abb5e61d61a97f59fa87635d4dcfad55fadc Mon Sep 17 00:00:00 2001 From: Bernhard Kirchen Date: Tue, 16 Apr 2024 22:37:18 +0200 Subject: [PATCH] Feature: HTTP power meter: support mW/kW as units --- include/Configuration.h | 2 ++ include/HttpPowerMeter.h | 3 ++- src/Configuration.cpp | 2 ++ src/HttpPowerMeter.cpp | 20 ++++++++++++++++---- src/WebApi_powermeter.cpp | 4 +++- webapp/src/locales/de.json | 3 ++- webapp/src/locales/en.json | 5 +++-- webapp/src/types/PowerMeterConfig.ts | 1 + webapp/src/views/PowerMeterAdminView.vue | 13 +++++++++++++ 9 files changed, 44 insertions(+), 9 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index 8d22248fd..570b7c3a1 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -64,6 +64,7 @@ struct INVERTER_CONFIG_T { struct POWERMETER_HTTP_PHASE_CONFIG_T { enum Auth { None, Basic, Digest }; + enum Unit { Watts = 0, MilliWatts = 1, KiloWatts = 2 }; bool Enabled; char Url[POWERMETER_MAX_HTTP_URL_STRLEN + 1]; Auth AuthType; @@ -73,6 +74,7 @@ struct POWERMETER_HTTP_PHASE_CONFIG_T { char HeaderValue[POWERMETER_MAX_HTTP_HEADER_VALUE_STRLEN + 1]; uint16_t Timeout; char JsonPath[POWERMETER_MAX_HTTP_JSON_PATH_STRLEN + 1]; + Unit PowerUnit; }; using PowerMeterHttpConfig = struct POWERMETER_HTTP_PHASE_CONFIG_T; diff --git a/include/HttpPowerMeter.h b/include/HttpPowerMeter.h index 97e43a2cf..db79fe09b 100644 --- a/include/HttpPowerMeter.h +++ b/include/HttpPowerMeter.h @@ -7,6 +7,7 @@ #include "Configuration.h" using Auth_t = PowerMeterHttpConfig::Auth; +using Unit_t = PowerMeterHttpConfig::Unit; class HttpPowerMeterClass { public: @@ -25,7 +26,7 @@ class HttpPowerMeterClass { String extractParam(String& authReq, const String& param, const char delimit); String getcNonce(const int len); String getDigestAuth(String& authReq, const String& username, const String& password, const String& method, const String& uri, unsigned int counter); - bool tryGetFloatValueForPhase(int phase, const char* jsonPath); + bool tryGetFloatValueForPhase(int phase, const char* jsonPath, Unit_t unit); void prepareRequest(uint32_t timeout, const char* httpHeader, const char* httpValue); String sha256(const String& data); }; diff --git a/src/Configuration.cpp b/src/Configuration.cpp index c74bc67b3..f21571cbb 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -175,6 +175,7 @@ bool ConfigurationClass::write() powermeter_phase["header_value"] = config.PowerMeter.Http_Phase[i].HeaderValue; powermeter_phase["timeout"] = config.PowerMeter.Http_Phase[i].Timeout; powermeter_phase["json_path"] = config.PowerMeter.Http_Phase[i].JsonPath; + powermeter_phase["unit"] = config.PowerMeter.Http_Phase[i].PowerUnit; } JsonObject powerlimiter = doc.createNestedObject("powerlimiter"); @@ -427,6 +428,7 @@ bool ConfigurationClass::read() strlcpy(config.PowerMeter.Http_Phase[i].HeaderValue, powermeter_phase["header_value"] | "", sizeof(config.PowerMeter.Http_Phase[i].HeaderValue)); config.PowerMeter.Http_Phase[i].Timeout = powermeter_phase["timeout"] | POWERMETER_HTTP_TIMEOUT; strlcpy(config.PowerMeter.Http_Phase[i].JsonPath, powermeter_phase["json_path"] | "", sizeof(config.PowerMeter.Http_Phase[i].JsonPath)); + config.PowerMeter.Http_Phase[i].PowerUnit = powermeter_phase["unit"] | PowerMeterHttpConfig::Unit::Watts; } JsonObject powerlimiter = doc["powerlimiter"]; diff --git a/src/HttpPowerMeter.cpp b/src/HttpPowerMeter.cpp index 798a9a2f8..ada189829 100644 --- a/src/HttpPowerMeter.cpp +++ b/src/HttpPowerMeter.cpp @@ -42,7 +42,7 @@ bool HttpPowerMeterClass::updateValues() continue; } - if(!tryGetFloatValueForPhase(i, phaseConfig.JsonPath)) { + if(!tryGetFloatValueForPhase(i, phaseConfig.JsonPath, phaseConfig.PowerUnit)) { MessageOutput.printf("[HttpPowerMeter] Getting the power of phase %d (from JSON fetched with Phase 1 config) failed.\r\n", i + 1); MessageOutput.printf("%s\r\n", httpPowerMeterError); return false; @@ -159,7 +159,9 @@ bool HttpPowerMeterClass::httpRequest(int phase, WiFiClient &wifiClient, const S httpResponse = httpClient.getString(); // very unfortunate that we cannot parse WifiClient stream directly httpClient.end(); - return tryGetFloatValueForPhase(phase, jsonPath); + // TODO(schlimmchen): postpone calling tryGetFloatValueForPhase, as it + // will be called twice for each phase when doing separate requests. + return tryGetFloatValueForPhase(phase, config.JsonPath, config.PowerUnit); } String HttpPowerMeterClass::extractParam(String& authReq, const String& param, const char delimit) { @@ -217,7 +219,7 @@ String HttpPowerMeterClass::getDigestAuth(String& authReq, const String& usernam return authorization; } -bool HttpPowerMeterClass::tryGetFloatValueForPhase(int phase, const char* jsonPath) +bool HttpPowerMeterClass::tryGetFloatValueForPhase(int phase, const char* jsonPath, Unit_t unit) { FirebaseJson json; json.setJsonData(httpResponse); @@ -227,7 +229,17 @@ bool HttpPowerMeterClass::tryGetFloatValueForPhase(int phase, const char* jsonPa return false; } - power[phase] = value.to(); + power[phase] = value.to(); // this is supposed to be in Watts + switch (unit) { + case Unit_t::MilliWatts: + power[phase] /= 1000; + break; + case Unit_t::KiloWatts: + power[phase] *= 1000; + break; + default: + break; + } return true; } diff --git a/src/WebApi_powermeter.cpp b/src/WebApi_powermeter.cpp index 88eeb069e..d66bfe433 100644 --- a/src/WebApi_powermeter.cpp +++ b/src/WebApi_powermeter.cpp @@ -39,6 +39,7 @@ void WebApiPowerMeterClass::decodeJsonPhaseConfig(JsonObject const& json, PowerM strlcpy(config.HeaderValue, json["header_value"].as().c_str(), sizeof(config.HeaderValue)); config.Timeout = json["timeout"].as(); strlcpy(config.JsonPath, json["json_path"].as().c_str(), sizeof(config.JsonPath)); + config.PowerUnit = json["unit"].as(); } void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request) @@ -71,8 +72,9 @@ void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request) 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; + phaseObject["json_path"] = String(config.PowerMeter.Http_Phase[i].JsonPath); + phaseObject["unit"] = config.PowerMeter.Http_Phase[i].PowerUnit; } response->setLength(); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 13808ec58..14bc10483 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -574,7 +574,8 @@ "httpHeaderKeyDescription": "Ein individueller HTTP request header kann hier definiert werden. Das kann z.B. verwendet werden um einen eigenen Authorization header mitzugeben.", "httpHeaderValue": "Optional: HTTP request header - Wert", "httpJsonPath": "JSON Pfad", - "httpJsonPathDescription": "JSON Pfad um den Leistungswert zu finden. Es verwendet die Selektions-Syntax von mobizt/FirebaseJson. Beispiele gibt es unten.", + "httpJsonPathDescription": "JSON Pfad um den Leistungswert zu finden. Es verwendet die Selektions-Syntax von mobizt/FirebaseJson. Beispiele gibt es oben.", + "httpUnit": "Einheit", "httpTimeout": "Timeout", "testHttpRequest": "Testen" }, diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 727eea19a..b841448cd 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -578,8 +578,9 @@ "httpHeaderKey": "Optional: HTTP request header - Key", "httpHeaderKeyDescription": "A custom HTTP request header can be defined. Might be useful if you have to send something like a custom Authorization header.", "httpHeaderValue": "Optional: HTTP request header - Value", - "httpJsonPath": "Json path", - "httpJsonPathDescription": "JSON path to find the power value in the response. This uses the JSON path query syntax from mobizt/FirebaseJson. See below for some examples.", + "httpJsonPath": "JSON path", + "httpJsonPathDescription": "JSON path to find the power value in the response. This uses the JSON path query syntax from mobizt/FirebaseJson. See above for examples.", + "httpUnit": "Unit", "httpTimeout": "Timeout", "testHttpRequest": "Run test", "milliSeconds": "ms" diff --git a/webapp/src/types/PowerMeterConfig.ts b/webapp/src/types/PowerMeterConfig.ts index d6eb9ee7d..5b38faebb 100644 --- a/webapp/src/types/PowerMeterConfig.ts +++ b/webapp/src/types/PowerMeterConfig.ts @@ -9,6 +9,7 @@ export interface PowerMeterHttpPhaseConfig { header_value: string; json_path: string; timeout: number; + unit: number; } export interface PowerMeterConfig { diff --git a/webapp/src/views/PowerMeterAdminView.vue b/webapp/src/views/PowerMeterAdminView.vue index e19a6b896..51cde9de8 100644 --- a/webapp/src/views/PowerMeterAdminView.vue +++ b/webapp/src/views/PowerMeterAdminView.vue @@ -189,6 +189,19 @@ placeholder="total_power" :tooltip="$t('powermeteradmin.httpJsonPathDescription')" /> +
+ +
+ +
+
+