mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Add missing MQTT lock.open (#60022)
* Add missing MQTT lock.open from: https://community.home-assistant.io/t/mqtt-lock-open/232823/13 based on https://github.com/home-assistant/core/pull/48008 * Update homeassistant/components/mqtt/lock.py Co-authored-by: Erik Montnemery <erik@montnemery.com> * Update homeassistant/components/mqtt/lock.py Co-authored-by: Erik Montnemery <erik@montnemery.com> * Update homeassistant/components/mqtt/lock.py Co-authored-by: Erik Montnemery <erik@montnemery.com> * removed `STATE_OPEN` from tests * Apply suggestions from code review * Format code * Update lock.py * Update test_lock.py * Update test_lock.py Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
parent
635d875b1d
commit
57fd632cd9
@ -4,7 +4,7 @@ import functools
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import lock
|
||||
from homeassistant.components.lock import LockEntity
|
||||
from homeassistant.components.lock import SUPPORT_OPEN, LockEntity
|
||||
from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@ -19,6 +19,7 @@ from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_hel
|
||||
|
||||
CONF_PAYLOAD_LOCK = "payload_lock"
|
||||
CONF_PAYLOAD_UNLOCK = "payload_unlock"
|
||||
CONF_PAYLOAD_OPEN = "payload_open"
|
||||
|
||||
CONF_STATE_LOCKED = "state_locked"
|
||||
CONF_STATE_UNLOCKED = "state_unlocked"
|
||||
@ -27,6 +28,7 @@ DEFAULT_NAME = "MQTT Lock"
|
||||
DEFAULT_OPTIMISTIC = False
|
||||
DEFAULT_PAYLOAD_LOCK = "LOCK"
|
||||
DEFAULT_PAYLOAD_UNLOCK = "UNLOCK"
|
||||
DEFAULT_PAYLOAD_OPEN = "OPEN"
|
||||
DEFAULT_STATE_LOCKED = "LOCKED"
|
||||
DEFAULT_STATE_UNLOCKED = "UNLOCKED"
|
||||
|
||||
@ -43,6 +45,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend(
|
||||
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
|
||||
vol.Optional(CONF_PAYLOAD_LOCK, default=DEFAULT_PAYLOAD_LOCK): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_UNLOCK, default=DEFAULT_PAYLOAD_UNLOCK): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_OPEN): cv.string,
|
||||
vol.Optional(CONF_STATE_LOCKED, default=DEFAULT_STATE_LOCKED): cv.string,
|
||||
vol.Optional(CONF_STATE_UNLOCKED, default=DEFAULT_STATE_UNLOCKED): cv.string,
|
||||
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
||||
@ -145,6 +148,11 @@ class MqttLock(MqttEntity, LockEntity):
|
||||
"""Return true if we do optimistic updates."""
|
||||
return self._optimistic
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
return SUPPORT_OPEN if CONF_PAYLOAD_OPEN in self._config else 0
|
||||
|
||||
async def async_lock(self, **kwargs):
|
||||
"""Lock the device.
|
||||
|
||||
@ -178,3 +186,20 @@ class MqttLock(MqttEntity, LockEntity):
|
||||
# Optimistically assume that the lock has changed state.
|
||||
self._state = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_open(self, **kwargs):
|
||||
"""Open the door latch.
|
||||
|
||||
This method is a coroutine.
|
||||
"""
|
||||
await mqtt.async_publish(
|
||||
self.hass,
|
||||
self._config[CONF_COMMAND_TOPIC],
|
||||
self._config[CONF_PAYLOAD_OPEN],
|
||||
self._config[CONF_QOS],
|
||||
self._config[CONF_RETAIN],
|
||||
)
|
||||
if self._optimistic:
|
||||
# Optimistically assume that the lock unlocks when opened.
|
||||
self._state = False
|
||||
self.async_write_ha_state()
|
||||
|
@ -6,12 +6,18 @@ import pytest
|
||||
from homeassistant.components.lock import (
|
||||
DOMAIN as LOCK_DOMAIN,
|
||||
SERVICE_LOCK,
|
||||
SERVICE_OPEN,
|
||||
SERVICE_UNLOCK,
|
||||
STATE_LOCKED,
|
||||
STATE_UNLOCKED,
|
||||
SUPPORT_OPEN,
|
||||
)
|
||||
from homeassistant.components.mqtt.lock import MQTT_LOCK_ATTRIBUTES_BLOCKED
|
||||
from homeassistant.const import ATTR_ASSUMED_STATE, ATTR_ENTITY_ID
|
||||
from homeassistant.const import (
|
||||
ATTR_ASSUMED_STATE,
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .test_common import (
|
||||
@ -69,6 +75,7 @@ async def test_controlling_state_via_topic(hass, mqtt_mock):
|
||||
state = hass.states.get("lock.test")
|
||||
assert state.state is STATE_UNLOCKED
|
||||
assert not state.attributes.get(ATTR_ASSUMED_STATE)
|
||||
assert not state.attributes.get(ATTR_SUPPORTED_FEATURES)
|
||||
|
||||
async_fire_mqtt_message(hass, "state-topic", "LOCKED")
|
||||
|
||||
@ -278,6 +285,122 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock):
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
||||
|
||||
|
||||
async def test_sending_mqtt_commands_support_open_and_optimistic(hass, mqtt_mock):
|
||||
"""Test open function of the lock 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",
|
||||
"payload_open": "OPEN",
|
||||
"state_locked": "LOCKED",
|
||||
"state_unlocked": "UNLOCKED",
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("lock.test")
|
||||
assert state.state is STATE_UNLOCKED
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
||||
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == SUPPORT_OPEN
|
||||
|
||||
await hass.services.async_call(
|
||||
LOCK_DOMAIN, SERVICE_LOCK, {ATTR_ENTITY_ID: "lock.test"}, blocking=True
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
await hass.services.async_call(
|
||||
LOCK_DOMAIN, SERVICE_UNLOCK, {ATTR_ENTITY_ID: "lock.test"}, blocking=True
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
await hass.services.async_call(
|
||||
LOCK_DOMAIN, SERVICE_OPEN, {ATTR_ENTITY_ID: "lock.test"}, blocking=True
|
||||
)
|
||||
|
||||
mqtt_mock.async_publish.assert_called_once_with("command-topic", "OPEN", 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_support_open_and_explicit_optimistic(
|
||||
hass, mqtt_mock
|
||||
):
|
||||
"""Test open function of the lock 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",
|
||||
"payload_open": "OPEN",
|
||||
"state_locked": "LOCKED",
|
||||
"state_unlocked": "UNLOCKED",
|
||||
"optimistic": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("lock.test")
|
||||
assert state.state is STATE_UNLOCKED
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
||||
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == SUPPORT_OPEN
|
||||
|
||||
await hass.services.async_call(
|
||||
LOCK_DOMAIN, SERVICE_LOCK, {ATTR_ENTITY_ID: "lock.test"}, blocking=True
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
await hass.services.async_call(
|
||||
LOCK_DOMAIN, SERVICE_UNLOCK, {ATTR_ENTITY_ID: "lock.test"}, blocking=True
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
await hass.services.async_call(
|
||||
LOCK_DOMAIN, SERVICE_OPEN, {ATTR_ENTITY_ID: "lock.test"}, blocking=True
|
||||
)
|
||||
|
||||
mqtt_mock.async_publish.assert_called_once_with("command-topic", "OPEN", 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_availability_when_connection_lost(hass, mqtt_mock):
|
||||
"""Test availability after MQTT disconnection."""
|
||||
await help_test_availability_when_connection_lost(
|
||||
|
Loading…
x
Reference in New Issue
Block a user