feature: add digest auth on power meter
Power Meter -> HTTP(S) + Jason configuration allows now basic and digest authentication (all Shelly Gen2 devices)
This commit is contained in:
parent
9a4eb75160
commit
006f63ed02
@ -31,6 +31,8 @@
|
|||||||
|
|
||||||
#define POWERMETER_MAX_PHASES 3
|
#define POWERMETER_MAX_PHASES 3
|
||||||
#define POWERMETER_MAX_HTTP_URL_STRLEN 1024
|
#define POWERMETER_MAX_HTTP_URL_STRLEN 1024
|
||||||
|
#define POWERMETER_MAX_USERNAME_STRLEN 64
|
||||||
|
#define POWERMETER_MAX_PASSWORD_STRLEN 64
|
||||||
#define POWERMETER_MAX_HTTP_HEADER_KEY_STRLEN 64
|
#define POWERMETER_MAX_HTTP_HEADER_KEY_STRLEN 64
|
||||||
#define POWERMETER_MAX_HTTP_HEADER_VALUE_STRLEN 256
|
#define POWERMETER_MAX_HTTP_HEADER_VALUE_STRLEN 256
|
||||||
#define POWERMETER_MAX_HTTP_JSON_PATH_STRLEN 256
|
#define POWERMETER_MAX_HTTP_JSON_PATH_STRLEN 256
|
||||||
@ -55,9 +57,13 @@ struct INVERTER_CONFIG_T {
|
|||||||
CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT];
|
CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum Auth { none, basic, digest };
|
||||||
struct POWERMETER_HTTP_PHASE_CONFIG_T {
|
struct POWERMETER_HTTP_PHASE_CONFIG_T {
|
||||||
bool Enabled;
|
bool Enabled;
|
||||||
char Url[POWERMETER_MAX_HTTP_URL_STRLEN + 1];
|
char Url[POWERMETER_MAX_HTTP_URL_STRLEN + 1];
|
||||||
|
Auth AuthType;
|
||||||
|
char Username[POWERMETER_MAX_USERNAME_STRLEN +1];
|
||||||
|
char Password[POWERMETER_MAX_USERNAME_STRLEN +1];
|
||||||
char HeaderKey[POWERMETER_MAX_HTTP_HEADER_KEY_STRLEN + 1];
|
char HeaderKey[POWERMETER_MAX_HTTP_HEADER_KEY_STRLEN + 1];
|
||||||
char HeaderValue[POWERMETER_MAX_HTTP_HEADER_VALUE_STRLEN + 1];
|
char HeaderValue[POWERMETER_MAX_HTTP_HEADER_VALUE_STRLEN + 1];
|
||||||
uint16_t Timeout;
|
uint16_t Timeout;
|
||||||
|
|||||||
@ -8,12 +8,14 @@ public:
|
|||||||
void init();
|
void init();
|
||||||
bool updateValues();
|
bool updateValues();
|
||||||
float getPower(int8_t phase);
|
float getPower(int8_t phase);
|
||||||
bool httpRequest(const char* url, const char* httpHeader, const char* httpValue, uint32_t timeout,
|
bool httpRequest(const char* url, Auth authType, const char* username, const char* password, const char* httpHeader, const char* httpValue, uint32_t timeout,
|
||||||
char* response, size_t responseSize, char* error, size_t errorSize);
|
char* response, size_t responseSize, char* error, size_t errorSize);
|
||||||
float getFloatValueByJsonPath(const char* jsonString, const char* jsonPath, float &value);
|
float getFloatValueByJsonPath(const char* jsonString, const char* jsonPath, float &value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
float power[POWERMETER_MAX_PHASES];
|
float power[POWERMETER_MAX_PHASES];
|
||||||
|
void extractUrlComponents(const String& url, String& protocol, String& hostname, String& uri);
|
||||||
|
String sha256(const String& data);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern HttpPowerMeterClass HttpPowerMeter;
|
extern HttpPowerMeterClass HttpPowerMeter;
|
||||||
|
|||||||
@ -40,6 +40,7 @@ lib_deps =
|
|||||||
https://github.com/coryjfowler/MCP_CAN_lib
|
https://github.com/coryjfowler/MCP_CAN_lib
|
||||||
plerup/EspSoftwareSerial@^8.0.1
|
plerup/EspSoftwareSerial@^8.0.1
|
||||||
mobizt/FirebaseJson @ ^3.0.6
|
mobizt/FirebaseJson @ ^3.0.6
|
||||||
|
rweather/Crypto@^0.4.0
|
||||||
|
|
||||||
extra_scripts =
|
extra_scripts =
|
||||||
pre:pio-scripts/auto_firmware_version.py
|
pre:pio-scripts/auto_firmware_version.py
|
||||||
|
|||||||
@ -140,6 +140,9 @@ bool ConfigurationClass::write()
|
|||||||
|
|
||||||
powermeter_phase["enabled"] = config.Powermeter_Http_Phase[i].Enabled;
|
powermeter_phase["enabled"] = config.Powermeter_Http_Phase[i].Enabled;
|
||||||
powermeter_phase["url"] = config.Powermeter_Http_Phase[i].Url;
|
powermeter_phase["url"] = config.Powermeter_Http_Phase[i].Url;
|
||||||
|
powermeter_phase["auth_type"] = config.Powermeter_Http_Phase[i].AuthType;
|
||||||
|
powermeter_phase["username"] = config.Powermeter_Http_Phase[i].Username;
|
||||||
|
powermeter_phase["password"] = config.Powermeter_Http_Phase[i].Password;
|
||||||
powermeter_phase["header_key"] = config.Powermeter_Http_Phase[i].HeaderKey;
|
powermeter_phase["header_key"] = config.Powermeter_Http_Phase[i].HeaderKey;
|
||||||
powermeter_phase["header_value"] = config.Powermeter_Http_Phase[i].HeaderValue;
|
powermeter_phase["header_value"] = config.Powermeter_Http_Phase[i].HeaderValue;
|
||||||
powermeter_phase["timeout"] = config.Powermeter_Http_Phase[i].Timeout;
|
powermeter_phase["timeout"] = config.Powermeter_Http_Phase[i].Timeout;
|
||||||
@ -346,6 +349,9 @@ bool ConfigurationClass::read()
|
|||||||
|
|
||||||
config.Powermeter_Http_Phase[i].Enabled = powermeter_phase["enabled"] | (i == 0);
|
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));
|
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;
|
||||||
|
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));
|
strlcpy(config.Powermeter_Http_Phase[i].HeaderKey, powermeter_phase["header_key"] | "", sizeof(config.Powermeter_Http_Phase[i].HeaderKey));
|
||||||
strlcpy(config.Powermeter_Http_Phase[i].HeaderValue, powermeter_phase["header_value"] | "", sizeof(config.Powermeter_Http_Phase[i].HeaderValue));
|
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;
|
config.Powermeter_Http_Phase[i].Timeout = powermeter_phase["timeout"] | POWERMETER_HTTP_TIMEOUT;
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
#include <WiFiClientSecure.h>
|
#include <WiFiClientSecure.h>
|
||||||
#include <HTTPClient.h>
|
#include <HTTPClient.h>
|
||||||
#include <FirebaseJson.h>
|
#include <FirebaseJson.h>
|
||||||
|
#include <Crypto.h>
|
||||||
|
#include <SHA256.h>
|
||||||
|
|
||||||
void HttpPowerMeterClass::init()
|
void HttpPowerMeterClass::init()
|
||||||
{
|
{
|
||||||
@ -32,7 +34,7 @@ bool HttpPowerMeterClass::updateValues()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (i == 0 || config.PowerMeter_HttpIndividualRequests) {
|
if (i == 0 || config.PowerMeter_HttpIndividualRequests) {
|
||||||
if (!httpRequest(phaseConfig.Url, phaseConfig.HeaderKey, phaseConfig.HeaderValue, phaseConfig.Timeout,
|
if (!httpRequest(phaseConfig.Url, phaseConfig.AuthType, phaseConfig.Username, phaseConfig.Password, phaseConfig.HeaderKey, phaseConfig.HeaderValue, phaseConfig.Timeout,
|
||||||
response, sizeof(response), errorMessage, sizeof(errorMessage))) {
|
response, sizeof(response), errorMessage, sizeof(errorMessage))) {
|
||||||
MessageOutput.printf("[HttpPowerMeter] Getting the power of phase %d failed. Error: %s\r\n",
|
MessageOutput.printf("[HttpPowerMeter] Getting the power of phase %d failed. Error: %s\r\n",
|
||||||
i + 1, errorMessage);
|
i + 1, errorMessage);
|
||||||
@ -49,24 +51,41 @@ bool HttpPowerMeterClass::updateValues()
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpPowerMeterClass::httpRequest(const char* url, const char* httpHeader, const char* httpValue, uint32_t timeout,
|
bool HttpPowerMeterClass::httpRequest(const char* url, Auth authType, const char* username, const char* password, const char* httpHeader, const char* httpValue, uint32_t timeout,
|
||||||
char* response, size_t responseSize, char* error, size_t errorSize)
|
char* response, size_t responseSize, char* error, size_t errorSize)
|
||||||
{
|
{
|
||||||
WiFiClient* wifiClient = NULL;
|
WiFiClient* wifiClient = NULL;
|
||||||
HTTPClient httpClient;
|
HTTPClient httpClient;
|
||||||
|
|
||||||
|
String newUrl = url;
|
||||||
|
String urlProtocol;
|
||||||
|
String urlHostname;
|
||||||
|
String urlUri;
|
||||||
|
extractUrlComponents(url, urlProtocol, urlHostname, urlUri);
|
||||||
|
|
||||||
response[0] = '\0';
|
response[0] = '\0';
|
||||||
error[0] = '\0';
|
error[0] = '\0';
|
||||||
|
|
||||||
if (String(url).substring(0, 6) == "https:") {
|
if (authType == Auth::basic) {
|
||||||
|
newUrl = urlProtocol;
|
||||||
|
newUrl += "://";
|
||||||
|
newUrl += username;
|
||||||
|
newUrl += ":";
|
||||||
|
newUrl += password;
|
||||||
|
newUrl += "@";
|
||||||
|
newUrl += urlHostname;
|
||||||
|
newUrl += urlUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (urlProtocol == "https") {
|
||||||
wifiClient = new WiFiClientSecure;
|
wifiClient = new WiFiClientSecure;
|
||||||
reinterpret_cast<WiFiClientSecure*>(wifiClient)->setInsecure();
|
reinterpret_cast<WiFiClientSecure*>(wifiClient)->setInsecure();
|
||||||
} else {
|
} else {
|
||||||
wifiClient = new WiFiClient;
|
wifiClient = new WiFiClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!httpClient.begin(*wifiClient, url)) {
|
if (!httpClient.begin(*wifiClient, newUrl)) {
|
||||||
snprintf_P(error, errorSize, "httpClient.begin failed");
|
snprintf_P(error, errorSize, "httpClient.begin(%s) failed", newUrl.c_str());
|
||||||
delete wifiClient;
|
delete wifiClient;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -82,8 +101,67 @@ bool HttpPowerMeterClass::httpRequest(const char* url, const char* httpHeader, c
|
|||||||
httpClient.addHeader(httpHeader, httpValue);
|
httpClient.addHeader(httpHeader, httpValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authType == Auth::digest) {
|
||||||
|
const char *headers[1] = {"WWW-Authenticate"};
|
||||||
|
httpClient.collectHeaders(headers, 1);
|
||||||
|
}
|
||||||
|
|
||||||
int httpCode = httpClient.GET();
|
int httpCode = httpClient.GET();
|
||||||
|
|
||||||
|
if (httpCode == HTTP_CODE_UNAUTHORIZED && authType == Auth::digest) {
|
||||||
|
// Handle authentication challenge
|
||||||
|
char realm[256]; // Buffer to store the realm received from the server
|
||||||
|
char nonce[256]; // Buffer to store the nonce received from the server
|
||||||
|
if (httpClient.hasHeader("WWW-Authenticate")) {
|
||||||
|
String authHeader = httpClient.header("WWW-Authenticate");
|
||||||
|
if (authHeader.indexOf("Digest") != -1) {
|
||||||
|
int realmIndex = authHeader.indexOf("realm=\"");
|
||||||
|
int nonceIndex = authHeader.indexOf("nonce=\"");
|
||||||
|
if (realmIndex != -1 && nonceIndex != -1) {
|
||||||
|
int realmEndIndex = authHeader.indexOf("\"", realmIndex + 7);
|
||||||
|
int nonceEndIndex = authHeader.indexOf("\"", nonceIndex + 7);
|
||||||
|
if (realmEndIndex != -1 && nonceEndIndex != -1) {
|
||||||
|
authHeader.substring(realmIndex + 7, realmEndIndex).toCharArray(realm, sizeof(realm));
|
||||||
|
authHeader.substring(nonceIndex + 7, nonceEndIndex).toCharArray(nonce, sizeof(nonce));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String cnonce = String(random(1000)); // Generate client nonce
|
||||||
|
String str = username;
|
||||||
|
str += ":";
|
||||||
|
str += realm;
|
||||||
|
str += ":";
|
||||||
|
str += password;
|
||||||
|
String ha1 = sha256(str);
|
||||||
|
str = "GET:";
|
||||||
|
str += urlUri;
|
||||||
|
String ha2 = sha256(str);
|
||||||
|
str = ha1;
|
||||||
|
str += ":";
|
||||||
|
str += nonce;
|
||||||
|
str += ":00000001:";
|
||||||
|
str += cnonce;
|
||||||
|
str += ":auth:";
|
||||||
|
str += ha2;
|
||||||
|
String response = sha256(str);
|
||||||
|
|
||||||
|
String authorization = "Digest username=\"";
|
||||||
|
authorization += username;
|
||||||
|
authorization += "\", realm=\"";
|
||||||
|
authorization += realm;
|
||||||
|
authorization += "\", nonce=\"";
|
||||||
|
authorization += nonce;
|
||||||
|
authorization += "\", uri=\"";
|
||||||
|
authorization += urlUri;
|
||||||
|
authorization += "\", cnonce=\"";
|
||||||
|
authorization += cnonce;
|
||||||
|
authorization += "\", nc=00000001, qop=auth, response=\"";
|
||||||
|
authorization += response;
|
||||||
|
authorization += "\", algorithm=SHA-256";
|
||||||
|
httpClient.addHeader("Authorization", authorization);
|
||||||
|
httpCode = httpClient.GET();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (httpCode == HTTP_CODE_OK) {
|
if (httpCode == HTTP_CODE_OK) {
|
||||||
String responseBody = httpClient.getString();
|
String responseBody = httpClient.getString();
|
||||||
@ -95,7 +173,7 @@ bool HttpPowerMeterClass::httpRequest(const char* url, const char* httpHeader, c
|
|||||||
snprintf(response, responseSize, responseBody.c_str());
|
snprintf(response, responseSize, responseBody.c_str());
|
||||||
}
|
}
|
||||||
} else if (httpCode <= 0) {
|
} else if (httpCode <= 0) {
|
||||||
snprintf_P(error, errorSize, "Error: %s", httpClient.errorToString(httpCode).c_str());
|
snprintf_P(error, errorSize, "Error(%s): %s", newUrl.c_str(), httpClient.errorToString(httpCode).c_str());
|
||||||
} else if (httpCode != HTTP_CODE_OK) {
|
} else if (httpCode != HTTP_CODE_OK) {
|
||||||
snprintf_P(error, errorSize, "Bad HTTP code: %d", httpCode);
|
snprintf_P(error, errorSize, "Bad HTTP code: %d", httpCode);
|
||||||
}
|
}
|
||||||
@ -127,4 +205,54 @@ float HttpPowerMeterClass::getFloatValueByJsonPath(const char* jsonString, const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove username:password if present in the hostname
|
||||||
|
int atIndex = hostname.indexOf("@");
|
||||||
|
if (atIndex != -1) {
|
||||||
|
hostname = hostname.substring(atIndex + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String HttpPowerMeterClass::sha256(const String& data) {
|
||||||
|
SHA256 sha256;
|
||||||
|
uint8_t hash[sha256.HASH_SIZE];
|
||||||
|
|
||||||
|
sha256.reset();
|
||||||
|
sha256.update(data.c_str(), data.length());
|
||||||
|
sha256.finalize(hash, sha256.HASH_SIZE);
|
||||||
|
|
||||||
|
String hashStr = "";
|
||||||
|
for (int i = 0; i < sha256.HASH_SIZE; i++) {
|
||||||
|
String hex = String(hash[i], HEX);
|
||||||
|
if (hex.length() == 1) {
|
||||||
|
hashStr += "0";
|
||||||
|
}
|
||||||
|
hashStr += hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashStr;
|
||||||
|
}
|
||||||
|
|
||||||
HttpPowerMeterClass HttpPowerMeter;
|
HttpPowerMeterClass HttpPowerMeter;
|
||||||
|
|||||||
@ -56,6 +56,9 @@ void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request)
|
|||||||
phaseObject[F("index")] = i + 1;
|
phaseObject[F("index")] = i + 1;
|
||||||
phaseObject[F("enabled")] = config.Powermeter_Http_Phase[i].Enabled;
|
phaseObject[F("enabled")] = config.Powermeter_Http_Phase[i].Enabled;
|
||||||
phaseObject[F("url")] = String(config.Powermeter_Http_Phase[i].Url);
|
phaseObject[F("url")] = String(config.Powermeter_Http_Phase[i].Url);
|
||||||
|
phaseObject[F("auth_type")]= config.Powermeter_Http_Phase[i].AuthType;
|
||||||
|
phaseObject[F("username")] = String(config.Powermeter_Http_Phase[i].Username);
|
||||||
|
phaseObject[F("password")] = String(config.Powermeter_Http_Phase[i].Password);
|
||||||
phaseObject[F("header_key")] = String(config.Powermeter_Http_Phase[i].HeaderKey);
|
phaseObject[F("header_key")] = String(config.Powermeter_Http_Phase[i].HeaderKey);
|
||||||
phaseObject[F("header_value")] = String(config.Powermeter_Http_Phase[i].HeaderValue);
|
phaseObject[F("header_value")] = String(config.Powermeter_Http_Phase[i].HeaderValue);
|
||||||
phaseObject[F("json_path")] = String(config.Powermeter_Http_Phase[i].JsonPath);
|
phaseObject[F("json_path")] = String(config.Powermeter_Http_Phase[i].JsonPath);
|
||||||
@ -137,6 +140,14 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((phase[F("auth_type")].as<Auth>() != Auth::none)
|
||||||
|
&& ( phase[F("username")].as<String>().length() == 0 || phase[F("password")].as<String>().length() == 0)) {
|
||||||
|
retMsg[F("message")] = F("Username or password must not be empty!");
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!phase.containsKey("timeout")
|
if (!phase.containsKey("timeout")
|
||||||
|| phase[F("timeout")].as<uint16_t>() <= 0) {
|
|| phase[F("timeout")].as<uint16_t>() <= 0) {
|
||||||
retMsg[F("message")] = F("Timeout must be greater than 0 ms!");
|
retMsg[F("message")] = F("Timeout must be greater than 0 ms!");
|
||||||
@ -173,6 +184,9 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
config.Powermeter_Http_Phase[i].Enabled = (i == 0 ? true : phase[F("enabled")].as<bool>());
|
config.Powermeter_Http_Phase[i].Enabled = (i == 0 ? true : phase[F("enabled")].as<bool>());
|
||||||
strlcpy(config.Powermeter_Http_Phase[i].Url, phase[F("url")].as<String>().c_str(), sizeof(config.Powermeter_Http_Phase[i].Url));
|
strlcpy(config.Powermeter_Http_Phase[i].Url, phase[F("url")].as<String>().c_str(), sizeof(config.Powermeter_Http_Phase[i].Url));
|
||||||
|
config.Powermeter_Http_Phase[i].AuthType = phase[F("auth_type")].as<Auth>();
|
||||||
|
strlcpy(config.Powermeter_Http_Phase[i].Username, phase[F("username")].as<String>().c_str(), sizeof(config.Powermeter_Http_Phase[i].Username));
|
||||||
|
strlcpy(config.Powermeter_Http_Phase[i].Password, phase[F("password")].as<String>().c_str(), sizeof(config.Powermeter_Http_Phase[i].Password));
|
||||||
strlcpy(config.Powermeter_Http_Phase[i].HeaderKey, phase[F("header_key")].as<String>().c_str(), sizeof(config.Powermeter_Http_Phase[i].HeaderKey));
|
strlcpy(config.Powermeter_Http_Phase[i].HeaderKey, phase[F("header_key")].as<String>().c_str(), sizeof(config.Powermeter_Http_Phase[i].HeaderKey));
|
||||||
strlcpy(config.Powermeter_Http_Phase[i].HeaderValue, phase[F("header_value")].as<String>().c_str(), sizeof(config.Powermeter_Http_Phase[i].HeaderValue));
|
strlcpy(config.Powermeter_Http_Phase[i].HeaderValue, phase[F("header_value")].as<String>().c_str(), sizeof(config.Powermeter_Http_Phase[i].HeaderValue));
|
||||||
config.Powermeter_Http_Phase[i].Timeout = phase[F("timeout")].as<uint16_t>();
|
config.Powermeter_Http_Phase[i].Timeout = phase[F("timeout")].as<uint16_t>();
|
||||||
@ -229,7 +243,8 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!root.containsKey("url") || !root.containsKey("header_key") || !root.containsKey("header_value")
|
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")) {
|
|| !root.containsKey("timeout") || !root.containsKey("json_path")) {
|
||||||
retMsg[F("message")] = F("Missing fields!");
|
retMsg[F("message")] = F("Missing fields!");
|
||||||
asyncJsonResponse->setLength();
|
asyncJsonResponse->setLength();
|
||||||
@ -241,8 +256,9 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
|||||||
errorMessage[256];
|
errorMessage[256];
|
||||||
char response[200];
|
char response[200];
|
||||||
|
|
||||||
if (HttpPowerMeter.httpRequest(root[F("url")].as<String>().c_str(), root[F("header_key")].as<String>().c_str(),
|
if (HttpPowerMeter.httpRequest(root[F("url")].as<String>().c_str(),
|
||||||
root[F("header_value")].as<String>().c_str(), root[F("timeout")].as<uint16_t>(),
|
root[F("auth_type")].as<Auth>(), root[F("username")].as<String>().c_str(), root[F("password")].as<String>().c_str(),
|
||||||
|
root[F("header_key")].as<String>().c_str(), root[F("header_value")].as<String>().c_str(), root[F("timeout")].as<uint16_t>(),
|
||||||
powerMeterResponse, sizeof(powerMeterResponse), errorMessage, sizeof(errorMessage))) {
|
powerMeterResponse, sizeof(powerMeterResponse), errorMessage, sizeof(errorMessage))) {
|
||||||
float power;
|
float power;
|
||||||
|
|
||||||
|
|||||||
@ -519,6 +519,9 @@
|
|||||||
"httpEnabled": "Phase enabled",
|
"httpEnabled": "Phase enabled",
|
||||||
"httpUrl": "URL",
|
"httpUrl": "URL",
|
||||||
"httpUrlDescription": "URL must start with http:// or https://. Some characters like spaces and = have to be encoded with URL encoding (%xx). Warning: SSL server certificate check is not implemented (MITM attacks are possible)! See below for some examples.",
|
"httpUrlDescription": "URL must start with http:// or https://. Some characters like spaces and = have to be encoded with URL encoding (%xx). Warning: SSL server certificate check is not implemented (MITM attacks are possible)! See below for some examples.",
|
||||||
|
"httpAuthorization": "Authorization Type",
|
||||||
|
"httpUsername": "Username",
|
||||||
|
"httpPassword": "Password",
|
||||||
"httpHeaderKey": "Optional: HTTP request header - Key",
|
"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.",
|
"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",
|
"httpHeaderValue": "Optional: HTTP request header - Value",
|
||||||
|
|||||||
@ -2,11 +2,14 @@ export interface PowerMeterHttpPhaseConfig {
|
|||||||
index: number;
|
index: number;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
url: string;
|
url: string;
|
||||||
|
auth_type: number;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
header_key: string;
|
header_key: string;
|
||||||
header_value: string;
|
header_value: string;
|
||||||
json_path: string;
|
json_path: string;
|
||||||
timeout: number;
|
timeout: number;
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface PowerMeterConfig {
|
export interface PowerMeterConfig {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
|||||||
@ -121,6 +121,26 @@
|
|||||||
prefix="GET "
|
prefix="GET "
|
||||||
:tooltip="$t('powermeteradmin.httpUrlDescription')" />
|
:tooltip="$t('powermeteradmin.httpUrlDescription')" />
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="inputTimezone" class="col-sm-2 col-form-label">{{ $t('powermeteradmin.httpAuthorization') }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select class="form-select" v-model="http_phase.auth_type">
|
||||||
|
<option v-for="source in powerMeterAuthList" :key="source.key" :value="source.key">
|
||||||
|
{{ source.value }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="http_phase.auth_type != 0">
|
||||||
|
<InputElement :label="$t('powermeteradmin.httpUsername')"
|
||||||
|
v-model="http_phase.username"
|
||||||
|
type="text" maxlength="64"/>
|
||||||
|
|
||||||
|
<InputElement :label="$t('powermeteradmin.httpPassword')"
|
||||||
|
v-model="http_phase.password"
|
||||||
|
type="password" maxlength="64"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<InputElement :label="$t('powermeteradmin.httpHeaderKey')"
|
<InputElement :label="$t('powermeteradmin.httpHeaderKey')"
|
||||||
v-model="http_phase.header_key"
|
v-model="http_phase.header_key"
|
||||||
type="text"
|
type="text"
|
||||||
@ -209,6 +229,11 @@ export default defineComponent({
|
|||||||
{ key: 3, value: this.$t('powermeteradmin.typeHTTP') },
|
{ key: 3, value: this.$t('powermeteradmin.typeHTTP') },
|
||||||
{ key: 4, value: this.$t('powermeteradmin.typeSML') },
|
{ key: 4, value: this.$t('powermeteradmin.typeSML') },
|
||||||
],
|
],
|
||||||
|
powerMeterAuthList: [
|
||||||
|
{ key: 0, value: "None" },
|
||||||
|
{ key: 1, value: "Basic" },
|
||||||
|
{ key: 2, value: "Digest" },
|
||||||
|
],
|
||||||
alertMessage: "",
|
alertMessage: "",
|
||||||
alertType: "info",
|
alertType: "info",
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
|
|||||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user