powermeter refactor: generalize HTTP request config
the parameters to peform an HTTP request by the HTTP(S)+JSON power meter have been generalized by introducing a new config struct. this is now used for all values which the HTTP(S)+JSON power meter can retrieve, and also used by the HTTP+SML power meter implementation. we anticipate that other feature will use this config as well. generalizing also allows to share serialization and deserialization methods in the configuration handler and the web API handler, leading to de-duplication of code and reduced flash memory usage. a new web UI component is implemented to manage a set of HTTP request settings.
This commit is contained in:
parent
ccba7d8036
commit
297b149f84
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "PinMapping.h"
|
#include "PinMapping.h"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
#define CONFIG_FILENAME "/config.json"
|
#define CONFIG_FILENAME "/config.json"
|
||||||
#define CONFIG_VERSION 0x00011c00 // 0.1.28 // make sure to clean all after change
|
#define CONFIG_VERSION 0x00011c00 // 0.1.28 // make sure to clean all after change
|
||||||
@ -30,14 +31,14 @@
|
|||||||
|
|
||||||
#define DEV_MAX_MAPPING_NAME_STRLEN 63
|
#define DEV_MAX_MAPPING_NAME_STRLEN 63
|
||||||
|
|
||||||
#define POWERMETER_MAX_PHASES 3
|
#define HTTP_REQUEST_MAX_URL_STRLEN 1024
|
||||||
#define POWERMETER_MAX_HTTP_URL_STRLEN 1024
|
#define HTTP_REQUEST_MAX_USERNAME_STRLEN 64
|
||||||
#define POWERMETER_MAX_USERNAME_STRLEN 64
|
#define HTTP_REQUEST_MAX_PASSWORD_STRLEN 64
|
||||||
#define POWERMETER_MAX_PASSWORD_STRLEN 64
|
#define HTTP_REQUEST_MAX_HEADER_KEY_STRLEN 64
|
||||||
#define POWERMETER_MAX_HTTP_HEADER_KEY_STRLEN 64
|
#define HTTP_REQUEST_MAX_HEADER_VALUE_STRLEN 256
|
||||||
#define POWERMETER_MAX_HTTP_HEADER_VALUE_STRLEN 256
|
|
||||||
#define POWERMETER_MAX_HTTP_JSON_PATH_STRLEN 256
|
#define POWERMETER_HTTP_JSON_MAX_VALUES 3
|
||||||
#define POWERMETER_HTTP_TIMEOUT 1000
|
#define POWERMETER_HTTP_JSON_MAX_PATH_STRLEN 256
|
||||||
|
|
||||||
struct CHANNEL_CONFIG_T {
|
struct CHANNEL_CONFIG_T {
|
||||||
uint16_t MaxChannelPower;
|
uint16_t MaxChannelPower;
|
||||||
@ -61,30 +62,36 @@ struct INVERTER_CONFIG_T {
|
|||||||
CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT];
|
CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct POWERMETER_HTTP_PHASE_CONFIG_T {
|
struct HTTP_REQUEST_CONFIG_T {
|
||||||
|
char Url[HTTP_REQUEST_MAX_URL_STRLEN + 1];
|
||||||
|
|
||||||
enum Auth { None, Basic, Digest };
|
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;
|
Auth AuthType;
|
||||||
char Username[POWERMETER_MAX_USERNAME_STRLEN +1];
|
|
||||||
char Password[POWERMETER_MAX_USERNAME_STRLEN +1];
|
char Username[HTTP_REQUEST_MAX_USERNAME_STRLEN + 1];
|
||||||
char HeaderKey[POWERMETER_MAX_HTTP_HEADER_KEY_STRLEN + 1];
|
char Password[HTTP_REQUEST_MAX_PASSWORD_STRLEN + 1];
|
||||||
char HeaderValue[POWERMETER_MAX_HTTP_HEADER_VALUE_STRLEN + 1];
|
char HeaderKey[HTTP_REQUEST_MAX_HEADER_KEY_STRLEN + 1];
|
||||||
|
char HeaderValue[HTTP_REQUEST_MAX_HEADER_VALUE_STRLEN + 1];
|
||||||
uint16_t Timeout;
|
uint16_t Timeout;
|
||||||
char JsonPath[POWERMETER_MAX_HTTP_JSON_PATH_STRLEN + 1];
|
};
|
||||||
|
using HttpRequestConfig = struct HTTP_REQUEST_CONFIG_T;
|
||||||
|
|
||||||
|
struct POWERMETER_HTTP_JSON_CONFIG_T {
|
||||||
|
HttpRequestConfig HttpRequest;
|
||||||
|
bool Enabled;
|
||||||
|
char JsonPath[POWERMETER_HTTP_JSON_MAX_PATH_STRLEN + 1];
|
||||||
|
|
||||||
|
enum Unit { Watts = 0, MilliWatts = 1, KiloWatts = 2 };
|
||||||
Unit PowerUnit;
|
Unit PowerUnit;
|
||||||
|
|
||||||
bool SignInverted;
|
bool SignInverted;
|
||||||
};
|
};
|
||||||
using PowerMeterHttpConfig = struct POWERMETER_HTTP_PHASE_CONFIG_T;
|
using PowerMeterHttpJsonConfig = struct POWERMETER_HTTP_JSON_CONFIG_T;
|
||||||
|
|
||||||
struct POWERMETER_TIBBER_CONFIG_T {
|
struct POWERMETER_HTTP_SML_CONFIG_T {
|
||||||
char Url[POWERMETER_MAX_HTTP_URL_STRLEN + 1];
|
HttpRequestConfig HttpRequest;
|
||||||
char Username[POWERMETER_MAX_USERNAME_STRLEN + 1];
|
|
||||||
char Password[POWERMETER_MAX_USERNAME_STRLEN + 1];
|
|
||||||
uint16_t Timeout;
|
|
||||||
};
|
};
|
||||||
using PowerMeterTibberConfig = struct POWERMETER_TIBBER_CONFIG_T;
|
using PowerMeterHttpSmlConfig = struct POWERMETER_HTTP_SML_CONFIG_T;
|
||||||
|
|
||||||
struct CONFIG_T {
|
struct CONFIG_T {
|
||||||
struct {
|
struct {
|
||||||
@ -204,10 +211,9 @@ struct CONFIG_T {
|
|||||||
char MqttTopicPowerMeter2[MQTT_MAX_TOPIC_STRLEN + 1];
|
char MqttTopicPowerMeter2[MQTT_MAX_TOPIC_STRLEN + 1];
|
||||||
char MqttTopicPowerMeter3[MQTT_MAX_TOPIC_STRLEN + 1];
|
char MqttTopicPowerMeter3[MQTT_MAX_TOPIC_STRLEN + 1];
|
||||||
uint32_t SdmAddress;
|
uint32_t SdmAddress;
|
||||||
uint32_t HttpInterval;
|
|
||||||
bool HttpIndividualRequests;
|
bool HttpIndividualRequests;
|
||||||
PowerMeterHttpConfig Http_Phase[POWERMETER_MAX_PHASES];
|
PowerMeterHttpJsonConfig HttpJson[POWERMETER_HTTP_JSON_MAX_VALUES];
|
||||||
PowerMeterTibberConfig Tibber;
|
PowerMeterHttpSmlConfig HttpSml;
|
||||||
} PowerMeter;
|
} PowerMeter;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
@ -280,6 +286,14 @@ public:
|
|||||||
INVERTER_CONFIG_T* getFreeInverterSlot();
|
INVERTER_CONFIG_T* getFreeInverterSlot();
|
||||||
INVERTER_CONFIG_T* getInverterConfig(const uint64_t serial);
|
INVERTER_CONFIG_T* getInverterConfig(const uint64_t serial);
|
||||||
void deleteInverterById(const uint8_t id);
|
void deleteInverterById(const uint8_t id);
|
||||||
|
|
||||||
|
static void serializeHttpRequestConfig(HttpRequestConfig const& source, JsonObject& target);
|
||||||
|
static void serializePowerMeterHttpJsonConfig(PowerMeterHttpJsonConfig const& source, JsonObject& target);
|
||||||
|
static void serializePowerMeterHttpSmlConfig(PowerMeterHttpSmlConfig const& source, JsonObject& target);
|
||||||
|
|
||||||
|
static void deserializeHttpRequestConfig(JsonObject const& source, HttpRequestConfig& target);
|
||||||
|
static void deserializePowerMeterHttpJsonConfig(JsonObject const& source, PowerMeterHttpJsonConfig& target);
|
||||||
|
static void deserializePowerMeterHttpSmlConfig(JsonObject const& source, PowerMeterHttpSmlConfig& target);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern ConfigurationClass Configuration;
|
extern ConfigurationClass Configuration;
|
||||||
|
|||||||
@ -8,8 +8,8 @@
|
|||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include "PowerMeterProvider.h"
|
#include "PowerMeterProvider.h"
|
||||||
|
|
||||||
using Auth_t = PowerMeterHttpConfig::Auth;
|
using Auth_t = HttpRequestConfig::Auth;
|
||||||
using Unit_t = PowerMeterHttpConfig::Unit;
|
using Unit_t = PowerMeterHttpJsonConfig::Unit;
|
||||||
|
|
||||||
class PowerMeterHttpJson : public PowerMeterProvider {
|
class PowerMeterHttpJson : public PowerMeterProvider {
|
||||||
public:
|
public:
|
||||||
@ -20,19 +20,19 @@ public:
|
|||||||
float getPowerTotal() const final;
|
float getPowerTotal() const final;
|
||||||
void doMqttPublish() const final;
|
void doMqttPublish() const final;
|
||||||
|
|
||||||
bool queryPhase(int phase, PowerMeterHttpConfig const& config);
|
bool queryValue(int phase, PowerMeterHttpJsonConfig const& config);
|
||||||
char httpPowerMeterError[256];
|
char httpPowerMeterError[256];
|
||||||
float getCached(size_t idx) { return _cache[idx]; }
|
float getCached(size_t idx) { return _cache[idx]; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t _lastPoll;
|
uint32_t _lastPoll;
|
||||||
std::array<float,POWERMETER_MAX_PHASES> _cache;
|
std::array<float, POWERMETER_HTTP_JSON_MAX_VALUES> _cache;
|
||||||
std::array<float,POWERMETER_MAX_PHASES> _powerValues;
|
std::array<float, POWERMETER_HTTP_JSON_MAX_VALUES> _powerValues;
|
||||||
std::unique_ptr<WiFiClient> wifiClient;
|
std::unique_ptr<WiFiClient> wifiClient;
|
||||||
std::unique_ptr<HTTPClient> httpClient;
|
std::unique_ptr<HTTPClient> httpClient;
|
||||||
String httpResponse;
|
String httpResponse;
|
||||||
|
|
||||||
bool httpRequest(int phase, const String& host, uint16_t port, const String& uri, bool https, PowerMeterHttpConfig const& config);
|
bool httpRequest(int phase, const String& host, uint16_t port, const String& uri, bool https, PowerMeterHttpJsonConfig const& config);
|
||||||
bool extractUrlComponents(String url, String& _protocol, String& _hostname, String& _uri, uint16_t& uint16_t, String& _base64Authorization);
|
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 extractParam(String& authReq, const String& param, const char delimit);
|
||||||
String getcNonce(const int len);
|
String getcNonce(const int len);
|
||||||
|
|||||||
@ -16,7 +16,7 @@ public:
|
|||||||
void loop() final;
|
void loop() final;
|
||||||
bool updateValues();
|
bool updateValues();
|
||||||
char tibberPowerMeterError[256];
|
char tibberPowerMeterError[256];
|
||||||
bool query(PowerMeterTibberConfig const& config);
|
bool query(HttpRequestConfig const& config);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t _lastPoll = 0;
|
uint32_t _lastPoll = 0;
|
||||||
@ -24,7 +24,7 @@ private:
|
|||||||
std::unique_ptr<WiFiClient> wifiClient;
|
std::unique_ptr<WiFiClient> wifiClient;
|
||||||
std::unique_ptr<HTTPClient> httpClient;
|
std::unique_ptr<HTTPClient> httpClient;
|
||||||
String httpResponse;
|
String httpResponse;
|
||||||
bool httpRequest(const String& host, uint16_t port, const String& uri, bool https, PowerMeterTibberConfig const& config);
|
bool httpRequest(const String& host, uint16_t port, const String& uri, bool https, HttpRequestConfig const& config);
|
||||||
bool extractUrlComponents(String url, String& _protocol, String& _hostname, String& _uri, uint16_t& uint16_t, String& _base64Authorization);
|
bool extractUrlComponents(String url, String& _protocol, String& _hostname, String& _uri, uint16_t& uint16_t, String& _base64Authorization);
|
||||||
void prepareRequest(uint32_t timeout);
|
void prepareRequest(uint32_t timeout);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -14,10 +14,8 @@ private:
|
|||||||
void onStatus(AsyncWebServerRequest* request);
|
void onStatus(AsyncWebServerRequest* request);
|
||||||
void onAdminGet(AsyncWebServerRequest* request);
|
void onAdminGet(AsyncWebServerRequest* request);
|
||||||
void onAdminPost(AsyncWebServerRequest* request);
|
void onAdminPost(AsyncWebServerRequest* request);
|
||||||
void decodeJsonPhaseConfig(JsonObject const& json, PowerMeterHttpConfig& config) const;
|
void onTestHttpJsonRequest(AsyncWebServerRequest* request);
|
||||||
void decodeJsonTibberConfig(JsonObject const& json, PowerMeterTibberConfig& config) const;
|
void onTestHttpSmlRequest(AsyncWebServerRequest* request);
|
||||||
void onTestHttpRequest(AsyncWebServerRequest* request);
|
|
||||||
void onTestTibberRequest(AsyncWebServerRequest* request);
|
|
||||||
|
|
||||||
AsyncWebServer* _server;
|
AsyncWebServer* _server;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -119,6 +119,8 @@
|
|||||||
#define POWERMETER_SOURCE 2
|
#define POWERMETER_SOURCE 2
|
||||||
#define POWERMETER_SDMADDRESS 1
|
#define POWERMETER_SDMADDRESS 1
|
||||||
|
|
||||||
|
#define HTTP_REQUEST_TIMEOUT_MS 1000
|
||||||
|
|
||||||
#define POWERLIMITER_ENABLED false
|
#define POWERLIMITER_ENABLED false
|
||||||
#define POWERLIMITER_SOLAR_PASSTHROUGH_ENABLED true
|
#define POWERLIMITER_SOLAR_PASSTHROUGH_ENABLED true
|
||||||
#define POWERLIMITER_SOLAR_PASSTHROUGH_LOSSES 3
|
#define POWERLIMITER_SOLAR_PASSTHROUGH_LOSSES 3
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "defaults.h"
|
#include "defaults.h"
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
#include <nvs_flash.h>
|
#include <nvs_flash.h>
|
||||||
|
|
||||||
@ -17,6 +16,33 @@ void ConfigurationClass::init()
|
|||||||
memset(&config, 0x0, sizeof(config));
|
memset(&config, 0x0, sizeof(config));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConfigurationClass::serializeHttpRequestConfig(HttpRequestConfig const& source, JsonObject& target)
|
||||||
|
{
|
||||||
|
JsonObject target_http_config = target["http_request"].to<JsonObject>();
|
||||||
|
target_http_config["url"] = source.Url;
|
||||||
|
target_http_config["auth_type"] = source.AuthType;
|
||||||
|
target_http_config["username"] = source.Username;
|
||||||
|
target_http_config["password"] = source.Password;
|
||||||
|
target_http_config["header_key"] = source.HeaderKey;
|
||||||
|
target_http_config["header_value"] = source.HeaderValue;
|
||||||
|
target_http_config["timeout"] = source.Timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigurationClass::serializePowerMeterHttpJsonConfig(PowerMeterHttpJsonConfig const& source, JsonObject& target)
|
||||||
|
{
|
||||||
|
serializeHttpRequestConfig(source.HttpRequest, target);
|
||||||
|
|
||||||
|
target["enabled"] = source.Enabled;
|
||||||
|
target["json_path"] = source.JsonPath;
|
||||||
|
target["unit"] = source.PowerUnit;
|
||||||
|
target["sign_inverted"] = source.SignInverted;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigurationClass::serializePowerMeterHttpSmlConfig(PowerMeterHttpSmlConfig const& source, JsonObject& target)
|
||||||
|
{
|
||||||
|
serializeHttpRequestConfig(source.HttpRequest, target);
|
||||||
|
}
|
||||||
|
|
||||||
bool ConfigurationClass::write()
|
bool ConfigurationClass::write()
|
||||||
{
|
{
|
||||||
File f = LittleFS.open(CONFIG_FILENAME, "w");
|
File f = LittleFS.open(CONFIG_FILENAME, "w");
|
||||||
@ -158,27 +184,14 @@ bool ConfigurationClass::write()
|
|||||||
powermeter["sdmaddress"] = config.PowerMeter.SdmAddress;
|
powermeter["sdmaddress"] = config.PowerMeter.SdmAddress;
|
||||||
powermeter["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests;
|
powermeter["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests;
|
||||||
|
|
||||||
JsonObject tibber = powermeter["tibber"].to<JsonObject>();
|
JsonObject powermeter_http_sml = powermeter["http_sml"].to<JsonObject>();
|
||||||
tibber["url"] = config.PowerMeter.Tibber.Url;
|
serializePowerMeterHttpSmlConfig(config.PowerMeter.HttpSml, powermeter_http_sml);
|
||||||
tibber["username"] = config.PowerMeter.Tibber.Username;
|
|
||||||
tibber["password"] = config.PowerMeter.Tibber.Password;
|
|
||||||
tibber["timeout"] = config.PowerMeter.Tibber.Timeout;
|
|
||||||
|
|
||||||
JsonArray powermeter_http_phases = powermeter["http_phases"].to<JsonArray>();
|
JsonArray powermeter_http_json = powermeter["http_json"].to<JsonArray>();
|
||||||
for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) {
|
for (uint8_t i = 0; i < POWERMETER_HTTP_JSON_MAX_VALUES; i++) {
|
||||||
JsonObject powermeter_phase = powermeter_http_phases.add<JsonObject>();
|
JsonObject powermeter_json_config = powermeter_http_json.add<JsonObject>();
|
||||||
|
serializePowerMeterHttpJsonConfig(config.PowerMeter.HttpJson[i],
|
||||||
powermeter_phase["enabled"] = config.PowerMeter.Http_Phase[i].Enabled;
|
powermeter_json_config);
|
||||||
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_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;
|
|
||||||
powermeter_phase["sign_inverted"] = config.PowerMeter.Http_Phase[i].SignInverted;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonObject powerlimiter = doc["powerlimiter"].to<JsonObject>();
|
JsonObject powerlimiter = doc["powerlimiter"].to<JsonObject>();
|
||||||
@ -246,6 +259,38 @@ bool ConfigurationClass::write()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConfigurationClass::deserializeHttpRequestConfig(JsonObject const& source, HttpRequestConfig& target)
|
||||||
|
{
|
||||||
|
JsonObject source_http_config = source["http_request"];
|
||||||
|
|
||||||
|
// http request parameters of HTTP/JSON power meter were
|
||||||
|
// previously stored alongside other settings
|
||||||
|
if (source_http_config.isNull()) { source_http_config = source; }
|
||||||
|
|
||||||
|
strlcpy(target.Url, source_http_config["url"] | "", sizeof(target.Url));
|
||||||
|
target.AuthType = source_http_config["auth_type"] | HttpRequestConfig::Auth::None;
|
||||||
|
strlcpy(target.Username, source_http_config["username"] | "", sizeof(target.Username));
|
||||||
|
strlcpy(target.Password, source_http_config["password"] | "", sizeof(target.Password));
|
||||||
|
strlcpy(target.HeaderKey, source_http_config["header_key"] | "", sizeof(target.HeaderKey));
|
||||||
|
strlcpy(target.HeaderValue, source_http_config["header_value"] | "", sizeof(target.HeaderValue));
|
||||||
|
target.Timeout = source_http_config["timeout"] | HTTP_REQUEST_TIMEOUT_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigurationClass::deserializePowerMeterHttpJsonConfig(JsonObject const& source, PowerMeterHttpJsonConfig& target)
|
||||||
|
{
|
||||||
|
deserializeHttpRequestConfig(source, target.HttpRequest);
|
||||||
|
|
||||||
|
target.Enabled = source["enabled"] | false;
|
||||||
|
strlcpy(target.JsonPath, source["json_path"] | "", sizeof(target.JsonPath));
|
||||||
|
target.PowerUnit = source["unit"] | PowerMeterHttpJsonConfig::Unit::Watts;
|
||||||
|
target.SignInverted = source["sign_inverted"] | false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigurationClass::deserializePowerMeterHttpSmlConfig(JsonObject const& source, PowerMeterHttpSmlConfig& target)
|
||||||
|
{
|
||||||
|
deserializeHttpRequestConfig(source, target.HttpRequest);
|
||||||
|
}
|
||||||
|
|
||||||
bool ConfigurationClass::read()
|
bool ConfigurationClass::read()
|
||||||
{
|
{
|
||||||
File f = LittleFS.open(CONFIG_FILENAME, "r", false);
|
File f = LittleFS.open(CONFIG_FILENAME, "r", false);
|
||||||
@ -424,27 +469,16 @@ bool ConfigurationClass::read()
|
|||||||
config.PowerMeter.SdmAddress = powermeter["sdmaddress"] | POWERMETER_SDMADDRESS;
|
config.PowerMeter.SdmAddress = powermeter["sdmaddress"] | POWERMETER_SDMADDRESS;
|
||||||
config.PowerMeter.HttpIndividualRequests = powermeter["http_individual_requests"] | false;
|
config.PowerMeter.HttpIndividualRequests = powermeter["http_individual_requests"] | false;
|
||||||
|
|
||||||
JsonObject tibber = powermeter["tibber"];
|
JsonObject powermeter_sml = powermeter["http_sml"];
|
||||||
strlcpy(config.PowerMeter.Tibber.Url, tibber["url"] | "", sizeof(config.PowerMeter.Tibber.Url));
|
deserializePowerMeterHttpSmlConfig(powermeter_sml, config.PowerMeter.HttpSml);
|
||||||
strlcpy(config.PowerMeter.Tibber.Username, tibber["username"] | "", sizeof(config.PowerMeter.Tibber.Username));
|
|
||||||
strlcpy(config.PowerMeter.Tibber.Password, tibber["password"] | "", sizeof(config.PowerMeter.Tibber.Password));
|
|
||||||
config.PowerMeter.Tibber.Timeout = tibber["timeout"] | POWERMETER_HTTP_TIMEOUT;
|
|
||||||
|
|
||||||
JsonArray powermeter_http_phases = powermeter["http_phases"];
|
JsonArray powermeter_http_json = powermeter["http_json"];
|
||||||
for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) {
|
if (powermeter_http_json.isNull()) {
|
||||||
JsonObject powermeter_phase = powermeter_http_phases[i].as<JsonObject>();
|
powermeter_http_json = powermeter["http_phases"]; // http_phases is a legacy key
|
||||||
|
}
|
||||||
config.PowerMeter.Http_Phase[i].Enabled = powermeter_phase["enabled"] | (i == 0);
|
for (uint8_t i = 0; i < POWERMETER_HTTP_JSON_MAX_VALUES; i++) {
|
||||||
strlcpy(config.PowerMeter.Http_Phase[i].Url, powermeter_phase["url"] | "", sizeof(config.PowerMeter.Http_Phase[i].Url));
|
JsonObject powermeter_json_config = powermeter_http_json[i].as<JsonObject>();
|
||||||
config.PowerMeter.Http_Phase[i].AuthType = powermeter_phase["auth_type"] | PowerMeterHttpConfig::Auth::None;
|
deserializePowerMeterHttpJsonConfig(powermeter_json_config, config.PowerMeter.HttpJson[i]);
|
||||||
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].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;
|
|
||||||
config.PowerMeter.Http_Phase[i].SignInverted = powermeter_phase["sign_inverted"] | false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonObject powerlimiter = doc["powerlimiter"];
|
JsonObject powerlimiter = doc["powerlimiter"];
|
||||||
|
|||||||
@ -27,16 +27,16 @@ void PowerMeterHttpJson::loop()
|
|||||||
|
|
||||||
_lastPoll = millis();
|
_lastPoll = millis();
|
||||||
|
|
||||||
for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) {
|
for (uint8_t i = 0; i < POWERMETER_HTTP_JSON_MAX_VALUES; i++) {
|
||||||
auto const& phaseConfig = config.PowerMeter.Http_Phase[i];
|
auto const& valueConfig = config.PowerMeter.HttpJson[i];
|
||||||
|
|
||||||
if (!phaseConfig.Enabled) {
|
if (!valueConfig.Enabled) {
|
||||||
_cache[i] = 0.0;
|
_cache[i] = 0.0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i == 0 || config.PowerMeter.HttpIndividualRequests) {
|
if (i == 0 || config.PowerMeter.HttpIndividualRequests) {
|
||||||
if (!queryPhase(i, phaseConfig)) {
|
if (!queryValue(i, valueConfig)) {
|
||||||
MessageOutput.printf("[PowerMeterHttpJson] Getting HTTP response for phase %d failed.\r\n", i + 1);
|
MessageOutput.printf("[PowerMeterHttpJson] Getting HTTP response for phase %d failed.\r\n", i + 1);
|
||||||
MessageOutput.printf("%s\r\n", httpPowerMeterError);
|
MessageOutput.printf("%s\r\n", httpPowerMeterError);
|
||||||
return;
|
return;
|
||||||
@ -44,7 +44,7 @@ void PowerMeterHttpJson::loop()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!tryGetFloatValueForPhase(i, phaseConfig.JsonPath, phaseConfig.PowerUnit, phaseConfig.SignInverted)) {
|
if(!tryGetFloatValueForPhase(i, valueConfig.JsonPath, valueConfig.PowerUnit, valueConfig.SignInverted)) {
|
||||||
MessageOutput.printf("[PowerMeterHttpJson] Reading power of phase %d (from JSON fetched with Phase 1 config) failed.\r\n", i + 1);
|
MessageOutput.printf("[PowerMeterHttpJson] Reading power of phase %d (from JSON fetched with Phase 1 config) failed.\r\n", i + 1);
|
||||||
MessageOutput.printf("%s\r\n", httpPowerMeterError);
|
MessageOutput.printf("%s\r\n", httpPowerMeterError);
|
||||||
return;
|
return;
|
||||||
@ -70,7 +70,7 @@ void PowerMeterHttpJson::doMqttPublish() const
|
|||||||
mqttPublish("power3", _powerValues[2]);
|
mqttPublish("power3", _powerValues[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PowerMeterHttpJson::queryPhase(int phase, PowerMeterHttpConfig const& config)
|
bool PowerMeterHttpJson::queryValue(int phase, PowerMeterHttpJsonConfig const& config)
|
||||||
{
|
{
|
||||||
//hostByName in WiFiGeneric fails to resolve local names. issue described in
|
//hostByName in WiFiGeneric fails to resolve local names. issue described in
|
||||||
//https://github.com/espressif/arduino-esp32/issues/3822
|
//https://github.com/espressif/arduino-esp32/issues/3822
|
||||||
@ -82,7 +82,7 @@ bool PowerMeterHttpJson::queryPhase(int phase, PowerMeterHttpConfig const& confi
|
|||||||
String uri;
|
String uri;
|
||||||
String base64Authorization;
|
String base64Authorization;
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
extractUrlComponents(config.Url, protocol, host, uri, port, base64Authorization);
|
extractUrlComponents(config.HttpRequest.Url, protocol, host, uri, port, base64Authorization);
|
||||||
|
|
||||||
IPAddress ipaddr((uint32_t)0);
|
IPAddress ipaddr((uint32_t)0);
|
||||||
//first check if "host" is already an IP adress
|
//first check if "host" is already an IP adress
|
||||||
@ -123,7 +123,7 @@ bool PowerMeterHttpJson::queryPhase(int phase, PowerMeterHttpConfig const& confi
|
|||||||
return httpRequest(phase, ipaddr.toString(), port, uri, https, config);
|
return httpRequest(phase, ipaddr.toString(), port, uri, https, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PowerMeterHttpJson::httpRequest(int phase, const String& host, uint16_t port, const String& uri, bool https, PowerMeterHttpConfig const& config)
|
bool PowerMeterHttpJson::httpRequest(int phase, const String& host, uint16_t port, const String& uri, bool https, PowerMeterHttpJsonConfig const& powerMeterConfig)
|
||||||
{
|
{
|
||||||
if (!httpClient) { httpClient = std::make_unique<HTTPClient>(); }
|
if (!httpClient) { httpClient = std::make_unique<HTTPClient>(); }
|
||||||
|
|
||||||
@ -132,6 +132,7 @@ bool PowerMeterHttpJson::httpRequest(int phase, const String& host, uint16_t por
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto const& config = powerMeterConfig.HttpRequest;
|
||||||
prepareRequest(config.Timeout, config.HeaderKey, config.HeaderValue);
|
prepareRequest(config.Timeout, config.HeaderKey, config.HeaderValue);
|
||||||
if (config.AuthType == Auth_t::Digest) {
|
if (config.AuthType == Auth_t::Digest) {
|
||||||
const char *headers[1] = {"WWW-Authenticate"};
|
const char *headers[1] = {"WWW-Authenticate"};
|
||||||
@ -178,7 +179,7 @@ bool PowerMeterHttpJson::httpRequest(int phase, const String& host, uint16_t por
|
|||||||
|
|
||||||
// TODO(schlimmchen): postpone calling tryGetFloatValueForPhase, as it
|
// TODO(schlimmchen): postpone calling tryGetFloatValueForPhase, as it
|
||||||
// will be called twice for each phase when doing separate requests.
|
// will be called twice for each phase when doing separate requests.
|
||||||
return tryGetFloatValueForPhase(phase, config.JsonPath, config.PowerUnit, config.SignInverted);
|
return tryGetFloatValueForPhase(phase, powerMeterConfig.JsonPath, powerMeterConfig.PowerUnit, powerMeterConfig.SignInverted);
|
||||||
}
|
}
|
||||||
|
|
||||||
String PowerMeterHttpJson::extractParam(String& authReq, const String& param, const char delimit) {
|
String PowerMeterHttpJson::extractParam(String& authReq, const String& param, const char delimit) {
|
||||||
|
|||||||
@ -24,15 +24,13 @@ void PowerMeterHttpSml::loop()
|
|||||||
|
|
||||||
_lastPoll = millis();
|
_lastPoll = millis();
|
||||||
|
|
||||||
auto const& tibberConfig = config.PowerMeter.Tibber;
|
if (!query(config.PowerMeter.HttpSml.HttpRequest)) {
|
||||||
|
|
||||||
if (!query(tibberConfig)) {
|
|
||||||
MessageOutput.printf("[PowerMeterHttpSml] Getting the power value failed.\r\n");
|
MessageOutput.printf("[PowerMeterHttpSml] Getting the power value failed.\r\n");
|
||||||
MessageOutput.printf("%s\r\n", tibberPowerMeterError);
|
MessageOutput.printf("%s\r\n", tibberPowerMeterError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PowerMeterHttpSml::query(PowerMeterTibberConfig const& config)
|
bool PowerMeterHttpSml::query(HttpRequestConfig const& config)
|
||||||
{
|
{
|
||||||
//hostByName in WiFiGeneric fails to resolve local names. issue described in
|
//hostByName in WiFiGeneric fails to resolve local names. issue described in
|
||||||
//https://github.com/espressif/arduino-esp32/issues/3822
|
//https://github.com/espressif/arduino-esp32/issues/3822
|
||||||
@ -85,7 +83,7 @@ bool PowerMeterHttpSml::query(PowerMeterTibberConfig const& config)
|
|||||||
return httpRequest(ipaddr.toString(), port, uri, https, config);
|
return httpRequest(ipaddr.toString(), port, uri, https, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PowerMeterHttpSml::httpRequest(const String& host, uint16_t port, const String& uri, bool https, PowerMeterTibberConfig const& config)
|
bool PowerMeterHttpSml::httpRequest(const String& host, uint16_t port, const String& uri, bool https, HttpRequestConfig const& config)
|
||||||
{
|
{
|
||||||
if (!httpClient) { httpClient = std::make_unique<HTTPClient>(); }
|
if (!httpClient) { httpClient = std::make_unique<HTTPClient>(); }
|
||||||
|
|
||||||
|
|||||||
@ -26,31 +26,8 @@ void WebApiPowerMeterClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
|||||||
_server->on("/api/powermeter/status", HTTP_GET, std::bind(&WebApiPowerMeterClass::onStatus, this, _1));
|
_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_GET, std::bind(&WebApiPowerMeterClass::onAdminGet, this, _1));
|
||||||
_server->on("/api/powermeter/config", HTTP_POST, std::bind(&WebApiPowerMeterClass::onAdminPost, 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));
|
_server->on("/api/powermeter/testhttpjsonrequest", HTTP_POST, std::bind(&WebApiPowerMeterClass::onTestHttpJsonRequest, this, _1));
|
||||||
_server->on("/api/powermeter/testtibberrequest", HTTP_POST, std::bind(&WebApiPowerMeterClass::onTestTibberRequest, this, _1));
|
_server->on("/api/powermeter/testhttpsmlrequest", HTTP_POST, std::bind(&WebApiPowerMeterClass::onTestHttpSmlRequest, 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));
|
|
||||||
config.PowerUnit = json["unit"].as<PowerMeterHttpConfig::Unit>();
|
|
||||||
config.SignInverted = json["sign_inverted"].as<bool>();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebApiPowerMeterClass::decodeJsonTibberConfig(JsonObject const& json, PowerMeterTibberConfig& config) const
|
|
||||||
{
|
|
||||||
strlcpy(config.Url, json["url"].as<String>().c_str(), sizeof(config.Url));
|
|
||||||
strlcpy(config.Username, json["username"].as<String>().c_str(), sizeof(config.Username));
|
|
||||||
strlcpy(config.Password, json["password"].as<String>().c_str(), sizeof(config.Password));
|
|
||||||
config.Timeout = json["timeout"].as<uint16_t>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request)
|
void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request)
|
||||||
@ -69,29 +46,14 @@ void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request)
|
|||||||
root["sdmaddress"] = config.PowerMeter.SdmAddress;
|
root["sdmaddress"] = config.PowerMeter.SdmAddress;
|
||||||
root["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests;
|
root["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests;
|
||||||
|
|
||||||
auto tibber = root["tibber"].to<JsonObject>();
|
auto httpSml = root["http_sml"].to<JsonObject>();
|
||||||
tibber["url"] = String(config.PowerMeter.Tibber.Url);
|
Configuration.serializePowerMeterHttpSmlConfig(config.PowerMeter.HttpSml, httpSml);
|
||||||
tibber["username"] = String(config.PowerMeter.Tibber.Username);
|
|
||||||
tibber["password"] = String(config.PowerMeter.Tibber.Password);
|
|
||||||
tibber["timeout"] = config.PowerMeter.Tibber.Timeout;
|
|
||||||
|
|
||||||
auto httpPhases = root["http_phases"].to<JsonArray>();
|
auto httpJson = root["http_json"].to<JsonArray>();
|
||||||
|
for (uint8_t i = 0; i < POWERMETER_HTTP_JSON_MAX_VALUES; i++) {
|
||||||
for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) {
|
auto valueConfig = httpJson.add<JsonObject>();
|
||||||
auto phaseObject = httpPhases.add<JsonObject>();
|
valueConfig["index"] = i + 1;
|
||||||
|
Configuration.serializePowerMeterHttpJsonConfig(config.PowerMeter.HttpJson[i], valueConfig);
|
||||||
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["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;
|
|
||||||
phaseObject["sign_inverted"] = config.PowerMeter.Http_Phase[i].SignInverted;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
@ -127,44 +89,52 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (static_cast<PowerMeterProvider::Type>(root["source"].as<uint8_t>()) == PowerMeterProvider::Type::HTTP_JSON) {
|
auto checkHttpConfig = [&](JsonObject const& cfg) -> bool {
|
||||||
JsonArray http_phases = root["http_phases"];
|
if (!cfg.containsKey("url")
|
||||||
for (uint8_t i = 0; i < http_phases.size(); i++) {
|
|| (!cfg["url"].as<String>().startsWith("http://")
|
||||||
JsonObject phase = http_phases[i].as<JsonObject>();
|
&& !cfg["url"].as<String>().startsWith("https://"))) {
|
||||||
|
retMsg["message"] = "URL must either start with http:// or https://!";
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (i > 0 && !phase["enabled"].as<bool>()) {
|
if ((cfg["auth_type"].as<uint8_t>() != HttpRequestConfig::Auth::None)
|
||||||
|
&& (cfg["username"].as<String>().length() == 0 || cfg["password"].as<String>().length() == 0)) {
|
||||||
|
retMsg["message"] = "Username or password must not be empty!";
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cfg.containsKey("timeout")
|
||||||
|
|| cfg["timeout"].as<uint16_t>() <= 0) {
|
||||||
|
retMsg["message"] = "Timeout must be greater than 0 ms!";
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (static_cast<PowerMeterProvider::Type>(root["source"].as<uint8_t>()) == PowerMeterProvider::Type::HTTP_JSON) {
|
||||||
|
JsonArray httpJson = root["http_json"];
|
||||||
|
for (uint8_t i = 0; i < httpJson.size(); i++) {
|
||||||
|
JsonObject valueConfig = httpJson[i].as<JsonObject>();
|
||||||
|
|
||||||
|
if (i > 0 && !valueConfig["enabled"].as<bool>()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i == 0 || phase["http_individual_requests"].as<bool>()) {
|
if (i == 0 || valueConfig["http_individual_requests"].as<bool>()) {
|
||||||
if (!phase.containsKey("url")
|
if (!checkHttpConfig(valueConfig["http_request"].as<JsonObject>())) {
|
||||||
|| (!phase["url"].as<String>().startsWith("http://")
|
|
||||||
&& !phase["url"].as<String>().startsWith("https://"))) {
|
|
||||||
retMsg["message"] = "URL must either start with http:// or https://!";
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!phase.containsKey("timeout")
|
|
||||||
|| phase["timeout"].as<uint16_t>() <= 0) {
|
|
||||||
retMsg["message"] = "Timeout must be greater than 0 ms!";
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!phase.containsKey("json_path")
|
if (!valueConfig.containsKey("json_path")
|
||||||
|| phase["json_path"].as<String>().length() == 0) {
|
|| valueConfig["json_path"].as<String>().length() == 0) {
|
||||||
retMsg["message"] = "Json path must not be empty!";
|
retMsg["message"] = "Json path must not be empty!";
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
@ -174,29 +144,8 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (static_cast<PowerMeterProvider::Type>(root["source"].as<uint8_t>()) == PowerMeterProvider::Type::HTTP_SML) {
|
if (static_cast<PowerMeterProvider::Type>(root["source"].as<uint8_t>()) == PowerMeterProvider::Type::HTTP_SML) {
|
||||||
JsonObject tibber = root["tibber"];
|
JsonObject httpSml = root["http_sml"];
|
||||||
|
if (!checkHttpConfig(httpSml["http_request"].as<JsonObject>())) {
|
||||||
if (!tibber.containsKey("url")
|
|
||||||
|| (!tibber["url"].as<String>().startsWith("http://")
|
|
||||||
&& !tibber["url"].as<String>().startsWith("https://"))) {
|
|
||||||
retMsg["message"] = "URL must either start with http:// or https://!";
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((tibber["username"].as<String>().length() == 0 || tibber["password"].as<String>().length() == 0)) {
|
|
||||||
retMsg["message"] = "Username or password must not be empty!";
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tibber.containsKey("timeout")
|
|
||||||
|| tibber["timeout"].as<uint16_t>() <= 0) {
|
|
||||||
retMsg["message"] = "Timeout must be greater than 0 ms!";
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,13 +161,15 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
config.PowerMeter.SdmAddress = root["sdmaddress"].as<uint8_t>();
|
config.PowerMeter.SdmAddress = root["sdmaddress"].as<uint8_t>();
|
||||||
config.PowerMeter.HttpIndividualRequests = root["http_individual_requests"].as<bool>();
|
config.PowerMeter.HttpIndividualRequests = root["http_individual_requests"].as<bool>();
|
||||||
|
|
||||||
decodeJsonTibberConfig(root["tibber"].as<JsonObject>(), config.PowerMeter.Tibber);
|
Configuration.deserializePowerMeterHttpSmlConfig(root["http_sml"].as<JsonObject>(),
|
||||||
|
config.PowerMeter.HttpSml);
|
||||||
|
|
||||||
JsonArray http_phases = root["http_phases"];
|
JsonArray httpJson = root["http_json"];
|
||||||
for (uint8_t i = 0; i < http_phases.size(); i++) {
|
for (uint8_t i = 0; i < httpJson.size(); i++) {
|
||||||
decodeJsonPhaseConfig(http_phases[i].as<JsonObject>(), config.PowerMeter.Http_Phase[i]);
|
Configuration.deserializePowerMeterHttpJsonConfig(httpJson[i].as<JsonObject>(),
|
||||||
|
config.PowerMeter.HttpJson[i]);
|
||||||
}
|
}
|
||||||
config.PowerMeter.Http_Phase[0].Enabled = true;
|
config.PowerMeter.HttpJson[0].Enabled = true;
|
||||||
|
|
||||||
WebApi.writeConfig(retMsg);
|
WebApi.writeConfig(retMsg);
|
||||||
|
|
||||||
@ -227,7 +178,7 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
PowerMeter.updateSettings();
|
PowerMeter.updateSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
void WebApiPowerMeterClass::onTestHttpJsonRequest(AsyncWebServerRequest* request)
|
||||||
{
|
{
|
||||||
if (!WebApi.checkCredentials(request)) {
|
if (!WebApi.checkCredentials(request)) {
|
||||||
return;
|
return;
|
||||||
@ -241,9 +192,15 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = asyncJsonResponse->getRoot();
|
auto& retMsg = asyncJsonResponse->getRoot();
|
||||||
|
|
||||||
if (!root.containsKey("url") || !root.containsKey("auth_type") || !root.containsKey("username") || !root.containsKey("password")
|
JsonObject requestConfig = root["http_request"];
|
||||||
|| !root.containsKey("header_key") || !root.containsKey("header_value")
|
if (!requestConfig.containsKey("url")
|
||||||
|| !root.containsKey("timeout") || !root.containsKey("json_path")) {
|
|| !requestConfig.containsKey("auth_type")
|
||||||
|
|| !requestConfig.containsKey("username")
|
||||||
|
|| !requestConfig.containsKey("password")
|
||||||
|
|| !requestConfig.containsKey("header_key")
|
||||||
|
|| !requestConfig.containsKey("header_value")
|
||||||
|
|| !requestConfig.containsKey("timeout")
|
||||||
|
|| !root.containsKey("json_path")) {
|
||||||
retMsg["message"] = "Missing fields!";
|
retMsg["message"] = "Missing fields!";
|
||||||
asyncJsonResponse->setLength();
|
asyncJsonResponse->setLength();
|
||||||
request->send(asyncJsonResponse);
|
request->send(asyncJsonResponse);
|
||||||
@ -253,10 +210,10 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
char response[256];
|
char response[256];
|
||||||
|
|
||||||
PowerMeterHttpConfig phaseConfig;
|
PowerMeterHttpJsonConfig httpJsonConfig;
|
||||||
decodeJsonPhaseConfig(root.as<JsonObject>(), phaseConfig);
|
Configuration.deserializePowerMeterHttpJsonConfig(root.as<JsonObject>(), httpJsonConfig);
|
||||||
auto upMeter = std::make_unique<PowerMeterHttpJson>();
|
auto upMeter = std::make_unique<PowerMeterHttpJson>();
|
||||||
if (upMeter->queryPhase(0/*phase*/, phaseConfig)) {
|
if (upMeter->queryValue(0/*value index*/, httpJsonConfig)) {
|
||||||
retMsg["type"] = "success";
|
retMsg["type"] = "success";
|
||||||
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", upMeter->getCached(0));
|
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", upMeter->getCached(0));
|
||||||
} else {
|
} else {
|
||||||
@ -268,7 +225,7 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
|||||||
request->send(asyncJsonResponse);
|
request->send(asyncJsonResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiPowerMeterClass::onTestTibberRequest(AsyncWebServerRequest* request)
|
void WebApiPowerMeterClass::onTestHttpSmlRequest(AsyncWebServerRequest* request)
|
||||||
{
|
{
|
||||||
if (!WebApi.checkCredentials(request)) {
|
if (!WebApi.checkCredentials(request)) {
|
||||||
return;
|
return;
|
||||||
@ -293,10 +250,10 @@ void WebApiPowerMeterClass::onTestTibberRequest(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
char response[256];
|
char response[256];
|
||||||
|
|
||||||
PowerMeterTibberConfig tibberConfig;
|
PowerMeterHttpSmlConfig httpSmlConfig;
|
||||||
decodeJsonTibberConfig(root.as<JsonObject>(), tibberConfig);
|
Configuration.deserializePowerMeterHttpSmlConfig(root.as<JsonObject>(), httpSmlConfig);
|
||||||
auto upMeter = std::make_unique<PowerMeterHttpSml>();
|
auto upMeter = std::make_unique<PowerMeterHttpSml>();
|
||||||
if (upMeter->query(tibberConfig)) {
|
if (upMeter->query(httpSmlConfig.HttpRequest)) {
|
||||||
retMsg["type"] = "success";
|
retMsg["type"] = "success";
|
||||||
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", upMeter->getPowerTotal());
|
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", upMeter->getPowerTotal());
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
77
webapp/src/components/HttpRequestSettings.vue
Normal file
77
webapp/src/components/HttpRequestSettings.vue
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<InputElement
|
||||||
|
:label="$t('httprequestsettings.url')"
|
||||||
|
v-model="cfg.url"
|
||||||
|
type="text"
|
||||||
|
maxlength="1024"
|
||||||
|
placeholder="http://admin:supersecret@mypowermeter.home/status"
|
||||||
|
prefix="GET "
|
||||||
|
:tooltip="$t('httprequestsettings.urlDescription')" />
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="auth_type" class="col-sm-2 col-form-label">{{ $t('httprequestsettings.authorization') }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select id="auth_type" class="form-select" v-model="cfg.auth_type">
|
||||||
|
<option v-for="a in authTypeList" :key="a.key" :value="a.key">
|
||||||
|
{{ $t('httprequestsettings.authType' + a.value) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<InputElement
|
||||||
|
v-if="cfg.auth_type != 0"
|
||||||
|
:label="$t('httprequestsettings.username')"
|
||||||
|
v-model="cfg.username"
|
||||||
|
type="text" maxlength="64"/>
|
||||||
|
|
||||||
|
<InputElement
|
||||||
|
v-if="cfg.auth_type != 0"
|
||||||
|
:label="$t('httprequestsettings.password')"
|
||||||
|
v-model="cfg.password"
|
||||||
|
type="password" maxlength="64"/>
|
||||||
|
|
||||||
|
<InputElement
|
||||||
|
:label="$t('httprequestsettings.headerKey')"
|
||||||
|
v-model="cfg.header_key"
|
||||||
|
type="text"
|
||||||
|
maxlength="64"
|
||||||
|
:tooltip="$t('httprequestsettings.headerKeyDescription')" />
|
||||||
|
|
||||||
|
<InputElement
|
||||||
|
:label="$t('httprequestsettings.headerValue')"
|
||||||
|
v-model="cfg.header_value"
|
||||||
|
type="text"
|
||||||
|
maxlength="256" />
|
||||||
|
|
||||||
|
<InputElement
|
||||||
|
:label="$t('httprequestsettings.timeout')"
|
||||||
|
v-model="cfg.timeout"
|
||||||
|
type="number"
|
||||||
|
:postfix="$t('httprequestsettings.milliSeconds')" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import InputElement from '@/components/InputElement.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
'cfg': { type: Object, required: true }
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
InputElement
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
authTypeList: [
|
||||||
|
{ key: 0, value: "None" },
|
||||||
|
{ key: 1, value: "Basic" },
|
||||||
|
{ key: 2, value: "Digest" },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -558,37 +558,47 @@
|
|||||||
"PowerMeterSource": "Stromzählertyp",
|
"PowerMeterSource": "Stromzählertyp",
|
||||||
"MQTT": "MQTT Konfiguration",
|
"MQTT": "MQTT Konfiguration",
|
||||||
"typeMQTT": "MQTT",
|
"typeMQTT": "MQTT",
|
||||||
"typeSDM1ph": "SDM 1 phase (SDM120/220/230)",
|
"typeSDM1ph": "SDM mit 1 Phase (SDM120/220/230)",
|
||||||
"typeSDM3ph": "SDM 3 phase (SDM72/630)",
|
"typeSDM3ph": "SDM mit 3 Phasen (SDM72/630)",
|
||||||
"typeHTTP": "HTTP(S) + JSON",
|
"typeHTTP_JSON": "HTTP(S) + JSON",
|
||||||
"typeSML": "SML (OBIS 16.7.0)",
|
"typeSML": "SML/OBIS via serieller Verbindung (z.B. Hichi TTL)",
|
||||||
"typeSMAHM2": "SMA Homemanager 2.0",
|
"typeSMAHM2": "SMA Homemanager 2.0",
|
||||||
"typeTIBBER": "Tibber Pulse (via Tibber Bridge)",
|
"typeHTTP_SML": "HTTP(S) + SML (z.B. Tibber Pulse via Tibber Bridge)",
|
||||||
"MqttTopicPowerMeter1": "MQTT topic - Stromzähler #1",
|
"MqttTopicPowerMeter1": "MQTT topic - Stromzähler #1",
|
||||||
"MqttTopicPowerMeter2": "MQTT topic - Stromzähler #2 (Optional)",
|
"MqttTopicPowerMeter2": "MQTT topic - Stromzähler #2 (Optional)",
|
||||||
"MqttTopicPowerMeter3": "MQTT topic - Stromzähler #3 (Optional)",
|
"MqttTopicPowerMeter3": "MQTT topic - Stromzähler #3 (Optional)",
|
||||||
"SDM": "SDM-Stromzähler Konfiguration",
|
"SDM": "SDM-Stromzähler Konfiguration",
|
||||||
"sdmaddress": "Modbus Adresse",
|
"sdmaddress": "Modbus Adresse",
|
||||||
"HTTP": "HTTP(S) + JSON - Allgemeine Konfiguration",
|
"HTTP_JSON": "HTTP(S) + JSON - Allgemeine Konfiguration",
|
||||||
"httpIndividualRequests": "Individuelle HTTP requests pro Phase",
|
"httpIndividualRequests": "Individuelle HTTP Anfragen pro Wert",
|
||||||
"urlExamplesHeading": "Beispiele für URLs",
|
"urlExamplesHeading": "Beispiele für URLs",
|
||||||
"jsonPathExamplesHeading": "Beispiele für JSON Pfade",
|
"jsonPathExamplesHeading": "Beispiele für JSON Pfade",
|
||||||
"jsonPathExamplesExplanation": "Die folgenden Pfade finden jeweils den Wert '123.4' im jeweiligen Beispiel-JSON.",
|
"jsonPathExamplesExplanation": "Die folgenden Pfade finden jeweils den Wert '123.4' im jeweiligen Beispiel-JSON.",
|
||||||
"httpUrlDescription": "Die URL muss mit http:// oder https:// beginnen. Manche Zeichen wie Leerzeichen und = müssen mit URL-Kodierung kodiert werden (%xx). Achtung: Ein Überprüfung von SSL Server Zertifikaten ist nicht implementiert (MITM-Attacken sind möglich)!.",
|
"httpValue": "Konfiguration für Wert {valueNumber}",
|
||||||
"httpPhase": "HTTP(S) + JSON Konfiguration - Phase {phaseNumber}",
|
"httpEnabled": "Wert aktiviert",
|
||||||
"httpEnabled": "Phase aktiviert",
|
|
||||||
"httpUrl": "URL",
|
|
||||||
"httpHeaderKey": "Optional: HTTP request header - Key",
|
|
||||||
"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",
|
"httpJsonPath": "JSON Pfad",
|
||||||
"httpJsonPathDescription": "Anwendungsspezifischer JSON-Pfad um den Leistungswert in the HTTP(S) Antwort zu finden, z.B. 'power/total/watts' oder nur 'total'.",
|
"httpJsonPathDescription": "Anwendungsspezifischer JSON-Pfad um den Leistungswert in the HTTP(S) Antwort zu finden, z.B. 'power/total/watts' oder nur 'total'.",
|
||||||
"httpUnit": "Einheit",
|
"httpUnit": "Einheit",
|
||||||
"httpSignInverted": "Vorzeichen umkehren",
|
"httpSignInverted": "Vorzeichen umkehren",
|
||||||
"httpSignInvertedHint": "Positive Werte werden als Leistungsabnahme aus dem Netz interpretiert. Diese Option muss aktiviert werden, wenn das Vorzeichen des Wertes die gegenteilige Bedeutung hat.",
|
"httpSignInvertedHint": "Positive Werte werden als Leistungsabnahme aus dem Netz interpretiert. Diese Option muss aktiviert werden, wenn das Vorzeichen des Wertes die gegenteilige Bedeutung hat.",
|
||||||
"httpTimeout": "Timeout",
|
"testHttpJsonRequest": "Konfiguration testen (HTTP(S)-Anfrage senden)",
|
||||||
"testHttpRequest": "Testen",
|
"testHttpSmlRequest": "Konfiguration testen (HTTP(S)-Anfrage senden)",
|
||||||
"TIBBER": "Tibber Pulse (via Tibber Bridge) - Konfiguration"
|
"HTTP_SML": "HTTP(S) + SML - Konfiguration"
|
||||||
|
},
|
||||||
|
"httprequestsettings": {
|
||||||
|
"url": "URL",
|
||||||
|
"urlDescription": "Die URL muss mit 'http://' oder 'https://' beginnen. Zeichen wie Leerzeichen und = müssen mit URL-kodiert werden (%xx). Achtung: Eine Überprüfung von SSL-Server-Zertifikaten ist nicht implementiert (MITM-Attacken sind möglich)!.",
|
||||||
|
"authorization": "Authentifizierungsverfahren",
|
||||||
|
"authTypeNone": "Ohne",
|
||||||
|
"authTypeBasic": "Basic",
|
||||||
|
"authTypeDigest": "Digest",
|
||||||
|
"username": "Benutzername",
|
||||||
|
"password": "Passwort",
|
||||||
|
"headerKey": "HTTP Header - Name",
|
||||||
|
"headerKeyDescription": "Optional. Ein benutzerdefinierter HTTP header kann definiert werden. Nützlich um z.B. ein (zusätzlichen) Authentifizierungstoken zu übermitteln.",
|
||||||
|
"headerValue": "HTTP Header - Wert",
|
||||||
|
"timeout": "Zeitüberschreitung",
|
||||||
|
"milliSeconds": "ms"
|
||||||
},
|
},
|
||||||
"powerlimiteradmin": {
|
"powerlimiteradmin": {
|
||||||
"PowerLimiterSettings": "Dynamic Power Limiter Einstellungen",
|
"PowerLimiterSettings": "Dynamic Power Limiter Einstellungen",
|
||||||
|
|||||||
@ -560,41 +560,47 @@
|
|||||||
"PowerMeterSource": "Power Meter type",
|
"PowerMeterSource": "Power Meter type",
|
||||||
"MQTT": "MQTT Parameter",
|
"MQTT": "MQTT Parameter",
|
||||||
"typeMQTT": "MQTT",
|
"typeMQTT": "MQTT",
|
||||||
"typeSDM1ph": "SDM 1 phase (SDM120/220/230)",
|
"typeSDM1ph": "SDM for 1 phase (SDM120/220/230)",
|
||||||
"typeSDM3ph": "SDM 3 phase (SDM72/630)",
|
"typeSDM3ph": "SDM for 3 phases (SDM72/630)",
|
||||||
"typeHTTP": "HTTP(s) + JSON",
|
"typeHTTP_JSON": "HTTP(S) + JSON",
|
||||||
"typeSML": "SML (OBIS 16.7.0)",
|
"typeSML": "SML/OBIS via serial connection (e.g. Hichi TTL)",
|
||||||
"typeSMAHM2": "SMA Homemanager 2.0",
|
"typeSMAHM2": "SMA Homemanager 2.0",
|
||||||
"typeTIBBER": "Tibber Pulse (via Tibber Bridge)",
|
"typeHTTP_SML": "HTTP(S) + SML (e.g. Tibber Pulse via Tibber Bridge)",
|
||||||
"MqttTopicPowerMeter1": "MQTT topic - Power meter #1",
|
"MqttTopicPowerMeter1": "MQTT topic - Power meter #1",
|
||||||
"MqttTopicPowerMeter2": "MQTT topic - Power meter #2",
|
"MqttTopicPowerMeter2": "MQTT topic - Power meter #2",
|
||||||
"MqttTopicPowerMeter3": "MQTT topic - Power meter #3",
|
"MqttTopicPowerMeter3": "MQTT topic - Power meter #3",
|
||||||
"SDM": "SDM-Power Meter Parameter",
|
"SDM": "SDM-Power Meter Parameter",
|
||||||
"sdmaddress": "Modbus Address",
|
"sdmaddress": "Modbus Address",
|
||||||
"HTTP": "HTTP(S) + Json - General configuration",
|
"HTTP": "HTTP(S) + JSON - General configuration",
|
||||||
"httpIndividualRequests": "Individual HTTP requests per phase",
|
"httpIndividualRequests": "Individual HTTP requests per value",
|
||||||
"urlExamplesHeading": "URL Examples",
|
"urlExamplesHeading": "URL Examples",
|
||||||
"jsonPathExamplesHeading": "JSON Path Examples",
|
"jsonPathExamplesHeading": "JSON Path Examples",
|
||||||
"jsonPathExamplesExplanation": "The following paths each find the value '123.4' in the respective example JSON.",
|
"jsonPathExamplesExplanation": "The following paths each find the value '123.4' in the respective example JSON.",
|
||||||
"httpPhase": "HTTP(S) + Json configuration - Phase {phaseNumber}",
|
"httpValue": "Configuration for value {valueNumber}",
|
||||||
"httpEnabled": "Phase enabled",
|
"httpEnabled": "Value enabled",
|
||||||
"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)!",
|
|
||||||
"httpAuthorization": "Authorization Type",
|
|
||||||
"httpUsername": "Username",
|
|
||||||
"httpPassword": "Password",
|
|
||||||
"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",
|
"httpJsonPath": "JSON path",
|
||||||
"httpJsonPathDescription": "Application specific JSON path to find the power value in the HTTP(S) response, e.g., 'power/total/watts' or simply 'total'.",
|
"httpJsonPathDescription": "Application specific JSON path to find the power value in the HTTP(S) response, e.g., 'power/total/watts' or simply 'total'.",
|
||||||
"httpUnit": "Unit",
|
"httpUnit": "Unit",
|
||||||
"httpSignInverted": "Change Sign",
|
"httpSignInverted": "Change Sign",
|
||||||
"httpSignInvertedHint": "Is is expected that positive values denote power usage from the grid. Check this option if the sign of this value has the opposite meaning.",
|
"httpSignInvertedHint": "Is is expected that positive values denote power usage from the grid. Check this option if the sign of this value has the opposite meaning.",
|
||||||
"httpTimeout": "Timeout",
|
"testHttpJsonRequest": "Test configuration (send HTTP(S) request)",
|
||||||
"testHttpRequest": "Run test",
|
"testHttpSmlRequest": "Test configuration (send HTTP(S) request)",
|
||||||
"milliSeconds": "ms",
|
"HTTP_SML": "Configuration"
|
||||||
"TIBBER": "Tibber Pulse (via Tibber Bridge) - Configuration"
|
},
|
||||||
|
"httprequestsettings": {
|
||||||
|
"url": "URL",
|
||||||
|
"urlDescription": "URL must start with 'http://' or 'https://'. Characters like spaces and '=' have to be URL-encoded (%xx). Warning: SSL server certificate check is not implemented (MITM attacks are possible)!",
|
||||||
|
"authorization": "Authorization Type",
|
||||||
|
"authTypeNone": "None",
|
||||||
|
"authTypeBasic": "Basic",
|
||||||
|
"authTypeDigest": "Digest",
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password",
|
||||||
|
"headerKey": "HTTP Header - Key",
|
||||||
|
"headerKeyDescription": "Optional. A custom HTTP header key-value pair can be defined. Useful, e.g., to send an (additional) authentication token.",
|
||||||
|
"headerValue": "HTTP Header - Value",
|
||||||
|
"timeout": "Timeout",
|
||||||
|
"milliSeconds": "ms"
|
||||||
},
|
},
|
||||||
"powerlimiteradmin": {
|
"powerlimiteradmin": {
|
||||||
"PowerLimiterSettings": "Dynamic Power Limiter Settings",
|
"PowerLimiterSettings": "Dynamic Power Limiter Settings",
|
||||||
|
|||||||
9
webapp/src/types/HttpRequestConfig.ts
Normal file
9
webapp/src/types/HttpRequestConfig.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export interface HttpRequestConfig {
|
||||||
|
url: string;
|
||||||
|
auth_type: number;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
header_key: string;
|
||||||
|
header_value: string;
|
||||||
|
timeout: number;
|
||||||
|
}
|
||||||
@ -1,23 +1,16 @@
|
|||||||
export interface PowerMeterHttpPhaseConfig {
|
import type { HttpRequestConfig } from '@/types/HttpRequestConfig';
|
||||||
|
|
||||||
|
export interface PowerMeterHttpJsonConfig {
|
||||||
index: number;
|
index: number;
|
||||||
|
http_request: HttpRequestConfig;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
url: string;
|
|
||||||
auth_type: number;
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
header_key: string;
|
|
||||||
header_value: string;
|
|
||||||
json_path: string;
|
json_path: string;
|
||||||
timeout: number;
|
|
||||||
unit: number;
|
unit: number;
|
||||||
sign_inverted: boolean;
|
sign_inverted: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PowerMeterTibberConfig {
|
export interface PowerMeterHttpSmlConfig {
|
||||||
url: string;
|
http_request: HttpRequestConfig;
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
timeout: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PowerMeterConfig {
|
export interface PowerMeterConfig {
|
||||||
@ -30,6 +23,6 @@ export interface PowerMeterConfig {
|
|||||||
mqtt_topic_powermeter_3: string;
|
mqtt_topic_powermeter_3: string;
|
||||||
sdmaddress: number;
|
sdmaddress: number;
|
||||||
http_individual_requests: boolean;
|
http_individual_requests: boolean;
|
||||||
http_phases: Array<PowerMeterHttpPhaseConfig>;
|
http_json: Array<PowerMeterHttpJsonConfig>;
|
||||||
tibber: PowerMeterTibberConfig;
|
http_sml: PowerMeterHttpSmlConfig;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -114,66 +114,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CardElement
|
<CardElement
|
||||||
v-for="(http_phase, index) in powerMeterConfigList.http_phases"
|
v-for="(httpJson, index) in powerMeterConfigList.http_json"
|
||||||
:key="http_phase.index"
|
:key="httpJson.index"
|
||||||
:text="$t('powermeteradmin.httpPhase', { phaseNumber: http_phase.index })"
|
:text="$t('powermeteradmin.httpValue', { valueNumber: httpJson.index })"
|
||||||
textVariant="text-bg-primary"
|
textVariant="text-bg-primary"
|
||||||
add-space>
|
add-space>
|
||||||
<InputElement
|
<InputElement
|
||||||
v-if="index > 0"
|
v-if="index > 0"
|
||||||
:label="$t('powermeteradmin.httpEnabled')"
|
:label="$t('powermeteradmin.httpEnabled')"
|
||||||
v-model="http_phase.enabled"
|
v-model="httpJson.enabled"
|
||||||
type="checkbox" wide />
|
type="checkbox" wide />
|
||||||
|
|
||||||
<div v-if="http_phase.enabled">
|
<div v-if="httpJson.enabled">
|
||||||
<div v-if="index == 0 || powerMeterConfigList.http_individual_requests">
|
|
||||||
<InputElement :label="$t('powermeteradmin.httpUrl')"
|
|
||||||
v-model="http_phase.url"
|
|
||||||
type="text"
|
|
||||||
maxlength="1024"
|
|
||||||
placeholder="http://admin:supersecret@mypowermeter.home/status"
|
|
||||||
prefix="GET "
|
|
||||||
:tooltip="$t('powermeteradmin.httpUrlDescription')" />
|
|
||||||
|
|
||||||
<div class="row mb-3">
|
<HttpRequestSettings :cfg="httpJson.http_request" v-if="index == 0 || powerMeterConfigList.http_individual_requests"/>
|
||||||
<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')"
|
|
||||||
v-model="http_phase.header_key"
|
|
||||||
type="text"
|
|
||||||
maxlength="64"
|
|
||||||
:tooltip="$t('powermeteradmin.httpHeaderKeyDescription')" />
|
|
||||||
|
|
||||||
<InputElement :label="$t('powermeteradmin.httpHeaderValue')"
|
|
||||||
v-model="http_phase.header_value"
|
|
||||||
type="text"
|
|
||||||
maxlength="256" />
|
|
||||||
|
|
||||||
<InputElement :label="$t('powermeteradmin.httpTimeout')"
|
|
||||||
v-model="http_phase.timeout"
|
|
||||||
type="number"
|
|
||||||
:postfix="$t('powermeteradmin.milliSeconds')" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<InputElement :label="$t('powermeteradmin.httpJsonPath')"
|
<InputElement :label="$t('powermeteradmin.httpJsonPath')"
|
||||||
v-model="http_phase.json_path"
|
v-model="httpJson.json_path"
|
||||||
type="text"
|
type="text"
|
||||||
maxlength="256"
|
maxlength="256"
|
||||||
placeholder="total_power"
|
placeholder="total_power"
|
||||||
@ -184,67 +141,48 @@
|
|||||||
{{ $t('powermeteradmin.httpUnit') }}
|
{{ $t('powermeteradmin.httpUnit') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<select id="power_unit" class="form-select" v-model="http_phase.unit">
|
<select id="power_unit" class="form-select" v-model="httpJson.unit">
|
||||||
<option value="1">mW</option>
|
<option v-for="u in unitTypeList" :key="u.key" :value="u.key">
|
||||||
<option value="0">W</option>
|
{{ u.value }}
|
||||||
<option value="2">kW</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<InputElement
|
<InputElement
|
||||||
:label="$t('powermeteradmin.httpSignInverted')"
|
:label="$t('powermeteradmin.httpSignInverted')"
|
||||||
v-model="http_phase.sign_inverted"
|
v-model="httpJson.sign_inverted"
|
||||||
:tooltip="$t('powermeteradmin.httpSignInvertedHint')"
|
:tooltip="$t('powermeteradmin.httpSignInvertedHint')"
|
||||||
type="checkbox" />
|
type="checkbox" />
|
||||||
|
|
||||||
<div class="text-center mb-3">
|
<div class="text-center mb-3">
|
||||||
<button type="button" class="btn btn-danger" @click="testHttpRequest(index)">
|
<button type="button" class="btn btn-danger" @click="testHttpJsonRequest(index)">
|
||||||
{{ $t('powermeteradmin.testHttpRequest') }}
|
{{ $t('powermeteradmin.testHttpJsonRequest') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BootstrapAlert v-model="testHttpRequestAlert[index].show" dismissible :variant="testHttpRequestAlert[index].type">
|
<BootstrapAlert v-model="testHttpJsonRequestAlert[index].show" dismissible :variant="testHttpJsonRequestAlert[index].type">
|
||||||
{{ testHttpRequestAlert[index].message }}
|
{{ testHttpJsonRequestAlert[index].message }}
|
||||||
</BootstrapAlert>
|
</BootstrapAlert>
|
||||||
</div>
|
</div>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="powerMeterConfigList.source === 6">
|
<div v-if="powerMeterConfigList.source === 6">
|
||||||
<CardElement :text="$t('powermeteradmin.TIBBER')"
|
<CardElement :text="$t('powermeteradmin.HTTP_SML')"
|
||||||
textVariant="text-bg-primary"
|
textVariant="text-bg-primary"
|
||||||
add-space>
|
add-space>
|
||||||
|
|
||||||
<InputElement :label="$t('powermeteradmin.httpUrl')"
|
<HttpRequestSettings :cfg="powerMeterConfigList.http_sml.http_request" />
|
||||||
v-model="powerMeterConfigList.tibber.url"
|
|
||||||
type="text"
|
|
||||||
maxlength="1024"
|
|
||||||
placeholder="http://admin:supersecret@mypowermeter.home/status"
|
|
||||||
prefix="GET "
|
|
||||||
:tooltip="$t('powermeteradmin.httpUrlDescription')" />
|
|
||||||
|
|
||||||
<InputElement :label="$t('powermeteradmin.httpUsername')"
|
|
||||||
v-model="powerMeterConfigList.tibber.username"
|
|
||||||
type="text" maxlength="64"/>
|
|
||||||
|
|
||||||
<InputElement :label="$t('powermeteradmin.httpPassword')"
|
|
||||||
v-model="powerMeterConfigList.tibber.password"
|
|
||||||
type="password" maxlength="64"/>
|
|
||||||
|
|
||||||
<InputElement :label="$t('powermeteradmin.httpTimeout')"
|
|
||||||
v-model="powerMeterConfigList.tibber.timeout"
|
|
||||||
type="number"
|
|
||||||
:postfix="$t('powermeteradmin.milliSeconds')" />
|
|
||||||
|
|
||||||
<div class="text-center mb-3">
|
<div class="text-center mb-3">
|
||||||
<button type="button" class="btn btn-danger" @click="testTibberRequest()">
|
<button type="button" class="btn btn-danger" @click="testHttpSmlRequest()">
|
||||||
{{ $t('powermeteradmin.testHttpRequest') }}
|
{{ $t('powermeteradmin.testHttpSmlRequest') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BootstrapAlert v-model="testTibberRequestAlert.show" dismissible :variant="testTibberRequestAlert.type">
|
<BootstrapAlert v-model="testHttpSmlRequestAlert.show" dismissible :variant="testHttpSmlRequestAlert.type">
|
||||||
{{ testTibberRequestAlert.message }}
|
{{ testHttpSmlRequestAlert.message }}
|
||||||
</BootstrapAlert>
|
</BootstrapAlert>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
</div>
|
</div>
|
||||||
@ -263,8 +201,9 @@ import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
|||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import FormFooter from '@/components/FormFooter.vue';
|
import FormFooter from '@/components/FormFooter.vue';
|
||||||
import InputElement from '@/components/InputElement.vue';
|
import InputElement from '@/components/InputElement.vue';
|
||||||
|
import HttpRequestSettings from '@/components/HttpRequestSettings.vue';
|
||||||
import { handleResponse, authHeader } from '@/utils/authentication';
|
import { handleResponse, authHeader } from '@/utils/authentication';
|
||||||
import type { PowerMeterHttpPhaseConfig, PowerMeterConfig } from "@/types/PowerMeterConfig";
|
import type { PowerMeterHttpJsonConfig, PowerMeterConfig } from "@/types/PowerMeterConfig";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@ -272,6 +211,7 @@ export default defineComponent({
|
|||||||
BootstrapAlert,
|
BootstrapAlert,
|
||||||
CardElement,
|
CardElement,
|
||||||
FormFooter,
|
FormFooter,
|
||||||
|
HttpRequestSettings,
|
||||||
InputElement
|
InputElement
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -282,21 +222,21 @@ export default defineComponent({
|
|||||||
{ key: 0, value: this.$t('powermeteradmin.typeMQTT') },
|
{ key: 0, value: this.$t('powermeteradmin.typeMQTT') },
|
||||||
{ key: 1, value: this.$t('powermeteradmin.typeSDM1ph') },
|
{ key: 1, value: this.$t('powermeteradmin.typeSDM1ph') },
|
||||||
{ key: 2, value: this.$t('powermeteradmin.typeSDM3ph') },
|
{ key: 2, value: this.$t('powermeteradmin.typeSDM3ph') },
|
||||||
{ key: 3, value: this.$t('powermeteradmin.typeHTTP') },
|
{ key: 3, value: this.$t('powermeteradmin.typeHTTP_JSON') },
|
||||||
{ key: 4, value: this.$t('powermeteradmin.typeSML') },
|
{ key: 4, value: this.$t('powermeteradmin.typeSML') },
|
||||||
{ key: 5, value: this.$t('powermeteradmin.typeSMAHM2') },
|
{ key: 5, value: this.$t('powermeteradmin.typeSMAHM2') },
|
||||||
{ key: 6, value: this.$t('powermeteradmin.typeTIBBER') },
|
{ key: 6, value: this.$t('powermeteradmin.typeHTTP_SML') },
|
||||||
],
|
],
|
||||||
powerMeterAuthList: [
|
unitTypeList: [
|
||||||
{ key: 0, value: "None" },
|
{ key: 1, value: "mW" },
|
||||||
{ key: 1, value: "Basic" },
|
{ key: 0, value: "W" },
|
||||||
{ key: 2, value: "Digest" },
|
{ key: 2, value: "kW" },
|
||||||
],
|
],
|
||||||
alertMessage: "",
|
alertMessage: "",
|
||||||
alertType: "info",
|
alertType: "info",
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
testHttpRequestAlert: [{message: "", type: "", show: false}] as { message: string; type: string; show: boolean; }[],
|
testHttpJsonRequestAlert: [{message: "", type: "", show: false}] as { message: string; type: string; show: boolean; }[],
|
||||||
testTibberRequestAlert: {message: "", type: "", show: false} as { message: string; type: string; show: boolean; }
|
testHttpSmlRequestAlert: {message: "", type: "", show: false} as { message: string; type: string; show: boolean; }
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@ -311,8 +251,8 @@ export default defineComponent({
|
|||||||
this.powerMeterConfigList = data;
|
this.powerMeterConfigList = data;
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
|
|
||||||
for (let i = 0; i < this.powerMeterConfigList.http_phases.length; i++) {
|
for (let i = 0; i < this.powerMeterConfigList.http_json.length; i++) {
|
||||||
this.testHttpRequestAlert.push({
|
this.testHttpJsonRequestAlert.push({
|
||||||
message: "",
|
message: "",
|
||||||
type: "",
|
type: "",
|
||||||
show: false,
|
show: false,
|
||||||
@ -341,27 +281,27 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
testHttpRequest(index: number) {
|
testHttpJsonRequest(index: number) {
|
||||||
let phaseConfig:PowerMeterHttpPhaseConfig;
|
let valueConfig:PowerMeterHttpJsonConfig;
|
||||||
|
|
||||||
if (this.powerMeterConfigList.http_individual_requests) {
|
if (this.powerMeterConfigList.http_individual_requests) {
|
||||||
phaseConfig = this.powerMeterConfigList.http_phases[index];
|
valueConfig = this.powerMeterConfigList.http_json[index];
|
||||||
} else {
|
} else {
|
||||||
phaseConfig = { ...this.powerMeterConfigList.http_phases[0] };
|
valueConfig = { ...this.powerMeterConfigList.http_json[0] };
|
||||||
phaseConfig.index = this.powerMeterConfigList.http_phases[index].index;
|
valueConfig.index = this.powerMeterConfigList.http_json[index].index;
|
||||||
phaseConfig.json_path = this.powerMeterConfigList.http_phases[index].json_path;
|
valueConfig.json_path = this.powerMeterConfigList.http_json[index].json_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.testHttpRequestAlert[index] = {
|
this.testHttpJsonRequestAlert[index] = {
|
||||||
message: "Sending HTTP request...",
|
message: "Sending HTTP request...",
|
||||||
type: "info",
|
type: "info",
|
||||||
show: true,
|
show: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", JSON.stringify(phaseConfig));
|
formData.append("data", JSON.stringify(valueConfig));
|
||||||
|
|
||||||
fetch("/api/powermeter/testhttprequest", {
|
fetch("/api/powermeter/testhttpjsonrequest", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
@ -369,7 +309,7 @@ export default defineComponent({
|
|||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then(
|
||||||
(response) => {
|
(response) => {
|
||||||
this.testHttpRequestAlert[index] = {
|
this.testHttpJsonRequestAlert[index] = {
|
||||||
message: response.message,
|
message: response.message,
|
||||||
type: response.type,
|
type: response.type,
|
||||||
show: true,
|
show: true,
|
||||||
@ -377,17 +317,17 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
testTibberRequest() {
|
testHttpSmlRequest() {
|
||||||
this.testTibberRequestAlert = {
|
this.testHttpSmlRequestAlert = {
|
||||||
message: "Sending Tibber request...",
|
message: "Sending HTTP SML request...",
|
||||||
type: "info",
|
type: "info",
|
||||||
show: true,
|
show: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", JSON.stringify(this.powerMeterConfigList.tibber));
|
formData.append("data", JSON.stringify(this.powerMeterConfigList.http_sml));
|
||||||
|
|
||||||
fetch("/api/powermeter/testtibberrequest", {
|
fetch("/api/powermeter/testhttpsmlrequest", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
@ -395,7 +335,7 @@ export default defineComponent({
|
|||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then(
|
||||||
(response) => {
|
(response) => {
|
||||||
this.testTibberRequestAlert = {
|
this.testHttpSmlRequestAlert = {
|
||||||
message: response.message,
|
message: response.message,
|
||||||
type: response.type,
|
type: response.type,
|
||||||
show: true,
|
show: true,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user