From 8392c7cd8c38ae832f9200dce29e2981008c5222 Mon Sep 17 00:00:00 2001 From: Fribur Date: Tue, 16 Jan 2024 16:34:46 -0500 Subject: [PATCH 1/2] fix to HttpPowermeter not using explicitly specified non standard should resolve the connection refused issues reported in https://github.com/helgeerbe/OpenDTU-OnBattery/issues/611 --- include/HttpPowerMeter.h | 4 +- src/HttpPowerMeter.cpp | 80 +++++++++++++++++++++++++--------------- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/include/HttpPowerMeter.h b/include/HttpPowerMeter.h index 95cc1781d..40eed04dd 100644 --- a/include/HttpPowerMeter.h +++ b/include/HttpPowerMeter.h @@ -19,9 +19,9 @@ class HttpPowerMeterClass { float power[POWERMETER_MAX_PHASES]; HTTPClient httpClient; String httpResponse; - bool httpRequest(int phase, WiFiClient &wifiClient, const String& host, const String& uri, bool https, Auth authType, const char* username, + bool httpRequest(int phase, WiFiClient &wifiClient, const String& host, uint16_t port, const String& uri, bool https, Auth authType, const char* username, const char* password, const char* httpHeader, const char* httpValue, uint32_t timeout, const char* jsonPath); - void extractUrlComponents(const String& url, String& protocol, String& host, String& uri); + bool extractUrlComponents(String url, String& _protocol, String& _hostname, String& _uri, uint16_t& uint16_t, String& _base64Authorization); 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); diff --git a/src/HttpPowerMeter.cpp b/src/HttpPowerMeter.cpp index ade8d3802..826abd776 100644 --- a/src/HttpPowerMeter.cpp +++ b/src/HttpPowerMeter.cpp @@ -54,7 +54,9 @@ bool HttpPowerMeterClass::queryPhase(int phase, const String& url, Auth authType String protocol; String host; String uri; - extractUrlComponents(url, protocol, host, uri); + String base64Authorization; + uint16_t port; + extractUrlComponents(url, protocol, host, uri, port, base64Authorization); IPAddress ipaddr((uint32_t)0); //first check if "host" is already an IP adress @@ -96,13 +98,12 @@ bool HttpPowerMeterClass::queryPhase(int phase, const String& url, Auth authType wifiClient = std::make_unique(); } - return httpRequest(phase, *wifiClient, ipaddr.toString(), uri, https, authType, username, password, httpHeader, httpValue, timeout, jsonPath); + return httpRequest(phase, *wifiClient, ipaddr.toString(), port, uri, https, authType, username, password, httpHeader, httpValue, timeout, jsonPath); } -bool HttpPowerMeterClass::httpRequest(int phase, WiFiClient &wifiClient, const String& host, const String& uri, bool https, Auth authType, const char* username, +bool HttpPowerMeterClass::httpRequest(int phase, WiFiClient &wifiClient, const String& host, uint16_t port, const String& uri, bool https, Auth authType, const char* username, const char* password, const char* httpHeader, const char* httpValue, uint32_t timeout, const char* jsonPath) { - int port = (https ? 443 : 80); if(!httpClient.begin(wifiClient, host, port, uri, https)){ snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError), PSTR("httpClient.begin() failed for %s://%s"), (https ? "https" : "http"), host.c_str()); return false; @@ -221,34 +222,55 @@ bool HttpPowerMeterClass::tryGetFloatValueForPhase(int phase, int httpCode, cons return success; } -void HttpPowerMeterClass::extractUrlComponents(const String& url, String& protocol, String& hostname, String& uri) { - // Find protocol delimiter - int protocolEndIndex = url.indexOf(":"); - if (protocolEndIndex != -1) { - protocol = url.substring(0, protocolEndIndex); - - // Find double slash delimiter - int doubleSlashIndex = url.indexOf("//", protocolEndIndex); - if (doubleSlashIndex != -1) { - // Find slash after double slash delimiter - int slashIndex = url.indexOf("/", doubleSlashIndex + 2); - if (slashIndex != -1) { - // Extract hostname and uri - hostname = url.substring(doubleSlashIndex + 2, slashIndex); - uri = url.substring(slashIndex); - } else { - // No slash after double slash delimiter, so the whole remaining part is the hostname - hostname = url.substring(doubleSlashIndex + 2); - uri = "/"; - } - } +//extract url component as done by httpClient::begin(String url, const char* expectedProtocol) https://github.com/espressif/arduino-esp32/blob/da6325dd7e8e152094b19fe63190907f38ef1ff0/libraries/HTTPClient/src/HTTPClient.cpp#L250 +bool HttpPowerMeterClass::extractUrlComponents(String url, String& _protocol, String& _host, String& _uri, uint16_t& _port, String& _base64Authorization) +{ + // check for : (http: or https: + int index = url.indexOf(':'); + if(index < 0) { + snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError), PSTR("failed to parse protocol")); + return false; } - // Remove username:password if present in the hostname - int atIndex = hostname.indexOf("@"); - if (atIndex != -1) { - hostname = hostname.substring(atIndex + 1); + _protocol = url.substring(0, index); + + //initialize port to default values for http or https. + //port will be overwritten below in case port is explicitly defined + _port = (_protocol == "https" ? 443 : 80); + + url.remove(0, (index + 3)); // remove http:// or https:// + + index = url.indexOf('/'); + if (index == -1) { + index = url.length(); + url += '/'; } + String host = url.substring(0, index); + url.remove(0, index); // remove host part + + // get Authorization + index = host.indexOf('@'); + if(index >= 0) { + // auth info + String auth = host.substring(0, index); + host.remove(0, index + 1); // remove auth part including @ + _base64Authorization = base64::encode(auth); + } + + // get port + index = host.indexOf(':'); + String the_host; + if(index >= 0) { + the_host = host.substring(0, index); // hostname + host.remove(0, (index + 1)); // remove hostname + : + _port = host.toInt(); // get port + } else { + the_host = host; + } + + _host = the_host; + _uri = url; + return true; } #define HASH_SIZE 32 From 75e3d03ea4025ac77140d4c63a7909c1ff6302a2 Mon Sep 17 00:00:00 2001 From: Fribur Date: Tue, 16 Jan 2024 19:32:02 -0500 Subject: [PATCH 2/2] Revert back to using FirebaseJson instead of ArduinoJson There is no convenient way to access arrays of Json objects and nested keys such as testarray/[2]/myvalue using ArduinoJson. It would at the very least to split the path into a number of individual strings and then access the value like json[testarray][2][myvalue] More details in https://arduinojson.org/v6/doc/deserialization/ . Conclusion: too complicated. --- platformio.ini | 1 + src/HttpPowerMeter.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/platformio.ini b/platformio.ini index 57bc9a3cf..3bd1645fb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -47,6 +47,7 @@ lib_deps = https://github.com/coryjfowler/MCP_CAN_lib plerup/EspSoftwareSerial @ ^8.0.1 https://github.com/dok-net/ghostl @ ^1.0.1 + mobizt/FirebaseJson @ ^3.0.6 rweather/Crypto@^0.4.0 extra_scripts = diff --git a/src/HttpPowerMeter.cpp b/src/HttpPowerMeter.cpp index 826abd776..4d6c12f4d 100644 --- a/src/HttpPowerMeter.cpp +++ b/src/HttpPowerMeter.cpp @@ -3,7 +3,7 @@ #include "HttpPowerMeter.h" #include "MessageOutput.h" #include -#include +#include #include #include #include @@ -204,13 +204,13 @@ bool HttpPowerMeterClass::tryGetFloatValueForPhase(int phase, int httpCode, cons bool success = false; if (httpCode == HTTP_CODE_OK) { httpResponse = httpClient.getString(); //very unfortunate that we cannot parse WifiClient stream directly - StaticJsonDocument<2048> json; //however creating these allocations on stack should be fine to avoid heap fragmentation - deserializeJson(json, httpResponse); - if(!json.containsKey(jsonPath)) - { + FirebaseJson json; + json.setJsonData(httpResponse); + FirebaseJsonData value; + if (!json.get(value, jsonPath)) { snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError), PSTR("[HttpPowerMeter] Couldn't find a value for phase %i with Json query \"%s\""), phase, jsonPath); }else { - power[phase] = json[jsonPath].as(); + power[phase] = value.to(); //MessageOutput.printf("Power for Phase %i: %5.2fW\r\n", phase, power[phase]); success = true; }