From c6bc47b32dce05b9ef486e3a57a3ed3aa0b7057f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 14 Apr 2019 05:34:39 +0200 Subject: [PATCH] Refactor MQTT climate to deduplicate code (#23044) --- homeassistant/components/mqtt/climate.py | 646 ++++++++++------------- 1 file changed, 272 insertions(+), 374 deletions(-) diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index fb443b29c61..e50aff8d209 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -33,61 +33,85 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'MQTT HVAC' -CONF_POWER_COMMAND_TOPIC = 'power_command_topic' -CONF_POWER_STATE_TOPIC = 'power_state_topic' -CONF_POWER_STATE_TEMPLATE = 'power_state_template' -CONF_MODE_COMMAND_TOPIC = 'mode_command_topic' -CONF_MODE_STATE_TOPIC = 'mode_state_topic' -CONF_MODE_STATE_TEMPLATE = 'mode_state_template' -CONF_TEMPERATURE_COMMAND_TOPIC = 'temperature_command_topic' -CONF_TEMPERATURE_STATE_TOPIC = 'temperature_state_topic' -CONF_TEMPERATURE_STATE_TEMPLATE = 'temperature_state_template' -CONF_TEMPERATURE_LOW_COMMAND_TOPIC = 'temperature_low_command_topic' -CONF_TEMPERATURE_LOW_STATE_TOPIC = 'temperature_low_state_topic' -CONF_TEMPERATURE_HIGH_COMMAND_TOPIC = 'temperature_high_command_topic' -CONF_TEMPERATURE_HIGH_STATE_TOPIC = 'temperature_high_state_topic' -CONF_FAN_MODE_COMMAND_TOPIC = 'fan_mode_command_topic' -CONF_FAN_MODE_STATE_TOPIC = 'fan_mode_state_topic' -CONF_FAN_MODE_STATE_TEMPLATE = 'fan_mode_state_template' -CONF_SWING_MODE_COMMAND_TOPIC = 'swing_mode_command_topic' -CONF_SWING_MODE_STATE_TOPIC = 'swing_mode_state_topic' -CONF_SWING_MODE_STATE_TEMPLATE = 'swing_mode_state_template' -CONF_AWAY_MODE_COMMAND_TOPIC = 'away_mode_command_topic' -CONF_AWAY_MODE_STATE_TOPIC = 'away_mode_state_topic' -CONF_AWAY_MODE_STATE_TEMPLATE = 'away_mode_state_template' -CONF_HOLD_COMMAND_TOPIC = 'hold_command_topic' -CONF_HOLD_STATE_TOPIC = 'hold_state_topic' -CONF_HOLD_STATE_TEMPLATE = 'hold_state_template' CONF_AUX_COMMAND_TOPIC = 'aux_command_topic' -CONF_AUX_STATE_TOPIC = 'aux_state_topic' CONF_AUX_STATE_TEMPLATE = 'aux_state_template' - -CONF_CURRENT_TEMPERATURE_TEMPLATE = 'current_temperature_template' -CONF_CURRENT_TEMPERATURE_TOPIC = 'current_temperature_topic' - -CONF_PAYLOAD_ON = 'payload_on' -CONF_PAYLOAD_OFF = 'payload_off' - +CONF_AUX_STATE_TOPIC = 'aux_state_topic' +CONF_AWAY_MODE_COMMAND_TOPIC = 'away_mode_command_topic' +CONF_AWAY_MODE_STATE_TEMPLATE = 'away_mode_state_template' +CONF_AWAY_MODE_STATE_TOPIC = 'away_mode_state_topic' +CONF_CURRENT_TEMP_TEMPLATE = 'current_temperature_template' +CONF_CURRENT_TEMP_TOPIC = 'current_temperature_topic' +CONF_FAN_MODE_COMMAND_TOPIC = 'fan_mode_command_topic' CONF_FAN_MODE_LIST = 'fan_modes' +CONF_FAN_MODE_STATE_TEMPLATE = 'fan_mode_state_template' +CONF_FAN_MODE_STATE_TOPIC = 'fan_mode_state_topic' +CONF_HOLD_COMMAND_TOPIC = 'hold_command_topic' +CONF_HOLD_STATE_TEMPLATE = 'hold_state_template' +CONF_HOLD_STATE_TOPIC = 'hold_state_topic' +CONF_MODE_COMMAND_TOPIC = 'mode_command_topic' CONF_MODE_LIST = 'modes' -CONF_SWING_MODE_LIST = 'swing_modes' +CONF_MODE_STATE_TEMPLATE = 'mode_state_template' +CONF_MODE_STATE_TOPIC = 'mode_state_topic' +CONF_PAYLOAD_OFF = 'payload_off' +CONF_PAYLOAD_ON = 'payload_on' +CONF_POWER_COMMAND_TOPIC = 'power_command_topic' +CONF_POWER_STATE_TEMPLATE = 'power_state_template' +CONF_POWER_STATE_TOPIC = 'power_state_topic' CONF_SEND_IF_OFF = 'send_if_off' - +CONF_SWING_MODE_COMMAND_TOPIC = 'swing_mode_command_topic' +CONF_SWING_MODE_LIST = 'swing_modes' +CONF_SWING_MODE_STATE_TEMPLATE = 'swing_mode_state_template' +CONF_SWING_MODE_STATE_TOPIC = 'swing_mode_state_topic' +CONF_TEMP_COMMAND_TOPIC = 'temperature_command_topic' +CONF_TEMP_HIGH_COMMAND_TOPIC = 'temperature_high_command_topic' +CONF_TEMP_HIGH_STATE_TEMPLATE = 'temperature_high_state_template' +CONF_TEMP_HIGH_STATE_TOPIC = 'temperature_high_state_topic' +CONF_TEMP_LOW_COMMAND_TOPIC = 'temperature_low_command_topic' +CONF_TEMP_LOW_STATE_TEMPLATE = 'temperature_low_state_template' +CONF_TEMP_LOW_STATE_TOPIC = 'temperature_low_state_topic' +CONF_TEMP_STATE_TEMPLATE = 'temperature_state_template' +CONF_TEMP_STATE_TOPIC = 'temperature_state_topic' CONF_TEMP_INITIAL = 'initial' -CONF_TEMP_MIN = 'min_temp' CONF_TEMP_MAX = 'max_temp' +CONF_TEMP_MIN = 'min_temp' CONF_TEMP_STEP = 'temp_step' TEMPLATE_KEYS = ( - CONF_POWER_STATE_TEMPLATE, - CONF_MODE_STATE_TEMPLATE, - CONF_TEMPERATURE_STATE_TEMPLATE, - CONF_FAN_MODE_STATE_TEMPLATE, - CONF_SWING_MODE_STATE_TEMPLATE, - CONF_AWAY_MODE_STATE_TEMPLATE, - CONF_HOLD_STATE_TEMPLATE, CONF_AUX_STATE_TEMPLATE, - CONF_CURRENT_TEMPERATURE_TEMPLATE + CONF_AWAY_MODE_STATE_TEMPLATE, + CONF_CURRENT_TEMP_TEMPLATE, + CONF_FAN_MODE_STATE_TEMPLATE, + CONF_HOLD_STATE_TEMPLATE, + CONF_MODE_STATE_TEMPLATE, + CONF_POWER_STATE_TEMPLATE, + CONF_SWING_MODE_STATE_TEMPLATE, + CONF_TEMP_HIGH_STATE_TEMPLATE, + CONF_TEMP_LOW_STATE_TEMPLATE, + CONF_TEMP_STATE_TEMPLATE, +) + +TOPIC_KEYS = ( + CONF_AUX_COMMAND_TOPIC, + CONF_AUX_STATE_TOPIC, + CONF_AWAY_MODE_COMMAND_TOPIC, + CONF_AWAY_MODE_STATE_TOPIC, + CONF_CURRENT_TEMP_TOPIC, + CONF_FAN_MODE_COMMAND_TOPIC, + CONF_FAN_MODE_STATE_TOPIC, + CONF_HOLD_COMMAND_TOPIC, + CONF_HOLD_STATE_TOPIC, + CONF_MODE_COMMAND_TOPIC, + CONF_MODE_STATE_TOPIC, + CONF_POWER_COMMAND_TOPIC, + CONF_POWER_STATE_TOPIC, + CONF_SWING_MODE_COMMAND_TOPIC, + CONF_SWING_MODE_STATE_TOPIC, + CONF_TEMP_COMMAND_TOPIC, + CONF_TEMP_HIGH_COMMAND_TOPIC, + CONF_TEMP_HIGH_STATE_TOPIC, + CONF_TEMP_LOW_COMMAND_TOPIC, + CONF_TEMP_LOW_STATE_TOPIC, + CONF_TEMP_STATE_TOPIC, ) SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema) @@ -98,9 +122,8 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({ vol.Optional(CONF_AWAY_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_AWAY_MODE_STATE_TEMPLATE): cv.template, vol.Optional(CONF_AWAY_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_CURRENT_TEMPERATURE_TEMPLATE): cv.template, - vol.Optional(CONF_CURRENT_TEMPERATURE_TOPIC): - mqtt.valid_subscribe_topic, + vol.Optional(CONF_CURRENT_TEMP_TEMPLATE): cv.template, + vol.Optional(CONF_CURRENT_TEMP_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_FAN_MODE_LIST, @@ -134,15 +157,13 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({ vol.Optional(CONF_TEMP_MIN, default=DEFAULT_MIN_TEMP): vol.Coerce(float), vol.Optional(CONF_TEMP_MAX, default=DEFAULT_MAX_TEMP): vol.Coerce(float), vol.Optional(CONF_TEMP_STEP, default=1.0): vol.Coerce(float), - vol.Optional(CONF_TEMPERATURE_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_TEMPERATURE_HIGH_COMMAND_TOPIC): - mqtt.valid_publish_topic, - vol.Optional(CONF_TEMPERATURE_HIGH_STATE_TOPIC): - mqtt.valid_subscribe_topic, - vol.Optional(CONF_TEMPERATURE_LOW_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_TEMPERATURE_LOW_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_TEMPERATURE_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_TEMPERATURE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_TEMP_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_TEMP_HIGH_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_TEMP_HIGH_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_TEMP_LOW_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_TEMP_LOW_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_TEMP_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_TEMP_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, }).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend( @@ -192,19 +213,19 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, self._sub_state = None self.hass = hass - self._topic = None - self._value_templates = None - self._target_temperature = None - self._target_temperature_low = None - self._target_temperature_high = None + self._aux = False + self._away = False self._current_fan_mode = None self._current_operation = None self._current_swing_mode = None - self._unit_of_measurement = hass.config.units.temperature_unit - self._away = False + self._current_temp = None self._hold = None - self._current_temperature = None - self._aux = False + self._target_temp = None + self._target_temp_high = None + self._target_temp_low = None + self._topic = None + self._unit_of_measurement = hass.config.units.temperature_unit + self._value_templates = None self._setup_from_config(config) @@ -235,43 +256,21 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, def _setup_from_config(self, config): """(Re)Setup the entity.""" self._topic = { - key: config.get(key) for key in ( - CONF_POWER_COMMAND_TOPIC, - CONF_MODE_COMMAND_TOPIC, - CONF_TEMPERATURE_COMMAND_TOPIC, - CONF_TEMPERATURE_LOW_COMMAND_TOPIC, - CONF_TEMPERATURE_HIGH_COMMAND_TOPIC, - CONF_FAN_MODE_COMMAND_TOPIC, - CONF_SWING_MODE_COMMAND_TOPIC, - CONF_AWAY_MODE_COMMAND_TOPIC, - CONF_HOLD_COMMAND_TOPIC, - CONF_AUX_COMMAND_TOPIC, - CONF_POWER_STATE_TOPIC, - CONF_MODE_STATE_TOPIC, - CONF_TEMPERATURE_STATE_TOPIC, - CONF_TEMPERATURE_LOW_STATE_TOPIC, - CONF_TEMPERATURE_HIGH_STATE_TOPIC, - CONF_FAN_MODE_STATE_TOPIC, - CONF_SWING_MODE_STATE_TOPIC, - CONF_AWAY_MODE_STATE_TOPIC, - CONF_HOLD_STATE_TOPIC, - CONF_AUX_STATE_TOPIC, - CONF_CURRENT_TEMPERATURE_TOPIC - ) + key: config.get(key) for key in TOPIC_KEYS } # set to None in non-optimistic mode - self._target_temperature = self._current_fan_mode = \ + self._target_temp = self._current_fan_mode = \ self._current_operation = self._current_swing_mode = None - self._target_temperature_low = None - self._target_temperature_high = None + self._target_temp_low = None + self._target_temp_high = None - if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is None: - self._target_temperature = config[CONF_TEMP_INITIAL] - if self._topic[CONF_TEMPERATURE_LOW_STATE_TOPIC] is None: - self._target_temperature_low = config[CONF_TEMP_INITIAL] - if self._topic[CONF_TEMPERATURE_HIGH_STATE_TOPIC] is None: - self._target_temperature_high = config[CONF_TEMP_INITIAL] + if self._topic[CONF_TEMP_STATE_TOPIC] is None: + self._target_temp = config[CONF_TEMP_INITIAL] + if self._topic[CONF_TEMP_LOW_STATE_TOPIC] is None: + self._target_temp_low = config[CONF_TEMP_INITIAL] + if self._topic[CONF_TEMP_HIGH_STATE_TOPIC] is None: + self._target_temp_high = config[CONF_TEMP_INITIAL] if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None: self._current_fan_mode = SPEED_LOW @@ -284,13 +283,18 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, self._aux = False value_templates = {} + for key in TEMPLATE_KEYS: + value_templates[key] = lambda value: value if CONF_VALUE_TEMPLATE in config: value_template = config.get(CONF_VALUE_TEMPLATE) value_template.hass = self.hass - value_templates = {key: value_template for key in TEMPLATE_KEYS} + value_templates = { + key: value_template.async_render_with_possible_json_value + for key in TEMPLATE_KEYS} for key in TEMPLATE_KEYS & config.keys(): - value_templates[key] = config.get(key) - value_templates[key].hass = self.hass + tpl = config[key] + value_templates[key] = tpl.async_render_with_possible_json_value + tpl.hass = self.hass self._value_templates = value_templates async def _subscribe_topics(self): @@ -298,217 +302,151 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, topics = {} qos = self._config[CONF_QOS] + def add_subscription(topics, topic, msg_callback): + if self._topic[topic] is not None: + topics[topic] = { + 'topic': self._topic[topic], + 'msg_callback': msg_callback, + 'qos': qos} + + def render_template(msg, template_name): + template = self._value_templates[template_name] + return template(msg.payload) + @callback - def handle_current_temp_received(msg): + def handle_temperature_received(msg, template_name, attr): + """Handle temperature coming via MQTT.""" + payload = render_template(msg, template_name) + + try: + setattr(self, attr, float(payload)) + self.async_write_ha_state() + except ValueError: + _LOGGER.error("Could not parse temperature from %s", payload) + + @callback + def handle_current_temperature_received(msg): """Handle current temperature coming via MQTT.""" - payload = msg.payload - if CONF_CURRENT_TEMPERATURE_TEMPLATE in self._value_templates: - payload =\ - self._value_templates[CONF_CURRENT_TEMPERATURE_TEMPLATE].\ - async_render_with_possible_json_value(payload) + handle_temperature_received( + msg, CONF_CURRENT_TEMP_TEMPLATE, '_current_temp') - try: - self._current_temperature = float(payload) - self.async_write_ha_state() - except ValueError: - _LOGGER.error("Could not parse temperature from %s", payload) - - if self._topic[CONF_CURRENT_TEMPERATURE_TOPIC] is not None: - topics[CONF_CURRENT_TEMPERATURE_TOPIC] = { - 'topic': self._topic[CONF_CURRENT_TEMPERATURE_TOPIC], - 'msg_callback': handle_current_temp_received, - 'qos': qos} + add_subscription(topics, CONF_CURRENT_TEMP_TOPIC, + handle_current_temperature_received) @callback - def handle_mode_received(msg): - """Handle receiving mode via MQTT.""" - payload = msg.payload - if CONF_MODE_STATE_TEMPLATE in self._value_templates: - payload = self._value_templates[CONF_MODE_STATE_TEMPLATE].\ - async_render_with_possible_json_value(payload) - - if payload not in self._config[CONF_MODE_LIST]: - _LOGGER.error("Invalid mode: %s", payload) - else: - self._current_operation = payload - self.async_write_ha_state() - - if self._topic[CONF_MODE_STATE_TOPIC] is not None: - topics[CONF_MODE_STATE_TOPIC] = { - 'topic': self._topic[CONF_MODE_STATE_TOPIC], - 'msg_callback': handle_mode_received, - 'qos': qos} - - @callback - def handle_temperature_received(msg): + def handle_target_temperature_received(msg): """Handle target temperature coming via MQTT.""" - payload = msg.payload - if CONF_TEMPERATURE_STATE_TEMPLATE in self._value_templates: - payload = \ - self._value_templates[CONF_TEMPERATURE_STATE_TEMPLATE].\ - async_render_with_possible_json_value(payload) + handle_temperature_received( + msg, CONF_TEMP_STATE_TEMPLATE, '_target_temp') - try: - self._target_temperature = float(payload) - self.async_write_ha_state() - except ValueError: - _LOGGER.error("Could not parse temperature from %s", payload) - - if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is not None: - topics[CONF_TEMPERATURE_STATE_TOPIC] = { - 'topic': self._topic[CONF_TEMPERATURE_STATE_TOPIC], - 'msg_callback': handle_temperature_received, - 'qos': qos} + add_subscription(topics, CONF_TEMP_STATE_TOPIC, + handle_target_temperature_received) @callback def handle_temperature_low_received(msg): """Handle target temperature low coming via MQTT.""" - try: - self._target_temperature_low = float(msg.payload) - self.async_write_ha_state() - except ValueError: - _LOGGER.error("Could not parse low temperature from %s", - msg.payload) + handle_temperature_received( + msg, CONF_TEMP_LOW_STATE_TEMPLATE, '_target_temp_low') - if self._topic[CONF_TEMPERATURE_LOW_STATE_TOPIC] is not None: - topics[CONF_TEMPERATURE_LOW_STATE_TOPIC] = { - 'topic': self._topic[CONF_TEMPERATURE_LOW_STATE_TOPIC], - 'msg_callback': handle_temperature_low_received, - 'qos': qos} + add_subscription(topics, CONF_TEMP_LOW_STATE_TOPIC, + handle_temperature_low_received) @callback def handle_temperature_high_received(msg): """Handle target temperature high coming via MQTT.""" - try: - self._target_temperature_high = float(msg.payload) - self.async_write_ha_state() - except ValueError: - _LOGGER.error("Could not parse high temperature from %s", - msg.payload) + handle_temperature_received( + msg, CONF_TEMP_HIGH_STATE_TEMPLATE, '_target_temp_high') - if self._topic[CONF_TEMPERATURE_HIGH_STATE_TOPIC] is not None: - topics[CONF_TEMPERATURE_HIGH_STATE_TOPIC] = { - 'topic': self._topic[CONF_TEMPERATURE_HIGH_STATE_TOPIC], - 'msg_callback': handle_temperature_high_received, - 'qos': qos} + add_subscription(topics, CONF_TEMP_HIGH_STATE_TOPIC, + handle_temperature_high_received) + + @callback + def handle_mode_received(msg, template_name, attr, mode_list): + """Handle receiving listed mode via MQTT.""" + payload = render_template(msg, template_name) + + if payload not in self._config[mode_list]: + _LOGGER.error("Invalid %s mode: %s", mode_list, payload) + else: + setattr(self, attr, payload) + self.async_write_ha_state() + + @callback + def handle_current_mode_received(msg): + """Handle receiving mode via MQTT.""" + handle_mode_received(msg, CONF_MODE_STATE_TEMPLATE, + '_current_operation', CONF_MODE_LIST) + + add_subscription(topics, CONF_MODE_STATE_TOPIC, + handle_current_mode_received) @callback def handle_fan_mode_received(msg): """Handle receiving fan mode via MQTT.""" - payload = msg.payload - if CONF_FAN_MODE_STATE_TEMPLATE in self._value_templates: - payload = \ - self._value_templates[CONF_FAN_MODE_STATE_TEMPLATE].\ - async_render_with_possible_json_value(payload) + handle_mode_received(msg, CONF_FAN_MODE_STATE_TEMPLATE, + '_current_fan_mode', CONF_FAN_MODE_LIST) - if payload not in self._config[CONF_FAN_MODE_LIST]: - _LOGGER.error("Invalid fan mode: %s", payload) - else: - self._current_fan_mode = payload - self.async_write_ha_state() - - if self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None: - topics[CONF_FAN_MODE_STATE_TOPIC] = { - 'topic': self._topic[CONF_FAN_MODE_STATE_TOPIC], - 'msg_callback': handle_fan_mode_received, - 'qos': qos} + add_subscription(topics, CONF_FAN_MODE_STATE_TOPIC, + handle_fan_mode_received) @callback def handle_swing_mode_received(msg): """Handle receiving swing mode via MQTT.""" - payload = msg.payload - if CONF_SWING_MODE_STATE_TEMPLATE in self._value_templates: - payload = \ - self._value_templates[CONF_SWING_MODE_STATE_TEMPLATE].\ - async_render_with_possible_json_value(payload) + handle_mode_received(msg, CONF_SWING_MODE_STATE_TEMPLATE, + '_current_swing_mode', CONF_SWING_MODE_LIST) - if payload not in self._config[CONF_SWING_MODE_LIST]: - _LOGGER.error("Invalid swing mode: %s", payload) + add_subscription(topics, CONF_SWING_MODE_STATE_TOPIC, + handle_swing_mode_received) + + @callback + def handle_onoff_mode_received(msg, template_name, attr): + """Handle receiving on/off mode via MQTT.""" + payload = render_template(msg, template_name) + payload_on = self._config[CONF_PAYLOAD_ON] + payload_off = self._config[CONF_PAYLOAD_OFF] + + if payload == "True": + payload = payload_on + elif payload == "False": + payload = payload_off + + if payload == payload_on: + setattr(self, attr, True) + elif payload == payload_off: + setattr(self, attr, False) else: - self._current_swing_mode = payload - self.async_write_ha_state() + _LOGGER.error("Invalid %s mode: %s", attr, payload) - if self._topic[CONF_SWING_MODE_STATE_TOPIC] is not None: - topics[CONF_SWING_MODE_STATE_TOPIC] = { - 'topic': self._topic[CONF_SWING_MODE_STATE_TOPIC], - 'msg_callback': handle_swing_mode_received, - 'qos': qos} + self.async_write_ha_state() @callback def handle_away_mode_received(msg): """Handle receiving away mode via MQTT.""" - payload = msg.payload - payload_on = self._config[CONF_PAYLOAD_ON] - payload_off = self._config[CONF_PAYLOAD_OFF] - if CONF_AWAY_MODE_STATE_TEMPLATE in self._value_templates: - payload = \ - self._value_templates[CONF_AWAY_MODE_STATE_TEMPLATE].\ - async_render_with_possible_json_value(payload) - if payload == "True": - payload = payload_on - elif payload == "False": - payload = payload_off + handle_onoff_mode_received( + msg, CONF_AWAY_MODE_STATE_TEMPLATE, '_away') - if payload == payload_on: - self._away = True - elif payload == payload_off: - self._away = False - else: - _LOGGER.error("Invalid away mode: %s", payload) - - self.async_write_ha_state() - - if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None: - topics[CONF_AWAY_MODE_STATE_TOPIC] = { - 'topic': self._topic[CONF_AWAY_MODE_STATE_TOPIC], - 'msg_callback': handle_away_mode_received, - 'qos': qos} + add_subscription(topics, CONF_AWAY_MODE_STATE_TOPIC, + handle_away_mode_received) @callback def handle_aux_mode_received(msg): """Handle receiving aux mode via MQTT.""" - payload = msg.payload - payload_on = self._config[CONF_PAYLOAD_ON] - payload_off = self._config[CONF_PAYLOAD_OFF] - if CONF_AUX_STATE_TEMPLATE in self._value_templates: - payload = self._value_templates[CONF_AUX_STATE_TEMPLATE].\ - async_render_with_possible_json_value(payload) - if payload == "True": - payload = payload_on - elif payload == "False": - payload = payload_off + handle_onoff_mode_received( + msg, CONF_AUX_STATE_TEMPLATE, '_aux') - if payload == payload_on: - self._aux = True - elif payload == payload_off: - self._aux = False - else: - _LOGGER.error("Invalid aux mode: %s", payload) - - self.async_write_ha_state() - - if self._topic[CONF_AUX_STATE_TOPIC] is not None: - topics[CONF_AUX_STATE_TOPIC] = { - 'topic': self._topic[CONF_AUX_STATE_TOPIC], - 'msg_callback': handle_aux_mode_received, - 'qos': qos} + add_subscription(topics, CONF_AUX_STATE_TOPIC, + handle_aux_mode_received) @callback def handle_hold_mode_received(msg): """Handle receiving hold mode via MQTT.""" - payload = msg.payload - if CONF_HOLD_STATE_TEMPLATE in self._value_templates: - payload = self._value_templates[CONF_HOLD_STATE_TEMPLATE].\ - async_render_with_possible_json_value(payload) + payload = render_template(msg, CONF_HOLD_STATE_TEMPLATE) self._hold = payload self.async_write_ha_state() - if self._topic[CONF_HOLD_STATE_TOPIC] is not None: - topics[CONF_HOLD_STATE_TOPIC] = { - 'topic': self._topic[CONF_HOLD_STATE_TOPIC], - 'msg_callback': handle_hold_mode_received, - 'qos': qos} + add_subscription(topics, CONF_HOLD_STATE_TOPIC, + handle_hold_mode_received) self._sub_state = await subscription.async_subscribe_topics( self.hass, self._sub_state, @@ -544,22 +482,22 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, @property def current_temperature(self): """Return the current temperature.""" - return self._current_temperature + return self._current_temp @property def target_temperature(self): """Return the temperature we try to reach.""" - return self._target_temperature + return self._target_temp @property def target_temperature_low(self): """Return the low target temperature we try to reach.""" - return self._target_temperature_low + return self._target_temp_low @property def target_temperature_high(self): """Return the high target temperature we try to reach.""" - return self._target_temperature_high + return self._target_temp_high @property def current_operation(self): @@ -601,48 +539,39 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, """Return the list of available fan modes.""" return self._config[CONF_FAN_MODE_LIST] + def _publish(self, topic, payload): + if self._topic[topic] is not None: + mqtt.async_publish( + self.hass, self._topic[topic], payload, + self._config[CONF_QOS], self._config[CONF_RETAIN]) + + def _set_temperature(self, temp, cmnd_topic, state_topic, attr): + if temp is not None: + if self._topic[state_topic] is None: + # optimistic mode + setattr(self, attr, temp) + + if (self._config[CONF_SEND_IF_OFF] or + self._current_operation != STATE_OFF): + self._publish(cmnd_topic, temp) + async def async_set_temperature(self, **kwargs): """Set new target temperatures.""" if kwargs.get(ATTR_OPERATION_MODE) is not None: operation_mode = kwargs.get(ATTR_OPERATION_MODE) await self.async_set_operation_mode(operation_mode) - if kwargs.get(ATTR_TEMPERATURE) is not None: - if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is None: - # optimistic mode - self._target_temperature = kwargs.get(ATTR_TEMPERATURE) + self._set_temperature( + kwargs.get(ATTR_TEMPERATURE), CONF_TEMP_COMMAND_TOPIC, + CONF_TEMP_STATE_TOPIC, '_target_temp') - if (self._config[CONF_SEND_IF_OFF] or - self._current_operation != STATE_OFF): - mqtt.async_publish( - self.hass, self._topic[CONF_TEMPERATURE_COMMAND_TOPIC], - kwargs.get(ATTR_TEMPERATURE), self._config[CONF_QOS], - self._config[CONF_RETAIN]) + self._set_temperature( + kwargs.get(ATTR_TARGET_TEMP_LOW), CONF_TEMP_LOW_COMMAND_TOPIC, + CONF_TEMP_LOW_STATE_TOPIC, '_target_temp_low') - if kwargs.get(ATTR_TARGET_TEMP_LOW) is not None: - if self._topic[CONF_TEMPERATURE_LOW_STATE_TOPIC] is None: - # optimistic mode - self._target_temperature_low = kwargs[ATTR_TARGET_TEMP_LOW] - - if (self._config[CONF_SEND_IF_OFF] or - self._current_operation != STATE_OFF): - mqtt.async_publish( - self.hass, self._topic[CONF_TEMPERATURE_LOW_COMMAND_TOPIC], - kwargs.get(ATTR_TARGET_TEMP_LOW), self._config[CONF_QOS], - self._config[CONF_RETAIN]) - - if kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None: - if self._topic[CONF_TEMPERATURE_HIGH_STATE_TOPIC] is None: - # optimistic mode - self._target_temperature_high = kwargs[ATTR_TARGET_TEMP_HIGH] - - if (self._config[CONF_SEND_IF_OFF] or - self._current_operation != STATE_OFF): - mqtt.async_publish( - self.hass, - self._topic[CONF_TEMPERATURE_HIGH_COMMAND_TOPIC], - kwargs.get(ATTR_TARGET_TEMP_HIGH), self._config[CONF_QOS], - self._config[CONF_RETAIN]) + self._set_temperature( + kwargs.get(ATTR_TARGET_TEMP_HIGH), CONF_TEMP_HIGH_COMMAND_TOPIC, + CONF_TEMP_HIGH_STATE_TOPIC, '_target_temp_high') # Always optimistic? self.async_write_ha_state() @@ -651,10 +580,8 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, """Set new swing mode.""" if (self._config[CONF_SEND_IF_OFF] or self._current_operation != STATE_OFF): - mqtt.async_publish( - self.hass, self._topic[CONF_SWING_MODE_COMMAND_TOPIC], - swing_mode, self._config[CONF_QOS], - self._config[CONF_RETAIN]) + self._publish(CONF_SWING_MODE_COMMAND_TOPIC, + swing_mode) if self._topic[CONF_SWING_MODE_STATE_TOPIC] is None: self._current_swing_mode = swing_mode @@ -664,10 +591,8 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, """Set new target temperature.""" if (self._config[CONF_SEND_IF_OFF] or self._current_operation != STATE_OFF): - mqtt.async_publish( - self.hass, self._topic[CONF_FAN_MODE_COMMAND_TOPIC], - fan_mode, self._config[CONF_QOS], - self._config[CONF_RETAIN]) + self._publish(CONF_FAN_MODE_COMMAND_TOPIC, + fan_mode) if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None: self._current_fan_mode = fan_mode @@ -675,24 +600,17 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, async def async_set_operation_mode(self, operation_mode) -> None: """Set new operation mode.""" - qos = self._config[CONF_QOS] - retain = self._config[CONF_RETAIN] - if self._topic[CONF_POWER_COMMAND_TOPIC] is not None: - if (self._current_operation == STATE_OFF and - operation_mode != STATE_OFF): - mqtt.async_publish( - self.hass, self._topic[CONF_POWER_COMMAND_TOPIC], - self._config[CONF_PAYLOAD_ON], qos, retain) - elif (self._current_operation != STATE_OFF and - operation_mode == STATE_OFF): - mqtt.async_publish( - self.hass, self._topic[CONF_POWER_COMMAND_TOPIC], - self._config[CONF_PAYLOAD_OFF], qos, retain) + if (self._current_operation == STATE_OFF and + operation_mode != STATE_OFF): + self._publish(CONF_POWER_COMMAND_TOPIC, + self._config[CONF_PAYLOAD_ON]) + elif (self._current_operation != STATE_OFF and + operation_mode == STATE_OFF): + self._publish(CONF_POWER_COMMAND_TOPIC, + self._config[CONF_PAYLOAD_OFF]) - if self._topic[CONF_MODE_COMMAND_TOPIC] is not None: - mqtt.async_publish( - self.hass, self._topic[CONF_MODE_COMMAND_TOPIC], - operation_mode, qos, retain) + self._publish(CONF_MODE_COMMAND_TOPIC, + operation_mode) if self._topic[CONF_MODE_STATE_TOPIC] is None: self._current_operation = operation_mode @@ -708,83 +626,63 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, """List of available swing modes.""" return self._config[CONF_SWING_MODE_LIST] - async def async_turn_away_mode_on(self): - """Turn away mode on.""" - if self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None: - mqtt.async_publish(self.hass, - self._topic[CONF_AWAY_MODE_COMMAND_TOPIC], - self._config[CONF_PAYLOAD_ON], - self._config[CONF_QOS], - self._config[CONF_RETAIN]) + def _set_away_mode(self, state): + self._publish(CONF_AWAY_MODE_COMMAND_TOPIC, + self._config[CONF_PAYLOAD_ON] if state + else self._config[CONF_PAYLOAD_OFF]) if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None: - self._away = True + self._away = state self.async_write_ha_state() + async def async_turn_away_mode_on(self): + """Turn away mode on.""" + self._set_away_mode(True) + async def async_turn_away_mode_off(self): """Turn away mode off.""" - if self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None: - mqtt.async_publish(self.hass, - self._topic[CONF_AWAY_MODE_COMMAND_TOPIC], - self._config[CONF_PAYLOAD_OFF], - self._config[CONF_QOS], - self._config[CONF_RETAIN]) - - if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None: - self._away = False - self.async_write_ha_state() + self._set_away_mode(False) async def async_set_hold_mode(self, hold_mode): """Update hold mode on.""" - if self._topic[CONF_HOLD_COMMAND_TOPIC] is not None: - mqtt.async_publish(self.hass, - self._topic[CONF_HOLD_COMMAND_TOPIC], - hold_mode, self._config[CONF_QOS], - self._config[CONF_RETAIN]) + self._publish(CONF_HOLD_COMMAND_TOPIC, hold_mode) if self._topic[CONF_HOLD_STATE_TOPIC] is None: self._hold = hold_mode self.async_write_ha_state() - async def async_turn_aux_heat_on(self): - """Turn auxiliary heater on.""" - if self._topic[CONF_AUX_COMMAND_TOPIC] is not None: - mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC], - self._config[CONF_PAYLOAD_ON], - self._config[CONF_QOS], - self._config[CONF_RETAIN]) + def _set_aux_heat(self, state): + self._publish(CONF_AUX_COMMAND_TOPIC, + self._config[CONF_PAYLOAD_ON] if state + else self._config[CONF_PAYLOAD_OFF]) if self._topic[CONF_AUX_STATE_TOPIC] is None: - self._aux = True + self._aux = state self.async_write_ha_state() + async def async_turn_aux_heat_on(self): + """Turn auxiliary heater on.""" + self._set_aux_heat(True) + async def async_turn_aux_heat_off(self): """Turn auxiliary heater off.""" - if self._topic[CONF_AUX_COMMAND_TOPIC] is not None: - mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC], - self._config[CONF_PAYLOAD_OFF], - self._config[CONF_QOS], - self._config[CONF_RETAIN]) - - if self._topic[CONF_AUX_STATE_TOPIC] is None: - self._aux = False - self.async_write_ha_state() + self._set_aux_heat(False) @property def supported_features(self): """Return the list of supported features.""" support = 0 - if (self._topic[CONF_TEMPERATURE_STATE_TOPIC] is not None) or \ - (self._topic[CONF_TEMPERATURE_COMMAND_TOPIC] is not None): + if (self._topic[CONF_TEMP_STATE_TOPIC] is not None) or \ + (self._topic[CONF_TEMP_COMMAND_TOPIC] is not None): support |= SUPPORT_TARGET_TEMPERATURE - if (self._topic[CONF_TEMPERATURE_LOW_STATE_TOPIC] is not None) or \ - (self._topic[CONF_TEMPERATURE_LOW_COMMAND_TOPIC] is not None): + if (self._topic[CONF_TEMP_LOW_STATE_TOPIC] is not None) or \ + (self._topic[CONF_TEMP_LOW_COMMAND_TOPIC] is not None): support |= SUPPORT_TARGET_TEMPERATURE_LOW - if (self._topic[CONF_TEMPERATURE_HIGH_STATE_TOPIC] is not None) or \ - (self._topic[CONF_TEMPERATURE_HIGH_COMMAND_TOPIC] is not None): + if (self._topic[CONF_TEMP_HIGH_STATE_TOPIC] is not None) or \ + (self._topic[CONF_TEMP_HIGH_COMMAND_TOPIC] is not None): support |= SUPPORT_TARGET_TEMPERATURE_HIGH if (self._topic[CONF_MODE_COMMAND_TOPIC] is not None) or \