Skip to content

Commit

Permalink
reuse power meter's HTTP config struct
Browse files Browse the repository at this point in the history
  • Loading branch information
schlimmchen committed Apr 18, 2024
1 parent 4bc4def commit e92701c
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 46 deletions.
5 changes: 3 additions & 2 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ struct INVERTER_CONFIG_T {
CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT];
};

enum Auth { none, basic, digest };
struct POWERMETER_HTTP_PHASE_CONFIG_T {
enum Auth { None, Basic, Digest };
bool Enabled;
char Url[POWERMETER_MAX_HTTP_URL_STRLEN + 1];
Auth AuthType;
Expand All @@ -74,6 +74,7 @@ struct POWERMETER_HTTP_PHASE_CONFIG_T {
uint16_t Timeout;
char JsonPath[POWERMETER_MAX_HTTP_JSON_PATH_STRLEN + 1];
};
using PowerMeterHttpConfig = struct POWERMETER_HTTP_PHASE_CONFIG_T;

struct CONFIG_T {
struct {
Expand Down Expand Up @@ -196,7 +197,7 @@ struct CONFIG_T {
uint32_t SdmAddress;
uint32_t HttpInterval;
bool HttpIndividualRequests;
POWERMETER_HTTP_PHASE_CONFIG_T Http_Phase[POWERMETER_MAX_PHASES];
PowerMeterHttpConfig Http_Phase[POWERMETER_MAX_PHASES];
} PowerMeter;

struct {
Expand Down
16 changes: 8 additions & 8 deletions include/HttpPowerMeter.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,30 @@
#include <stdint.h>
#include <Arduino.h>
#include <HTTPClient.h>
#include "Configuration.h"

using Auth_t = PowerMeterHttpConfig::Auth;

class HttpPowerMeterClass {
public:
void init();
bool updateValues();
float getPower(int8_t phase);
char httpPowerMeterError[256];
bool queryPhase(int phase, const String& url, Auth authType, const char* username, const char* password,
const char* httpHeader, const char* httpValue, uint32_t timeout, const char* jsonPath);

bool queryPhase(int phase, PowerMeterHttpConfig const& config);

private:
private:
float power[POWERMETER_MAX_PHASES];
HTTPClient httpClient;
String httpResponse;
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);
bool httpRequest(int phase, WiFiClient &wifiClient, const String& host, uint16_t port, const String& uri, bool https, PowerMeterHttpConfig const& config);
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);
bool tryGetFloatValueForPhase(int phase, const char* jsonPath);
void prepareRequest(uint32_t timeout, const char* httpHeader, const char* httpValue);
String sha256(const String& data);
void prepareRequest(uint32_t timeout, const char* httpHeader, const char* httpValue);
String sha256(const String& data);
};

extern HttpPowerMeterClass HttpPowerMeter;
4 changes: 3 additions & 1 deletion include/WebApi_powermeter.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>

#include <ArduinoJson.h>
#include "Configuration.h"

class WebApiPowerMeterClass {
public:
Expand All @@ -13,6 +14,7 @@ class WebApiPowerMeterClass {
void onStatus(AsyncWebServerRequest* request);
void onAdminGet(AsyncWebServerRequest* request);
void onAdminPost(AsyncWebServerRequest* request);
void decodeJsonPhaseConfig(JsonObject const& json, PowerMeterHttpConfig& config) const;
void onTestHttpRequest(AsyncWebServerRequest* request);

AsyncWebServer* _server;
Expand Down
2 changes: 1 addition & 1 deletion src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ bool ConfigurationClass::read()

config.PowerMeter.Http_Phase[i].Enabled = powermeter_phase["enabled"] | (i == 0);
strlcpy(config.PowerMeter.Http_Phase[i].Url, powermeter_phase["url"] | "", sizeof(config.PowerMeter.Http_Phase[i].Url));
config.PowerMeter.Http_Phase[i].AuthType = powermeter_phase["auth_type"] | Auth::none;
config.PowerMeter.Http_Phase[i].AuthType = powermeter_phase["auth_type"] | PowerMeterHttpConfig::Auth::None;
strlcpy(config.PowerMeter.Http_Phase[i].Username, powermeter_phase["username"] | "", sizeof(config.PowerMeter.Http_Phase[i].Username));
strlcpy(config.PowerMeter.Http_Phase[i].Password, powermeter_phase["password"] | "", sizeof(config.PowerMeter.Http_Phase[i].Password));
strlcpy(config.PowerMeter.Http_Phase[i].HeaderKey, powermeter_phase["header_key"] | "", sizeof(config.PowerMeter.Http_Phase[i].HeaderKey));
Expand Down
33 changes: 15 additions & 18 deletions src/HttpPowerMeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,18 @@ float HttpPowerMeterClass::getPower(int8_t phase)

bool HttpPowerMeterClass::updateValues()
{
const CONFIG_T& config = Configuration.get();
auto const& config = Configuration.get();

for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) {
POWERMETER_HTTP_PHASE_CONFIG_T phaseConfig = config.PowerMeter.Http_Phase[i];
auto const& phaseConfig = config.PowerMeter.Http_Phase[i];

if (!phaseConfig.Enabled) {
power[i] = 0.0;
continue;
}

if (i == 0 || config.PowerMeter.HttpIndividualRequests) {
if (!queryPhase(i, phaseConfig.Url, phaseConfig.AuthType, phaseConfig.Username, phaseConfig.Password, phaseConfig.HeaderKey, phaseConfig.HeaderValue, phaseConfig.Timeout,
phaseConfig.JsonPath)) {
if (!queryPhase(i, phaseConfig)) {
MessageOutput.printf("[HttpPowerMeter] Getting the power of phase %d failed.\r\n", i + 1);
MessageOutput.printf("%s\r\n", httpPowerMeterError);
return false;
Expand All @@ -50,8 +49,7 @@ bool HttpPowerMeterClass::updateValues()
return true;
}

bool HttpPowerMeterClass::queryPhase(int phase, const String& url, Auth authType, const char* username, const char* password,
const char* httpHeader, const char* httpValue, uint32_t timeout, const char* jsonPath)
bool HttpPowerMeterClass::queryPhase(int phase, PowerMeterHttpConfig const& config)
{
//hostByName in WiFiGeneric fails to resolve local names. issue described in
//https://github.com/espressif/arduino-esp32/issues/3822
Expand All @@ -63,7 +61,7 @@ bool HttpPowerMeterClass::queryPhase(int phase, const String& url, Auth authType
String uri;
String base64Authorization;
uint16_t port;
extractUrlComponents(url, protocol, host, uri, port, base64Authorization);
extractUrlComponents(config.Url, protocol, host, uri, port, base64Authorization);

IPAddress ipaddr((uint32_t)0);
//first check if "host" is already an IP adress
Expand Down Expand Up @@ -105,43 +103,42 @@ bool HttpPowerMeterClass::queryPhase(int phase, const String& url, Auth authType
wifiClient = std::make_unique<WiFiClient>();
}

return httpRequest(phase, *wifiClient, ipaddr.toString(), port, uri, https, authType, username, password, httpHeader, httpValue, timeout, jsonPath);
return httpRequest(phase, *wifiClient, ipaddr.toString(), port, uri, https, config);
}

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)
bool HttpPowerMeterClass::httpRequest(int phase, WiFiClient &wifiClient, const String& host, uint16_t port, const String& uri, bool https, PowerMeterHttpConfig const& config)
{
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;
}

prepareRequest(timeout, httpHeader, httpValue);
if (authType == Auth::digest) {
prepareRequest(config.Timeout, config.HeaderKey, config.HeaderValue);
if (config.AuthType == Auth_t::Digest) {
const char *headers[1] = {"WWW-Authenticate"};
httpClient.collectHeaders(headers, 1);
} else if (authType == Auth::basic) {
String authString = username;
} else if (config.AuthType == Auth_t::Basic) {
String authString = config.Username;
authString += ":";
authString += password;
authString += config.Password;
String auth = "Basic ";
auth.concat(base64::encode(authString));
httpClient.addHeader("Authorization", auth);
}
int httpCode = httpClient.GET();

if (httpCode == HTTP_CODE_UNAUTHORIZED && authType == Auth::digest) {
if (httpCode == HTTP_CODE_UNAUTHORIZED && config.AuthType == Auth_t::Digest) {
// Handle authentication challenge
if (httpClient.hasHeader("WWW-Authenticate")) {
String authReq = httpClient.header("WWW-Authenticate");
String authorization = getDigestAuth(authReq, String(username), String(password), "GET", String(uri), 1);
String authorization = getDigestAuth(authReq, String(config.Username), String(config.Password), "GET", String(uri), 1);
httpClient.end();
if(!httpClient.begin(wifiClient, host, port, uri, https)){
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError), PSTR("httpClient.begin() failed for %s://%s using digest auth"), (https ? "https" : "http"), host.c_str());
return false;
}

prepareRequest(timeout, httpHeader, httpValue);
prepareRequest(config.Timeout, config.HeaderKey, config.HeaderValue);
httpClient.addHeader("Authorization", authorization);
httpCode = httpClient.GET();
}
Expand Down
35 changes: 19 additions & 16 deletions src/WebApi_powermeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ void WebApiPowerMeterClass::init(AsyncWebServer& server, Scheduler& scheduler)
_server->on("/api/powermeter/testhttprequest", HTTP_POST, std::bind(&WebApiPowerMeterClass::onTestHttpRequest, this, _1));
}

void WebApiPowerMeterClass::decodeJsonPhaseConfig(JsonObject const& json, PowerMeterHttpConfig& config) const
{
config.Enabled = json["enabled"].as<bool>();
strlcpy(config.Url, json["url"].as<String>().c_str(), sizeof(config.Url));
config.AuthType = json["auth_type"].as<PowerMeterHttpConfig::Auth>();
strlcpy(config.Username, json["username"].as<String>().c_str(), sizeof(config.Username));
strlcpy(config.Password, json["password"].as<String>().c_str(), sizeof(config.Password));
strlcpy(config.HeaderKey, json["header_key"].as<String>().c_str(), sizeof(config.HeaderKey));
strlcpy(config.HeaderValue, json["header_value"].as<String>().c_str(), sizeof(config.HeaderValue));
config.Timeout = json["timeout"].as<uint16_t>();
strlcpy(config.JsonPath, json["json_path"].as<String>().c_str(), sizeof(config.JsonPath));
}

void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request)
{
AsyncJsonResponse* response = new AsyncJsonResponse(false, 2048);
Expand Down Expand Up @@ -137,7 +150,7 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
return;
}

if ((phase["auth_type"].as<Auth>() != Auth::none)
if ((phase["auth_type"].as<uint8_t>() != PowerMeterHttpConfig::Auth::None)
&& ( phase["username"].as<String>().length() == 0 || phase["password"].as<String>().length() == 0)) {
retMsg["message"] = "Username or password must not be empty!";
response->setLength();
Expand Down Expand Up @@ -178,18 +191,9 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)

JsonArray http_phases = root["http_phases"];
for (uint8_t i = 0; i < http_phases.size(); i++) {
JsonObject phase = http_phases[i].as<JsonObject>();

config.PowerMeter.Http_Phase[i].Enabled = (i == 0 ? true : phase["enabled"].as<bool>());
strlcpy(config.PowerMeter.Http_Phase[i].Url, phase["url"].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Url));
config.PowerMeter.Http_Phase[i].AuthType = phase["auth_type"].as<Auth>();
strlcpy(config.PowerMeter.Http_Phase[i].Username, phase["username"].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Username));
strlcpy(config.PowerMeter.Http_Phase[i].Password, phase["password"].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Password));
strlcpy(config.PowerMeter.Http_Phase[i].HeaderKey, phase["header_key"].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].HeaderKey));
strlcpy(config.PowerMeter.Http_Phase[i].HeaderValue, phase["header_value"].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].HeaderValue));
config.PowerMeter.Http_Phase[i].Timeout = phase["timeout"].as<uint16_t>();
strlcpy(config.PowerMeter.Http_Phase[i].JsonPath, phase["json_path"].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].JsonPath));
decodeJsonPhaseConfig(http_phases[i].as<JsonObject>(), config.PowerMeter.Http_Phase[i]);
}
config.PowerMeter.Http_Phase[0].Enabled = true;

WebApi.writeConfig(retMsg);

Expand Down Expand Up @@ -252,10 +256,9 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
char response[256];

int phase = 0;//"absuing" index 0 of the float power[3] in HttpPowerMeter to store the result
if (HttpPowerMeter.queryPhase(phase, root["url"].as<String>().c_str(),
root["auth_type"].as<Auth>(), root["username"].as<String>().c_str(), root["password"].as<String>().c_str(),
root["header_key"].as<String>().c_str(), root["header_value"].as<String>().c_str(), root["timeout"].as<uint16_t>(),
root["json_path"].as<String>().c_str())) {
PowerMeterHttpConfig phaseConfig;
decodeJsonPhaseConfig(root.as<JsonObject>(), phaseConfig);
if (HttpPowerMeter.queryPhase(phase, phaseConfig)) {
retMsg["type"] = "success";
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", HttpPowerMeter.getPower(phase + 1));
} else {
Expand Down

0 comments on commit e92701c

Please sign in to comment.