Feature: support Tibber bridge as power meter interface
This commit is contained in:
parent
83c59d7811
commit
8ec1695d1b
@ -78,6 +78,14 @@ struct POWERMETER_HTTP_PHASE_CONFIG_T {
|
||||
};
|
||||
using PowerMeterHttpConfig = struct POWERMETER_HTTP_PHASE_CONFIG_T;
|
||||
|
||||
struct POWERMETER_TIBBER_CONFIG_T {
|
||||
char Url[POWERMETER_MAX_HTTP_URL_STRLEN + 1];
|
||||
char Username[POWERMETER_MAX_USERNAME_STRLEN + 1];
|
||||
char Password[POWERMETER_MAX_USERNAME_STRLEN + 1];
|
||||
uint16_t Timeout;
|
||||
};
|
||||
using PowerMeterTibberConfig = struct POWERMETER_TIBBER_CONFIG_T;
|
||||
|
||||
struct CONFIG_T {
|
||||
struct {
|
||||
uint32_t Version;
|
||||
@ -200,6 +208,7 @@ struct CONFIG_T {
|
||||
uint32_t HttpInterval;
|
||||
bool HttpIndividualRequests;
|
||||
PowerMeterHttpConfig Http_Phase[POWERMETER_MAX_PHASES];
|
||||
PowerMeterTibberConfig Tibber;
|
||||
} PowerMeter;
|
||||
|
||||
struct {
|
||||
|
||||
@ -26,13 +26,19 @@ public:
|
||||
SDM3PH = 2,
|
||||
HTTP = 3,
|
||||
SML = 4,
|
||||
SMAHM2 = 5
|
||||
SMAHM2 = 5,
|
||||
TIBBER = 6
|
||||
};
|
||||
void init(Scheduler& scheduler);
|
||||
float getPowerTotal(bool forceUpdate = true);
|
||||
uint32_t getLastPowerMeterUpdate();
|
||||
bool isDataValid();
|
||||
|
||||
const std::list<OBISHandler> smlHandlerList{
|
||||
{{0x01, 0x00, 0x10, 0x07, 0x00, 0xff}, &smlOBISW, &_powerMeter1Power},
|
||||
{{0x01, 0x00, 0x01, 0x08, 0x00, 0xff}, &smlOBISWh, &_powerMeterImport},
|
||||
{{0x01, 0x00, 0x02, 0x08, 0x00, 0xff}, &smlOBISWh, &_powerMeterExport}
|
||||
};
|
||||
private:
|
||||
void loop();
|
||||
void mqtt();
|
||||
@ -68,11 +74,6 @@ private:
|
||||
void readPowerMeter();
|
||||
|
||||
bool smlReadLoop();
|
||||
const std::list<OBISHandler> smlHandlerList{
|
||||
{{0x01, 0x00, 0x10, 0x07, 0x00, 0xff}, &smlOBISW, &_powerMeter1Power},
|
||||
{{0x01, 0x00, 0x01, 0x08, 0x00, 0xff}, &smlOBISWh, &_powerMeterImport},
|
||||
{{0x01, 0x00, 0x02, 0x08, 0x00, 0xff}, &smlOBISWh, &_powerMeterExport}
|
||||
};
|
||||
};
|
||||
|
||||
extern PowerMeterClass PowerMeter;
|
||||
|
||||
23
include/TibberPowerMeter.h
Normal file
23
include/TibberPowerMeter.h
Normal file
@ -0,0 +1,23 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <Arduino.h>
|
||||
#include <HTTPClient.h>
|
||||
#include "Configuration.h"
|
||||
|
||||
class TibberPowerMeterClass {
|
||||
public:
|
||||
bool updateValues();
|
||||
char tibberPowerMeterError[256];
|
||||
bool query(PowerMeterTibberConfig const& config);
|
||||
|
||||
private:
|
||||
HTTPClient httpClient;
|
||||
String httpResponse;
|
||||
bool httpRequest(WiFiClient &wifiClient, const String& host, uint16_t port, const String& uri, bool https, PowerMeterTibberConfig const& config);
|
||||
bool extractUrlComponents(String url, String& _protocol, String& _hostname, String& _uri, uint16_t& uint16_t, String& _base64Authorization);
|
||||
void prepareRequest(uint32_t timeout);
|
||||
};
|
||||
|
||||
extern TibberPowerMeterClass TibberPowerMeter;
|
||||
@ -15,7 +15,9 @@ private:
|
||||
void onAdminGet(AsyncWebServerRequest* request);
|
||||
void onAdminPost(AsyncWebServerRequest* request);
|
||||
void decodeJsonPhaseConfig(JsonObject const& json, PowerMeterHttpConfig& config) const;
|
||||
void decodeJsonTibberConfig(JsonObject const& json, PowerMeterTibberConfig& config) const;
|
||||
void onTestHttpRequest(AsyncWebServerRequest* request);
|
||||
void onTestTibberRequest(AsyncWebServerRequest* request);
|
||||
|
||||
AsyncWebServer* _server;
|
||||
};
|
||||
|
||||
@ -159,6 +159,12 @@ bool ConfigurationClass::write()
|
||||
powermeter["sdmaddress"] = config.PowerMeter.SdmAddress;
|
||||
powermeter["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests;
|
||||
|
||||
JsonObject tibber = powermeter["tibber"].to<JsonObject>();
|
||||
tibber["url"] = config.PowerMeter.Tibber.Url;
|
||||
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>();
|
||||
for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) {
|
||||
JsonObject powermeter_phase = powermeter_http_phases.add<JsonObject>();
|
||||
@ -420,6 +426,12 @@ bool ConfigurationClass::read()
|
||||
config.PowerMeter.SdmAddress = powermeter["sdmaddress"] | POWERMETER_SDMADDRESS;
|
||||
config.PowerMeter.HttpIndividualRequests = powermeter["http_individual_requests"] | false;
|
||||
|
||||
JsonObject tibber = powermeter["tibber"];
|
||||
strlcpy(config.PowerMeter.Tibber.Url, tibber["url"] | "", sizeof(config.PowerMeter.Tibber.Url));
|
||||
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"];
|
||||
for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) {
|
||||
JsonObject powermeter_phase = powermeter_http_phases[i].as<JsonObject>();
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include "Configuration.h"
|
||||
#include "PinMapping.h"
|
||||
#include "HttpPowerMeter.h"
|
||||
#include "TibberPowerMeter.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include "MessageOutput.h"
|
||||
@ -96,6 +97,9 @@ void PowerMeterClass::init(Scheduler& scheduler)
|
||||
case Source::SMAHM2:
|
||||
SMA_HM.init(scheduler, config.PowerMeter.VerboseLogging);
|
||||
break;
|
||||
|
||||
case Source::TIBBER:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,6 +278,11 @@ void PowerMeterClass::readPowerMeter()
|
||||
_powerMeter3Power = SMA_HM.getPowerL3();
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
else if (configuredSource == Source::TIBBER) {
|
||||
if (TibberPowerMeter.updateValues()) {
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PowerMeterClass::smlReadLoop()
|
||||
|
||||
188
src/TibberPowerMeter.cpp
Normal file
188
src/TibberPowerMeter.cpp
Normal file
@ -0,0 +1,188 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "Configuration.h"
|
||||
#include "TibberPowerMeter.h"
|
||||
#include "MessageOutput.h"
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <base64.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <PowerMeter.h>
|
||||
|
||||
bool TibberPowerMeterClass::updateValues()
|
||||
{
|
||||
auto const& config = Configuration.get();
|
||||
|
||||
auto const& tibberConfig = config.PowerMeter.Tibber;
|
||||
|
||||
if (!query(tibberConfig)) {
|
||||
MessageOutput.printf("[TibberPowerMeter] Getting the power of tibber failed.\r\n");
|
||||
MessageOutput.printf("%s\r\n", tibberPowerMeterError);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TibberPowerMeterClass::query(PowerMeterTibberConfig const& config)
|
||||
{
|
||||
//hostByName in WiFiGeneric fails to resolve local names. issue described in
|
||||
//https://github.com/espressif/arduino-esp32/issues/3822
|
||||
//and in depth analyzed in https://github.com/espressif/esp-idf/issues/2507#issuecomment-761836300
|
||||
//in conclusion: we cannot rely on httpClient.begin(*wifiClient, url) to resolve IP adresses.
|
||||
//have to do it manually here. Feels Hacky...
|
||||
String protocol;
|
||||
String host;
|
||||
String uri;
|
||||
String base64Authorization;
|
||||
uint16_t port;
|
||||
extractUrlComponents(config.Url, protocol, host, uri, port, base64Authorization);
|
||||
|
||||
IPAddress ipaddr((uint32_t)0);
|
||||
//first check if "host" is already an IP adress
|
||||
if (!ipaddr.fromString(host))
|
||||
{
|
||||
//"host"" is not an IP address so try to resolve the IP adress
|
||||
//first try locally via mDNS, then via DNS. WiFiGeneric::hostByName() will spam the console if done the otherway around.
|
||||
const bool mdnsEnabled = Configuration.get().Mdns.Enabled;
|
||||
if (!mdnsEnabled) {
|
||||
snprintf_P(tibberPowerMeterError, sizeof(tibberPowerMeterError), PSTR("Error resolving host %s via DNS, try to enable mDNS in Network Settings"), host.c_str());
|
||||
//ensure we try resolving via DNS even if mDNS is disabled
|
||||
if(!WiFiGenericClass::hostByName(host.c_str(), ipaddr)){
|
||||
snprintf_P(tibberPowerMeterError, sizeof(tibberPowerMeterError), PSTR("Error resolving host %s via DNS"), host.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ipaddr = MDNS.queryHost(host);
|
||||
if (ipaddr == INADDR_NONE){
|
||||
snprintf_P(tibberPowerMeterError, sizeof(tibberPowerMeterError), PSTR("Error resolving host %s via mDNS"), host.c_str());
|
||||
//when we cannot find local server via mDNS, try resolving via DNS
|
||||
if(!WiFiGenericClass::hostByName(host.c_str(), ipaddr)){
|
||||
snprintf_P(tibberPowerMeterError, sizeof(tibberPowerMeterError), PSTR("Error resolving host %s via DNS"), host.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// secureWifiClient MUST be created before HTTPClient
|
||||
// see discussion: https://github.com/helgeerbe/OpenDTU-OnBattery/issues/381
|
||||
std::unique_ptr<WiFiClient> wifiClient;
|
||||
|
||||
bool https = protocol == "https";
|
||||
if (https) {
|
||||
auto secureWifiClient = std::make_unique<WiFiClientSecure>();
|
||||
secureWifiClient->setInsecure();
|
||||
wifiClient = std::move(secureWifiClient);
|
||||
} else {
|
||||
wifiClient = std::make_unique<WiFiClient>();
|
||||
}
|
||||
|
||||
return httpRequest(*wifiClient, ipaddr.toString(), port, uri, https, config);
|
||||
}
|
||||
|
||||
bool TibberPowerMeterClass::httpRequest(WiFiClient &wifiClient, const String& host, uint16_t port, const String& uri, bool https, PowerMeterTibberConfig const& config)
|
||||
{
|
||||
if(!httpClient.begin(wifiClient, host, port, uri, https)){
|
||||
snprintf_P(tibberPowerMeterError, sizeof(tibberPowerMeterError), PSTR("httpClient.begin() failed for %s://%s"), (https ? "https" : "http"), host.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
prepareRequest(config.Timeout);
|
||||
|
||||
String authString = config.Username;
|
||||
authString += ":";
|
||||
authString += config.Password;
|
||||
String auth = "Basic ";
|
||||
auth.concat(base64::encode(authString));
|
||||
httpClient.addHeader("Authorization", auth);
|
||||
|
||||
int httpCode = httpClient.GET();
|
||||
|
||||
if (httpCode <= 0) {
|
||||
snprintf_P(tibberPowerMeterError, sizeof(tibberPowerMeterError), PSTR("HTTP Error %s"), httpClient.errorToString(httpCode).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (httpCode != HTTP_CODE_OK) {
|
||||
snprintf_P(tibberPowerMeterError, sizeof(tibberPowerMeterError), PSTR("Bad HTTP code: %d"), httpCode);
|
||||
return false;
|
||||
}
|
||||
|
||||
while (httpClient.getStream().available()) {
|
||||
double readVal = 0;
|
||||
unsigned char smlCurrentChar = httpClient.getStream().read();
|
||||
sml_states_t smlCurrentState = smlState(smlCurrentChar);
|
||||
if (smlCurrentState == SML_LISTEND) {
|
||||
for (auto& handler: PowerMeter.smlHandlerList) {
|
||||
if (smlOBISCheck(handler.OBIS)) {
|
||||
handler.Fn(readVal);
|
||||
*handler.Arg = readVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
httpClient.end();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//extract url component as done by httpClient::begin(String url, const char* expectedProtocol) https://github.com/espressif/arduino-esp32/blob/da6325dd7e8e152094b19fe63190907f38ef1ff0/libraries/HTTPClient/src/HTTPClient.cpp#L250
|
||||
bool TibberPowerMeterClass::extractUrlComponents(String url, String& _protocol, String& _host, String& _uri, uint16_t& _port, String& _base64Authorization)
|
||||
{
|
||||
// check for : (http: or https:
|
||||
int index = url.indexOf(':');
|
||||
if(index < 0) {
|
||||
snprintf_P(tibberPowerMeterError, sizeof(tibberPowerMeterError), PSTR("failed to parse protocol"));
|
||||
return false;
|
||||
}
|
||||
|
||||
_protocol = url.substring(0, index);
|
||||
|
||||
//initialize port to default values for http or https.
|
||||
//port will be overwritten below in case port is explicitly defined
|
||||
_port = (_protocol == "https" ? 443 : 80);
|
||||
|
||||
url.remove(0, (index + 3)); // remove http:// or https://
|
||||
|
||||
index = url.indexOf('/');
|
||||
if (index == -1) {
|
||||
index = url.length();
|
||||
url += '/';
|
||||
}
|
||||
String host = url.substring(0, index);
|
||||
url.remove(0, index); // remove host part
|
||||
|
||||
// get Authorization
|
||||
index = host.indexOf('@');
|
||||
if(index >= 0) {
|
||||
// auth info
|
||||
String auth = host.substring(0, index);
|
||||
host.remove(0, index + 1); // remove auth part including @
|
||||
_base64Authorization = base64::encode(auth);
|
||||
}
|
||||
|
||||
// get port
|
||||
index = host.indexOf(':');
|
||||
String the_host;
|
||||
if(index >= 0) {
|
||||
the_host = host.substring(0, index); // hostname
|
||||
host.remove(0, (index + 1)); // remove hostname + :
|
||||
_port = host.toInt(); // get port
|
||||
} else {
|
||||
the_host = host;
|
||||
}
|
||||
|
||||
_host = the_host;
|
||||
_uri = url;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TibberPowerMeterClass::prepareRequest(uint32_t timeout) {
|
||||
httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
httpClient.setUserAgent("OpenDTU-OnBattery");
|
||||
httpClient.setConnectTimeout(timeout);
|
||||
httpClient.setTimeout(timeout);
|
||||
httpClient.addHeader("Content-Type", "application/json");
|
||||
httpClient.addHeader("Accept", "application/json");
|
||||
}
|
||||
|
||||
TibberPowerMeterClass TibberPowerMeter;
|
||||
@ -13,6 +13,7 @@
|
||||
#include "PowerLimiter.h"
|
||||
#include "PowerMeter.h"
|
||||
#include "HttpPowerMeter.h"
|
||||
#include "TibberPowerMeter.h"
|
||||
#include "WebApi.h"
|
||||
#include "helper.h"
|
||||
|
||||
@ -26,6 +27,7 @@ void WebApiPowerMeterClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
||||
_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/testhttprequest", HTTP_POST, std::bind(&WebApiPowerMeterClass::onTestHttpRequest, this, _1));
|
||||
_server->on("/api/powermeter/testtibberrequest", HTTP_POST, std::bind(&WebApiPowerMeterClass::onTestTibberRequest, this, _1));
|
||||
}
|
||||
|
||||
void WebApiPowerMeterClass::decodeJsonPhaseConfig(JsonObject const& json, PowerMeterHttpConfig& config) const
|
||||
@ -43,6 +45,14 @@ void WebApiPowerMeterClass::decodeJsonPhaseConfig(JsonObject const& json, PowerM
|
||||
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)
|
||||
{
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
@ -60,6 +70,12 @@ void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request)
|
||||
root["sdmaddress"] = config.PowerMeter.SdmAddress;
|
||||
root["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests;
|
||||
|
||||
auto tibber = root["tibber"].to<JsonObject>();
|
||||
tibber["url"] = String(config.PowerMeter.Tibber.Url);
|
||||
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>();
|
||||
|
||||
for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) {
|
||||
@ -158,6 +174,34 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
||||
}
|
||||
}
|
||||
|
||||
if (static_cast<PowerMeterClass::Source>(root["source"].as<uint8_t>()) == PowerMeterClass::Source::TIBBER) {
|
||||
JsonObject tibber = root["tibber"];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
config.PowerMeter.Enabled = root["enabled"].as<bool>();
|
||||
config.PowerMeter.VerboseLogging = root["verbose_logging"].as<bool>();
|
||||
@ -170,6 +214,8 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
||||
config.PowerMeter.SdmAddress = root["sdmaddress"].as<uint8_t>();
|
||||
config.PowerMeter.HttpIndividualRequests = root["http_individual_requests"].as<bool>();
|
||||
|
||||
decodeJsonTibberConfig(root["tibber"].as<JsonObject>(), config.PowerMeter.Tibber);
|
||||
|
||||
JsonArray http_phases = root["http_phases"];
|
||||
for (uint8_t i = 0; i < http_phases.size(); i++) {
|
||||
decodeJsonPhaseConfig(http_phases[i].as<JsonObject>(), config.PowerMeter.Http_Phase[i]);
|
||||
@ -228,3 +274,42 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
||||
asyncJsonResponse->setLength();
|
||||
request->send(asyncJsonResponse);
|
||||
}
|
||||
|
||||
void WebApiPowerMeterClass::onTestTibberRequest(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentials(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncJsonResponse* asyncJsonResponse = new AsyncJsonResponse();
|
||||
JsonDocument root;
|
||||
if (!WebApi.parseRequestData(request, asyncJsonResponse, root)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& retMsg = asyncJsonResponse->getRoot();
|
||||
|
||||
if (!root.containsKey("url") || !root.containsKey("username") || !root.containsKey("password")
|
||||
|| !root.containsKey("timeout")) {
|
||||
retMsg["message"] = "Missing fields!";
|
||||
asyncJsonResponse->setLength();
|
||||
request->send(asyncJsonResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
char response[256];
|
||||
|
||||
PowerMeterTibberConfig tibberConfig;
|
||||
decodeJsonTibberConfig(root.as<JsonObject>(), tibberConfig);
|
||||
if (TibberPowerMeter.query(tibberConfig)) {
|
||||
retMsg["type"] = "success";
|
||||
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", PowerMeter.getPowerTotal());
|
||||
} else {
|
||||
snprintf_P(response, sizeof(response), "%s", TibberPowerMeter.tibberPowerMeterError);
|
||||
}
|
||||
|
||||
retMsg["message"] = response;
|
||||
asyncJsonResponse->setLength();
|
||||
request->send(asyncJsonResponse);
|
||||
}
|
||||
|
||||
@ -563,6 +563,7 @@
|
||||
"typeHTTP": "HTTP(S) + JSON",
|
||||
"typeSML": "SML (OBIS 16.7.0)",
|
||||
"typeSMAHM2": "SMA Homemanager 2.0",
|
||||
"typeTIBBER": "Tibber Pulse (via Tibber Bridge)",
|
||||
"MqttTopicPowerMeter1": "MQTT topic - Stromzähler #1",
|
||||
"MqttTopicPowerMeter2": "MQTT topic - Stromzähler #2 (Optional)",
|
||||
"MqttTopicPowerMeter3": "MQTT topic - Stromzähler #3 (Optional)",
|
||||
@ -587,7 +588,8 @@
|
||||
"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.",
|
||||
"httpTimeout": "Timeout",
|
||||
"testHttpRequest": "Testen"
|
||||
"testHttpRequest": "Testen",
|
||||
"TIBBER": "Tibber Pulse (via Tibber Bridge) - Konfiguration"
|
||||
},
|
||||
"powerlimiteradmin": {
|
||||
"PowerLimiterSettings": "Dynamic Power Limiter Einstellungen",
|
||||
|
||||
@ -565,6 +565,7 @@
|
||||
"typeHTTP": "HTTP(s) + JSON",
|
||||
"typeSML": "SML (OBIS 16.7.0)",
|
||||
"typeSMAHM2": "SMA Homemanager 2.0",
|
||||
"typeTIBBER": "Tibber Pulse (via Tibber Bridge)",
|
||||
"MqttTopicPowerMeter1": "MQTT topic - Power meter #1",
|
||||
"MqttTopicPowerMeter2": "MQTT topic - Power meter #2",
|
||||
"MqttTopicPowerMeter3": "MQTT topic - Power meter #3",
|
||||
@ -593,7 +594,8 @@
|
||||
"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",
|
||||
"testHttpRequest": "Run test",
|
||||
"milliSeconds": "ms"
|
||||
"milliSeconds": "ms",
|
||||
"TIBBER": "Tibber Pulse (via Tibber Bridge) - Configuration"
|
||||
},
|
||||
"powerlimiteradmin": {
|
||||
"PowerLimiterSettings": "Dynamic Power Limiter Settings",
|
||||
|
||||
@ -13,6 +13,13 @@ export interface PowerMeterHttpPhaseConfig {
|
||||
sign_inverted: boolean;
|
||||
}
|
||||
|
||||
export interface PowerMeterTibberConfig {
|
||||
url: string;
|
||||
username: string;
|
||||
password: string;
|
||||
timeout: number;
|
||||
}
|
||||
|
||||
export interface PowerMeterConfig {
|
||||
enabled: boolean;
|
||||
verbose_logging: boolean;
|
||||
@ -25,4 +32,5 @@ export interface PowerMeterConfig {
|
||||
sdmaddress: number;
|
||||
http_individual_requests: boolean;
|
||||
http_phases: Array<PowerMeterHttpPhaseConfig>;
|
||||
tibber: PowerMeterTibberConfig;
|
||||
}
|
||||
|
||||
@ -220,6 +220,44 @@
|
||||
</div>
|
||||
</CardElement>
|
||||
</div>
|
||||
|
||||
<div v-if="powerMeterConfigList.source === 6">
|
||||
<CardElement :text="$t('powermeteradmin.TIBBER')"
|
||||
textVariant="text-bg-primary"
|
||||
add-space>
|
||||
|
||||
<InputElement :label="$t('powermeteradmin.httpUrl')"
|
||||
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">
|
||||
<button type="button" class="btn btn-danger" @click="testTibberRequest()">
|
||||
{{ $t('powermeteradmin.testHttpRequest') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<BootstrapAlert v-model="testTibberRequestAlert.show" dismissible :variant="testTibberRequestAlert.type">
|
||||
{{ testTibberRequestAlert.message }}
|
||||
</BootstrapAlert>
|
||||
</CardElement>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormFooter @reload="getPowerMeterConfig"/>
|
||||
@ -257,6 +295,7 @@ export default defineComponent({
|
||||
{ key: 3, value: this.$t('powermeteradmin.typeHTTP') },
|
||||
{ key: 4, value: this.$t('powermeteradmin.typeSML') },
|
||||
{ key: 5, value: this.$t('powermeteradmin.typeSMAHM2') },
|
||||
{ key: 6, value: this.$t('powermeteradmin.typeTIBBER') },
|
||||
],
|
||||
powerMeterAuthList: [
|
||||
{ key: 0, value: "None" },
|
||||
@ -266,7 +305,8 @@ export default defineComponent({
|
||||
alertMessage: "",
|
||||
alertType: "info",
|
||||
showAlert: false,
|
||||
testHttpRequestAlert: [{message: "", type: "", show: false}] as { message: string; type: string; show: boolean; }[]
|
||||
testHttpRequestAlert: [{message: "", type: "", show: false}] as { message: string; type: string; show: boolean; }[],
|
||||
testTibberRequestAlert: {message: "", type: "", show: false} as { message: string; type: string; show: boolean; }
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@ -347,6 +387,32 @@ export default defineComponent({
|
||||
}
|
||||
)
|
||||
},
|
||||
testTibberRequest() {
|
||||
this.testTibberRequestAlert = {
|
||||
message: "Sending Tibber request...",
|
||||
type: "info",
|
||||
show: true,
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("data", JSON.stringify(this.powerMeterConfigList.tibber));
|
||||
|
||||
fetch("/api/powermeter/testtibberrequest", {
|
||||
method: "POST",
|
||||
headers: authHeader(),
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(response) => {
|
||||
this.testTibberRequestAlert = {
|
||||
message: response.message,
|
||||
type: response.type,
|
||||
show: true,
|
||||
};
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user