forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
mqtt garage door component (home-assistant#1742)
- Loading branch information
Showing
2 changed files
with
270 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
""" | ||
Support for MQTT garage doors. | ||
For more details about this platform, please refer to the documentation at | ||
https://home-assistant.io/components/garage_door.mqtt/ | ||
""" | ||
import logging | ||
|
||
import voluptuous as vol | ||
from homeassistant.const import (STATE_OPEN, STATE_CLOSED, SERVICE_OPEN, | ||
SERVICE_CLOSE) | ||
import homeassistant.components.mqtt as mqtt | ||
from homeassistant.components.garage_door import GarageDoorDevice | ||
from homeassistant.const import ( | ||
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE) | ||
from homeassistant.components.mqtt import ( | ||
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN) | ||
import homeassistant.helpers.config_validation as cv | ||
from homeassistant.helpers import template | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
CONF_STATE_OPEN = 'state_open' | ||
CONF_STATE_CLOSED = 'state_closed' | ||
CONF_SERVICE_OPEN = 'service_open' | ||
CONF_SERVICE_CLOSE = 'service_close' | ||
|
||
DEFAULT_NAME = 'MQTT Garage Door' | ||
DEFAULT_OPTIMISTIC = False | ||
DEFAULT_RETAIN = False | ||
|
||
DEPENDENCIES = ['mqtt'] | ||
|
||
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ | ||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, | ||
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, | ||
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, | ||
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, | ||
vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string, | ||
vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string, | ||
vol.Optional(CONF_SERVICE_OPEN, default=SERVICE_OPEN): cv.string, | ||
vol.Optional(CONF_SERVICE_CLOSE, default=SERVICE_CLOSE): cv.string | ||
}) | ||
|
||
|
||
def setup_platform(hass, config, add_devices_callback, discovery_info=None): | ||
"""Add MQTT Garage Door.""" | ||
add_devices_callback([MqttGarageDoor( | ||
hass, | ||
config[CONF_NAME], | ||
config.get(CONF_STATE_TOPIC), | ||
config[CONF_COMMAND_TOPIC], | ||
config[CONF_QOS], | ||
config[CONF_RETAIN], | ||
config[CONF_STATE_OPEN], | ||
config[CONF_STATE_CLOSED], | ||
config[CONF_SERVICE_OPEN], | ||
config[CONF_SERVICE_CLOSE], | ||
config[CONF_OPTIMISTIC], | ||
config.get(CONF_VALUE_TEMPLATE))]) | ||
|
||
|
||
# pylint: disable=too-many-arguments, too-many-instance-attributes | ||
class MqttGarageDoor(GarageDoorDevice): | ||
"""Representation of a MQTT garage door.""" | ||
|
||
def __init__(self, hass, name, state_topic, command_topic, qos, retain, | ||
state_open, state_closed, service_open, service_close, | ||
optimistic, value_template): | ||
"""Initialize the garage door.""" | ||
self._hass = hass | ||
self._name = name | ||
self._state_topic = state_topic | ||
self._command_topic = command_topic | ||
self._qos = qos | ||
self._retain = retain | ||
self._state_open = state_open | ||
self._state_closed = state_closed | ||
self._service_open = service_open | ||
self._service_close = service_close | ||
self._optimistic = optimistic or state_topic is None | ||
self._state = False | ||
|
||
def message_received(topic, payload, qos): | ||
"""A new MQTT message has been received.""" | ||
if value_template is not None: | ||
payload = template.render_with_possible_json_value( | ||
hass, value_template, payload) | ||
if payload == self._state_open: | ||
self._state = True | ||
self.update_ha_state() | ||
elif payload == self._state_closed: | ||
self._state = False | ||
self.update_ha_state() | ||
|
||
if self._state_topic is None: | ||
# Force into optimistic mode. | ||
self._optimistic = True | ||
else: | ||
mqtt.subscribe(hass, self._state_topic, message_received, | ||
self._qos) | ||
|
||
@property | ||
def name(self): | ||
"""Return the name of the garage door if any.""" | ||
return self._name | ||
|
||
@property | ||
def is_opened(self): | ||
"""Return true if door is closed.""" | ||
return self._state | ||
|
||
@property | ||
def is_closed(self): | ||
"""Return true if door is closed.""" | ||
return self._state is False | ||
|
||
@property | ||
def assumed_state(self): | ||
"""Return true if we do optimistic updates.""" | ||
return self._optimistic | ||
|
||
def close_door(self): | ||
"""Close the door.""" | ||
mqtt.publish(self.hass, self._command_topic, self._service_close, | ||
self._qos, self._retain) | ||
if self._optimistic: | ||
# Optimistically assume that door has changed state. | ||
self._state = False | ||
self.update_ha_state() | ||
|
||
def open_door(self): | ||
"""Open the door.""" | ||
mqtt.publish(self.hass, self._command_topic, self._service_open, | ||
self._qos, self._retain) | ||
if self._optimistic: | ||
# Optimistically assume that door has changed state. | ||
self._state = True | ||
self.update_ha_state() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
"""The tests for the MQTT Garge door platform.""" | ||
import unittest | ||
|
||
from homeassistant.bootstrap import _setup_component | ||
from homeassistant.const import STATE_OPEN, STATE_CLOSED, ATTR_ASSUMED_STATE | ||
|
||
import homeassistant.components.garage_door as garage_door | ||
from tests.common import ( | ||
mock_mqtt_component, fire_mqtt_message, get_test_home_assistant) | ||
|
||
|
||
class TestGarageDoorMQTT(unittest.TestCase): | ||
"""Test the MQTT Garage door.""" | ||
|
||
def setUp(self): # pylint: disable=invalid-name | ||
"""Setup things to be run when tests are started.""" | ||
self.hass = get_test_home_assistant() | ||
self.mock_publish = mock_mqtt_component(self.hass) | ||
|
||
def tearDown(self): # pylint: disable=invalid-name | ||
""""Stop everything that was started.""" | ||
self.hass.stop() | ||
|
||
def test_fail_setup_if_no_command_topic(self): | ||
"""Test if command fails with command topic.""" | ||
self.hass.config.components = ['mqtt'] | ||
assert not _setup_component(self.hass, garage_door.DOMAIN, { | ||
garage_door.DOMAIN: { | ||
'platform': 'mqtt', | ||
'name': 'test', | ||
'state_topic': '/home/garage_door/door' | ||
} | ||
}) | ||
self.assertIsNone(self.hass.states.get('garage_door.test')) | ||
|
||
def test_controlling_state_via_topic(self): | ||
"""Test the controlling state via topic.""" | ||
assert _setup_component(self.hass, garage_door.DOMAIN, { | ||
garage_door.DOMAIN: { | ||
'platform': 'mqtt', | ||
'name': 'test', | ||
'state_topic': 'state-topic', | ||
'command_topic': 'command-topic', | ||
'state_open': 1, | ||
'state_closed': 0, | ||
'service_open': 1, | ||
'service_close': 0 | ||
} | ||
}) | ||
|
||
state = self.hass.states.get('garage_door.test') | ||
self.assertEqual(STATE_CLOSED, state.state) | ||
self.assertIsNone(state.attributes.get(ATTR_ASSUMED_STATE)) | ||
|
||
fire_mqtt_message(self.hass, 'state-topic', '1') | ||
self.hass.pool.block_till_done() | ||
|
||
state = self.hass.states.get('garage_door.test') | ||
self.assertEqual(STATE_OPEN, state.state) | ||
|
||
fire_mqtt_message(self.hass, 'state-topic', '0') | ||
self.hass.pool.block_till_done() | ||
|
||
state = self.hass.states.get('garage_door.test') | ||
self.assertEqual(STATE_CLOSED, state.state) | ||
|
||
def test_sending_mqtt_commands_and_optimistic(self): | ||
"""Test the sending MQTT commands in optimistic mode.""" | ||
assert _setup_component(self.hass, garage_door.DOMAIN, { | ||
garage_door.DOMAIN: { | ||
'platform': 'mqtt', | ||
'name': 'test', | ||
'command_topic': 'command-topic', | ||
'state_open': 'beer state open', | ||
'state_closed': 'beer state closed', | ||
'service_open': 'beer open', | ||
'service_close': 'beer close', | ||
'qos': '2' | ||
} | ||
}) | ||
|
||
state = self.hass.states.get('garage_door.test') | ||
self.assertEqual(STATE_CLOSED, state.state) | ||
self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) | ||
|
||
garage_door.open_door(self.hass, 'garage_door.test') | ||
self.hass.pool.block_till_done() | ||
|
||
self.assertEqual(('command-topic', 'beer open', 2, False), | ||
self.mock_publish.mock_calls[-1][1]) | ||
state = self.hass.states.get('garage_door.test') | ||
self.assertEqual(STATE_OPEN, state.state) | ||
|
||
garage_door.close_door(self.hass, 'garage_door.test') | ||
self.hass.pool.block_till_done() | ||
|
||
self.assertEqual(('command-topic', 'beer close', 2, False), | ||
self.mock_publish.mock_calls[-1][1]) | ||
state = self.hass.states.get('garage_door.test') | ||
self.assertEqual(STATE_CLOSED, state.state) | ||
|
||
def test_controlling_state_via_topic_and_json_message(self): | ||
"""Test the controlling state via topic and JSON message.""" | ||
assert _setup_component(self.hass, garage_door.DOMAIN, { | ||
garage_door.DOMAIN: { | ||
'platform': 'mqtt', | ||
'name': 'test', | ||
'state_topic': 'state-topic', | ||
'command_topic': 'command-topic', | ||
'state_open': 'beer open', | ||
'state_closed': 'beer closed', | ||
'service_open': 'beer service open', | ||
'service_close': 'beer service close', | ||
'value_template': '{{ value_json.val }}' | ||
} | ||
}) | ||
|
||
state = self.hass.states.get('garage_door.test') | ||
self.assertEqual(STATE_CLOSED, state.state) | ||
|
||
fire_mqtt_message(self.hass, 'state-topic', '{"val":"beer open"}') | ||
self.hass.pool.block_till_done() | ||
|
||
state = self.hass.states.get('garage_door.test') | ||
self.assertEqual(STATE_OPEN, state.state) | ||
|
||
fire_mqtt_message(self.hass, 'state-topic', '{"val":"beer closed"}') | ||
self.hass.pool.block_till_done() | ||
|
||
state = self.hass.states.get('garage_door.test') | ||
self.assertEqual(STATE_CLOSED, state.state) |