Add open and opening state support to MQTT lock (#117110)

This commit is contained in:
Jan Bouwhuis 2024-05-08 22:52:57 +02:00 committed by GitHub
parent 159f0fcce7
commit 840d8cb39f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 68 additions and 11 deletions

View file

@ -31,6 +31,8 @@ from .const import (
CONF_PAYLOAD_RESET,
CONF_QOS,
CONF_RETAIN,
CONF_STATE_OPEN,
CONF_STATE_OPENING,
CONF_STATE_TOPIC,
)
from .debug_info import log_messages
@ -56,6 +58,7 @@ CONF_PAYLOAD_OPEN = "payload_open"
CONF_STATE_LOCKED = "state_locked"
CONF_STATE_LOCKING = "state_locking"
CONF_STATE_UNLOCKED = "state_unlocked"
CONF_STATE_UNLOCKING = "state_unlocking"
CONF_STATE_JAMMED = "state_jammed"
@ -67,6 +70,8 @@ DEFAULT_PAYLOAD_OPEN = "OPEN"
DEFAULT_PAYLOAD_RESET = "None"
DEFAULT_STATE_LOCKED = "LOCKED"
DEFAULT_STATE_LOCKING = "LOCKING"
DEFAULT_STATE_OPEN = "OPEN"
DEFAULT_STATE_OPENING = "OPENING"
DEFAULT_STATE_UNLOCKED = "UNLOCKED"
DEFAULT_STATE_UNLOCKING = "UNLOCKING"
DEFAULT_STATE_JAMMED = "JAMMED"
@ -90,6 +95,8 @@ PLATFORM_SCHEMA_MODERN = MQTT_RW_SCHEMA.extend(
vol.Optional(CONF_STATE_JAMMED, default=DEFAULT_STATE_JAMMED): cv.string,
vol.Optional(CONF_STATE_LOCKED, default=DEFAULT_STATE_LOCKED): cv.string,
vol.Optional(CONF_STATE_LOCKING, default=DEFAULT_STATE_LOCKING): cv.string,
vol.Optional(CONF_STATE_OPEN, default=DEFAULT_STATE_OPEN): cv.string,
vol.Optional(CONF_STATE_OPENING, default=DEFAULT_STATE_OPENING): cv.string,
vol.Optional(CONF_STATE_UNLOCKED, default=DEFAULT_STATE_UNLOCKED): cv.string,
vol.Optional(CONF_STATE_UNLOCKING, default=DEFAULT_STATE_UNLOCKING): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
@ -102,6 +109,8 @@ STATE_CONFIG_KEYS = [
CONF_STATE_JAMMED,
CONF_STATE_LOCKED,
CONF_STATE_LOCKING,
CONF_STATE_OPEN,
CONF_STATE_OPENING,
CONF_STATE_UNLOCKED,
CONF_STATE_UNLOCKING,
]
@ -189,6 +198,8 @@ class MqttLock(MqttEntity, LockEntity):
"_attr_is_jammed",
"_attr_is_locked",
"_attr_is_locking",
"_attr_is_open",
"_attr_is_opening",
"_attr_is_unlocking",
},
)
@ -202,6 +213,8 @@ class MqttLock(MqttEntity, LockEntity):
elif payload in self._valid_states:
self._attr_is_locked = payload == self._config[CONF_STATE_LOCKED]
self._attr_is_locking = payload == self._config[CONF_STATE_LOCKING]
self._attr_is_open = payload == self._config[CONF_STATE_OPEN]
self._attr_is_opening = payload == self._config[CONF_STATE_OPENING]
self._attr_is_unlocking = payload == self._config[CONF_STATE_UNLOCKING]
self._attr_is_jammed = payload == self._config[CONF_STATE_JAMMED]
@ -286,5 +299,5 @@ class MqttLock(MqttEntity, LockEntity):
)
if self._optimistic:
# Optimistically assume that the lock unlocks when opened.
self._attr_is_locked = False
self._attr_is_open = True
self.async_write_ha_state()

View file

