From c2b49931be76bae5dddfcb451d5df9d9fa22feb1 Mon Sep 17 00:00:00 2001 From: Bernhard Kirchen Date: Sun, 3 Sep 2023 18:29:15 +0200 Subject: [PATCH 1/2] Fix: must call Pylontech Home Assistent init() method previously, the Pytlontech Home Assistent class implementation had an init() method, that was never called, as it did nothing. the class relied on its loop() method being called from the main loop(). after switching to the TaskScheduler approach, the Pylontech Home Assistent class init() method was adjusted to register a task that calls the loop() method periodically. however, the init() method was still not called. --- src/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.cpp b/src/main.cpp index 38fcbc2e6..702a54f6f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -116,6 +116,7 @@ void setup() MqttHandleVedirect.init(scheduler); MqttHandleHass.init(scheduler); MqttHandleVedirectHass.init(scheduler); + MqttHandlePylontechHass.init(scheduler); MqttHandleHuawei.init(scheduler); MqttHandlePowerLimiter.init(scheduler); MessageOutput.println("done"); From 1865113842449bd3010a7c654099a3ba7beadada Mon Sep 17 00:00:00 2001 From: Bernhard Kirchen Date: Sun, 3 Sep 2023 17:46:48 +0200 Subject: [PATCH 2/2] 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. --- include/Battery.h | 1 - include/BatteryStats.h | 13 +- ...ylontechHass.h => MqttHandleBatteryHass.h} | 10 +- src/Battery.cpp | 12 +- src/BatteryStats.cpp | 35 ++- src/MqttHandleBatteryHass.cpp | 237 ++++++++++++++++++ src/MqttHandlePylontechHass.cpp | 209 --------------- src/WebApi_battery.cpp | 3 +- src/main.cpp | 4 +- 9 files changed, 288 insertions(+), 236 deletions(-) rename include/{MqttHandlePylontechHass.h => MqttHandleBatteryHass.h} (78%) create mode 100644 src/MqttHandleBatteryHass.cpp delete mode 100644 src/MqttHandlePylontechHass.cpp diff --git a/include/Battery.h b/include/Battery.h index ffb6f47d8..b5f5ace63 100644 --- a/include/Battery.h +++ b/include/Battery.h @@ -29,7 +29,6 @@ class BatteryClass { Task _loopTask; - uint32_t _lastMqttPublish = 0; mutable std::mutex _mutex; std::unique_ptr _upProvider = nullptr; }; diff --git a/include/BatteryStats.h b/include/BatteryStats.h index 8ff129f41..36eed06a3 100644 --- a/include/BatteryStats.h +++ b/include/BatteryStats.h @@ -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: diff --git a/include/MqttHandlePylontechHass.h b/include/MqttHandleBatteryHass.h similarity index 78% rename from include/MqttHandlePylontechHass.h rename to include/MqttHandleBatteryHass.h index 64f5a841f..f328a6f88 100644 --- a/include/MqttHandlePylontechHass.h +++ b/include/MqttHandleBatteryHass.h @@ -4,11 +4,10 @@ #include #include -class MqttHandlePylontechHassClass { +class MqttHandleBatteryHassClass { public: void init(Scheduler& scheduler); - void publishConfig(); - void forceUpdate(); + void forceUpdate() { _doPublish = true; } private: void loop(); @@ -19,9 +18,8 @@ class MqttHandlePylontechHassClass { 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; \ No newline at end of file +extern MqttHandleBatteryHassClass MqttHandleBatteryHass; diff --git a/src/Battery.cpp b/src/Battery.cpp index 9fdc6e273..381fdc952 100644 --- a/src/Battery.cpp +++ b/src/Battery.cpp @@ -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(); } diff --git a/src/BatteryStats.cpp b/src/BatteryStats.cpp index c2c9d9d02..606a372fd 100644 --- a/src/BatteryStats.cpp +++ b/src/BatteryStats.cpp @@ -5,6 +5,7 @@ #include "Configuration.h" #include "MqttSettings.h" #include "JkBmsDataPoints.h" +#include "MqttSettings.h" template 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 diff --git a/src/MqttHandleBatteryHass.cpp b/src/MqttHandleBatteryHass.cpp new file mode 100644 index 000000000..7cad09222 --- /dev/null +++ b/src/MqttHandleBatteryHass.cpp @@ -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); +} diff --git a/src/MqttHandlePylontechHass.cpp b/src/MqttHandlePylontechHass.cpp deleted file mode 100644 index d4afbb2d3..000000000 --- a/src/MqttHandlePylontechHass.cpp +++ /dev/null @@ -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); -} diff --git a/src/WebApi_battery.cpp b/src/WebApi_battery.cpp index eec84d7b7..b96eb2cf6 100644 --- a/src/WebApi_battery.cpp +++ b/src/WebApi_battery.cpp @@ -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(); } diff --git a/src/main.cpp b/src/main.cpp index 702a54f6f..dd080832e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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");