210 lines
7.3 KiB
C++
210 lines
7.3 KiB
C++
// 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);
|
|
}
|