diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index b70ffa80145..620502695c8 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -31,6 +31,8 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'MQTT HVAC' +CONF_ACTION_TEMPLATE = 'action_template' +CONF_ACTION_TOPIC = 'action_topic' CONF_AUX_COMMAND_TOPIC = 'aux_command_topic' CONF_AUX_STATE_TEMPLATE = 'aux_state_template' CONF_AUX_STATE_TOPIC = 'aux_state_topic' @@ -83,6 +85,7 @@ TEMPLATE_KEYS = ( CONF_HOLD_STATE_TEMPLATE, CONF_MODE_STATE_TEMPLATE, CONF_POWER_STATE_TEMPLATE, + CONF_ACTION_TEMPLATE, CONF_SWING_MODE_STATE_TEMPLATE, CONF_TEMP_HIGH_STATE_TEMPLATE, CONF_TEMP_LOW_STATE_TEMPLATE, @@ -103,6 +106,7 @@ TOPIC_KEYS = ( CONF_MODE_STATE_TOPIC, CONF_POWER_COMMAND_TOPIC, CONF_POWER_STATE_TOPIC, + CONF_ACTION_TOPIC, CONF_SWING_MODE_COMMAND_TOPIC, CONF_SWING_MODE_STATE_TOPIC, CONF_TEMP_COMMAND_TOPIC, @@ -149,6 +153,8 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({ vol.Optional(CONF_POWER_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean, vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean, + vol.Optional(CONF_ACTION_TEMPLATE): cv.template, + vol.Optional(CONF_ACTION_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_SWING_MODE_LIST, default=[STATE_ON, HVAC_MODE_OFF]): cv.ensure_list, @@ -214,6 +220,7 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, self._sub_state = None self.hass = hass + self._action = None self._aux = False self._away = False self._current_fan_mode = None @@ -279,6 +286,7 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, self._current_swing_mode = HVAC_MODE_OFF if self._topic[CONF_MODE_STATE_TOPIC] is None: self._current_operation = HVAC_MODE_OFF + self._action = None self._away = False self._hold = None self._aux = False @@ -314,6 +322,17 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, template = self._value_templates[template_name] return template(msg.payload) + @callback + def handle_action_received(msg): + """Handle receiving action via MQTT.""" + payload = render_template(msg, CONF_ACTION_TEMPLATE) + + self._action = payload + self.async_write_ha_state() + + add_subscription(topics, CONF_ACTION_TOPIC, + handle_action_received) + @callback def handle_temperature_received(msg, template_name, attr): """Handle temperature coming via MQTT.""" @@ -503,6 +522,11 @@ class MqttClimate(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, """Return the high target temperature we try to reach.""" return self._target_temp_high + @property + def hvac_action(self): + """Return the current running hvac operation if supported.""" + return self._action + @property def hvac_mode(self): """Return current operation ie. heat, cool, idle.""" diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index 6792675f594..fe971ff57f3 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -432,6 +432,20 @@ async def test_set_away_mode(hass, mqtt_mock): assert state.attributes.get('preset_mode') is None +async def test_set_hvac_action(hass, mqtt_mock): + """Test setting of the HVAC action.""" + config = copy.deepcopy(DEFAULT_CONFIG) + config['climate']['action_topic'] = 'action' + assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get('hvac_action') is None + + async_fire_mqtt_message(hass, 'action', 'cool') + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get('hvac_action') == 'cool' + + async def test_set_hold_pessimistic(hass, mqtt_mock): """Test setting the hold mode in pessimistic mode.""" config = copy.deepcopy(DEFAULT_CONFIG) @@ -548,6 +562,7 @@ async def test_set_with_templates(hass, mqtt_mock, caplog): config = copy.deepcopy(DEFAULT_CONFIG) # By default, just unquote the JSON-strings config['climate']['value_template'] = '{{ value_json }}' + config['climate']['action_template'] = '{{ value_json }}' # Something more complicated for hold mode config['climate']['hold_state_template'] = \ '{{ value_json.attribute }}' @@ -555,6 +570,7 @@ async def test_set_with_templates(hass, mqtt_mock, caplog): config['climate']['aux_state_template'] = \ "{{ value == 'switchmeon' }}" + config['climate']['action_topic'] = 'action' config['climate']['mode_state_topic'] = 'mode-state' config['climate']['fan_mode_state_topic'] = 'fan-state' config['climate']['swing_mode_state_topic'] = 'swing-state' @@ -636,6 +652,11 @@ async def test_set_with_templates(hass, mqtt_mock, caplog): state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get('current_temperature') == 74656 + # Action + async_fire_mqtt_message(hass, 'action', '"cool"') + state = hass.states.get(ENTITY_CLIMATE) + assert state.attributes.get('hvac_action') == 'cool' + async def test_min_temp_custom(hass, mqtt_mock): """Test a custom min temp."""