diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index b9c095a054a..235eacc9454 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -79,12 +79,14 @@ class MqttLock(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, def __init__(self, config, config_entry, discovery_hash): """Initialize the lock.""" - self._config = config self._unique_id = config.get(CONF_UNIQUE_ID) self._state = False self._sub_state = None self._optimistic = False + # Load config + self._setup_from_config(config) + device_config = config.get(CONF_DEVICE) MqttAttributes.__init__(self, config) @@ -101,13 +103,19 @@ class MqttLock(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate, async def discovery_update(self, discovery_payload): """Handle updated discovery message.""" config = PLATFORM_SCHEMA(discovery_payload) - self._config = config + self._setup_from_config(config) await self.attributes_discovery_update(config) await self.availability_discovery_update(config) await self.device_info_discovery_update(config) await self._subscribe_topics() self.async_write_ha_state() + def _setup_from_config(self, config): + """(Re)Setup the entity.""" + self._config = config + + self._optimistic = config[CONF_OPTIMISTIC] + async def _subscribe_topics(self): """(Re)Subscribe to topics.""" value_template = self._config.get(CONF_VALUE_TEMPLATE) diff --git a/tests/components/lock/common.py b/tests/components/lock/common.py index 2150b3cb894..c5a71a3eb96 100644 --- a/tests/components/lock/common.py +++ b/tests/components/lock/common.py @@ -6,6 +6,7 @@ components. Instead call the service directly. from homeassistant.components.lock import DOMAIN from homeassistant.const import ( ATTR_CODE, ATTR_ENTITY_ID, SERVICE_LOCK, SERVICE_UNLOCK, SERVICE_OPEN) +from homeassistant.core import callback from homeassistant.loader import bind_hass @@ -21,6 +22,19 @@ def lock(hass, entity_id=None, code=None): hass.services.call(DOMAIN, SERVICE_LOCK, data) +@callback +@bind_hass +def async_lock(hass, entity_id=None, code=None): + """Lock all or specified locks.""" + data = {} + if code: + data[ATTR_CODE] = code + if entity_id: + data[ATTR_ENTITY_ID] = entity_id + + hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_LOCK, data)) + + @bind_hass def unlock(hass, entity_id=None, code=None): """Unlock all or specified locks.""" @@ -33,6 +47,19 @@ def unlock(hass, entity_id=None, code=None): hass.services.call(DOMAIN, SERVICE_UNLOCK, data) +@callback +@bind_hass +def async_unlock(hass, entity_id=None, code=None): + """Lock all or specified locks.""" + data = {} + if code: + data[ATTR_CODE] = code + if entity_id: + data[ATTR_ENTITY_ID] = entity_id + + hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_UNLOCK, data)) + + @bind_hass def open_lock(hass, entity_id=None, code=None): """Open all or specified locks.""" @@ -43,3 +70,16 @@ def open_lock(hass, entity_id=None, code=None): data[ATTR_ENTITY_ID] = entity_id hass.services.call(DOMAIN, SERVICE_OPEN, data) + + +@callback +@bind_hass +def async_open_lock(hass, entity_id=None, code=None): + """Lock all or specified locks.""" + data = {} + if code: + data[ATTR_CODE] = code + if entity_id: + data[ATTR_ENTITY_ID] = entity_id + + hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_OPEN, data)) diff --git a/tests/components/mqtt/test_lock.py b/tests/components/mqtt/test_lock.py index 52dd3ecfbdb..cc629b2165d 100644 --- a/tests/components/mqtt/test_lock.py +++ b/tests/components/mqtt/test_lock.py @@ -11,6 +11,7 @@ from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, async_fire_mqtt_message, async_mock_mqtt_component, mock_registry) +from tests.components.lock import common async def test_controlling_state_via_topic(hass, mqtt_mock): @@ -75,6 +76,82 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock): assert state.state is STATE_UNLOCKED +async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): + """Test optimistic mode without state topic.""" + assert await async_setup_component(hass, lock.DOMAIN, { + lock.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'command_topic': 'command-topic', + 'payload_lock': 'LOCK', + 'payload_unlock': 'UNLOCK' + } + }) + + state = hass.states.get('lock.test') + assert state.state is STATE_UNLOCKED + assert state.attributes.get(ATTR_ASSUMED_STATE) + + common.async_lock(hass, 'lock.test') + await hass.async_block_till_done() + + mqtt_mock.async_publish.assert_called_once_with( + 'command-topic', 'LOCK', 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get('lock.test') + assert state.state is STATE_LOCKED + assert state.attributes.get(ATTR_ASSUMED_STATE) + + common.async_unlock(hass, 'lock.test') + await hass.async_block_till_done() + + mqtt_mock.async_publish.assert_called_once_with( + 'command-topic', 'UNLOCK', 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get('lock.test') + assert state.state is STATE_UNLOCKED + assert state.attributes.get(ATTR_ASSUMED_STATE) + + +async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock): + """Test optimistic mode without state topic.""" + assert await async_setup_component(hass, lock.DOMAIN, { + lock.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'payload_lock': 'LOCK', + 'payload_unlock': 'UNLOCK', + 'optimistic': True + } + }) + + state = hass.states.get('lock.test') + assert state.state is STATE_UNLOCKED + assert state.attributes.get(ATTR_ASSUMED_STATE) + + common.async_lock(hass, 'lock.test') + await hass.async_block_till_done() + + mqtt_mock.async_publish.assert_called_once_with( + 'command-topic', 'LOCK', 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get('lock.test') + assert state.state is STATE_LOCKED + assert state.attributes.get(ATTR_ASSUMED_STATE) + + common.async_unlock(hass, 'lock.test') + await hass.async_block_till_done() + + mqtt_mock.async_publish.assert_called_once_with( + 'command-topic', 'UNLOCK', 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get('lock.test') + assert state.state is STATE_UNLOCKED + assert state.attributes.get(ATTR_ASSUMED_STATE) + + async def test_default_availability_payload(hass, mqtt_mock): """Test availability by default payload with defined topic.""" assert await async_setup_component(hass, lock.DOMAIN, {