diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 3c75648892b..929ae0fc455 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -58,6 +58,7 @@ CONF_WILL_MESSAGE = 'will_message' CONF_STATE_TOPIC = 'state_topic' CONF_COMMAND_TOPIC = 'command_topic' +CONF_AVAILABILITY_TOPIC = 'availability_topic' CONF_QOS = 'qos' CONF_RETAIN = 'retain' diff --git a/homeassistant/components/switch/mqtt.py b/homeassistant/components/switch/mqtt.py index e9c282e4c45..1169e5a3d5d 100644 --- a/homeassistant/components/switch/mqtt.py +++ b/homeassistant/components/switch/mqtt.py @@ -11,7 +11,8 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.components.mqtt import ( - CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN) + CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_AVAILABILITY_TOPIC, CONF_QOS, + CONF_RETAIN) from homeassistant.components.switch import SwitchDevice from homeassistant.const import ( CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_OFF, @@ -50,6 +51,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), config.get(CONF_COMMAND_TOPIC), + config.get(CONF_AVAILABILITY_TOPIC), config.get(CONF_QOS), config.get(CONF_RETAIN), config.get(CONF_PAYLOAD_ON), @@ -62,13 +64,16 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): class MqttSwitch(SwitchDevice): """Representation of a switch that can be toggled using MQTT.""" - def __init__(self, name, state_topic, command_topic, qos, retain, - payload_on, payload_off, optimistic, value_template): + def __init__(self, name, state_topic, command_topic, availability_topic, + qos, retain, payload_on, payload_off, optimistic, + value_template): """Initialize the MQTT switch.""" self._state = False self._name = name self._state_topic = state_topic self._command_topic = command_topic + self._availability_topic = availability_topic + self._available = True if availability_topic is None else False self._qos = qos self._retain = retain self._payload_on = payload_on @@ -83,8 +88,8 @@ class MqttSwitch(SwitchDevice): This method is a coroutine. """ @callback - def message_received(topic, payload, qos): - """Handle new MQTT messages.""" + def state_message_received(topic, payload, qos): + """Handle new MQTT state messages.""" if self._template is not None: payload = self._template.async_render_with_possible_json_value( payload) @@ -95,12 +100,28 @@ class MqttSwitch(SwitchDevice): self.hass.async_add_job(self.async_update_ha_state()) + @callback + def availability_message_received(topic, payload, qos): + """Handle new MQTT availability messages.""" + if payload == self._payload_on: + self._available = True + elif payload == self._payload_off: + self._available = False + + self.hass.async_add_job(self.async_update_ha_state()) + if self._state_topic is None: # Force into optimistic mode. self._optimistic = True else: yield from mqtt.async_subscribe( - self.hass, self._state_topic, message_received, self._qos) + self.hass, self._state_topic, state_message_received, + self._qos) + + if self._availability_topic is not None: + yield from mqtt.async_subscribe( + self.hass, self._availability_topic, + availability_message_received, self._qos) @property def should_poll(self): @@ -112,6 +133,11 @@ class MqttSwitch(SwitchDevice): """Return the name of the switch.""" return self._name + @property + def available(self) -> bool: + """Return if switch is available.""" + return self._available + @property def is_on(self): """Return true if device is on.""" diff --git a/tests/components/switch/test_mqtt.py b/tests/components/switch/test_mqtt.py index 8215eae26cc..133978a7bd8 100644 --- a/tests/components/switch/test_mqtt.py +++ b/tests/components/switch/test_mqtt.py @@ -2,7 +2,8 @@ import unittest from homeassistant.setup import setup_component -from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ASSUMED_STATE +from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNAVAILABLE,\ + ATTR_ASSUMED_STATE import homeassistant.components.switch as switch from tests.common import ( mock_mqtt_component, fire_mqtt_message, get_test_home_assistant) @@ -110,3 +111,45 @@ class TestSensorMQTT(unittest.TestCase): state = self.hass.states.get('switch.test') self.assertEqual(STATE_OFF, state.state) + + def test_controlling_availability(self): + """Test the controlling state via topic.""" + assert setup_component(self.hass, switch.DOMAIN, { + switch.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'availability_topic': 'availability_topic', + 'payload_on': 1, + 'payload_off': 0 + } + }) + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability_topic', '1') + self.hass.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_OFF, state.state) + self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + + fire_mqtt_message(self.hass, 'availability_topic', '0') + self.hass.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'state-topic', '1') + self.hass.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability_topic', '1') + self.hass.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_ON, state.state)