dynamically add HA thermostat modes based on model

This commit is contained in:
proddy
2025-12-13 11:22:22 +01:00
parent c71b54fb5b
commit d3b4bab40a
3 changed files with 69 additions and 36 deletions

View File

@@ -2041,24 +2041,40 @@ bool EMSdevice::generate_values(JsonObject output, const int8_t tag_filter, cons
// create the Home Assistant configs for each device value / entity // create the Home Assistant configs for each device value / entity
// this is called when an MQTT publish is done via an EMS Device in emsesp.cpp::publish_device_values() // this is called when an MQTT publish is done via an EMS Device in emsesp.cpp::publish_device_values()
void EMSdevice::mqtt_ha_entity_config_create() { void EMSdevice::mqtt_ha_entity_config_create() {
bool create_device_config = !ha_config_done(); // do we need to create the main Discovery device config with this entity? bool create_device_config = !ha_config_done(); // do we need to create the main Discovery device config with this entity?
uint16_t count = 0; uint16_t count = 0;
const char * const ** mode_options = nullptr;
// if it's a thermostat go fetch the list of modes
if (device_type() == EMSdevice::DeviceType::THERMOSTAT) {
for (auto & dv : devicevalues_) {
// make sure it's a type DeviceValueType::ENUM
if ((dv.type == DeviceValueType::ENUM) && !strcmp(dv.short_name, FL_(mode)[0])) {
// get options
mode_options = dv.options;
break;
}
}
}
// check the state of each of the device values // check the state of each of the device values
// create the discovery topic if if hasn't already been created, not a command (like reset) and is active and visible // create the discovery topic if if hasn't already been created, not a command (like reset) and is active and visible
for (auto & dv : devicevalues_) { for (auto & dv : devicevalues_) {
// create climate when we reach the haclimate entity // create climate when we reach the haclimate entity
if (!strcmp(dv.short_name, FL_(haclimate)[0]) && !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE) && dv.has_state(DeviceValueState::DV_ACTIVE)) { if (!strcmp(dv.short_name, FL_(haclimate)[0]) && !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE) && dv.has_state(DeviceValueState::DV_ACTIVE)) {
if (*(int8_t *)(dv.value_p) == 1 && (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) || dv.has_state(DeviceValueState::DV_HA_CLIMATE_NO_RT))) { int8_t haclimate_value = *(int8_t *)(dv.value_p);
if (Mqtt::publish_ha_climate_config(dv, true, false)) { // roomTemp bool has_config_created = dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED);
dv.remove_state(DeviceValueState::DV_HA_CLIMATE_NO_RT); bool has_climate_no_rt = dv.has_state(DeviceValueState::DV_HA_CLIMATE_NO_RT);
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED); bool needs_update = !has_config_created || (haclimate_value == 1 ? has_climate_no_rt : !has_climate_no_rt);
count++;
} if (needs_update) {
} else if (*(int8_t *)(dv.value_p) == 0 bool has_room_temp = (haclimate_value == 1);
&& (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) || !dv.has_state(DeviceValueState::DV_HA_CLIMATE_NO_RT))) { if (Mqtt::publish_ha_climate_config(dv, has_room_temp, mode_options, false)) {
if (Mqtt::publish_ha_climate_config(dv, false, false)) { // no roomTemp if (has_room_temp) {
dv.add_state(DeviceValueState::DV_HA_CLIMATE_NO_RT); dv.remove_state(DeviceValueState::DV_HA_CLIMATE_NO_RT);
} else {
dv.add_state(DeviceValueState::DV_HA_CLIMATE_NO_RT);
}
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED); dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);
count++; count++;
} }
@@ -2075,7 +2091,7 @@ void EMSdevice::mqtt_ha_entity_config_create() {
} }
// SRC thermostats mapped to connect/src1/... // SRC thermostats mapped to connect/src1/...
if (dv.tag >= DeviceValueTAG::TAG_SRC1 && dv.tag <= DeviceValueTAG::TAG_SRC16 && !strcmp(dv.short_name, FL_(selRoomTemp)[0])) { if (dv.tag >= DeviceValueTAG::TAG_SRC1 && dv.tag <= DeviceValueTAG::TAG_SRC16 && !strcmp(dv.short_name, FL_(selRoomTemp)[0])) {
Mqtt::publish_ha_climate_config(dv, true, false); Mqtt::publish_ha_climate_config(dv, true, mode_options, false);
} }
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE

View File

