powermeter refactor: split providers into their own classes
it is important to separate the capabilities of each power meter provider into their own class/source file, as the providers work fundamentally different and their implementations must not be intermangled, which made maintenance and improvements a nightmare in the past.
This commit is contained in:
parent
8ec1695d1b
commit
2397e5cdf5
@ -1,79 +1,27 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "Configuration.h"
|
||||
#include <espMqttClient.h>
|
||||
#include <Arduino.h>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include "SDM.h"
|
||||
#include "sml.h"
|
||||
#include "PowerMeterProvider.h"
|
||||
#include <TaskSchedulerDeclarations.h>
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
typedef struct {
|
||||
const unsigned char OBIS[6];
|
||||
void (*Fn)(double&);
|
||||
float* Arg;
|
||||
} OBISHandler;
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
class PowerMeterClass {
|
||||
public:
|
||||
enum class Source : unsigned {
|
||||
MQTT = 0,
|
||||
SDM1PH = 1,
|
||||
SDM3PH = 2,
|
||||
HTTP = 3,
|
||||
SML = 4,
|
||||
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}
|
||||
};
|
||||
void updateSettings();
|
||||
|
||||
float getPowerTotal() const;
|
||||
uint32_t getLastUpdate() const;
|
||||
bool isDataValid() const;
|
||||
|
||||
private:
|
||||
void loop();
|
||||
void mqtt();
|
||||
|
||||
void onMqttMessage(const espMqttClientTypes::MessageProperties& properties,
|
||||
const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
|
||||
|
||||
Task _loopTask;
|
||||
|
||||
bool _verboseLogging = true;
|
||||
uint32_t _lastPowerMeterCheck;
|
||||
// Used in Power limiter for safety check
|
||||
uint32_t _lastPowerMeterUpdate;
|
||||
|
||||
float _powerMeter1Power = 0.0;
|
||||
float _powerMeter2Power = 0.0;
|
||||
float _powerMeter3Power = 0.0;
|
||||
float _powerMeter1Voltage = 0.0;
|
||||
float _powerMeter2Voltage = 0.0;
|
||||
float _powerMeter3Voltage = 0.0;
|
||||
float _powerMeterImport = 0.0;
|
||||
float _powerMeterExport = 0.0;
|
||||
|
||||
std::map<String, float*> _mqttSubscriptions;
|
||||
|
||||
mutable std::mutex _mutex;
|
||||
|
||||
static char constexpr _sdmSerialPortOwner[] = "SDM power meter";
|
||||
std::unique_ptr<HardwareSerial> _upSdmSerial = nullptr;
|
||||
std::unique_ptr<SDM> _upSdm = nullptr;
|
||||
std::unique_ptr<SoftwareSerial> _upSmlSerial = nullptr;
|
||||
|
||||
void readPowerMeter();
|
||||
|
||||
bool smlReadLoop();
|
||||
std::unique_ptr<PowerMeterProvider> _upProvider = nullptr;
|
||||
};
|
||||
|
||||
extern PowerMeterClass PowerMeter;
|
||||
|
||||
@ -5,22 +5,29 @@
|
||||
#include <Arduino.h>
|
||||
#include <HTTPClient.h>
|
||||
#include "Configuration.h"
|
||||
#include "PowerMeterProvider.h"
|
||||
|
||||
using Auth_t = PowerMeterHttpConfig::Auth;
|
||||
using Unit_t = PowerMeterHttpConfig::Unit;
|
||||
|
||||
class HttpPowerMeterClass {
|
||||
class PowerMeterHttpJson : public PowerMeterProvider {
|
||||
public:
|
||||
void init();
|
||||
bool updateValues();
|
||||
float getPower(int8_t phase);
|
||||
char httpPowerMeterError[256];
|
||||
bool init() final { return true; }
|
||||
void deinit() final { }
|
||||
void loop() final;
|
||||
float getPowerTotal() const final;
|
||||
void doMqttPublish() const final;
|
||||
|
||||
bool queryPhase(int phase, PowerMeterHttpConfig const& config);
|
||||
char httpPowerMeterError[256];
|
||||
|
||||
private:
|
||||
float power[POWERMETER_MAX_PHASES];
|
||||
uint32_t _lastPoll;
|
||||
std::array<float,POWERMETER_MAX_PHASES> _cache;
|
||||
std::array<float,POWERMETER_MAX_PHASES> _powerValues;
|
||||
HTTPClient httpClient;
|
||||
String httpResponse;
|
||||
|
||||
bool httpRequest(int phase, WiFiClient &wifiClient, const String& host, uint16_t port, const String& uri, bool https, PowerMeterHttpConfig const& config);
|
||||
bool extractUrlComponents(String url, String& _protocol, String& _hostname, String& _uri, uint16_t& uint16_t, String& _base64Authorization);
|
||||
String extractParam(String& authReq, const String& param, const char delimit);
|
||||
@ -30,5 +37,3 @@ private:
|
||||
void prepareRequest(uint32_t timeout, const char* httpHeader, const char* httpValue);
|
||||
String sha256(const String& data);
|
||||
};
|
||||
|
||||
extern HttpPowerMeterClass HttpPowerMeter;
|
||||
@ -1,23 +1,46 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <stdint.h>
|
||||
#include <Arduino.h>
|
||||
#include <HTTPClient.h>
|
||||
#include "Configuration.h"
|
||||
#include "PowerMeterProvider.h"
|
||||
#include "sml.h"
|
||||
|
||||
class TibberPowerMeterClass {
|
||||
class PowerMeterHttpSml : public PowerMeterProvider {
|
||||
public:
|
||||
bool init() final { return true; }
|
||||
void deinit() final { }
|
||||
void loop() final;
|
||||
float getPowerTotal() const final;
|
||||
void doMqttPublish() const final;
|
||||
bool updateValues();
|
||||
char tibberPowerMeterError[256];
|
||||
bool query(PowerMeterTibberConfig const& config);
|
||||
|
||||
private:
|
||||
mutable std::mutex _mutex;
|
||||
|
||||
uint32_t _lastPoll = 0;
|
||||
|
||||
float _activePower = 0.0;
|
||||
|
||||
typedef struct {
|
||||
const unsigned char OBIS[6];
|
||||
void (*Fn)(double&);
|
||||
float* Arg;
|
||||
} OBISHandler;
|
||||
|
||||
const std::list<OBISHandler> smlHandlerList{
|
||||
{{0x01, 0x00, 0x10, 0x07, 0x00, 0xff}, &smlOBISW, &_activePower}
|
||||
};
|
||||
|
||||
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;
|
||||
28
include/PowerMeterMqtt.h
Normal file
28
include/PowerMeterMqtt.h
Normal file
@ -0,0 +1,28 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "PowerMeterProvider.h"
|
||||
#include <espMqttClient.h>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
class PowerMeterMqtt : public PowerMeterProvider {
|
||||
public:
|
||||
bool init() final;
|
||||
void deinit() final;
|
||||
void loop() final { }
|
||||
float getPowerTotal() const final;
|
||||
void doMqttPublish() const final;
|
||||
|
||||
private:
|
||||
void onMqttMessage(const espMqttClientTypes::MessageProperties& properties,
|
||||
const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
|
||||
|
||||
float _powerValueOne = 0;
|
||||
float _powerValueTwo = 0;
|
||||
float _powerValueThree = 0;
|
||||
|
||||
std::map<String, float*> _mqttSubscriptions;
|
||||
|
||||
mutable std::mutex _mutex;
|
||||
};
|
||||
46
include/PowerMeterProvider.h
Normal file
46
include/PowerMeterProvider.h
Normal file
@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "Configuration.h"
|
||||
|
||||
class PowerMeterProvider {
|
||||
public:
|
||||
virtual ~PowerMeterProvider() { }
|
||||
|
||||
enum class Type : unsigned {
|
||||
MQTT = 0,
|
||||
SDM1PH = 1,
|
||||
SDM3PH = 2,
|
||||
HTTP = 3,
|
||||
SML = 4,
|
||||
SMAHM2 = 5,
|
||||
TIBBER = 6
|
||||
};
|
||||
|
||||
// returns true if the provider is ready for use, false otherwise
|
||||
virtual bool init() = 0;
|
||||
|
||||
virtual void deinit() = 0;
|
||||
virtual void loop() = 0;
|
||||
virtual float getPowerTotal() const = 0;
|
||||
|
||||
uint32_t getLastUpdate() const { return _lastUpdate; }
|
||||
bool isDataValid() const;
|
||||
void mqttLoop() const;
|
||||
|
||||
protected:
|
||||
PowerMeterProvider() {
|
||||
auto const& config = Configuration.get();
|
||||
_verboseLogging = config.PowerMeter.VerboseLogging;
|
||||
}
|
||||
|
||||
void gotUpdate() { _lastUpdate = millis(); }
|
||||
|
||||
bool _verboseLogging;
|
||||
|
||||
private:
|
||||
virtual void doMqttPublish() const = 0;
|
||||
|
||||
uint32_t _lastUpdate = 0;
|
||||
mutable uint32_t _lastMqttPublish = 0;
|
||||
};
|
||||
33
include/PowerMeterSerialSdm.h
Normal file
33
include/PowerMeterSerialSdm.h
Normal file
@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include "PowerMeterProvider.h"
|
||||
#include "SDM.h"
|
||||
|
||||
class PowerMeterSerialSdm : public PowerMeterProvider {
|
||||
public:
|
||||
bool init() final;
|
||||
void deinit() final;
|
||||
void loop() final;
|
||||
float getPowerTotal() const final;
|
||||
void doMqttPublish() const final;
|
||||
|
||||
private:
|
||||
uint32_t _lastPoll;
|
||||
|
||||
float _phase1Power = 0.0;
|
||||
float _phase2Power = 0.0;
|
||||
float _phase3Power = 0.0;
|
||||
float _phase1Voltage = 0.0;
|
||||
float _phase2Voltage = 0.0;
|
||||
float _phase3Voltage = 0.0;
|
||||
float _energyImport = 0.0;
|
||||
float _energyExport = 0.0;
|
||||
|
||||
mutable std::mutex _mutex;
|
||||
|
||||
static char constexpr _sdmSerialPortOwner[] = "SDM power meter";
|
||||
std::unique_ptr<HardwareSerial> _upSdmSerial = nullptr;
|
||||
std::unique_ptr<SDM> _upSdm = nullptr;
|
||||
};
|
||||
39
include/PowerMeterSerialSml.h
Normal file
39
include/PowerMeterSerialSml.h
Normal file
@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "PowerMeterProvider.h"
|
||||
#include "Configuration.h"
|
||||
#include "sml.h"
|
||||
#include <SoftwareSerial.h>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
|
||||
class PowerMeterSerialSml : public PowerMeterProvider {
|
||||
public:
|
||||
bool init() final;
|
||||
void deinit() final;
|
||||
void loop() final;
|
||||
float getPowerTotal() const final { return _activePower; }
|
||||
void doMqttPublish() const final;
|
||||
|
||||
private:
|
||||
float _activePower = 0.0;
|
||||
float _energyImport = 0.0;
|
||||
float _energyExport = 0.0;
|
||||
|
||||
mutable std::mutex _mutex;
|
||||
|
||||
std::unique_ptr<SoftwareSerial> _upSmlSerial = nullptr;
|
||||
|
||||
typedef struct {
|
||||
const unsigned char OBIS[6];
|
||||
void (*Fn)(double&);
|
||||
float* Arg;
|
||||
} OBISHandler;
|
||||
|
||||
const std::list<OBISHandler> smlHandlerList{
|
||||
{{0x01, 0x00, 0x10, 0x07, 0x00, 0xff}, &smlOBISW, &_activePower},
|
||||
{{0x01, 0x00, 0x01, 0x08, 0x00, 0xff}, &smlOBISWh, &_energyImport},
|
||||
{{0x01, 0x00, 0x02, 0x08, 0x00, 0xff}, &smlOBISWh, &_energyExport}
|
||||
};
|
||||
};
|
||||
@ -5,17 +5,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <TaskSchedulerDeclarations.h>
|
||||
#include "PowerMeterProvider.h"
|
||||
|
||||
class SMA_HMClass {
|
||||
class PowerMeterUdpSmaHomeManager : public PowerMeterProvider {
|
||||
public:
|
||||
void init(Scheduler& scheduler, bool verboseLogging);
|
||||
void loop();
|
||||
void event1();
|
||||
float getPowerTotal() const { return _powerMeterPower; }
|
||||
float getPowerL1() const { return _powerMeterL1; }
|
||||
float getPowerL2() const { return _powerMeterL2; }
|
||||
float getPowerL3() const { return _powerMeterL3; }
|
||||
bool init() final;
|
||||
void deinit() final;
|
||||
void loop() final;
|
||||
float getPowerTotal() const final { return _powerMeterPower; }
|
||||
void doMqttPublish() const final;
|
||||
|
||||
private:
|
||||
void Soutput(int kanal, int index, int art, int tarif,
|
||||
@ -23,14 +21,10 @@ private:
|
||||
|
||||
uint8_t* decodeGroup(uint8_t* offset, uint16_t grouplen);
|
||||
|
||||
bool _verboseLogging = false;
|
||||
float _powerMeterPower = 0.0;
|
||||
float _powerMeterL1 = 0.0;
|
||||
float _powerMeterL2 = 0.0;
|
||||
float _powerMeterL3 = 0.0;
|
||||
uint32_t _previousMillis = 0;
|
||||
uint32_t _serial = 0;
|
||||
Task _loopTask;
|
||||
};
|
||||
|
||||
extern SMA_HMClass SMA_HM;
|
||||
@ -294,7 +294,7 @@ void DisplayGraphicClass::loop()
|
||||
_display->drawBox(0, y, _display->getDisplayWidth(), lineHeight);
|
||||
_display->setDrawColor(1);
|
||||
|
||||
auto acPower = PowerMeter.getPowerTotal(false);
|
||||
auto acPower = PowerMeter.getPowerTotal();
|
||||
if (acPower > 999) {
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_meter_power_kw[_display_language], (acPower / 1000));
|
||||
} else {
|
||||
|
||||
@ -371,12 +371,12 @@ void HuaweiCanClass::loop()
|
||||
}
|
||||
}
|
||||
|
||||
if (PowerMeter.getLastPowerMeterUpdate() > _lastPowerMeterUpdateReceivedMillis &&
|
||||
if (PowerMeter.getLastUpdate() > _lastPowerMeterUpdateReceivedMillis &&
|
||||
_autoPowerEnabledCounter > 0) {
|
||||
// We have received a new PowerMeter value. Also we're _autoPowerEnabled
|
||||
// So we're good to calculate a new limit
|
||||
|
||||
_lastPowerMeterUpdateReceivedMillis = PowerMeter.getLastPowerMeterUpdate();
|
||||
_lastPowerMeterUpdateReceivedMillis = PowerMeter.getLastUpdate();
|
||||
|
||||
// Calculate new power limit
|
||||
float newPowerLimit = -1 * round(PowerMeter.getPowerTotal());
|
||||
|
||||
@ -201,7 +201,7 @@ void PowerLimiterClass::loop()
|
||||
// arrives. this can be the case for readings provided by networked meter
|
||||
// readers, where a packet needs to travel through the network for some
|
||||
// time after the actual measurement was done by the reader.
|
||||
if (PowerMeter.isDataValid() && PowerMeter.getLastPowerMeterUpdate() <= (*_oInverterStatsMillis + 2000)) {
|
||||
if (PowerMeter.isDataValid() && PowerMeter.getLastUpdate() <= (*_oInverterStatsMillis + 2000)) {
|
||||
return announceStatus(Status::PowerMeterPending);
|
||||
}
|
||||
|
||||
|
||||
@ -1,18 +1,12 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "PowerMeter.h"
|
||||
#include "Configuration.h"
|
||||
#include "PinMapping.h"
|
||||
#include "HttpPowerMeter.h"
|
||||
#include "TibberPowerMeter.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "SerialPortManager.h"
|
||||
#include <ctime>
|
||||
#include <SMA_HM.h>
|
||||
#include "PowerMeterHttpJson.h"
|
||||
#include "PowerMeterHttpSml.h"
|
||||
#include "PowerMeterMqtt.h"
|
||||
#include "PowerMeterSerialSdm.h"
|
||||
#include "PowerMeterSerialSml.h"
|
||||
#include "PowerMeterUdpSmaHomeManager.h"
|
||||
|
||||
PowerMeterClass PowerMeter;
|
||||
|
||||
@ -23,285 +17,74 @@ void PowerMeterClass::init(Scheduler& scheduler)
|
||||
_loopTask.setIterations(TASK_FOREVER);
|
||||
_loopTask.enable();
|
||||
|
||||
_lastPowerMeterCheck = 0;
|
||||
_lastPowerMeterUpdate = 0;
|
||||
|
||||
for (auto const& s: _mqttSubscriptions) { MqttSettings.unsubscribe(s.first); }
|
||||
_mqttSubscriptions.clear();
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
if (!config.PowerMeter.Enabled) {
|
||||
return;
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
const PinMapping_t& pin = PinMapping.get();
|
||||
MessageOutput.printf("[PowerMeter] rx = %d, tx = %d, dere = %d\r\n",
|
||||
pin.powermeter_rx, pin.powermeter_tx, pin.powermeter_dere);
|
||||
|
||||
switch(static_cast<Source>(config.PowerMeter.Source)) {
|
||||
case Source::MQTT: {
|
||||
auto subscribe = [this](char const* topic, float* target) {
|
||||
if (strlen(topic) == 0) { return; }
|
||||
MqttSettings.subscribe(topic, 0,
|
||||
std::bind(&PowerMeterClass::onMqttMessage,
|
||||
this, std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3, std::placeholders::_4,
|
||||
std::placeholders::_5, std::placeholders::_6)
|
||||
);
|
||||
_mqttSubscriptions.try_emplace(topic, target);
|
||||
};
|
||||
|
||||
subscribe(config.PowerMeter.MqttTopicPowerMeter1, &_powerMeter1Power);
|
||||
subscribe(config.PowerMeter.MqttTopicPowerMeter2, &_powerMeter2Power);
|
||||
subscribe(config.PowerMeter.MqttTopicPowerMeter3, &_powerMeter3Power);
|
||||
break;
|
||||
}
|
||||
|
||||
case Source::SDM1PH:
|
||||
case Source::SDM3PH: {
|
||||
if (pin.powermeter_rx < 0 || pin.powermeter_tx < 0) {
|
||||
MessageOutput.println("[PowerMeter] invalid pin config for SDM power meter (RX and TX pins must be defined)");
|
||||
return;
|
||||
}
|
||||
|
||||
auto oHwSerialPort = SerialPortManager.allocatePort(_sdmSerialPortOwner);
|
||||
if (!oHwSerialPort) { return; }
|
||||
|
||||
_upSdmSerial = std::make_unique<HardwareSerial>(*oHwSerialPort);
|
||||
_upSdmSerial->end(); // make sure the UART will be re-initialized
|
||||
_upSdm = std::make_unique<SDM>(*_upSdmSerial, 9600, pin.powermeter_dere,
|
||||
SERIAL_8N1, pin.powermeter_rx, pin.powermeter_tx);
|
||||
_upSdm->begin();
|
||||
break;
|
||||
}
|
||||
|
||||
case Source::HTTP:
|
||||
HttpPowerMeter.init();
|
||||
break;
|
||||
|
||||
case Source::SML:
|
||||
if (pin.powermeter_rx < 0) {
|
||||
MessageOutput.println("[PowerMeter] invalid pin config for SML power meter (RX pin must be defined)");
|
||||
return;
|
||||
}
|
||||
|
||||
pinMode(pin.powermeter_rx, INPUT);
|
||||
_upSmlSerial = std::make_unique<SoftwareSerial>();
|
||||
_upSmlSerial->begin(9600, SWSERIAL_8N1, pin.powermeter_rx, -1, false, 128, 95);
|
||||
_upSmlSerial->enableRx(true);
|
||||
_upSmlSerial->enableTx(false);
|
||||
_upSmlSerial->flush();
|
||||
break;
|
||||
|
||||
case Source::SMAHM2:
|
||||
SMA_HM.init(scheduler, config.PowerMeter.VerboseLogging);
|
||||
break;
|
||||
|
||||
case Source::TIBBER:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PowerMeterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
|
||||
{
|
||||
for (auto const& subscription: _mqttSubscriptions) {
|
||||
if (subscription.first != topic) { continue; }
|
||||
|
||||
std::string value(reinterpret_cast<const char*>(payload), len);
|
||||
try {
|
||||
*subscription.second = std::stof(value);
|
||||
}
|
||||
catch(std::invalid_argument const& e) {
|
||||
MessageOutput.printf("PowerMeterClass: cannot parse payload of topic '%s' as float: %s\r\n",
|
||||
topic, value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (_verboseLogging) {
|
||||
MessageOutput.printf("PowerMeterClass: Updated from '%s', TotalPower: %5.2f\r\n",
|
||||
topic, getPowerTotal());
|
||||
}
|
||||
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
}
|
||||
|
||||
float PowerMeterClass::getPowerTotal(bool forceUpdate)
|
||||
{
|
||||
if (forceUpdate) {
|
||||
CONFIG_T& config = Configuration.get();
|
||||
if (config.PowerMeter.Enabled
|
||||
&& (millis() - _lastPowerMeterUpdate) > (1000)) {
|
||||
readPowerMeter();
|
||||
}
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
return _powerMeter1Power + _powerMeter2Power + _powerMeter3Power;
|
||||
}
|
||||
|
||||
uint32_t PowerMeterClass::getLastPowerMeterUpdate()
|
||||
void PowerMeterClass::updateSettings()
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
return _lastPowerMeterUpdate;
|
||||
|
||||
if (_upProvider) {
|
||||
_upProvider->deinit();
|
||||
_upProvider = nullptr;
|
||||
}
|
||||
|
||||
bool PowerMeterClass::isDataValid()
|
||||
{
|
||||
auto const& config = Configuration.get();
|
||||
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
if (!config.PowerMeter.Enabled) { return; }
|
||||
|
||||
bool valid = config.PowerMeter.Enabled &&
|
||||
_lastPowerMeterUpdate > 0 &&
|
||||
((millis() - _lastPowerMeterUpdate) < (30 * 1000));
|
||||
|
||||
// reset if timed out to avoid glitch once
|
||||
// (millis() - _lastPowerMeterUpdate) overflows
|
||||
if (!valid) { _lastPowerMeterUpdate = 0; }
|
||||
|
||||
return valid;
|
||||
switch(static_cast<PowerMeterProvider::Type>(config.PowerMeter.Source)) {
|
||||
case PowerMeterProvider::Type::MQTT:
|
||||
_upProvider = std::make_unique<PowerMeterMqtt>();
|
||||
break;
|
||||
case PowerMeterProvider::Type::SDM1PH:
|
||||
case PowerMeterProvider::Type::SDM3PH:
|
||||
_upProvider = std::make_unique<PowerMeterSerialSdm>();
|
||||
break;
|
||||
case PowerMeterProvider::Type::HTTP:
|
||||
_upProvider = std::make_unique<PowerMeterHttpJson>();
|
||||
break;
|
||||
case PowerMeterProvider::Type::SML:
|
||||
_upProvider = std::make_unique<PowerMeterSerialSml>();
|
||||
break;
|
||||
case PowerMeterProvider::Type::SMAHM2:
|
||||
_upProvider = std::make_unique<PowerMeterUdpSmaHomeManager>();
|
||||
break;
|
||||
case PowerMeterProvider::Type::TIBBER:
|
||||
_upProvider = std::make_unique<PowerMeterHttpSml>();
|
||||
break;
|
||||
}
|
||||
|
||||
void PowerMeterClass::mqtt()
|
||||
if (!_upProvider->init()) {
|
||||
_upProvider = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
float PowerMeterClass::getPowerTotal() const
|
||||
{
|
||||
if (!MqttSettings.getConnected()) { return; }
|
||||
|
||||
String topic = "powermeter";
|
||||
auto totalPower = getPowerTotal();
|
||||
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
MqttSettings.publish(topic + "/power1", String(_powerMeter1Power));
|
||||
MqttSettings.publish(topic + "/power2", String(_powerMeter2Power));
|
||||
MqttSettings.publish(topic + "/power3", String(_powerMeter3Power));
|
||||
MqttSettings.publish(topic + "/powertotal", String(totalPower));
|
||||
MqttSettings.publish(topic + "/voltage1", String(_powerMeter1Voltage));
|
||||
MqttSettings.publish(topic + "/voltage2", String(_powerMeter2Voltage));
|
||||
MqttSettings.publish(topic + "/voltage3", String(_powerMeter3Voltage));
|
||||
MqttSettings.publish(topic + "/import", String(_powerMeterImport));
|
||||
MqttSettings.publish(topic + "/export", String(_powerMeterExport));
|
||||
if (!_upProvider) { return 0.0; }
|
||||
return _upProvider->getPowerTotal();
|
||||
}
|
||||
|
||||
uint32_t PowerMeterClass::getLastUpdate() const
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
if (!_upProvider) { return 0; }
|
||||
return _upProvider->getLastUpdate();
|
||||
}
|
||||
|
||||
bool PowerMeterClass::isDataValid() const
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
if (!_upProvider) { return false; }
|
||||
return _upProvider->isDataValid();
|
||||
}
|
||||
|
||||
void PowerMeterClass::loop()
|
||||
{
|
||||
CONFIG_T const& config = Configuration.get();
|
||||
_verboseLogging = config.PowerMeter.VerboseLogging;
|
||||
|
||||
if (!config.PowerMeter.Enabled) { return; }
|
||||
|
||||
if (static_cast<Source>(config.PowerMeter.Source) == Source::SML &&
|
||||
nullptr != _upSmlSerial) {
|
||||
if (!smlReadLoop()) { return; }
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
|
||||
if ((millis() - _lastPowerMeterCheck) < (config.PowerMeter.Interval * 1000)) {
|
||||
return;
|
||||
}
|
||||
|
||||
readPowerMeter();
|
||||
|
||||
MessageOutput.printf("PowerMeterClass: TotalPower: %5.2f\r\n", getPowerTotal());
|
||||
|
||||
mqtt();
|
||||
|
||||
_lastPowerMeterCheck = millis();
|
||||
}
|
||||
|
||||
void PowerMeterClass::readPowerMeter()
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
uint8_t _address = config.PowerMeter.SdmAddress;
|
||||
Source configuredSource = static_cast<Source>(config.PowerMeter.Source);
|
||||
|
||||
if (configuredSource == Source::SDM1PH) {
|
||||
if (!_upSdm) { return; }
|
||||
|
||||
// this takes a "very long" time as each readVal() is a synchronous
|
||||
// exchange of serial messages. cache the values and write later.
|
||||
auto phase1Power = _upSdm->readVal(SDM_PHASE_1_POWER, _address);
|
||||
auto phase1Voltage = _upSdm->readVal(SDM_PHASE_1_VOLTAGE, _address);
|
||||
auto energyImport = _upSdm->readVal(SDM_IMPORT_ACTIVE_ENERGY, _address);
|
||||
auto energyExport = _upSdm->readVal(SDM_EXPORT_ACTIVE_ENERGY, _address);
|
||||
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
_powerMeter1Power = static_cast<float>(phase1Power);
|
||||
_powerMeter2Power = 0;
|
||||
_powerMeter3Power = 0;
|
||||
_powerMeter1Voltage = static_cast<float>(phase1Voltage);
|
||||
_powerMeter2Voltage = 0;
|
||||
_powerMeter3Voltage = 0;
|
||||
_powerMeterImport = static_cast<float>(energyImport);
|
||||
_powerMeterExport = static_cast<float>(energyExport);
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
else if (configuredSource == Source::SDM3PH) {
|
||||
if (!_upSdm) { return; }
|
||||
|
||||
// this takes a "very long" time as each readVal() is a synchronous
|
||||
// exchange of serial messages. cache the values and write later.
|
||||
auto phase1Power = _upSdm->readVal(SDM_PHASE_1_POWER, _address);
|
||||
auto phase2Power = _upSdm->readVal(SDM_PHASE_2_POWER, _address);
|
||||
auto phase3Power = _upSdm->readVal(SDM_PHASE_3_POWER, _address);
|
||||
auto phase1Voltage = _upSdm->readVal(SDM_PHASE_1_VOLTAGE, _address);
|
||||
auto phase2Voltage = _upSdm->readVal(SDM_PHASE_2_VOLTAGE, _address);
|
||||
auto phase3Voltage = _upSdm->readVal(SDM_PHASE_3_VOLTAGE, _address);
|
||||
auto energyImport = _upSdm->readVal(SDM_IMPORT_ACTIVE_ENERGY, _address);
|
||||
auto energyExport = _upSdm->readVal(SDM_EXPORT_ACTIVE_ENERGY, _address);
|
||||
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
_powerMeter1Power = static_cast<float>(phase1Power);
|
||||
_powerMeter2Power = static_cast<float>(phase2Power);
|
||||
_powerMeter3Power = static_cast<float>(phase3Power);
|
||||
_powerMeter1Voltage = static_cast<float>(phase1Voltage);
|
||||
_powerMeter2Voltage = static_cast<float>(phase2Voltage);
|
||||
_powerMeter3Voltage = static_cast<float>(phase3Voltage);
|
||||
_powerMeterImport = static_cast<float>(energyImport);
|
||||
_powerMeterExport = static_cast<float>(energyExport);
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
else if (configuredSource == Source::HTTP) {
|
||||
if (HttpPowerMeter.updateValues()) {
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
_powerMeter1Power = HttpPowerMeter.getPower(1);
|
||||
_powerMeter2Power = HttpPowerMeter.getPower(2);
|
||||
_powerMeter3Power = HttpPowerMeter.getPower(3);
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
}
|
||||
else if (configuredSource == Source::SMAHM2) {
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
_powerMeter1Power = SMA_HM.getPowerL1();
|
||||
_powerMeter2Power = SMA_HM.getPowerL2();
|
||||
_powerMeter3Power = SMA_HM.getPowerL3();
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
else if (configuredSource == Source::TIBBER) {
|
||||
if (TibberPowerMeter.updateValues()) {
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PowerMeterClass::smlReadLoop()
|
||||
{
|
||||
while (_upSmlSerial->available()) {
|
||||
double readVal = 0;
|
||||
unsigned char smlCurrentChar = _upSmlSerial->read();
|
||||
sml_states_t smlCurrentState = smlState(smlCurrentChar);
|
||||
if (smlCurrentState == SML_LISTEND) {
|
||||
for (auto& handler: smlHandlerList) {
|
||||
if (smlOBISCheck(handler.OBIS)) {
|
||||
handler.Fn(readVal);
|
||||
*handler.Arg = readVal;
|
||||
}
|
||||
}
|
||||
} else if (smlCurrentState == SML_FINAL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (!_upProvider) { return; }
|
||||
_upProvider->loop();
|
||||
_upProvider->mqttLoop();
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "Configuration.h"
|
||||
#include "HttpPowerMeter.h"
|
||||
#include "PowerMeterHttpJson.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "MqttSettings.h"
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include "mbedtls/sha256.h"
|
||||
@ -9,48 +10,63 @@
|
||||
#include <memory>
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
void HttpPowerMeterClass::init()
|
||||
{
|
||||
}
|
||||
|
||||
float HttpPowerMeterClass::getPower(int8_t phase)
|
||||
{
|
||||
if (phase < 1 || phase > POWERMETER_MAX_PHASES) { return 0.0; }
|
||||
|
||||
return power[phase - 1];
|
||||
}
|
||||
|
||||
bool HttpPowerMeterClass::updateValues()
|
||||
void PowerMeterHttpJson::loop()
|
||||
{
|
||||
auto const& config = Configuration.get();
|
||||
if ((millis() - _lastPoll) < (config.PowerMeter.Interval * 1000)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_lastPoll = millis();
|
||||
|
||||
for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) {
|
||||
auto const& phaseConfig = config.PowerMeter.Http_Phase[i];
|
||||
|
||||
if (!phaseConfig.Enabled) {
|
||||
power[i] = 0.0;
|
||||
_cache[i] = 0.0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == 0 || config.PowerMeter.HttpIndividualRequests) {
|
||||
if (!queryPhase(i, phaseConfig)) {
|
||||
MessageOutput.printf("[HttpPowerMeter] Getting the power of 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);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!tryGetFloatValueForPhase(i, phaseConfig.JsonPath, phaseConfig.PowerUnit, phaseConfig.SignInverted)) {
|
||||
MessageOutput.printf("[HttpPowerMeter] Getting the 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);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HttpPowerMeterClass::queryPhase(int phase, PowerMeterHttpConfig const& config)
|
||||
gotUpdate();
|
||||
|
||||
_powerValues = _cache;
|
||||
}
|
||||
|
||||
float PowerMeterHttpJson::getPowerTotal() const
|
||||
{
|
||||
float sum = 0.0;
|
||||
for (auto v: _powerValues) { sum += v; }
|
||||
return sum;
|
||||
}
|
||||
|
||||
void PowerMeterHttpJson::doMqttPublish() const
|
||||
{
|
||||
String topic = "powermeter";
|
||||
auto power = getPowerTotal();
|
||||
|
||||
MqttSettings.publish(topic + "/power1", String(_powerValues[0]));
|
||||
MqttSettings.publish(topic + "/power2", String(_powerValues[1]));
|
||||
MqttSettings.publish(topic + "/power3", String(_powerValues[2]));
|
||||
MqttSettings.publish(topic + "/powertotal", String(power));
|
||||
}
|
||||
|
||||
bool PowerMeterHttpJson::queryPhase(int phase, PowerMeterHttpConfig const& config)
|
||||
{
|
||||
//hostByName in WiFiGeneric fails to resolve local names. issue described in
|
||||
//https://github.com/espressif/arduino-esp32/issues/3822
|
||||
@ -107,7 +123,7 @@ bool HttpPowerMeterClass::queryPhase(int phase, PowerMeterHttpConfig const& conf
|
||||
return httpRequest(phase, *wifiClient, ipaddr.toString(), port, uri, https, config);
|
||||
}
|
||||
|
||||
bool HttpPowerMeterClass::httpRequest(int phase, WiFiClient &wifiClient, const String& host, uint16_t port, const String& uri, bool https, PowerMeterHttpConfig const& config)
|
||||
bool PowerMeterHttpJson::httpRequest(int phase, WiFiClient &wifiClient, const String& host, uint16_t port, const String& uri, bool https, PowerMeterHttpConfig const& config)
|
||||
{
|
||||
if(!httpClient.begin(wifiClient, host, port, uri, https)){
|
||||
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError), PSTR("httpClient.begin() failed for %s://%s"), (https ? "https" : "http"), host.c_str());
|
||||
@ -163,13 +179,13 @@ bool HttpPowerMeterClass::httpRequest(int phase, WiFiClient &wifiClient, const S
|
||||
return tryGetFloatValueForPhase(phase, config.JsonPath, config.PowerUnit, config.SignInverted);
|
||||
}
|
||||
|
||||
String HttpPowerMeterClass::extractParam(String& authReq, const String& param, const char delimit) {
|
||||
String PowerMeterHttpJson::extractParam(String& authReq, const String& param, const char delimit) {
|
||||
int _begin = authReq.indexOf(param);
|
||||
if (_begin == -1) { return ""; }
|
||||
return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length()));
|
||||
}
|
||||
|
||||
String HttpPowerMeterClass::getcNonce(const int len) {
|
||||
String PowerMeterHttpJson::getcNonce(const int len) {
|
||||
static const char alphanum[] = "0123456789"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz";
|
||||
@ -180,7 +196,7 @@ String HttpPowerMeterClass::getcNonce(const int len) {
|
||||
return s;
|
||||
}
|
||||
|
||||
String HttpPowerMeterClass::getDigestAuth(String& authReq, const String& username, const String& password, const String& method, const String& uri, unsigned int counter) {
|
||||
String PowerMeterHttpJson::getDigestAuth(String& authReq, const String& username, const String& password, const String& method, const String& uri, unsigned int counter) {
|
||||
// extracting required parameters for RFC 2617 Digest
|
||||
String realm = extractParam(authReq, "realm=\"", '"');
|
||||
String nonce = extractParam(authReq, "nonce=\"", '"');
|
||||
@ -218,13 +234,13 @@ String HttpPowerMeterClass::getDigestAuth(String& authReq, const String& usernam
|
||||
return authorization;
|
||||
}
|
||||
|
||||
bool HttpPowerMeterClass::tryGetFloatValueForPhase(int phase, String jsonPath, Unit_t unit, bool signInverted)
|
||||
bool PowerMeterHttpJson::tryGetFloatValueForPhase(int phase, String jsonPath, Unit_t unit, bool signInverted)
|
||||
{
|
||||
JsonDocument root;
|
||||
const DeserializationError error = deserializeJson(root, httpResponse);
|
||||
if (error) {
|
||||
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError),
|
||||
PSTR("[HttpPowerMeter] Unable to parse server response as JSON"));
|
||||
PSTR("[PowerMeterHttpJson] Unable to parse server response as JSON"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -286,32 +302,32 @@ bool HttpPowerMeterClass::tryGetFloatValueForPhase(int phase, String jsonPath, U
|
||||
|
||||
if (!value.is<float>()) {
|
||||
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError),
|
||||
PSTR("[HttpPowerMeter] not a float: '%s'"),
|
||||
PSTR("[PowerMeterHttpJson] not a float: '%s'"),
|
||||
value.as<String>().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// this value is supposed to be in Watts and positive if energy is consumed.
|
||||
power[phase] = value.as<float>();
|
||||
_cache[phase] = value.as<float>();
|
||||
|
||||
switch (unit) {
|
||||
case Unit_t::MilliWatts:
|
||||
power[phase] /= 1000;
|
||||
_cache[phase] /= 1000;
|
||||
break;
|
||||
case Unit_t::KiloWatts:
|
||||
power[phase] *= 1000;
|
||||
_cache[phase] *= 1000;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (signInverted) { power[phase] *= -1; }
|
||||
if (signInverted) { _cache[phase] *= -1; }
|
||||
|
||||
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 HttpPowerMeterClass::extractUrlComponents(String url, String& _protocol, String& _host, String& _uri, uint16_t& _port, String& _base64Authorization)
|
||||
bool PowerMeterHttpJson::extractUrlComponents(String url, String& _protocol, String& _host, String& _uri, uint16_t& _port, String& _base64Authorization)
|
||||
{
|
||||
// check for : (http: or https:
|
||||
int index = url.indexOf(':');
|
||||
@ -361,7 +377,7 @@ bool HttpPowerMeterClass::extractUrlComponents(String url, String& _protocol, St
|
||||
return true;
|
||||
}
|
||||
|
||||
String HttpPowerMeterClass::sha256(const String& data) {
|
||||
String PowerMeterHttpJson::sha256(const String& data) {
|
||||
uint8_t hash[32];
|
||||
|
||||
mbedtls_sha256_context ctx;
|
||||
@ -379,7 +395,7 @@ String HttpPowerMeterClass::sha256(const String& data) {
|
||||
return res;
|
||||
}
|
||||
|
||||
void HttpPowerMeterClass::prepareRequest(uint32_t timeout, const char* httpHeader, const char* httpValue) {
|
||||
void PowerMeterHttpJson::prepareRequest(uint32_t timeout, const char* httpHeader, const char* httpValue) {
|
||||
httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
httpClient.setUserAgent("OpenDTU-OnBattery");
|
||||
httpClient.setConnectTimeout(timeout);
|
||||
@ -391,5 +407,3 @@ void HttpPowerMeterClass::prepareRequest(uint32_t timeout, const char* httpHeade
|
||||
httpClient.addHeader(httpHeader, httpValue);
|
||||
}
|
||||
}
|
||||
|
||||
HttpPowerMeterClass HttpPowerMeter;
|
||||
@ -1,28 +1,44 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "Configuration.h"
|
||||
#include "TibberPowerMeter.h"
|
||||
#include "PowerMeterHttpSml.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "MqttSettings.h"
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <base64.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <PowerMeter.h>
|
||||
|
||||
bool TibberPowerMeterClass::updateValues()
|
||||
float PowerMeterHttpSml::getPowerTotal() const
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
return _activePower;
|
||||
}
|
||||
|
||||
void PowerMeterHttpSml::doMqttPublish() const
|
||||
{
|
||||
String topic = "powermeter";
|
||||
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
MqttSettings.publish(topic + "/powertotal", String(_activePower));
|
||||
}
|
||||
|
||||
void PowerMeterHttpSml::loop()
|
||||
{
|
||||
auto const& config = Configuration.get();
|
||||
if ((millis() - _lastPoll) < (config.PowerMeter.Interval * 1000)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_lastPoll = millis();
|
||||
|
||||
auto const& tibberConfig = config.PowerMeter.Tibber;
|
||||
|
||||
if (!query(tibberConfig)) {
|
||||
MessageOutput.printf("[TibberPowerMeter] Getting the power of tibber failed.\r\n");
|
||||
MessageOutput.printf("[PowerMeterHttpSml] Getting the power value failed.\r\n");
|
||||
MessageOutput.printf("%s\r\n", tibberPowerMeterError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TibberPowerMeterClass::query(PowerMeterTibberConfig const& config)
|
||||
bool PowerMeterHttpSml::query(PowerMeterTibberConfig const& config)
|
||||
{
|
||||
//hostByName in WiFiGeneric fails to resolve local names. issue described in
|
||||
//https://github.com/espressif/arduino-esp32/issues/3822
|
||||
@ -79,7 +95,7 @@ bool TibberPowerMeterClass::query(PowerMeterTibberConfig const& config)
|
||||
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)
|
||||
bool PowerMeterHttpSml::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());
|
||||
@ -112,10 +128,12 @@ bool TibberPowerMeterClass::httpRequest(WiFiClient &wifiClient, const String& ho
|
||||
unsigned char smlCurrentChar = httpClient.getStream().read();
|
||||
sml_states_t smlCurrentState = smlState(smlCurrentChar);
|
||||
if (smlCurrentState == SML_LISTEND) {
|
||||
for (auto& handler: PowerMeter.smlHandlerList) {
|
||||
for (auto& handler: smlHandlerList) {
|
||||
if (smlOBISCheck(handler.OBIS)) {
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
handler.Fn(readVal);
|
||||
*handler.Arg = readVal;
|
||||
gotUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -126,7 +144,7 @@ bool TibberPowerMeterClass::httpRequest(WiFiClient &wifiClient, const String& ho
|
||||
}
|
||||
|
||||
//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)
|
||||
bool PowerMeterHttpSml::extractUrlComponents(String url, String& _protocol, String& _host, String& _uri, uint16_t& _port, String& _base64Authorization)
|
||||
{
|
||||
// check for : (http: or https:
|
||||
int index = url.indexOf(':');
|
||||
@ -176,7 +194,7 @@ bool TibberPowerMeterClass::extractUrlComponents(String url, String& _protocol,
|
||||
return true;
|
||||
}
|
||||
|
||||
void TibberPowerMeterClass::prepareRequest(uint32_t timeout) {
|
||||
void PowerMeterHttpSml::prepareRequest(uint32_t timeout) {
|
||||
httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
httpClient.setUserAgent("OpenDTU-OnBattery");
|
||||
httpClient.setConnectTimeout(timeout);
|
||||
@ -184,5 +202,3 @@ void TibberPowerMeterClass::prepareRequest(uint32_t timeout) {
|
||||
httpClient.addHeader("Content-Type", "application/json");
|
||||
httpClient.addHeader("Accept", "application/json");
|
||||
}
|
||||
|
||||
TibberPowerMeterClass TibberPowerMeter;
|
||||
74
src/PowerMeterMqtt.cpp
Normal file
74
src/PowerMeterMqtt.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "PowerMeterMqtt.h"
|
||||
#include "Configuration.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "MessageOutput.h"
|
||||
|
||||
bool PowerMeterMqtt::init()
|
||||
{
|
||||
auto subscribe = [this](char const* topic, float* target) {
|
||||
if (strlen(topic) == 0) { return; }
|
||||
MqttSettings.subscribe(topic, 0,
|
||||
std::bind(&PowerMeterMqtt::onMqttMessage,
|
||||
this, std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3, std::placeholders::_4,
|
||||
std::placeholders::_5, std::placeholders::_6)
|
||||
);
|
||||
_mqttSubscriptions.try_emplace(topic, target);
|
||||
};
|
||||
|
||||
auto const& config = Configuration.get();
|
||||
subscribe(config.PowerMeter.MqttTopicPowerMeter1, &_powerValueOne);
|
||||
subscribe(config.PowerMeter.MqttTopicPowerMeter2, &_powerValueTwo);
|
||||
subscribe(config.PowerMeter.MqttTopicPowerMeter3, &_powerValueThree);
|
||||
|
||||
return _mqttSubscriptions.size() > 0;
|
||||
}
|
||||
|
||||
void PowerMeterMqtt::deinit()
|
||||
{
|
||||
for (auto const& s: _mqttSubscriptions) { MqttSettings.unsubscribe(s.first); }
|
||||
_mqttSubscriptions.clear();
|
||||
}
|
||||
|
||||
void PowerMeterMqtt::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
|
||||
{
|
||||
for (auto const& subscription: _mqttSubscriptions) {
|
||||
if (subscription.first != topic) { continue; }
|
||||
|
||||
std::string value(reinterpret_cast<const char*>(payload), len);
|
||||
try {
|
||||
*subscription.second = std::stof(value);
|
||||
}
|
||||
catch(std::invalid_argument const& e) {
|
||||
MessageOutput.printf("[PowerMeterMqtt] cannot parse payload of topic '%s' as float: %s\r\n",
|
||||
topic, value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (_verboseLogging) {
|
||||
MessageOutput.printf("[PowerMeterMqtt] Updated from '%s', TotalPower: %5.2f\r\n",
|
||||
topic, getPowerTotal());
|
||||
}
|
||||
|
||||
gotUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
float PowerMeterMqtt::getPowerTotal() const
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
return _powerValueOne + _powerValueTwo + _powerValueThree;
|
||||
}
|
||||
|
||||
void PowerMeterMqtt::doMqttPublish() const
|
||||
{
|
||||
String topic = "powermeter";
|
||||
auto totalPower = getPowerTotal();
|
||||
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
MqttSettings.publish(topic + "/power1", String(_powerValueOne));
|
||||
MqttSettings.publish(topic + "/power2", String(_powerValueTwo));
|
||||
MqttSettings.publish(topic + "/power3", String(_powerValueThree));
|
||||
MqttSettings.publish(topic + "/powertotal", String(totalPower));
|
||||
}
|
||||
22
src/PowerMeterProvider.cpp
Normal file
22
src/PowerMeterProvider.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "PowerMeterProvider.h"
|
||||
#include "MqttSettings.h"
|
||||
|
||||
bool PowerMeterProvider::isDataValid() const
|
||||
{
|
||||
return _lastUpdate > 0 && ((millis() - _lastUpdate) < (30 * 1000));
|
||||
}
|
||||
|
||||
void PowerMeterProvider::mqttLoop() const
|
||||
{
|
||||
if (!MqttSettings.getConnected()) { return; }
|
||||
|
||||
if (!isDataValid()) { return; }
|
||||
|
||||
auto constexpr halfOfAllMillis = std::numeric_limits<uint32_t>::max() / 2;
|
||||
if ((_lastUpdate - _lastMqttPublish) > halfOfAllMillis) { return; }
|
||||
|
||||
doMqttPublish();
|
||||
|
||||
_lastMqttPublish = millis();
|
||||
}
|
||||
113
src/PowerMeterSerialSdm.cpp
Normal file
113
src/PowerMeterSerialSdm.cpp
Normal file
@ -0,0 +1,113 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "PowerMeterSerialSdm.h"
|
||||
#include "Configuration.h"
|
||||
#include "PinMapping.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "SerialPortManager.h"
|
||||
|
||||
void PowerMeterSerialSdm::deinit()
|
||||
{
|
||||
if (_upSdmSerial) {
|
||||
_upSdmSerial->end();
|
||||
_upSdmSerial = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool PowerMeterSerialSdm::init()
|
||||
{
|
||||
const PinMapping_t& pin = PinMapping.get();
|
||||
|
||||
MessageOutput.printf("[PowerMeterSerialSdm] rx = %d, tx = %d, dere = %d\r\n",
|
||||
pin.powermeter_rx, pin.powermeter_tx, pin.powermeter_dere);
|
||||
|
||||
if (pin.powermeter_rx < 0 || pin.powermeter_tx < 0) {
|
||||
MessageOutput.println("[PowerMeterSerialSdm] invalid pin config for SDM "
|
||||
"power meter (RX and TX pins must be defined)");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto oHwSerialPort = SerialPortManager.allocatePort(_sdmSerialPortOwner);
|
||||
if (!oHwSerialPort) { return false; }
|
||||
|
||||
_upSdmSerial = std::make_unique<HardwareSerial>(*oHwSerialPort);
|
||||
_upSdmSerial->end(); // make sure the UART will be re-initialized
|
||||
_upSdm = std::make_unique<SDM>(*_upSdmSerial, 9600, pin.powermeter_dere,
|
||||
SERIAL_8N1, pin.powermeter_rx, pin.powermeter_tx);
|
||||
_upSdm->begin();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
float PowerMeterSerialSdm::getPowerTotal() const
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
return _phase1Power + _phase2Power + _phase3Power;
|
||||
}
|
||||
|
||||
void PowerMeterSerialSdm::doMqttPublish() const
|
||||
{
|
||||
String topic = "powermeter";
|
||||
auto power = getPowerTotal();
|
||||
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
MqttSettings.publish(topic + "/power1", String(_phase1Power));
|
||||
MqttSettings.publish(topic + "/power2", String(_phase2Power));
|
||||
MqttSettings.publish(topic + "/power3", String(_phase3Power));
|
||||
MqttSettings.publish(topic + "/powertotal", String(power));
|
||||
MqttSettings.publish(topic + "/voltage1", String(_phase1Voltage));
|
||||
MqttSettings.publish(topic + "/voltage2", String(_phase2Voltage));
|
||||
MqttSettings.publish(topic + "/voltage3", String(_phase3Voltage));
|
||||
MqttSettings.publish(topic + "/import", String(_energyImport));
|
||||
MqttSettings.publish(topic + "/export", String(_energyExport));
|
||||
}
|
||||
|
||||
void PowerMeterSerialSdm::loop()
|
||||
{
|
||||
if (!_upSdm) { return; }
|
||||
|
||||
auto const& config = Configuration.get();
|
||||
|
||||
if ((millis() - _lastPoll) < (config.PowerMeter.Interval * 1000)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t addr = config.PowerMeter.SdmAddress;
|
||||
|
||||
// reading takes a "very long" time as each readVal() is a synchronous
|
||||
// exchange of serial messages. cache the values and write later to
|
||||
// enforce consistent values.
|
||||
float phase1Power = _upSdm->readVal(SDM_PHASE_1_POWER, addr);
|
||||
float phase2Power = 0.0;
|
||||
float phase3Power = 0.0;
|
||||
float phase1Voltage = _upSdm->readVal(SDM_PHASE_1_VOLTAGE, addr);
|
||||
float phase2Voltage = 0.0;
|
||||
float phase3Voltage = 0.0;
|
||||
float energyImport = _upSdm->readVal(SDM_IMPORT_ACTIVE_ENERGY, addr);
|
||||
float energyExport = _upSdm->readVal(SDM_EXPORT_ACTIVE_ENERGY, addr);
|
||||
|
||||
if (static_cast<PowerMeterProvider::Type>(config.PowerMeter.Source) == PowerMeterProvider::Type::SDM3PH) {
|
||||
phase2Power = _upSdm->readVal(SDM_PHASE_2_POWER, addr);
|
||||
phase3Power = _upSdm->readVal(SDM_PHASE_3_POWER, addr);
|
||||
phase2Voltage = _upSdm->readVal(SDM_PHASE_2_VOLTAGE, addr);
|
||||
phase3Voltage = _upSdm->readVal(SDM_PHASE_3_VOLTAGE, addr);
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
_phase1Power = static_cast<float>(phase1Power);
|
||||
_phase2Power = static_cast<float>(phase2Power);
|
||||
_phase3Power = static_cast<float>(phase3Power);
|
||||
_phase1Voltage = static_cast<float>(phase1Voltage);
|
||||
_phase2Voltage = static_cast<float>(phase2Voltage);
|
||||
_phase3Voltage = static_cast<float>(phase3Voltage);
|
||||
_energyImport = static_cast<float>(energyImport);
|
||||
_energyExport = static_cast<float>(energyExport);
|
||||
}
|
||||
|
||||
gotUpdate();
|
||||
|
||||
MessageOutput.printf("[PowerMeterSerialSdm] TotalPower: %5.2f\r\n", getPowerTotal());
|
||||
|
||||
_lastPoll = millis();
|
||||
}
|
||||
68
src/PowerMeterSerialSml.cpp
Normal file
68
src/PowerMeterSerialSml.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "PowerMeterSerialSml.h"
|
||||
#include "Configuration.h"
|
||||
#include "PinMapping.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "MqttSettings.h"
|
||||
|
||||
bool PowerMeterSerialSml::init()
|
||||
{
|
||||
const PinMapping_t& pin = PinMapping.get();
|
||||
|
||||
MessageOutput.printf("[PowerMeterSerialSml] rx = %d\r\n", pin.powermeter_rx);
|
||||
|
||||
if (pin.powermeter_rx < 0) {
|
||||
MessageOutput.println("[PowerMeterSerialSml] invalid pin config "
|
||||
"for serial SML power meter (RX pin must be defined)");
|
||||
return false;
|
||||
}
|
||||
|
||||
pinMode(pin.powermeter_rx, INPUT);
|
||||
_upSmlSerial = std::make_unique<SoftwareSerial>();
|
||||
_upSmlSerial->begin(9600, SWSERIAL_8N1, pin.powermeter_rx, -1, false, 128, 95);
|
||||
_upSmlSerial->enableRx(true);
|
||||
_upSmlSerial->enableTx(false);
|
||||
_upSmlSerial->flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PowerMeterSerialSml::deinit()
|
||||
{
|
||||
if (!_upSmlSerial) { return; }
|
||||
_upSmlSerial->end();
|
||||
}
|
||||
|
||||
void PowerMeterSerialSml::doMqttPublish() const
|
||||
{
|
||||
String topic = "powermeter";
|
||||
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
MqttSettings.publish(topic + "/powertotal", String(_activePower));
|
||||
MqttSettings.publish(topic + "/import", String(_energyImport));
|
||||
MqttSettings.publish(topic + "/export", String(_energyExport));
|
||||
}
|
||||
|
||||
void PowerMeterSerialSml::loop()
|
||||
{
|
||||
if (!_upSmlSerial) { return; }
|
||||
|
||||
while (_upSmlSerial->available()) {
|
||||
double readVal = 0;
|
||||
unsigned char smlCurrentChar = _upSmlSerial->read();
|
||||
sml_states_t smlCurrentState = smlState(smlCurrentChar);
|
||||
if (smlCurrentState == SML_LISTEND) {
|
||||
for (auto& handler: smlHandlerList) {
|
||||
if (smlOBISCheck(handler.OBIS)) {
|
||||
handler.Fn(readVal);
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
*handler.Arg = readVal;
|
||||
}
|
||||
}
|
||||
} else if (smlCurrentState == SML_FINAL) {
|
||||
gotUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
MessageOutput.printf("[PowerMeterSerialSml]: TotalPower: %5.2f\r\n", getPowerTotal());
|
||||
}
|
||||
@ -2,51 +2,50 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Holger-Steffen Stapf
|
||||
*/
|
||||
#include "SMA_HM.h"
|
||||
#include "PowerMeterUdpSmaHomeManager.h"
|
||||
#include <Arduino.h>
|
||||
#include "Configuration.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include "MqttSettings.h"
|
||||
#include <WiFiUdp.h>
|
||||
#include "MessageOutput.h"
|
||||
|
||||
unsigned int multicastPort = 9522; // local port to listen on
|
||||
IPAddress multicastIP(239, 12, 255, 254);
|
||||
WiFiUDP SMAUdp;
|
||||
static constexpr unsigned int multicastPort = 9522; // local port to listen on
|
||||
static const IPAddress multicastIP(239, 12, 255, 254);
|
||||
static WiFiUDP SMAUdp;
|
||||
|
||||
constexpr uint32_t interval = 1000;
|
||||
|
||||
SMA_HMClass SMA_HM;
|
||||
|
||||
void SMA_HMClass::Soutput(int kanal, int index, int art, int tarif,
|
||||
void PowerMeterUdpSmaHomeManager::Soutput(int kanal, int index, int art, int tarif,
|
||||
char const* name, float value, uint32_t timestamp)
|
||||
{
|
||||
if (!_verboseLogging) { return; }
|
||||
|
||||
MessageOutput.printf("SMA_HM: %s = %.1f (timestamp %d)\r\n",
|
||||
MessageOutput.printf("[PowerMeterUdpSmaHomeManager] %s = %.1f (timestamp %d)\r\n",
|
||||
name, value, timestamp);
|
||||
}
|
||||
|
||||
void SMA_HMClass::init(Scheduler& scheduler, bool verboseLogging)
|
||||
bool PowerMeterUdpSmaHomeManager::init()
|
||||
{
|
||||
_verboseLogging = verboseLogging;
|
||||
scheduler.addTask(_loopTask);
|
||||
_loopTask.setCallback(std::bind(&SMA_HMClass::loop, this));
|
||||
_loopTask.setIterations(TASK_FOREVER);
|
||||
_loopTask.enable();
|
||||
SMAUdp.begin(multicastPort);
|
||||
SMAUdp.beginMulticast(multicastIP, multicastPort);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SMA_HMClass::loop()
|
||||
void PowerMeterUdpSmaHomeManager::deinit()
|
||||
{
|
||||
uint32_t currentMillis = millis();
|
||||
if (currentMillis - _previousMillis >= interval) {
|
||||
_previousMillis = currentMillis;
|
||||
event1();
|
||||
}
|
||||
SMAUdp.stop();
|
||||
}
|
||||
|
||||
uint8_t* SMA_HMClass::decodeGroup(uint8_t* offset, uint16_t grouplen)
|
||||
void PowerMeterUdpSmaHomeManager::doMqttPublish() const
|
||||
{
|
||||
String topic = "powermeter";
|
||||
|
||||
MqttSettings.publish(topic + "/powertotal", String(_powerMeterPower));
|
||||
MqttSettings.publish(topic + "/power1", String(_powerMeterL1));
|
||||
MqttSettings.publish(topic + "/power2", String(_powerMeterL2));
|
||||
MqttSettings.publish(topic + "/power3", String(_powerMeterL3));
|
||||
}
|
||||
|
||||
uint8_t* PowerMeterUdpSmaHomeManager::decodeGroup(uint8_t* offset, uint16_t grouplen)
|
||||
{
|
||||
float Pbezug = 0;
|
||||
float BezugL1 = 0;
|
||||
@ -149,7 +148,7 @@ uint8_t* SMA_HMClass::decodeGroup(uint8_t* offset, uint16_t grouplen)
|
||||
continue;
|
||||
}
|
||||
|
||||
MessageOutput.printf("SMA_HM: Skipped unknown measurement: %d %d %d %d\r\n",
|
||||
MessageOutput.printf("[PowerMeterUdpSmaHomeManager] Skipped unknown measurement: %d %d %d %d\r\n",
|
||||
kanal, index, art, tarif);
|
||||
offset += art;
|
||||
}
|
||||
@ -157,15 +156,20 @@ uint8_t* SMA_HMClass::decodeGroup(uint8_t* offset, uint16_t grouplen)
|
||||
return offset;
|
||||
}
|
||||
|
||||
void SMA_HMClass::event1()
|
||||
void PowerMeterUdpSmaHomeManager::loop()
|
||||
{
|
||||
uint32_t currentMillis = millis();
|
||||
if (currentMillis - _previousMillis < interval) { return; }
|
||||
|
||||
_previousMillis = currentMillis;
|
||||
|
||||
int packetSize = SMAUdp.parsePacket();
|
||||
if (!packetSize) { return; }
|
||||
|
||||
uint8_t buffer[1024];
|
||||
int rSize = SMAUdp.read(buffer, 1024);
|
||||
if (buffer[0] != 'S' || buffer[1] != 'M' || buffer[2] != 'A') {
|
||||
MessageOutput.println("SMA_HM: Not an SMA packet?");
|
||||
MessageOutput.println("[PowerMeterUdpSmaHomeManager] Not an SMA packet?");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -196,7 +200,7 @@ void SMA_HMClass::event1()
|
||||
continue;
|
||||
}
|
||||
|
||||
MessageOutput.printf("SMA_HM: Unhandled group 0x%04x with length %d\r\n",
|
||||
MessageOutput.printf("[PowerMeterUdpSmaHomeManager] Unhandled group 0x%04x with length %d\r\n",
|
||||
grouptag, grouplen);
|
||||
offset += grouplen;
|
||||
} while (grouplen > 0 && offset + 4 < buffer + rSize);
|
||||
@ -12,8 +12,8 @@
|
||||
#include "MqttSettings.h"
|
||||
#include "PowerLimiter.h"
|
||||
#include "PowerMeter.h"
|
||||
#include "HttpPowerMeter.h"
|
||||
#include "TibberPowerMeter.h"
|
||||
#include "PowerMeterHttpJson.h"
|
||||
#include "PowerMeterHttpSml.h"
|
||||
#include "WebApi.h"
|
||||
#include "helper.h"
|
||||
|
||||
@ -128,7 +128,7 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
||||
return;
|
||||
}
|
||||
|
||||
if (static_cast<PowerMeterClass::Source>(root["source"].as<uint8_t>()) == PowerMeterClass::Source::HTTP) {
|
||||
if (static_cast<PowerMeterProvider::Type>(root["source"].as<uint8_t>()) == PowerMeterProvider::Type::HTTP) {
|
||||
JsonArray http_phases = root["http_phases"];
|
||||
for (uint8_t i = 0; i < http_phases.size(); i++) {
|
||||
JsonObject phase = http_phases[i].as<JsonObject>();
|
||||
@ -174,7 +174,7 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
||||
}
|
||||
}
|
||||
|
||||
if (static_cast<PowerMeterClass::Source>(root["source"].as<uint8_t>()) == PowerMeterClass::Source::TIBBER) {
|
||||
if (static_cast<PowerMeterProvider::Type>(root["source"].as<uint8_t>()) == PowerMeterProvider::Type::TIBBER) {
|
||||
JsonObject tibber = root["tibber"];
|
||||
|
||||
if (!tibber.containsKey("url")
|
||||
@ -260,14 +260,14 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
||||
|
||||
char response[256];
|
||||
|
||||
int phase = 0;//"absuing" index 0 of the float power[3] in HttpPowerMeter to store the result
|
||||
PowerMeterHttpConfig phaseConfig;
|
||||
decodeJsonPhaseConfig(root.as<JsonObject>(), phaseConfig);
|
||||
if (HttpPowerMeter.queryPhase(phase, phaseConfig)) {
|
||||
auto upMeter = std::make_unique<PowerMeterHttpJson>();
|
||||
if (upMeter->queryPhase(0/*phase*/, phaseConfig)) {
|
||||
retMsg["type"] = "success";
|
||||
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", HttpPowerMeter.getPower(phase + 1));
|
||||
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", upMeter->getPowerTotal());
|
||||
} else {
|
||||
snprintf_P(response, sizeof(response), "%s", HttpPowerMeter.httpPowerMeterError);
|
||||
snprintf_P(response, sizeof(response), "%s", upMeter->httpPowerMeterError);
|
||||
}
|
||||
|
||||
retMsg["message"] = response;
|
||||
@ -302,11 +302,12 @@ void WebApiPowerMeterClass::onTestTibberRequest(AsyncWebServerRequest* request)
|
||||
|
||||
PowerMeterTibberConfig tibberConfig;
|
||||
decodeJsonTibberConfig(root.as<JsonObject>(), tibberConfig);
|
||||
if (TibberPowerMeter.query(tibberConfig)) {
|
||||
auto upMeter = std::make_unique<PowerMeterHttpSml>();
|
||||
if (upMeter->query(tibberConfig)) {
|
||||
retMsg["type"] = "success";
|
||||
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", PowerMeter.getPowerTotal());
|
||||
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", upMeter->getPowerTotal());
|
||||
} else {
|
||||
snprintf_P(response, sizeof(response), "%s", TibberPowerMeter.tibberPowerMeterError);
|
||||
snprintf_P(response, sizeof(response), "%s", upMeter->tibberPowerMeterError);
|
||||
}
|
||||
|
||||
retMsg["message"] = response;
|
||||
|
||||
@ -98,12 +98,12 @@ void WebApiWsLiveClass::generateOnBatteryJsonResponse(JsonVariant& root, bool al
|
||||
if (!all) { _lastPublishBattery = millis(); }
|
||||
}
|
||||
|
||||
if (all || (PowerMeter.getLastPowerMeterUpdate() - _lastPublishPowerMeter) < halfOfAllMillis) {
|
||||
if (all || (PowerMeter.getLastUpdate() - _lastPublishPowerMeter) < halfOfAllMillis) {
|
||||
auto powerMeterObj = root["power_meter"].to<JsonObject>();
|
||||
powerMeterObj["enabled"] = config.PowerMeter.Enabled;
|
||||
|
||||
if (config.PowerMeter.Enabled) {
|
||||
addTotalField(powerMeterObj, "Power", PowerMeter.getPowerTotal(false), "W", 1);
|
||||
addTotalField(powerMeterObj, "Power", PowerMeter.getPowerTotal(), "W", 1);
|
||||
}
|
||||
|
||||
if (!all) { _lastPublishPowerMeter = millis(); }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user