Merge pull request #640 from schlimmchen:jkbms-home-assistent-pr
JK BMS Home Assistent Integration
This commit is contained in:
commit
df5b416b3f
@ -29,7 +29,6 @@ class BatteryClass {
|
|||||||
|
|
||||||
Task _loopTask;
|
Task _loopTask;
|
||||||
|
|
||||||
uint32_t _lastMqttPublish = 0;
|
|
||||||
mutable std::mutex _mutex;
|
mutable std::mutex _mutex;
|
||||||
std::unique_ptr<BatteryProvider> _upProvider = nullptr;
|
std::unique_ptr<BatteryProvider> _upProvider = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -23,15 +23,24 @@ class BatteryStats {
|
|||||||
// convert stats to JSON for web application live view
|
// convert stats to JSON for web application live view
|
||||||
virtual void getLiveViewData(JsonVariant& root) const;
|
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; }
|
bool isValid() const { return _lastUpdateSoC > 0 && _lastUpdate > 0; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
virtual void mqttPublish() const;
|
||||||
|
|
||||||
String _manufacturer = "unknown";
|
String _manufacturer = "unknown";
|
||||||
uint8_t _SoC = 0;
|
uint8_t _SoC = 0;
|
||||||
uint32_t _lastUpdateSoC = 0;
|
uint32_t _lastUpdateSoC = 0;
|
||||||
uint32_t _lastUpdate = 0;
|
uint32_t _lastUpdate = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t _lastMqttPublish = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PylontechBatteryStats : public BatteryStats {
|
class PylontechBatteryStats : public BatteryStats {
|
||||||
@ -89,6 +98,8 @@ class JkBmsBatteryStats : public BatteryStats {
|
|||||||
|
|
||||||
void mqttPublish() const final;
|
void mqttPublish() const final;
|
||||||
|
|
||||||
|
uint32_t getMqttFullPublishIntervalMs() const final { return 60 * 1000; }
|
||||||
|
|
||||||
void updateFrom(JkBms::DataPointContainer const& dp);
|
void updateFrom(JkBms::DataPointContainer const& dp);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@ -4,11 +4,10 @@
|
|||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
|
|
||||||
class MqttHandlePylontechHassClass {
|
class MqttHandleBatteryHassClass {
|
||||||
public:
|
public:
|
||||||
void init(Scheduler& scheduler);
|
void init(Scheduler& scheduler);
|
||||||
void publishConfig();
|
void forceUpdate() { _doPublish = true; }
|
||||||
void forceUpdate();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loop();
|
void loop();
|
||||||
@ -19,9 +18,8 @@ private:
|
|||||||
|
|
||||||
Task _loopTask;
|
Task _loopTask;
|
||||||
|
|
||||||
bool _wasConnected = false;
|
bool _doPublish = true;
|
||||||
bool _updateForced = false;
|
|
||||||
String serial = "0001"; // pseudo-serial, can be replaced in future with real serialnumber
|
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
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include "MqttSettings.h"
|
|
||||||
#include "PylontechCanReceiver.h"
|
#include "PylontechCanReceiver.h"
|
||||||
#include "JkBmsController.h"
|
#include "JkBmsController.h"
|
||||||
#include "VictronSmartShunt.h"
|
#include "VictronSmartShunt.h"
|
||||||
@ -76,14 +75,5 @@ void BatteryClass::loop()
|
|||||||
|
|
||||||
_upProvider->loop();
|
_upProvider->loop();
|
||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
_upProvider->getStats()->mqttLoop();
|
||||||
|
|
||||||
if (!MqttSettings.getConnected()
|
|
||||||
|| (millis() - _lastMqttPublish) < (config.Mqtt.PublishInterval * 1000)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_upProvider->getStats()->mqttPublish();
|
|
||||||
|
|
||||||
_lastMqttPublish = millis();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include "MqttSettings.h"
|
#include "MqttSettings.h"
|
||||||
#include "JkBmsDataPoints.h"
|
#include "JkBmsDataPoints.h"
|
||||||
|
#include "MqttSettings.h"
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static void addLiveViewInSection(JsonVariant& root,
|
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
|
void BatteryStats::mqttPublish() const
|
||||||
{
|
{
|
||||||
MqttSettings.publish(F("battery/manufacturer"), _manufacturer);
|
MqttSettings.publish(F("battery/manufacturer"), _manufacturer);
|
||||||
@ -236,11 +262,10 @@ void JkBmsBatteryStats::mqttPublish() const
|
|||||||
Label::BatterySoCPercent // already published by base class
|
Label::BatterySoCPercent // already published by base class
|
||||||
};
|
};
|
||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
// regularly publish all topics regardless of whether or not their value changed
|
||||||
|
bool neverFullyPublished = _lastFullMqttPublish == 0;
|
||||||
// publish all topics every minute, unless the retain flag is enabled
|
bool intervalElapsed = _lastFullMqttPublish + getMqttFullPublishIntervalMs() < millis();
|
||||||
bool fullPublish = _lastFullMqttPublish + 60 * 1000 < millis();
|
bool fullPublish = neverFullyPublished || intervalElapsed;
|
||||||
fullPublish &= !config.Mqtt.Retain;
|
|
||||||
|
|
||||||
for (auto iter = _dataPoints.cbegin(); iter != _dataPoints.cend(); ++iter) {
|
for (auto iter = _dataPoints.cbegin(); iter != _dataPoints.cend(); ++iter) {
|
||||||
// skip data points that did not change since last published
|
// 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 "AsyncJson.h"
|
||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include "PylontechCanReceiver.h"
|
#include "MqttHandleBatteryHass.h"
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
#include "WebApi_battery.h"
|
#include "WebApi_battery.h"
|
||||||
#include "WebApi_errors.h"
|
#include "WebApi_errors.h"
|
||||||
@ -111,4 +111,5 @@ void WebApiBatteryClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
request->send(response);
|
request->send(response);
|
||||||
|
|
||||||
Battery.updateSettings();
|
Battery.updateSettings();
|
||||||
|
MqttHandleBatteryHass.forceUpdate();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
#include "MqttHandleDtu.h"
|
#include "MqttHandleDtu.h"
|
||||||
#include "MqttHandleHass.h"
|
#include "MqttHandleHass.h"
|
||||||
#include "MqttHandleVedirectHass.h"
|
#include "MqttHandleVedirectHass.h"
|
||||||
#include "MqttHandlePylontechHass.h"
|
#include "MqttHandleBatteryHass.h"
|
||||||
#include "MqttHandleInverter.h"
|
#include "MqttHandleInverter.h"
|
||||||
#include "MqttHandleInverterTotal.h"
|
#include "MqttHandleInverterTotal.h"
|
||||||
#include "MqttHandleVedirect.h"
|
#include "MqttHandleVedirect.h"
|
||||||
@ -116,6 +116,7 @@ void setup()
|
|||||||
MqttHandleVedirect.init(scheduler);
|
MqttHandleVedirect.init(scheduler);
|
||||||
MqttHandleHass.init(scheduler);
|
MqttHandleHass.init(scheduler);
|
||||||
MqttHandleVedirectHass.init(scheduler);
|
MqttHandleVedirectHass.init(scheduler);
|
||||||
|
MqttHandleBatteryHass.init(scheduler);
|
||||||
MqttHandleHuawei.init(scheduler);
|
MqttHandleHuawei.init(scheduler);
|
||||||
MqttHandlePowerLimiter.init(scheduler);
|
MqttHandlePowerLimiter.init(scheduler);
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user