mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-09 17:29:50 +03:00
MQTT updates: added HA discovery, removed heartbeat - HomeAssistant Discovery #288
This commit is contained in:
@@ -75,34 +75,30 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
return command_info(value, id, object);
|
||||
});
|
||||
|
||||
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
|
||||
mqtt_format_ = settings.mqtt_format; // single, nested or ha
|
||||
|
||||
if (mqtt_format_ == MQTT_format::HA) {
|
||||
register_mqtt_ha_config();
|
||||
}
|
||||
});
|
||||
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
|
||||
register_mqtt_ha_config();
|
||||
}
|
||||
}
|
||||
|
||||
// create the config topic for Home Assistant MQTT Discovery
|
||||
// homeassistant/sensor/ems-esp/boiler
|
||||
// state is /state
|
||||
// config is /config
|
||||
// create the config topics for Home Assistant MQTT Discovery
|
||||
// for each of the main elements
|
||||
void Boiler::register_mqtt_ha_config() {
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||
Mqtt::register_mqtt_ha_binary_sensor(F("Boiler DHW"), "tapwater_active");
|
||||
Mqtt::register_mqtt_ha_binary_sensor(F("Boiler Heating"), "heating_active");
|
||||
|
||||
/*
|
||||
* not finished yet - see https://github.com/proddy/EMS-ESP/issues/288
|
||||
|
||||
doc["name"] = "boiler";
|
||||
doc["uniq_id"] = "boiler";
|
||||
|
||||
// Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA
|
||||
Mqtt::publish("homeassistant/sensor/ems-esp/boiler/config", doc, true); // publish the config payload with retain flag
|
||||
|
||||
*/
|
||||
Mqtt::register_mqtt_ha_sensor(F("Service Code"), this->device_type(), "serviceCode", "", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Service Code number"), this->device_type(), "serviceCodeNumber", "", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Boiler WW Selected Temp"), this->device_type(), "wWSelTemp", "°C", "mdi:coolant-temperature");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Selected flow temperature"), this->device_type(), "selFlowTemp", "°C", "mdi:coolant-temperature");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Current flow temperature"), this->device_type(), "curFlowTemp", "°C", "mdi:coolant-temperature");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Warm Water set temperature"), this->device_type(), "wWSetTemp", "°C", "mdi:coolant-temperature");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Warm Water current temperature (intern)"), this->device_type(), "wWCurTmp", "°C", "mdi:coolant-temperature");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Warm Water current temperature (extern)"), this->device_type(), "wWCurTmp2", "°C", "mdi:coolant-temperature");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Pump modulation"), this->device_type(), "pumpMod", "%", "mdi:sine-wave");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Heat Pump modulation"), this->device_type(), "pumpMod2", "%", "mdi:sine-wave");
|
||||
}
|
||||
|
||||
// send stuff to the Web UI
|
||||
void Boiler::device_info_web(JsonArray & root) {
|
||||
JsonObject dataElement;
|
||||
|
||||
@@ -341,13 +337,14 @@ bool Boiler::export_values(JsonObject & output) {
|
||||
|
||||
// publish values via MQTT
|
||||
void Boiler::publish_values() {
|
||||
// const size_t capacity = JSON_OBJECT_SIZE(56); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/
|
||||
// DynamicJsonDocument doc(capacity);
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
|
||||
JsonObject output = doc.to<JsonObject>();
|
||||
if (export_values(output)) {
|
||||
Mqtt::publish(F("boiler_data"), doc.as<JsonObject>());
|
||||
}
|
||||
|
||||
// send out heating and tapwater status - even if there is no change (force = true)
|
||||
check_active();
|
||||
}
|
||||
|
||||
// called after a process command is called, to check values and see if we need to force an MQTT publish
|
||||
@@ -491,6 +488,19 @@ void Boiler::show_values(uuid::console::Shell & shell) {
|
||||
* If a value has changed, post it immediately to MQTT so we get real time data
|
||||
*/
|
||||
void Boiler::check_active() {
|
||||
if ((boilerState_ & 0x09) != (last_boilerState & 0x09)) {
|
||||
char s[5];
|
||||
Mqtt::publish(F("heating_active"), Helpers::render_boolean(s, ((boilerState_ & 0x09) == 0x09)));
|
||||
}
|
||||
if ((boilerState_ & 0x0A) != (last_boilerState & 0x0A)) {
|
||||
char s[5];
|
||||
Mqtt::publish(F("tapwater_active"), Helpers::render_boolean(s, ((boilerState_ & 0x0A) == 0x0A)));
|
||||
EMSESP::tap_water_active((boilerState_ & 0x0A) == 0x0A);
|
||||
}
|
||||
last_boilerState = boilerState_;
|
||||
|
||||
/*
|
||||
|
||||
// hot tap water, using flow to check instead of the burner power
|
||||
// send these values back to the main EMSESP, so other classes (e.g. Shower) can use it
|
||||
if (Helpers::hasValue(wWCurFlow_) && Helpers::hasValue(burnGas_) && (wWType_ > 0) && (wWType_ < 3)) {
|
||||
@@ -500,20 +510,21 @@ void Boiler::check_active() {
|
||||
|
||||
// heating
|
||||
// using a quick hack for checking the heating by looking at the Selected Flow Temp, but doesn't work for all boilers apparently
|
||||
if (Helpers::hasValue(tap_water_active_, EMS_VALUE_BOOL) && Helpers::hasValue(selFlowTemp_) && Helpers::hasValue(burnGas_)) {
|
||||
if (Helpers::hasValue(selFlowTemp_) && Helpers::hasValue(burnGas_)) {
|
||||
heating_active_ = (!tap_water_active_ && ((selFlowTemp_ >= EMS_BOILER_SELFLOWTEMP_HEATING) && (burnGas_ != EMS_VALUE_BOOL_OFF)));
|
||||
}
|
||||
|
||||
// see if the heating or hot tap water has changed, if so send
|
||||
// last_boilerActive stores heating in bit 1 and tap water in bit 2
|
||||
if (Helpers::hasValue(tap_water_active_, EMS_VALUE_BOOL) && Helpers::hasValue(heating_active_, EMS_VALUE_BOOL)) {
|
||||
uint8_t latest_boilerState = (tap_water_active_ << 1) + heating_active_;
|
||||
if (latest_boilerState != last_boilerState) {
|
||||
last_boilerState = latest_boilerState;
|
||||
Mqtt::publish(F("tapwater_active"), tap_water_active_);
|
||||
Mqtt::publish(F("heating_active"), heating_active_);
|
||||
}
|
||||
uint8_t latest_boilerState = (tap_water_active_ << 1) + heating_active_;
|
||||
if (latest_boilerState != last_boilerState) {
|
||||
last_boilerState = latest_boilerState;
|
||||
static char s[10];
|
||||
Mqtt::publish(F("tapwater_active"), Helpers::render_boolean(s, tap_water_active_));
|
||||
static char s2[10];
|
||||
Mqtt::publish(F("heating_active"), Helpers::render_boolean(s2, heating_active_));
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// 0x33
|
||||
@@ -533,6 +544,7 @@ void Boiler::process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram) {
|
||||
changed_ |= telegram->read_value(curFlowTemp_, 1);
|
||||
changed_ |= telegram->read_value(selBurnPow_, 3); // burn power max setting
|
||||
changed_ |= telegram->read_value(curBurnPow_, 4);
|
||||
changed_ |= telegram->read_value(boilerState_, 5);
|
||||
|
||||
changed_ |= telegram->read_bitvalue(burnGas_, 7, 0);
|
||||
changed_ |= telegram->read_bitvalue(fanWork_, 7, 2);
|
||||
|
||||
@@ -47,12 +47,13 @@ class Boiler : public EMSdevice {
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
void register_mqtt_ha_config();
|
||||
void register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, const char * entity);
|
||||
void register_mqtt_ha_sensor(const __FlashStringHelper * name, const char * entity, const char * uom, const char * icon);
|
||||
void check_active();
|
||||
bool export_values(JsonObject & doc);
|
||||
|
||||
uint8_t last_boilerState = 0xFF; // remember last state of heating and warm water on/off
|
||||
uint8_t mqtt_format_; // single, nested or ha
|
||||
bool changed_ = false;
|
||||
bool changed_ = false;
|
||||
|
||||
static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33;
|
||||
static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D;
|
||||
@@ -95,6 +96,7 @@ class Boiler : public EMSdevice {
|
||||
uint8_t sysPress_ = EMS_VALUE_UINT_NOTSET; // System pressure
|
||||
char serviceCodeChar_[3] = {'\0'}; // 2 character status/service code
|
||||
uint16_t serviceCode_ = EMS_VALUE_USHORT_NOTSET; // error/service code
|
||||
uint8_t boilerState_ = EMS_VALUE_BOOL_NOTSET; // State flag, used on HT3
|
||||
|
||||
// UBAMonitorSlow - 0x19 on EMS1
|
||||
int16_t extTemp_ = EMS_VALUE_SHORT_NOTSET; // Outside temperature
|
||||
@@ -141,8 +143,8 @@ class Boiler : public EMSdevice {
|
||||
uint8_t setWWPumpPow_ = EMS_VALUE_UINT_NOTSET; // ww pump speed/power?
|
||||
|
||||
// other internal calculated params
|
||||
uint8_t tap_water_active_ = EMS_VALUE_BOOL_NOTSET; // Hot tap water is on/off
|
||||
uint8_t heating_active_ = EMS_VALUE_BOOL_NOTSET; // Central heating is on/off
|
||||
bool tap_water_active_ = false; // Hot tap water is on/off
|
||||
bool heating_active_ = false; // Central heating is on/off
|
||||
uint8_t pumpMod2_ = EMS_VALUE_UINT_NOTSET; // heatpump modulation from 0xE3 (heatpumps)
|
||||
|
||||
bool command_info(const char * value, const int8_t id, JsonObject & output);
|
||||
@@ -187,4 +189,4 @@ class Boiler : public EMSdevice {
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -62,16 +62,17 @@ Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
|
||||
|
||||
// output json to web UI
|
||||
void Mixing::device_info_web(JsonArray & root) {
|
||||
if (type_ == Type::NONE) {
|
||||
if (type() == Type::NONE) {
|
||||
return; // don't have any values yet
|
||||
}
|
||||
|
||||
if (type_ == Type::WWC) {
|
||||
if (type() == Type::WWC) {
|
||||
render_value_json(root, "", F("Warm Water Circuit"), hc_, nullptr);
|
||||
render_value_json(root, "", F("Current warm water temperature"), flowTemp_, F_(degrees), 10);
|
||||
render_value_json(root, "", F("Current pump status"), pump_, nullptr);
|
||||
render_value_json(root, "", F("Current pump status"), pump_, nullptr, EMS_VALUE_BOOL);
|
||||
render_value_json(root, "", F("Current temperature status"), status_, nullptr);
|
||||
} else {
|
||||
// HC
|
||||
render_value_json(root, "", F("Heating Circuit"), hc_, nullptr);
|
||||
render_value_json(root, "", F("Current flow temperature"), flowTemp_, F_(degrees), 10);
|
||||
render_value_json(root, "", F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
|
||||
@@ -93,14 +94,14 @@ bool Mixing::updated_values() {
|
||||
void Mixing::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
|
||||
if (type_ == Type::NONE) {
|
||||
if (type() == Type::NONE) {
|
||||
return; // don't have any values yet
|
||||
}
|
||||
|
||||
if (type_ == Type::WWC) {
|
||||
if (type() == Type::WWC) {
|
||||
print_value(shell, 2, F("Warm Water Circuit"), hc_, nullptr);
|
||||
print_value(shell, 4, F("Current warm water temperature"), flowTemp_, F_(degrees), 10);
|
||||
print_value(shell, 4, F("Current pump status"), pump_, nullptr);
|
||||
print_value(shell, 4, F("Current pump status"), pump_, nullptr, EMS_VALUE_BOOL);
|
||||
print_value(shell, 4, F("Current temperature status"), status_, nullptr);
|
||||
} else {
|
||||
print_value(shell, 2, F("Heating Circuit"), hc_, nullptr);
|
||||
@@ -110,7 +111,6 @@ void Mixing::show_values(uuid::console::Shell & shell) {
|
||||
print_value(shell, 4, F("Current valve status"), status_, F_(percent));
|
||||
}
|
||||
|
||||
|
||||
shell.println();
|
||||
}
|
||||
|
||||
@@ -129,13 +129,34 @@ void Mixing::publish_values() {
|
||||
strlcpy(topic, "mixing_data", 30);
|
||||
strlcat(topic, Helpers::itoa(s, device_id() - 0x20 + 1), 30); // append hc to topic
|
||||
Mqtt::publish(topic, doc.as<JsonObject>());
|
||||
|
||||
// if we're using Home Assistant and haven't created the MQTT Discovery topics, do it now
|
||||
if ((Mqtt::mqtt_format() == Mqtt::Format::HA) && (!ha_created_)) {
|
||||
register_mqtt_ha_config();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// publish config topic for HA MQTT Discovery
|
||||
void Mixing::register_mqtt_ha_config() {
|
||||
if (this->type() == Type::HC) {
|
||||
Mqtt::register_mqtt_ha_sensor(F("Current flow temperature"), this->device_type(), "flowTemp", "°C", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Setpoint flow temperature"), this->device_type(), "flowSetTemp", "°C", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Current pump status"), this->device_type(), "pumpStatus", "", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Current valve status"), this->device_type(), "valveStatus", "", "");
|
||||
} else {
|
||||
// WWC
|
||||
Mqtt::register_mqtt_ha_sensor(F("Current flow temperature"), this->device_type(), "wwTemp", "°C", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Current pump status"), this->device_type(), "pumpStatus", "", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Current temperature status"), this->device_type(), "tempStatus", "", "");
|
||||
}
|
||||
ha_created_ = true;
|
||||
}
|
||||
|
||||
// creates JSON doc from values
|
||||
// returns false if empty
|
||||
bool Mixing::export_values(JsonObject & output) {
|
||||
switch (type_) {
|
||||
switch (this->type()) {
|
||||
case Type::HC:
|
||||
output["type"] = "hc";
|
||||
if (Helpers::hasValue(flowTemp_)) {
|
||||
@@ -159,7 +180,8 @@ bool Mixing::export_values(JsonObject & output) {
|
||||
output["wwTemp"] = (float)flowTemp_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(pump_)) {
|
||||
output["pumpStatus"] = pump_;
|
||||
char s[5]; // for formatting strings
|
||||
output["pumpStatus"] = Helpers::render_value(s, pump_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(status_)) {
|
||||
output["tempStatus"] = status_;
|
||||
@@ -179,11 +201,11 @@ bool Mixing::export_values(JsonObject & output) {
|
||||
// e.g. A0 00 FF 00 01 D7 00 00 00 80 00 00 00 00 03 C5
|
||||
// A0 0B FF 00 01 D7 00 00 00 80 00 00 00 00 03 80
|
||||
void Mixing::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram) {
|
||||
type_ = Type::HC;
|
||||
hc_ = telegram->type_id - 0x02D7 + 1; // determine which circuit this is
|
||||
type(Type::HC);
|
||||
hc_ = telegram->type_id - 0x02D7 + 1; // determine which circuit this is
|
||||
changed_ |= telegram->read_value(flowTemp_, 3); // is * 10
|
||||
changed_ |= telegram->read_value(flowSetTemp_, 5);
|
||||
changed_ |= telegram->read_value(pump_, 0);
|
||||
changed_ |= telegram->read_bitvalue(pump_, 2, 0);
|
||||
changed_ |= telegram->read_value(status_, 2); // valve status
|
||||
}
|
||||
|
||||
@@ -191,10 +213,10 @@ void Mixing::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> tele
|
||||
// e.g. A9 00 FF 00 02 32 02 6C 00 3C 00 3C 3C 46 02 03 03 00 3C // on 0x28
|
||||
// A8 00 FF 00 02 31 02 35 00 3C 00 3C 3C 46 02 03 03 00 3C // in 0x29
|
||||
void Mixing::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram) {
|
||||
type_ = Type::WWC;
|
||||
hc_ = telegram->type_id - 0x0331 + 1; // determine which circuit this is. There are max 2.
|
||||
type(Type::WWC);
|
||||
hc_ = telegram->type_id - 0x0331 + 1; // determine which circuit this is. There are max 2.
|
||||
changed_ |= telegram->read_value(flowTemp_, 0); // is * 10
|
||||
changed_ |= telegram->read_value(pump_, 2);
|
||||
changed_ |= telegram->read_bitvalue(pump_, 2, 0);
|
||||
changed_ |= telegram->read_value(status_, 11); // temp status
|
||||
}
|
||||
|
||||
@@ -202,18 +224,20 @@ void Mixing::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> tel
|
||||
// e.g. A0 00 FF 00 00 0C 01 00 00 00 00 00 54
|
||||
// A1 00 FF 00 00 0C 02 04 00 01 1D 00 82
|
||||
void Mixing::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
type_ = Type::HC;
|
||||
type(Type::HC);
|
||||
hc_ = device_id() - 0x20 + 1;
|
||||
uint8_t ismixed = 0;
|
||||
changed_ |= telegram->read_value(ismixed, 0); // check if circuit is active, 0-off, 1-unmixed, 2-mixed
|
||||
if (ismixed == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ismixed == 2) { // we have a mixed circuit
|
||||
changed_ |= telegram->read_value(flowTemp_, 3); // is * 10
|
||||
changed_ |= telegram->read_value(flowSetTemp_, 5);
|
||||
changed_ |= telegram->read_value(status_, 2); // valve status
|
||||
}
|
||||
|
||||
changed_ |= telegram->read_bitvalue(pump_, 1, 0); // pump is also in unmixed circuits
|
||||
}
|
||||
|
||||
@@ -221,14 +245,14 @@ void Mixing::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram)
|
||||
// e.g. Mixing Module -> All, type 0xAB, telegram: 21 00 AB 00 2D 01 BE 64 04 01 00 (CRC=15) #data=7
|
||||
// see also https://github.com/proddy/EMS-ESP/issues/386
|
||||
void Mixing::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
type_ = Type::HC;
|
||||
type(Type::HC);
|
||||
|
||||
// the heating circuit is determine by which device_id it is, 0x20 - 0x23
|
||||
// 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module
|
||||
// see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918
|
||||
hc_ = device_id() - 0x20 + 1;
|
||||
changed_ |= telegram->read_value(flowTemp_, 1); // is * 10
|
||||
changed_ |= telegram->read_value(pump_, 3);
|
||||
changed_ |= telegram->read_bitvalue(pump_, 3, 0);
|
||||
changed_ |= telegram->read_value(flowSetTemp_, 0);
|
||||
changed_ |= telegram->read_value(status_, 4); // valve status -100 to 100
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ class Mixing : public EMSdevice {
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
bool export_values(JsonObject & doc);
|
||||
void register_mqtt_ha_config();
|
||||
bool command_info(const char * value, const int8_t id, JsonObject & output);
|
||||
|
||||
void process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram);
|
||||
@@ -60,6 +61,14 @@ class Mixing : public EMSdevice {
|
||||
WWC // warm water circuit
|
||||
};
|
||||
|
||||
Type type() const {
|
||||
return type_;
|
||||
}
|
||||
|
||||
void type(Type new_type) {
|
||||
type_ = new_type;
|
||||
}
|
||||
|
||||
private:
|
||||
uint16_t hc_ = EMS_VALUE_USHORT_NOTSET;
|
||||
uint16_t flowTemp_ = EMS_VALUE_USHORT_NOTSET;
|
||||
@@ -68,7 +77,8 @@ class Mixing : public EMSdevice {
|
||||
uint8_t flowSetTemp_ = EMS_VALUE_UINT_NOTSET;
|
||||
Type type_ = Type::NONE;
|
||||
|
||||
bool changed_ = false;
|
||||
bool changed_ = false;
|
||||
bool ha_created_ = false; // for HA MQTT Discovery
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -116,8 +116,33 @@ void Solar::publish_values() {
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||
JsonObject output = doc.to<JsonObject>();
|
||||
if (export_values(output)) {
|
||||
Mqtt::publish(F("sm_data"), doc.as<JsonObject>());
|
||||
Mqtt::publish(F("solar_data"), doc.as<JsonObject>());
|
||||
}
|
||||
|
||||
// if we're using Home Assistant and haven't created the MQTT Discovery topics, do it now
|
||||
if ((Mqtt::mqtt_format() == Mqtt::Format::HA) && (!ha_created_)) {
|
||||
register_mqtt_ha_config();
|
||||
}
|
||||
}
|
||||
|
||||
// publish config topic for HA MQTT Discovery
|
||||
void Solar::register_mqtt_ha_config() {
|
||||
Mqtt::register_mqtt_ha_sensor(F("Collector temperature (TS1)"), this->device_type(), "collectorTemp", "°C", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Bottom temperature (TS2)"), this->device_type(), "tankBottomTemp", "°C", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Bottom temperature (TS5)"), this->device_type(), "tankBottomTemp2", "°C", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Heat exchanger temperature (TS6)"), this->device_type(), "heatExchangerTemp", "°C", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Solar pump modulation (PS1)"), this->device_type(), "solarPumpModulation", "%", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Cylinder pump modulation (PS5)"), this->device_type(), "cylinderPumpModulation", "%", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Pump working time"), this->device_type(), "pumpWorkMin", "", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Energy last hour"), this->device_type(), "energyLastHour", "", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Energy today"), this->device_type(), "energyToday", "", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Energy total"), this->device_type(), "energyTotal", "", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Solar Pump (PS1) active"), this->device_type(), "solarPump", "", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Valve (VS2) status"), this->device_type(), "valveStatus", "", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Tank Heated"), this->device_type(), "tankHeated", "", "");
|
||||
Mqtt::register_mqtt_ha_sensor(F("Collector shutdown"), this->device_type(), "collectorShutdown", "", "");
|
||||
|
||||
ha_created_ = true;
|
||||
}
|
||||
|
||||
// creates JSON doc from values
|
||||
|
||||
@@ -46,6 +46,7 @@ class Solar : public EMSdevice {
|
||||
|
||||
bool export_values(JsonObject & doc);
|
||||
bool command_info(const char * value, const int8_t id, JsonObject & output);
|
||||
void register_mqtt_ha_config();
|
||||
|
||||
int16_t collectorTemp_ = EMS_VALUE_SHORT_NOTSET; // TS1: Temperature sensor for collector array 1
|
||||
int16_t tankBottomTemp_ = EMS_VALUE_SHORT_NOTSET; // TS2: Temperature sensor 1 cylinder, bottom (solar thermal system)
|
||||
@@ -67,7 +68,8 @@ class Solar : public EMSdevice {
|
||||
uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET;
|
||||
uint8_t userFlag_ = EMS_VALUE_BOOL_NOTSET;
|
||||
|
||||
bool changed_ = false;
|
||||
bool changed_ = false;
|
||||
bool ha_created_ = false; // for HA MQTT Discovery
|
||||
|
||||
void process_SM10Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_SM100Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
@@ -137,10 +137,6 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
|
||||
}
|
||||
}
|
||||
|
||||
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
|
||||
mqtt_format_ = settings.mqtt_format; // single, nested or ha
|
||||
});
|
||||
|
||||
if (actual_master_thermostat != device_id) {
|
||||
LOG_DEBUG(F("Adding new thermostat with device ID 0x%02X"), device_id);
|
||||
return; // don't fetch data if more than 1 thermostat
|
||||
@@ -236,7 +232,7 @@ bool Thermostat::updated_values() {
|
||||
// info API command
|
||||
// returns the same MQTT publish payload in Nested format
|
||||
bool Thermostat::command_info(const char * value, const int8_t id, JsonObject & output) {
|
||||
return (export_values(MQTT_format::NESTED, output));
|
||||
return (export_values(Mqtt::Format::NESTED, output));
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
@@ -247,9 +243,11 @@ void Thermostat::publish_values() {
|
||||
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||
JsonObject output = doc.to<JsonObject>();
|
||||
export_values(mqtt_format_, output);
|
||||
|
||||
if (mqtt_format_ == MQTT_format::NESTED) {
|
||||
export_values(Mqtt::mqtt_format(), output);
|
||||
|
||||
// if we're in SINGLE mode the MQTT would have been published on the export_values() function for each hc
|
||||
if (Mqtt::mqtt_format() != Mqtt::Format::SINGLE) {
|
||||
Mqtt::publish(F("thermostat_data"), output);
|
||||
}
|
||||
}
|
||||
@@ -300,7 +298,7 @@ bool Thermostat::export_values(uint8_t mqtt_format, JsonObject & rootThermostat)
|
||||
}
|
||||
|
||||
// send this specific data using the thermostat_data topic
|
||||
if (mqtt_format != MQTT_format::NESTED) {
|
||||
if (mqtt_format != Mqtt::Format::NESTED) {
|
||||
Mqtt::publish(F("thermostat_data"), rootThermostat);
|
||||
rootThermostat.clear(); // clear object
|
||||
}
|
||||
@@ -313,7 +311,7 @@ bool Thermostat::export_values(uint8_t mqtt_format, JsonObject & rootThermostat)
|
||||
has_data = true;
|
||||
|
||||
// if the MQTT format is 'nested' or 'ha' then create the parent object hc<n>
|
||||
if ((mqtt_format == MQTT_format::NESTED) || (mqtt_format == MQTT_format::HA)) {
|
||||
if ((mqtt_format == Mqtt::Format::NESTED) || (mqtt_format == Mqtt::Format::HA)) {
|
||||
char hc_name[10]; // hc{1-4}
|
||||
strlcpy(hc_name, "hc", 10);
|
||||
char s[3];
|
||||
@@ -395,10 +393,10 @@ bool Thermostat::export_values(uint8_t mqtt_format, JsonObject & rootThermostat)
|
||||
}
|
||||
|
||||
// mode - always force showing this when in HA so not to break HA's climate component
|
||||
if ((Helpers::hasValue(hc->mode)) || (mqtt_format == MQTT_format::HA)) {
|
||||
if ((Helpers::hasValue(hc->mode)) || (mqtt_format == Mqtt::Format::HA)) {
|
||||
uint8_t hc_mode = hc->get_mode(flags);
|
||||
// if we're sending to HA the only valid mode types are heat, auto and off
|
||||
if (mqtt_format == MQTT_format::HA) {
|
||||
if (mqtt_format == Mqtt::Format::HA) {
|
||||
if ((hc_mode == HeatingCircuit::Mode::MANUAL) || (hc_mode == HeatingCircuit::Mode::DAY)) {
|
||||
hc_mode = HeatingCircuit::Mode::HEAT;
|
||||
} else if ((hc_mode == HeatingCircuit::Mode::NIGHT) || (hc_mode == HeatingCircuit::Mode::OFF)) {
|
||||
@@ -422,23 +420,17 @@ bool Thermostat::export_values(uint8_t mqtt_format, JsonObject & rootThermostat)
|
||||
|
||||
// if format is single, send immediately and clear object for next hc
|
||||
// the topic will have the hc number appended
|
||||
if ((mqtt_format == MQTT_format::SINGLE) || (mqtt_format == MQTT_format::CUSTOM)) {
|
||||
if ((mqtt_format == Mqtt::Format::SINGLE) || (mqtt_format == Mqtt::Format::CUSTOM)) {
|
||||
char topic[30];
|
||||
char s[3];
|
||||
strlcpy(topic, "thermostat_data", 30);
|
||||
strlcat(topic, Helpers::itoa(s, hc->hc_num()), 30); // append hc to topic
|
||||
Mqtt::publish(topic, rootThermostat);
|
||||
rootThermostat.clear(); // clear object
|
||||
} else if (mqtt_format == MQTT_format::HA) {
|
||||
} else if ((mqtt_format == Mqtt::Format::HA) && (!hc->ha_registered())) {
|
||||
// see if we have already registered this with HA MQTT Discovery, if not send the config
|
||||
if (!hc->ha_registered()) {
|
||||
register_mqtt_ha_config(hc->hc_num());
|
||||
hc->ha_registered(true);
|
||||
}
|
||||
// send the thermostat topic and payload data
|
||||
std::string topic(100, '\0');
|
||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/state"), hc->hc_num());
|
||||
Mqtt::publish(topic, rootThermostat);
|
||||
register_mqtt_ha_config(hc->hc_num());
|
||||
hc->ha_registered(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -527,27 +519,24 @@ std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(std::sha
|
||||
}
|
||||
|
||||
// publish config topic for HA MQTT Discovery
|
||||
// homeassistant/climate/ems-esp/hc<num>
|
||||
// state is /state
|
||||
// config is /config
|
||||
// homeassistant/climate/ems-esp/thermostat_hc1/config
|
||||
void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||
|
||||
std::string hc_text(10, '\0');
|
||||
snprintf_P(&hc_text[0], hc_text.capacity() + 1, PSTR("hc%d"), hc_num);
|
||||
doc["name"] = hc_text;
|
||||
doc["uniq_id"] = hc_text;
|
||||
std::string str1(40, '\0');
|
||||
snprintf_P(&str1[0], str1.capacity() + 1, PSTR("Thermostat hc%d"), hc_num);
|
||||
doc["name"] = str1;
|
||||
|
||||
// topic root is homeassistant/climate/ems-esp/hc<1..n>/
|
||||
std::string root(100, '\0');
|
||||
snprintf_P(&root[0], root.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d"), hc_num);
|
||||
doc["~"] = root;
|
||||
std::string str2(40, '\0');
|
||||
snprintf_P(&str2[0], str2.capacity() + 1, PSTR("thermostat_hc%d"), hc_num);
|
||||
doc["uniq_id"] = str2;
|
||||
|
||||
doc["mode_cmd_t"] = "~/cmd_mode";
|
||||
doc["mode_stat_t"] = "~/state";
|
||||
doc["temp_cmd_t"] = "~/cmd_temp";
|
||||
doc["temp_stat_t"] = "~/state";
|
||||
doc["curr_temp_t"] = "~/state";
|
||||
doc["~"] = F("ems-esp");
|
||||
doc["mode_cmd_t"] = F("~/thermostat");
|
||||
doc["temp_cmd_t"] = F("~/thermostat");
|
||||
doc["mode_stat_t"] = F("~/thermostat_data");
|
||||
doc["temp_stat_t"] = F("~/thermostat_data");
|
||||
doc["curr_temp_t"] = F("~/thermostat_data");
|
||||
|
||||
std::string mode_str(30, '\0');
|
||||
snprintf_P(&mode_str[0], 30, PSTR("{{value_json.hc%d.mode}}"), hc_num);
|
||||
@@ -561,40 +550,60 @@ void Thermostat::register_mqtt_ha_config(uint8_t hc_num) {
|
||||
snprintf_P(&currtemp_str[0], 30, PSTR("{{value_json.hc%d.currtemp}}"), hc_num);
|
||||
doc["curr_temp_tpl"] = currtemp_str;
|
||||
|
||||
doc["min_temp"] = "5";
|
||||
doc["max_temp"] = "40";
|
||||
doc["temp_step"] = "0.5";
|
||||
doc["min_temp"] = F("5");
|
||||
doc["max_temp"] = F("40");
|
||||
doc["temp_step"] = F("0.5");
|
||||
|
||||
JsonArray modes = doc.createNestedArray("modes");
|
||||
JsonArray modes = doc.createNestedArray(F("modes"));
|
||||
uint8_t flags = this->model();
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_RC20_2) {
|
||||
modes.add("night");
|
||||
modes.add("day");
|
||||
modes.add(F("night"));
|
||||
modes.add(F("day"));
|
||||
} else if ((flags == EMSdevice::EMS_DEVICE_FLAG_RC300) || (flags == EMSdevice::EMS_DEVICE_FLAG_RC100)) {
|
||||
modes.add("eco");
|
||||
modes.add("comfort");
|
||||
modes.add("auto");
|
||||
modes.add(F("eco"));
|
||||
modes.add(F("comfort"));
|
||||
modes.add(F("auto"));
|
||||
} else if (flags == EMSdevice::EMS_DEVICE_FLAG_JUNKERS) {
|
||||
modes.add("nofrost");
|
||||
modes.add("eco");
|
||||
modes.add("heat");
|
||||
modes.add("auto");
|
||||
modes.add(F("nofrost"));
|
||||
modes.add(F("eco"));
|
||||
modes.add(F("heat"));
|
||||
modes.add(F("auto"));
|
||||
} else { // default for all other thermostats
|
||||
modes.add("night");
|
||||
modes.add("day");
|
||||
modes.add("auto");
|
||||
modes.add(F("night"));
|
||||
modes.add(F("day"));
|
||||
modes.add(F("auto"));
|
||||
}
|
||||
|
||||
JsonObject dev = doc.createNestedObject(F("dev"));
|
||||
JsonArray ids = dev.createNestedArray(F("ids"));
|
||||
ids.add(F("ems-esp"));
|
||||
|
||||
std::string topic(100, '\0'); // e.g homeassistant/climate/hc1/thermostat/config
|
||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/config"), hc_num);
|
||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/thermostat_hc%d/config"), hc_num);
|
||||
// Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA
|
||||
Mqtt::publish_retain(topic, doc.as<JsonObject>(), true); // publish the config payload with retain flag
|
||||
|
||||
// subscribe to the temp and mode commands
|
||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/cmd_temp"), hc_num);
|
||||
register_mqtt_topic(topic, [&](const char * m) { thermostat_cmd_temp(m); });
|
||||
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/climate/ems-esp/hc%d/cmd_mode"), hc_num);
|
||||
register_mqtt_topic(topic, [&](const char * m) { thermostat_cmd_mode(m); });
|
||||
// enable the thermostat topic to take both mode strings and floats
|
||||
register_mqtt_topic("thermostat", [&](const char * m) { return thermostat_ha_cmd(m); });
|
||||
}
|
||||
|
||||
// for HA specifically when receiving over MQTT in the thermostat topic
|
||||
// it could be either a 'mode' or a float value
|
||||
// return true if it parses the message correctly
|
||||
bool Thermostat::thermostat_ha_cmd(const char * message) {
|
||||
// check if it's json. We know the message isn't empty
|
||||
if (message[0] == '{') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for mode first
|
||||
if (!set_mode(message, AUTO_HEATING_CIRCUIT)) {
|
||||
// handle as a numerical temperature value
|
||||
float f = strtof((char *)message, 0);
|
||||
set_temperature(f, HeatingCircuit::Mode::AUTO, AUTO_HEATING_CIRCUIT);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// decodes the thermostat mode for the heating circuit based on the thermostat type
|
||||
@@ -1463,7 +1472,6 @@ bool Thermostat::set_mode(const char * value, const int8_t id) {
|
||||
return set_mode_n(HeatingCircuit::Mode::COMFORT, hc_num);
|
||||
}
|
||||
|
||||
LOG_WARNING(F("Invalid mode %s. Cannot set"), mode.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1789,18 +1797,6 @@ bool Thermostat::set_temperature(const float temperature, const uint8_t mode, co
|
||||
return false;
|
||||
}
|
||||
|
||||
// for HA specifically when receiving over MQTT
|
||||
bool Thermostat::thermostat_cmd_temp(const char * message) {
|
||||
float f = strtof((char *)message, 0);
|
||||
return set_temperature(f, HeatingCircuit::Mode::AUTO, AUTO_HEATING_CIRCUIT);
|
||||
}
|
||||
|
||||
// for HA specifically when receiving over MQTT
|
||||
// message payload holds the text name of the mode e.g. "auto"
|
||||
bool Thermostat::thermostat_cmd_mode(const char * message) {
|
||||
return set_mode(message, AUTO_HEATING_CIRCUIT);
|
||||
}
|
||||
|
||||
bool Thermostat::set_temperature_value(const char * value, const int8_t id, const uint8_t mode) {
|
||||
float f = 0;
|
||||
uint8_t hc_num = (id == -1) ? AUTO_HEATING_CIRCUIT : id;
|
||||
@@ -1874,7 +1870,13 @@ void Thermostat::add_commands() {
|
||||
// common to all thermostats
|
||||
register_mqtt_cmd(F("wwmode"), [&](const char * value, const int8_t id) { return set_wwmode(value, id); });
|
||||
register_mqtt_cmd(F("temp"), [&](const char * value, const int8_t id) { return set_temp(value, id); });
|
||||
register_mqtt_cmd(F("mode"), [&](const char * value, const int8_t id) { return set_mode(value, id); });
|
||||
register_mqtt_cmd(F("mode"), [=](const char * value, const int8_t id) {
|
||||
if (!set_mode(value, id)) {
|
||||
LOG_WARNING(F("Invalid mode %s. Cannot set"), value);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
uint8_t model = this->model();
|
||||
switch (model) {
|
||||
|
||||
@@ -120,7 +120,6 @@ class Thermostat : public EMSdevice {
|
||||
|
||||
std::string datetime_; // date and time stamp
|
||||
|
||||
uint8_t mqtt_format_; // single, nested or ha
|
||||
bool changed_ = false;
|
||||
|
||||
// Installation parameters
|
||||
@@ -220,6 +219,8 @@ class Thermostat : public EMSdevice {
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(const uint8_t hc_num);
|
||||
|
||||
void register_mqtt_ha_config(uint8_t hc_num);
|
||||
bool thermostat_ha_cmd(const char * message);
|
||||
|
||||
bool command_info(const char * value, const int8_t id, JsonObject & output);
|
||||
|
||||
void process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram);
|
||||
@@ -252,10 +253,6 @@ class Thermostat : public EMSdevice {
|
||||
bool set_temperature(const float temperature, const std::string & mode, const uint8_t hc_num);
|
||||
bool set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num);
|
||||
|
||||
// for HA specifically. MQTT functions.
|
||||
bool thermostat_cmd_temp(const char * message);
|
||||
bool thermostat_cmd_mode(const char * message);
|
||||
|
||||
// set functions - these use the id/hc
|
||||
bool set_mode(const char * value, const int8_t id);
|
||||
bool set_control(const char * value, const int8_t id);
|
||||
|
||||
Reference in New Issue
Block a user