Feature: support Tibber bridge as power meter interface

This commit is contained in:
Marvin Carstensen 2024-04-23 19:17:01 +02:00 committed by Bernhard Kirchen
parent 83c59d7811
commit 8ec1695d1b
12 changed files with 416 additions and 9 deletions

View File

@ -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 {

View File

@ -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;

View 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;

View File

@ -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;
};

View File

@ -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>();

View File

@ -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
View 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;

View File

@ -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);
}

View File

@ -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",

View File

@ -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",

View File

@ -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;
}

View File

@ -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>