diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index b83b57002a..eaa87afcb1 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -22,6 +22,8 @@ from esphome.const import ( CONF_MODE_STATE_TOPIC, CONF_ON_STATE, CONF_PRESET, + CONF_PRESET_COMMAND_TOPIC, + CONF_PRESET_STATE_TOPIC, CONF_SWING_MODE, CONF_SWING_MODE_COMMAND_TOPIC, CONF_SWING_MODE_STATE_TOPIC, @@ -143,6 +145,12 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA). cv.Optional(CONF_MODE_STATE_TOPIC): cv.All( cv.requires_component("mqtt"), cv.publish_topic ), + cv.Optional(CONF_PRESET_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_PRESET_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All( cv.requires_component("mqtt"), cv.publish_topic ), @@ -217,7 +225,12 @@ async def setup_climate_core_(var, config): cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC])) if CONF_MODE_STATE_TOPIC in config: cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC])) - + if CONF_PRESET_COMMAND_TOPIC in config: + cg.add( + mqtt_.set_custom_preset_command_topic(config[CONF_PRESET_COMMAND_TOPIC]) + ) + if CONF_PRESET_STATE_TOPIC in config: + cg.add(mqtt_.set_custom_preset_state_topic(config[CONF_PRESET_STATE_TOPIC])) if CONF_SWING_MODE_COMMAND_TOPIC in config: cg.add( mqtt_.set_custom_swing_mode_command_topic( diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 349c185fcc..8dd03dd5c8 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -66,18 +66,42 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // temperature units are always coerced to Celsius internally root[MQTT_TEMPERATURE_UNIT] = "C"; - if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { - // away_mode_command_topic - root[MQTT_AWAY_MODE_COMMAND_TOPIC] = this->get_away_command_topic(); - // away_mode_state_topic - root[MQTT_AWAY_MODE_STATE_TOPIC] = this->get_away_state_topic(); + if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) { + // preset_mode_command_topic + root[MQTT_PRESET_MODE_COMMAND_TOPIC] = this->get_preset_command_topic(); + // preset_mode_state_topic + root[MQTT_PRESET_MODE_STATE_TOPIC] = this->get_preset_state_topic(); + // presets + JsonArray presets = root.createNestedArray("presets"); + if (traits.supports_preset(CLIMATE_PRESET_HOME)) + presets.add("home"); + if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { + // away_mode_command_topic + root[MQTT_AWAY_MODE_COMMAND_TOPIC] = this->get_away_command_topic(); + // away_mode_state_topic + root[MQTT_AWAY_MODE_STATE_TOPIC] = this->get_away_state_topic(); + presets.add("away"); + } + if (traits.supports_preset(CLIMATE_PRESET_BOOST)) + presets.add("boost"); + if (traits.supports_preset(CLIMATE_PRESET_COMFORT)) + presets.add("comfort"); + if (traits.supports_preset(CLIMATE_PRESET_ECO)) + presets.add("eco"); + if (traits.supports_preset(CLIMATE_PRESET_SLEEP)) + presets.add("sleep"); + if (traits.supports_preset(CLIMATE_PRESET_ACTIVITY)) + presets.add("activity"); + for (const auto &preset : traits.get_supported_custom_presets()) + presets.add(preset); } + if (traits.get_supports_action()) { // action_topic root[MQTT_ACTION_TOPIC] = this->get_action_state_topic(); } - if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) { + if (traits.get_supports_fan_modes()) { // fan_mode_command_topic root[MQTT_FAN_MODE_COMMAND_TOPIC] = this->get_fan_mode_command_topic(); // fan_mode_state_topic @@ -196,6 +220,14 @@ void MQTTClimateComponent::setup() { }); } + if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) { + this->subscribe(this->get_preset_command_topic(), [this](const std::string &topic, const std::string &payload) { + auto call = this->device_->make_call(); + call.set_preset(payload); + call.perform(); + }); + } + if (traits.get_supports_fan_modes()) { this->subscribe(this->get_fan_mode_command_topic(), [this](const std::string &topic, const std::string &payload) { auto call = this->device_->make_call(); @@ -273,6 +305,42 @@ bool MQTTClimateComponent::publish_state_() { if (!this->publish(this->get_away_state_topic(), payload)) success = false; } + if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) { + std::string payload; + if (this->device_->preset.has_value()) { + switch (this->device_->preset.value()) { + case CLIMATE_PRESET_NONE: + payload = "none"; + break; + case CLIMATE_PRESET_HOME: + payload = "home"; + break; + case CLIMATE_PRESET_AWAY: + payload = "away"; + break; + case CLIMATE_PRESET_BOOST: + payload = "boost"; + break; + case CLIMATE_PRESET_COMFORT: + payload = "comfort"; + break; + case CLIMATE_PRESET_ECO: + payload = "eco"; + break; + case CLIMATE_PRESET_SLEEP: + payload = "sleep"; + break; + case CLIMATE_PRESET_ACTIVITY: + payload = "activity"; + break; + } + } + if (this->device_->custom_preset.has_value()) + payload = this->device_->custom_preset.value(); + if (!this->publish(this->get_preset_state_topic(), payload)) + success = false; + } + if (traits.get_supports_action()) { const char *payload = "unknown"; switch (this->device_->action) { diff --git a/esphome/components/mqtt/mqtt_climate.h b/esphome/components/mqtt/mqtt_climate.h index ea3e2ab3fa..a93070fe66 100644 --- a/esphome/components/mqtt/mqtt_climate.h +++ b/esphome/components/mqtt/mqtt_climate.h @@ -35,6 +35,8 @@ class MQTTClimateComponent : public mqtt::MQTTComponent { MQTT_COMPONENT_CUSTOM_TOPIC(fan_mode, command) MQTT_COMPONENT_CUSTOM_TOPIC(swing_mode, state) MQTT_COMPONENT_CUSTOM_TOPIC(swing_mode, command) + MQTT_COMPONENT_CUSTOM_TOPIC(preset, state) + MQTT_COMPONENT_CUSTOM_TOPIC(preset, command) protected: const EntityBase *get_entity() const override; diff --git a/esphome/const.py b/esphome/const.py index 802f4fd000..95e00bdca0 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -551,8 +551,10 @@ CONF_POWER_SAVE_MODE = "power_save_mode" CONF_POWER_SUPPLY = "power_supply" CONF_PRESET = "preset" CONF_PRESET_BOOST = "preset_boost" +CONF_PRESET_COMMAND_TOPIC = "preset_command_topic" CONF_PRESET_ECO = "preset_eco" CONF_PRESET_SLEEP = "preset_sleep" +CONF_PRESET_STATE_TOPIC = "preset_state_topic" CONF_PRESSURE = "pressure" CONF_PRIORITY = "priority" CONF_PROJECT = "project"