@ -13,6 +13,8 @@ from homeassistant.components.lock import (
STATE_JAMMED,
STATE_LOCKED,
STATE_LOCKING,
STATE_OPEN,
STATE_OPENING,
STATE_UNLOCKED,
STATE_UNLOCKING,
LockEntityFeature,
@ -75,8 +77,10 @@ CONFIG_WITH_STATES = {
"payload_unlock": "UNLOCK",
"state_locked": "closed",
"state_locking": "closing",
"state_unlocked": "open",
"state_unlocking": "opening",
"state_open": "open",
"state_opening": "opening",
"state_unlocked": "unlocked",
"state_unlocking": "unlocking",
}
}
}
@ -87,8 +91,10 @@ CONFIG_WITH_STATES = {
[
(CONFIG_WITH_STATES, "closed", STATE_LOCKED),
(CONFIG_WITH_STATES, "closing", STATE_LOCKING),
(CONFIG_WITH_STATES, "open", STATE_UNLOCKED),
(CONFIG_WITH_STATES, "opening", STATE_UNLOCKING),
(CONFIG_WITH_STATES, "open", STATE_OPEN),
(CONFIG_WITH_STATES, "opening", STATE_OPENING),
(CONFIG_WITH_STATES, "unlocked", STATE_UNLOCKED),
(CONFIG_WITH_STATES, "unlocking", STATE_UNLOCKING),
],
)
async def test_controlling_state_via_topic(
@ -117,8 +123,10 @@ async def test_controlling_state_via_topic(
[
(CONFIG_WITH_STATES, "closed", STATE_LOCKED),
(CONFIG_WITH_STATES, "closing", STATE_LOCKING),
(CONFIG_WITH_STATES, "open", STATE_UNLOCKED),
(CONFIG_WITH_STATES, "opening", STATE_UNLOCKING),
(CONFIG_WITH_STATES, "open", STATE_OPEN),
(CONFIG_WITH_STATES, "opening", STATE_OPENING),
(CONFIG_WITH_STATES, "unlocked", STATE_UNLOCKED),
(CONFIG_WITH_STATES, "unlocking", STATE_UNLOCKING),
(CONFIG_WITH_STATES, "None", STATE_UNKNOWN),
],
)
@ -168,7 +176,7 @@ async def test_controlling_non_default_state_via_topic(
CONFIG_WITH_STATES,
({"value_template": "{{ value_json.val }}"},),
),
'{"val":"opening"}',
'{"val":"unlocking"}',
STATE_UNLOCKING,
),
(
@ -178,6 +186,24 @@ async def test_controlling_non_default_state_via_topic(
({"value_template": "{{ value_json.val }}"},),
),
'{"val":"open"}',
STATE_OPEN,
),
(
help_custom_config(
lock.DOMAIN,
CONFIG_WITH_STATES,
({"value_template": "{{ value_json.val }}"},),
),
'{"val":"opening"}',
STATE_OPENING,
),
(
help_custom_config(
lock.DOMAIN,
CONFIG_WITH_STATES,
({"value_template": "{{ value_json.val }}"},),
),
'{"val":"unlocked"}',
STATE_UNLOCKED,
),
(
@ -237,7 +263,7 @@ async def test_controlling_state_via_topic_and_json_message(
({"value_template": "{{ value_json.val }}"},),
),
'{"val":"open"}',
STATE_UNLOCKED,
STATE_OPEN,
),
(
help_custom_config(
@ -246,6 +272,24 @@ async def test_controlling_state_via_topic_and_json_message(
({"value_template": "{{ value_json.val }}"},),
),
'{"val":"opening"}',
STATE_OPENING,
),
(
help_custom_config(
lock.DOMAIN,
CONFIG_WITH_STATES,
({"value_template": "{{ value_json.val }}"},),
),
'{"val":"unlocked"}',
STATE_UNLOCKED,
),
(
help_custom_config(
lock.DOMAIN,
CONFIG_WITH_STATES,
({"value_template": "{{ value_json.val }}"},),
),
'{"val":"unlocking"}',
STATE_UNLOCKING,
),
],
@ -483,7 +527,7 @@ async def test_sending_mqtt_commands_support_open_and_optimistic(
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.state is STATE_OPEN
assert state.attributes.get(ATTR_ASSUMED_STATE)
@ -545,7 +589,7 @@ async def test_sending_mqtt_commands_support_open_and_explicit_optimistic(
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.state is STATE_OPEN
assert state.attributes.get(ATTR_ASSUMED_STATE)