@@ -546,7 +546,7 @@ void Mqtt::ha_status() {
dev["mf"] = "EMS-ESP"; dev["mf"] = "EMS-ESP";
dev["mdl"] = "EMS-ESP"; dev["mdl"] = "EMS-ESP";
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
dev["cu"] = "http://" + (EMSESP::system_.ethernet_connected() ? ETH.localIP().toString() : WiFi.localIP().toString()); dev["cu"] = std::string("http://") + EMSESP::system_.get_ip_or_hostname().c_str();
#endif #endif
JsonArray ids = dev["ids"].to<JsonArray>(); JsonArray ids = dev["ids"].to<JsonArray>();
ids.add(Mqtt::basename()); ids.add(Mqtt::basename());
@@ -1103,6 +1103,12 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
// https://github.com/emsesp/EMS-ESP32/discussions/1459#discussioncomment-7694873 // https://github.com/emsesp/EMS-ESP32/discussions/1459#discussioncomment-7694873
add_ha_classes(doc.as<JsonObject>(), device_type, type, uom, entity); add_ha_classes(doc.as<JsonObject>(), device_type, type, uom, entity);
// add origin
JsonObject origin_json = doc["o"].to<JsonObject>();
origin_json["name"] = "EMS-ESP";
origin_json["sw"] = EMSESP_APP_VERSION;
origin_json["url"] = "https://emsesp.org";
// add dev section // add dev section
if (device_type == EMSdevice::DeviceType::SYSTEM) { if (device_type == EMSdevice::DeviceType::SYSTEM) {
add_ha_dev_section(doc.as<JsonObject>(), nullptr, nullptr, nullptr, nullptr, false); add_ha_dev_section(doc.as<JsonObject>(), nullptr, nullptr, nullptr, nullptr, false);
@@ -1236,7 +1242,9 @@ void Mqtt::add_ha_classes(JsonObject doc, const uint8_t device_type, const uint8
} }
} }
bool Mqtt::publish_ha_climate_config(const DeviceValue & dv, const bool has_roomtemp, const bool remove) { // publish the HA climate config
// https://www.home-assistant.io/integrations/climate.mqtt/
bool Mqtt::publish_ha_climate_config(const DeviceValue & dv, const bool has_roomtemp, const char * const ** mode_options, const bool remove) {
int8_t tag = dv.tag; int8_t tag = dv.tag;
int16_t min = dv.min; int16_t min = dv.min;
uint32_t max = dv.max; uint32_t max = dv.max;
@@ -1356,34 +1364,43 @@ bool Mqtt::publish_ha_climate_config(const DeviceValue & dv, const bool has_room
doc["act_t"] = "~/boiler_data"; doc["act_t"] = "~/boiler_data";
doc["act_tpl"] = "{% if value_json.hpactivity=='cooling'%}cooling{%elif value_json.heatingactive=='on'%}heating{%else%}idle{%endif%}"; doc["act_tpl"] = "{% if value_json.hpactivity=='cooling'%}cooling{%elif value_json.heatingactive=='on'%}heating{%else%}idle{%endif%}";
// the HA climate component only responds to auto, heat and off // map EMS modes to HA climate modes
JsonArray modes = doc["modes"].to<JsonArray>(); // EMS modes: auto, manual, heat, off, night, day, nofrost, eco, comfort, cool)
// HA supports: auto, off, cool, heat, dry, fan_only
// go through dv.options and map to HA climate modes if (mode_options != nullptr) {
// https://www.home-assistant.io/integrations/climate.mqtt/ // scan through mode_options and add to modes
// HA supports: ["auto", "off", "cool", "heat", "dry", "fan_only"]
if (dv.options != nullptr) {
bool found_auto = false; bool found_auto = false;
bool found_heat = false; bool found_heat = false;
bool found_off = false; bool found_off = false;
for (uint8_t i = 0; i < dv.options_size; i++) { bool found_cool = false;
const char * option = dv.options[i][0]; for (uint8_t i = 0; i < Helpers::count_items(mode_options); i++) {
if (strcmp(option, "auto") == 0) { const char * mode = mode_options[i][0]; // take EN
if (!strcmp(mode, FL_(auto)[0])) {
found_auto = true; found_auto = true;
} else if (strcmp(option, "heat") == 0) { } else if (!strcmp(mode, FL_(heat)[0])) {
found_heat = true; found_heat = true;
} else if (strcmp(option, "off") == 0) { } else if (!strcmp(mode, FL_(off)[0])) {
found_off = true; found_off = true;
} else if (!strcmp(mode, FL_(cool)[0])) {
found_cool = true;
} }
} }
if (found_auto) {
modes.add("auto"); // only add modes if we found at least one
} if (found_auto || found_heat || found_off || found_cool) {
if (found_heat) { JsonArray modes = doc["modes"].to<JsonArray>();
modes.add("heat"); if (found_auto) {
} modes.add("auto");
if (found_off) { }
modes.add("off"); if (found_heat) {
modes.add("heat");
}
if (found_off) {
modes.add("off");
}
if (found_cool) {
modes.add("cool");
}
} }
} }

View File

@@ -113,7 +113,7 @@ class Mqtt {
const bool create_device_config = false); const bool create_device_config = false);
static bool publish_system_ha_sensor_config(uint8_t type, const char * name, const char * entity, const uint8_t uom); static bool publish_system_ha_sensor_config(uint8_t type, const char * name, const char * entity, const uint8_t uom);
static bool publish_ha_climate_config(const DeviceValue & dv, const bool has_roomtemp, const bool remove = false); static bool publish_ha_climate_config(const DeviceValue & dv, const bool has_roomtemp, const char * const ** mode_options, const bool remove = false);
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type); static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type);
static void show_mqtt(uuid::console::Shell & shell); static void show_mqtt(uuid::console::Shell & shell);