Feature: JK BMS Home Assistent integration
* pylontech HA integration: remove unused method/variable * make MqttHandlePylontechHassClass::publishConfig() private. there are no outside users of that method. * rename to MqttHandleBatteryHass * battery HA integration: merge methods and bring back forceUpdate(). even though the forceUpdate() method was not in use before, it makes sense to implement it and use it when the battery config changes. rather than controlling a separate flag, it now changes the _doPublish flag of the class, which also triggers publishing the device config to Home Assistant when an MQTT connection problem was detected. since both situations are now handled similarly, we can merge the loop() and publishConfig() methods. * battery: provider specific sensors for HA * move Battery MQTT loop to BatteryStats the BatteryStats class should handle the MQTT publishing, including the interval. for the calculation of a reasonable Home Assistent expiration value this class now also knows the maximum publish interval. * JK BMS: fix publishing values for Home Assistent Home Assistent values expire, because we set them to expire after three MQTT publish durations. for that reason, we need to re-publish all values after our self-inflicted full publish interval. * define JK BMS sensors for Home Assistent closes #482.
This commit is contained in:
parent
c2b49931be
commit
1865113842
@ -29,7 +29,6 @@ class BatteryClass {
|
||||
|
||||
Task _loopTask;
|
||||
|
||||
uint32_t _lastMqttPublish = 0;
|
||||
mutable std::mutex _mutex;
|
||||
std::unique_ptr<BatteryProvider> _upProvider = nullptr;
|
||||
};
|
||||
|
||||
@ -23,15 +23,24 @@ class BatteryStats {
|
||||
// convert stats to JSON for web application live view
|
||||
virtual void getLiveViewData(JsonVariant& root) const;
|
||||
|
||||
virtual void mqttPublish() const;
|
||||
void mqttLoop();
|
||||
|
||||
// the interval at which all battery datums will be re-published, even
|
||||
// if they did not change. used to calculate Home Assistent expiration.
|
||||
virtual uint32_t getMqttFullPublishIntervalMs() const;
|
||||
|
||||
bool isValid() const { return _lastUpdateSoC > 0 && _lastUpdate > 0; }
|
||||
|
||||
protected:
|
||||
virtual void mqttPublish() const;
|
||||
|
||||
String _manufacturer = "unknown";
|
||||
uint8_t _SoC = 0;
|
||||
uint32_t _lastUpdateSoC = 0;
|
||||
uint32_t _lastUpdate = 0;
|
||||
|
||||
private:
|
||||
uint32_t _lastMqttPublish = 0;
|
||||
};
|
||||
|
||||
class PylontechBatteryStats : public BatteryStats {
|
||||
@ -89,6 +98,8 @@ class JkBmsBatteryStats : public BatteryStats {
|
||||
|
||||
void mqttPublish() const final;
|
||||
|
||||
uint32_t getMqttFullPublishIntervalMs() const final { return 60 * 1000; }
|
||||
|
||||
void updateFrom(JkBms::DataPointContainer const& dp);
|
||||
|
||||
private:
|
||||
|
||||
@ -4,11 +4,10 @@
|
||||
#include <ArduinoJson.h>
|
||||
#include <TaskSchedulerDeclarations.h>
|
||||
|
||||
class MqttHandlePylontechHassClass {
|
||||
class MqttHandleBatteryHassClass {
|
||||
public:
|
||||
void init(Scheduler& scheduler);
|
||||
void publishConfig();
|
||||
void forceUpdate();
|
||||
void forceUpdate() { _doPublish = true; }
|
||||
|
||||
private:
|
||||
void loop();
|
||||
@ -19,9 +18,8 @@ private:
|
||||
|
||||
Task _loopTask;
|
||||
|
||||
bool _wasConnected = false;
|
||||
bool _updateForced = false;
|
||||
bool _doPublish = true;
|
||||
String serial = "0001"; // pseudo-serial, can be replaced in future with real serialnumber
|
||||
};
|
||||
|
||||
extern MqttHandlePylontechHassClass MqttHandlePylontechHass;
|
||||
extern MqttHandleBatteryHassClass MqttHandleBatteryHass;
|
||||
@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "Battery.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "PylontechCanReceiver.h"
|
||||
#include "JkBmsController.h"
|
||||
#include "VictronSmartShunt.h"
|
||||
@ -76,14 +75,5 @@ void BatteryClass::loop()
|
||||
|
||||
_upProvider->loop();
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
if (!MqttSettings.getConnected()
|
||||
|| (millis() - _lastMqttPublish) < (config.Mqtt.PublishInterval * 1000)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_upProvider->getStats()->mqttPublish();
|
||||
|
||||
_lastMqttPublish = millis();
|
||||
_upProvider->getStats()->mqttLoop();
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include "Configuration.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "JkBmsDataPoints.h"
|
||||
#include "MqttSettings.h"
|
||||
|
||||
template<typename T>
|
||||
static void addLiveViewInSection(JsonVariant& root,
|
||||
@ -187,6 +188,31 @@ void JkBmsBatteryStats::getJsonData(JsonVariant& root, bool verbose) const
|
||||
}
|
||||
}
|
||||
|
||||
void BatteryStats::mqttLoop()
|
||||
{
|
||||
auto& config = Configuration.get();
|
||||
|
||||
if (!MqttSettings.getConnected()
|
||||
|| (millis() - _lastMqttPublish) < (config.Mqtt.PublishInterval * 1000)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mqttPublish();
|
||||
|
||||
_lastMqttPublish = millis();
|
||||
}
|
||||
|
||||
uint32_t BatteryStats::getMqttFullPublishIntervalMs() const
|
||||
{
|
||||
auto& config = Configuration.get();
|
||||
|
||||
// this is the default interval, see mqttLoop(). mqttPublish()
|
||||
// implementations in derived classes may choose to publish some values
|
||||
// with a lower frequency and hence implement this method with a different
|
||||
// return value.
|
||||
return config.Mqtt.PublishInterval * 1000;
|
||||
}
|
||||
|
||||
void BatteryStats::mqttPublish() const
|
||||
{
|
||||
MqttSettings.publish(F("battery/manufacturer"), _manufacturer);
|
||||
@ -236,11 +262,10 @@ void JkBmsBatteryStats::mqttPublish() const
|
||||
Label::BatterySoCPercent // already published by base class
|
||||
};
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
// publish all topics every minute, unless the retain flag is enabled
|
||||
bool fullPublish = _lastFullMqttPublish + 60 * 1000 < millis();
|
||||
fullPublish &= !config.Mqtt.Retain;
|
||||
// regularly publish all topics regardless of whether or not their value changed
|
||||
bool neverFullyPublished = _lastFullMqttPublish == 0;
|
||||
bool intervalElapsed = _lastFullMqttPublish + getMqttFullPublishIntervalMs() < millis();
|
||||
bool fullPublish = neverFullyPublished || intervalElapsed;
|
||||
|
||||
for (auto iter = _dataPoints.cbegin(); iter != _dataPoints.cend(); ++iter) {
|
||||
// skip data points that did not change since last published
|
||||
|
||||
237
src/MqttHandleBatteryHass.cpp
Normal file
237
src/MqttHandleBatteryHass.cpp
Normal file
@ -0,0 +1,237 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "PylontechCanReceiver.h"
|
||||
#include "Battery.h"
|
||||
#include "MqttHandleBatteryHass.h"
|
||||
#include "Configuration.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "Utils.h"
|
||||
|
||||
MqttHandleBatteryHassClass MqttHandleBatteryHass;
|
||||
|
||||
void MqttHandleBatteryHassClass::init(Scheduler& scheduler)
|
||||
{
|
||||
scheduler.addTask(_loopTask);
|
||||
_loopTask.setCallback(std::bind(&MqttHandleBatteryHassClass::loop, this));
|
||||
_loopTask.setIterations(TASK_FOREVER);
|
||||
_loopTask.enable();
|
||||
}
|
||||
|
||||
void MqttHandleBatteryHassClass::loop()
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
if (!config.Battery.Enabled) { return; }
|
||||
|
||||
if (!config.Mqtt.Hass.Enabled) { return; }
|
||||
|
||||
// TODO(schlimmchen): this cannot make sure that transient
|
||||
// connection problems are actually always noticed.
|
||||
if (!MqttSettings.getConnected()) {
|
||||
_doPublish = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// only publish HA config once when (re-)connecting
|
||||
// to the MQTT broker or on config changes.
|
||||
if (!_doPublish) { return; }
|
||||
|
||||
// the MQTT battery provider does not re-publish the SoC under a different
|
||||
// known topic. we don't know the manufacture either. HASS auto-discovery
|
||||
// for that provider makes no sense.
|
||||
if (config.Battery.Provider != 2) {
|
||||
publishSensor("Manufacturer", "mdi:factory", "manufacturer");
|
||||
publishSensor("Data Age", "mdi:timer-sand", "dataAge", "duration", "measurement", "s");
|
||||
publishSensor("State of Charge (SoC)", "mdi:battery-medium", "stateOfCharge", "battery", "measurement", "%");
|
||||
}
|
||||
|
||||
switch (config.Battery.Provider) {
|
||||
case 0: // Pylontech Battery
|
||||
publishSensor("Battery voltage", NULL, "voltage", "voltage", "measurement", "V");
|
||||
publishSensor("Battery current", NULL, "current", "current", "measurement", "A");
|
||||
publishSensor("Temperature", NULL, "temperature", "temperature", "measurement", "°C");
|
||||
publishSensor("State of Health (SOH)", "mdi:heart-plus", "stateOfHealth", NULL, "measurement", "%");
|
||||
publishSensor("Charge voltage (BMS)", NULL, "settings/chargeVoltage", "voltage", "measurement", "V");
|
||||
publishSensor("Charge current limit", NULL, "settings/chargeCurrentLimitation", "current", "measurement", "A");
|
||||
publishSensor("Discharge current limit", NULL, "settings/dischargeCurrentLimitation", "current", "measurement", "A");
|
||||
|
||||
publishBinarySensor("Alarm Discharge current", "mdi:alert", "alarm/overCurrentDischarge", "1", "0");
|
||||
publishBinarySensor("Warning Discharge current", "mdi:alert-outline", "warning/highCurrentDischarge", "1", "0");
|
||||
|
||||
publishBinarySensor("Alarm Temperature low", "mdi:thermometer-low", "alarm/underTemperature", "1", "0");
|
||||
publishBinarySensor("Warning Temperature low", "mdi:thermometer-low", "warning/lowTemperature", "1", "0");
|
||||
|
||||
publishBinarySensor("Alarm Temperature high", "mdi:thermometer-high", "alarm/overTemperature", "1", "0");
|
||||
publishBinarySensor("Warning Temperature high", "mdi:thermometer-high", "warning/highTemperature", "1", "0");
|
||||
|
||||
publishBinarySensor("Alarm Voltage low", "mdi:alert", "alarm/underVoltage", "1", "0");
|
||||
publishBinarySensor("Warning Voltage low", "mdi:alert-outline", "warning/lowVoltage", "1", "0");
|
||||
|
||||
publishBinarySensor("Alarm Voltage high", "mdi:alert", "alarm/overVoltage", "1", "0");
|
||||
publishBinarySensor("Warning Voltage high", "mdi:alert-outline", "warning/highVoltage", "1", "0");
|
||||
|
||||
publishBinarySensor("Alarm BMS internal", "mdi:alert", "alarm/bmsInternal", "1", "0");
|
||||
publishBinarySensor("Warning BMS internal", "mdi:alert-outline", "warning/bmsInternal", "1", "0");
|
||||
|
||||
publishBinarySensor("Alarm High charge current", "mdi:alert", "alarm/overCurrentCharge", "1", "0");
|
||||
publishBinarySensor("Warning High charge current", "mdi:alert-outline", "warning/highCurrentCharge", "1", "0");
|
||||
|
||||
publishBinarySensor("Charge enabled", "mdi:battery-arrow-up", "charging/chargeEnabled", "1", "0");
|
||||
publishBinarySensor("Discharge enabled", "mdi:battery-arrow-down", "charging/dischargeEnabled", "1", "0");
|
||||
publishBinarySensor("Charge immediately", "mdi:alert", "charging/chargeImmediately", "1", "0");
|
||||
break;
|
||||
case 1: // JK BMS
|
||||
// caption icon topic dev. class state class unit
|
||||
publishSensor("Voltage", "mdi:battery-charging", "BatteryVoltageMilliVolt", "voltage", "measurement", "mV");
|
||||
publishSensor("Current", "mdi:current-dc", "BatteryCurrentMilliAmps", "current", "measurement", "mA");
|
||||
publishSensor("BMS Temperature", "mdi:thermometer", "BmsTempCelsius", "temperature", "measurement", "°C");
|
||||
publishSensor("Cell Voltage Diff", "mdi:battery-alert", "CellDiffMilliVolt", "voltage", "measurement", "mV");
|
||||
publishSensor("Charge Cycles", "mdi:counter", "BatteryCycles");
|
||||
publishSensor("Cycle Capacity", "mdi:battery-sync", "BatteryCycleCapacity");
|
||||
|
||||
publishBinarySensor("Charging Possible", "mdi:battery-arrow-up", "status/ChargingActive", "1", "0");
|
||||
publishBinarySensor("Discharging Possible", "mdi:battery-arrow-down", "status/DischargingActive", "1", "0");
|
||||
publishBinarySensor("Balancing Active", "mdi:scale-balance", "status/BalancingActive", "1", "0");
|
||||
|
||||
#define PBS(a, b, c) publishBinarySensor("Alarm: " a, "mdi:" b, "alarms/" c, "1", "0")
|
||||
PBS("Low Capacity", "battery-alert-variant-outline", "LowCapacity");
|
||||
PBS("BMS Overtemperature", "thermometer-alert", "BmsOvertemperature");
|
||||
PBS("Charging Overvoltage", "fuse-alert", "ChargingOvervoltage");
|
||||
PBS("Discharge Undervoltage", "fuse-alert", "DischargeUndervoltage");
|
||||
PBS("Battery Overtemperature", "thermometer-alert", "BatteryOvertemperature");
|
||||
PBS("Charging Overcurrent", "fuse-alert", "ChargingOvercurrent");
|
||||
PBS("Discharging Overcurrent", "fuse-alert", "DischargeOvercurrent");
|
||||
PBS("Cell Voltage Difference", "battery-alert", "CellVoltageDifference");
|
||||
PBS("Battery Box Overtemperature", "thermometer-alert", "BatteryBoxOvertemperature");
|
||||
PBS("Battery Undertemperature", "thermometer-alert", "BatteryUndertemperature");
|
||||
PBS("Cell Overvoltage", "battery-alert", "CellOvervoltage");
|
||||
PBS("Cell Undervoltage", "battery-alert", "CellUndervoltage");
|
||||
#undef PBS
|
||||
break;
|
||||
case 2: // SoC from MQTT
|
||||
break;
|
||||
case 3: // Victron SmartShunt
|
||||
break;
|
||||
}
|
||||
|
||||
_doPublish = false;
|
||||
}
|
||||
|
||||
void MqttHandleBatteryHassClass::publishSensor(const char* caption, const char* icon, const char* subTopic, const char* deviceClass, const char* stateClass, const char* unitOfMeasurement )
|
||||
{
|
||||
String sensorId = caption;
|
||||
sensorId.replace(" ", "_");
|
||||
sensorId.replace(".", "");
|
||||
sensorId.replace("(", "");
|
||||
sensorId.replace(")", "");
|
||||
sensorId.toLowerCase();
|
||||
|
||||
String configTopic = "sensor/dtu_battery_" + serial
|
||||
+ "/" + sensorId
|
||||
+ "/config";
|
||||
|
||||
String statTopic = MqttSettings.getPrefix() + "battery/";
|
||||
// omit serial to avoid a breaking change
|
||||
// statTopic.concat(serial);
|
||||
// statTopic.concat("/");
|
||||
statTopic.concat(subTopic);
|
||||
|
||||
DynamicJsonDocument root(1024);
|
||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
||||
return;
|
||||
}
|
||||
root["name"] = caption;
|
||||
root["stat_t"] = statTopic;
|
||||
root["uniq_id"] = serial + "_" + sensorId;
|
||||
|
||||
if (icon != NULL) {
|
||||
root["icon"] = icon;
|
||||
}
|
||||
|
||||
if (unitOfMeasurement != NULL) {
|
||||
root["unit_of_meas"] = unitOfMeasurement;
|
||||
}
|
||||
|
||||
JsonObject deviceObj = root.createNestedObject("dev");
|
||||
createDeviceInfo(deviceObj);
|
||||
|
||||
if (Configuration.get().Mqtt.Hass.Expire) {
|
||||
root["exp_aft"] = Battery.getStats()->getMqttFullPublishIntervalMs() * 3;
|
||||
}
|
||||
if (deviceClass != NULL) {
|
||||
root["dev_cla"] = deviceClass;
|
||||
}
|
||||
if (stateClass != NULL) {
|
||||
root["stat_cla"] = stateClass;
|
||||
}
|
||||
|
||||
char buffer[512];
|
||||
serializeJson(root, buffer);
|
||||
publish(configTopic, buffer);
|
||||
|
||||
}
|
||||
|
||||
void MqttHandleBatteryHassClass::publishBinarySensor(const char* caption, const char* icon, const char* subTopic, const char* payload_on, const char* payload_off)
|
||||
{
|
||||
String sensorId = caption;
|
||||
sensorId.replace(" ", "_");
|
||||
sensorId.replace(".", "");
|
||||
sensorId.replace("(", "");
|
||||
sensorId.replace(")", "");
|
||||
sensorId.replace(":", "");
|
||||
sensorId.toLowerCase();
|
||||
|
||||
String configTopic = "binary_sensor/dtu_battery_" + serial
|
||||
+ "/" + sensorId
|
||||
+ "/config";
|
||||
|
||||
String statTopic = MqttSettings.getPrefix() + "battery/";
|
||||
// omit serial to avoid a breaking change
|
||||
// statTopic.concat(serial);
|
||||
// statTopic.concat("/");
|
||||
statTopic.concat(subTopic);
|
||||
|
||||
DynamicJsonDocument root(1024);
|
||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
||||
return;
|
||||
}
|
||||
root["name"] = caption;
|
||||
root["uniq_id"] = serial + "_" + sensorId;
|
||||
root["stat_t"] = statTopic;
|
||||
root["pl_on"] = payload_on;
|
||||
root["pl_off"] = payload_off;
|
||||
|
||||
if (icon != NULL) {
|
||||
root["icon"] = icon;
|
||||
}
|
||||
|
||||
JsonObject deviceObj = root.createNestedObject("dev");
|
||||
createDeviceInfo(deviceObj);
|
||||
|
||||
char buffer[512];
|
||||
serializeJson(root, buffer);
|
||||
publish(configTopic, buffer);
|
||||
}
|
||||
|
||||
void MqttHandleBatteryHassClass::createDeviceInfo(JsonObject& object)
|
||||
{
|
||||
object["name"] = "Battery(" + serial + ")";
|
||||
|
||||
auto& config = Configuration.get();
|
||||
if (config.Battery.Provider == 1) {
|
||||
object["name"] = "JK BMS (" + Battery.getStats()->getManufacturer() + ")";
|
||||
}
|
||||
|
||||
object["ids"] = serial;
|
||||
object["cu"] = String("http://") + NetworkSettings.localIP().toString();
|
||||
object["mf"] = "OpenDTU";
|
||||
object["mdl"] = Battery.getStats()->getManufacturer();
|
||||
object["sw"] = AUTO_GIT_HASH;
|
||||
}
|
||||
|
||||
void MqttHandleBatteryHassClass::publish(const String& subtopic, const String& payload)
|
||||
{
|
||||
String topic = Configuration.get().Mqtt.Hass.Topic;
|
||||
topic += subtopic;
|
||||
MqttSettings.publishGeneric(topic.c_str(), payload.c_str(), Configuration.get().Mqtt.Hass.Retain);
|
||||
}
|
||||
@ -1,209 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "PylontechCanReceiver.h"
|
||||
#include "Battery.h"
|
||||
#include "MqttHandlePylontechHass.h"
|
||||
#include "Configuration.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "Utils.h"
|
||||
|
||||
MqttHandlePylontechHassClass MqttHandlePylontechHass;
|
||||
|
||||
void MqttHandlePylontechHassClass::init(Scheduler& scheduler)
|
||||
{
|
||||
scheduler.addTask(_loopTask);
|
||||
_loopTask.setCallback(std::bind(&MqttHandlePylontechHassClass::loop, this));
|
||||
_loopTask.setIterations(TASK_FOREVER);
|
||||
_loopTask.enable();
|
||||
}
|
||||
|
||||
void MqttHandlePylontechHassClass::loop()
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
if (!config.Battery.Enabled) {
|
||||
return;
|
||||
}
|
||||
if (_updateForced) {
|
||||
publishConfig();
|
||||
_updateForced = false;
|
||||
}
|
||||
|
||||
if (MqttSettings.getConnected() && !_wasConnected) {
|
||||
// Connection established
|
||||
_wasConnected = true;
|
||||
publishConfig();
|
||||
} else if (!MqttSettings.getConnected() && _wasConnected) {
|
||||
// Connection lost
|
||||
_wasConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
void MqttHandlePylontechHassClass::forceUpdate()
|
||||
{
|
||||
_updateForced = true;
|
||||
}
|
||||
|
||||
void MqttHandlePylontechHassClass::publishConfig()
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
if ((!config.Mqtt.Hass.Enabled) || (!config.Battery.Enabled)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MqttSettings.getConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// device info
|
||||
publishSensor("Manufacturer", "mdi:factory", "manufacturer");
|
||||
|
||||
// battery info
|
||||
publishSensor("Battery voltage", NULL, "voltage", "voltage", "measurement", "V");
|
||||
publishSensor("Battery current", NULL, "current", "current", "measurement", "A");
|
||||
publishSensor("Temperature", NULL, "temperature", "temperature", "measurement", "°C");
|
||||
publishSensor("State of Charge (SOC)", NULL, "stateOfCharge", "battery", "measurement", "%");
|
||||
publishSensor("State of Health (SOH)", "mdi:heart-plus", "stateOfHealth", NULL, "measurement", "%");
|
||||
publishSensor("Charge voltage (BMS)", NULL, "settings/chargeVoltage", "voltage", "measurement", "V");
|
||||
publishSensor("Charge current limit", NULL, "settings/chargeCurrentLimitation", "current", "measurement", "A");
|
||||
publishSensor("Discharge current limit", NULL, "settings/dischargeCurrentLimitation", "current", "measurement", "A");
|
||||
|
||||
publishBinarySensor("Alarm Discharge current", "mdi:alert", "alarm/overCurrentDischarge", "1", "0");
|
||||
publishBinarySensor("Warning Discharge current", "mdi:alert-outline", "warning/highCurrentDischarge", "1", "0");
|
||||
|
||||
publishBinarySensor("Alarm Temperature low", "mdi:thermometer-low", "alarm/underTemperature", "1", "0");
|
||||
publishBinarySensor("Warning Temperature low", "mdi:thermometer-low", "warning/lowTemperature", "1", "0");
|
||||
|
||||
publishBinarySensor("Alarm Temperature high", "mdi:thermometer-high", "alarm/overTemperature", "1", "0");
|
||||
publishBinarySensor("Warning Temperature high", "mdi:thermometer-high", "warning/highTemperature", "1", "0");
|
||||
|
||||
publishBinarySensor("Alarm Voltage low", "mdi:alert", "alarm/underVoltage", "1", "0");
|
||||
publishBinarySensor("Warning Voltage low", "mdi:alert-outline", "warning/lowVoltage", "1", "0");
|
||||
|
||||
publishBinarySensor("Alarm Voltage high", "mdi:alert", "alarm/overVoltage", "1", "0");
|
||||
publishBinarySensor("Warning Voltage high", "mdi:alert-outline", "warning/highVoltage", "1", "0");
|
||||
|
||||
publishBinarySensor("Alarm BMS internal", "mdi:alert", "alarm/bmsInternal", "1", "0");
|
||||
publishBinarySensor("Warning BMS internal", "mdi:alert-outline", "warning/bmsInternal", "1", "0");
|
||||
|
||||
publishBinarySensor("Alarm High charge current", "mdi:alert", "alarm/overCurrentCharge", "1", "0");
|
||||
publishBinarySensor("Warning High charge current", "mdi:alert-outline", "warning/highCurrentCharge", "1", "0");
|
||||
|
||||
publishBinarySensor("Charge enabled", "mdi:battery-arrow-up", "charging/chargeEnabled", "1", "0");
|
||||
publishBinarySensor("Discharge enabled", "mdi:battery-arrow-down", "charging/dischargeEnabled", "1", "0");
|
||||
publishBinarySensor("Charge immediately", "mdi:alert", "charging/chargeImmediately", "1", "0");
|
||||
|
||||
yield();
|
||||
}
|
||||
|
||||
void MqttHandlePylontechHassClass::publishSensor(const char* caption, const char* icon, const char* subTopic, const char* deviceClass, const char* stateClass, const char* unitOfMeasurement )
|
||||
{
|
||||
String sensorId = caption;
|
||||
sensorId.replace(" ", "_");
|
||||
sensorId.replace(".", "");
|
||||
sensorId.replace("(", "");
|
||||
sensorId.replace(")", "");
|
||||
sensorId.toLowerCase();
|
||||
|
||||
String configTopic = "sensor/dtu_battery_" + serial
|
||||
+ "/" + sensorId
|
||||
+ "/config";
|
||||
|
||||
String statTopic = MqttSettings.getPrefix() + "battery/";
|
||||
// omit serial to avoid a breaking change
|
||||
// statTopic.concat(serial);
|
||||
// statTopic.concat("/");
|
||||
statTopic.concat(subTopic);
|
||||
|
||||
DynamicJsonDocument root(1024);
|
||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
||||
return;
|
||||
}
|
||||
root["name"] = caption;
|
||||
root["stat_t"] = statTopic;
|
||||
root["uniq_id"] = serial + "_" + sensorId;
|
||||
|
||||
if (icon != NULL) {
|
||||
root["icon"] = icon;
|
||||
}
|
||||
|
||||
if (unitOfMeasurement != NULL) {
|
||||
root["unit_of_meas"] = unitOfMeasurement;
|
||||
}
|
||||
|
||||
JsonObject deviceObj = root.createNestedObject("dev");
|
||||
createDeviceInfo(deviceObj);
|
||||
|
||||
if (Configuration.get().Mqtt.Hass.Expire) {
|
||||
root["exp_aft"] = Configuration.get().Mqtt.PublishInterval * 3;
|
||||
}
|
||||
if (deviceClass != NULL) {
|
||||
root["dev_cla"] = deviceClass;
|
||||
}
|
||||
if (stateClass != NULL) {
|
||||
root["stat_cla"] = stateClass;
|
||||
}
|
||||
|
||||
char buffer[512];
|
||||
serializeJson(root, buffer);
|
||||
publish(configTopic, buffer);
|
||||
|
||||
}
|
||||
|
||||
void MqttHandlePylontechHassClass::publishBinarySensor(const char* caption, const char* icon, const char* subTopic, const char* payload_on, const char* payload_off)
|
||||
{
|
||||
String sensorId = caption;
|
||||
sensorId.replace(" ", "_");
|
||||
sensorId.replace(".", "");
|
||||
sensorId.replace("(", "");
|
||||
sensorId.replace(")", "");
|
||||
sensorId.toLowerCase();
|
||||
|
||||
String configTopic = "binary_sensor/dtu_battery_" + serial
|
||||
+ "/" + sensorId
|
||||
+ "/config";
|
||||
|
||||
String statTopic = MqttSettings.getPrefix() + "battery/";
|
||||
// omit serial to avoid a breaking change
|
||||
// statTopic.concat(serial);
|
||||
// statTopic.concat("/");
|
||||
statTopic.concat(subTopic);
|
||||
|
||||
DynamicJsonDocument root(1024);
|
||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
||||
return;
|
||||
}
|
||||
root["name"] = caption;
|
||||
root["uniq_id"] = serial + "_" + sensorId;
|
||||
root["stat_t"] = statTopic;
|
||||
root["pl_on"] = payload_on;
|
||||
root["pl_off"] = payload_off;
|
||||
|
||||
if (icon != NULL) {
|
||||
root["icon"] = icon;
|
||||
}
|
||||
|
||||
JsonObject deviceObj = root.createNestedObject("dev");
|
||||
createDeviceInfo(deviceObj);
|
||||
|
||||
char buffer[512];
|
||||
serializeJson(root, buffer);
|
||||
publish(configTopic, buffer);
|
||||
}
|
||||
|
||||
void MqttHandlePylontechHassClass::createDeviceInfo(JsonObject& object)
|
||||
{
|
||||
object["name"] = "Battery(" + serial + ")";
|
||||
object["ids"] = serial;
|
||||
object["cu"] = String("http://") + NetworkSettings.localIP().toString();
|
||||
object["mf"] = "OpenDTU";
|
||||
object["mdl"] = Battery.getStats()->getManufacturer();
|
||||
object["sw"] = AUTO_GIT_HASH;
|
||||
}
|
||||
|
||||
void MqttHandlePylontechHassClass::publish(const String& subtopic, const String& payload)
|
||||
{
|
||||
String topic = Configuration.get().Mqtt.Hass.Topic;
|
||||
topic += subtopic;
|
||||
MqttSettings.publishGeneric(topic.c_str(), payload.c_str(), Configuration.get().Mqtt.Hass.Retain);
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
#include "AsyncJson.h"
|
||||
#include "Battery.h"
|
||||
#include "Configuration.h"
|
||||
#include "PylontechCanReceiver.h"
|
||||
#include "MqttHandleBatteryHass.h"
|
||||
#include "WebApi.h"
|
||||
#include "WebApi_battery.h"
|
||||
#include "WebApi_errors.h"
|
||||
@ -111,4 +111,5 @@ void WebApiBatteryClass::onAdminPost(AsyncWebServerRequest* request)
|
||||
request->send(response);
|
||||
|
||||
Battery.updateSettings();
|
||||
MqttHandleBatteryHass.forceUpdate();
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
#include "MqttHandleDtu.h"
|
||||
#include "MqttHandleHass.h"
|
||||
#include "MqttHandleVedirectHass.h"
|
||||
#include "MqttHandlePylontechHass.h"
|
||||
#include "MqttHandleBatteryHass.h"
|
||||
#include "MqttHandleInverter.h"
|
||||
#include "MqttHandleInverterTotal.h"
|
||||
#include "MqttHandleVedirect.h"
|
||||
@ -116,7 +116,7 @@ void setup()
|
||||
MqttHandleVedirect.init(scheduler);
|
||||
MqttHandleHass.init(scheduler);
|
||||
MqttHandleVedirectHass.init(scheduler);
|
||||
MqttHandlePylontechHass.init(scheduler);
|
||||
MqttHandleBatteryHass.init(scheduler);
|
||||
MqttHandleHuawei.init(scheduler);
|
||||
MqttHandlePowerLimiter.init(scheduler);
|
||||
MessageOutput.println("done");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user