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

View file

@ -13,6 +13,8 @@ from homeassistant.components.lock import (
STATE_JAMMED, STATE_JAMMED,
STATE_LOCKED, STATE_LOCKED,
STATE_LOCKING, STATE_LOCKING,
STATE_OPEN,
STATE_OPENING,
STATE_UNLOCKED, STATE_UNLOCKED,
STATE_UNLOCKING, STATE_UNLOCKING,
LockEntityFeature, LockEntityFeature,
@ -75,8 +77,10 @@ CONFIG_WITH_STATES = {
"payload_unlock": "UNLOCK", "payload_unlock": "UNLOCK",
"state_locked": "closed", "state_locked": "closed",
"state_locking": "closing", "state_locking": "closing",
"state_unlocked": "open", "state_open": "open",
"state_unlocking": "opening", "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, "closed", STATE_LOCKED),
(CONFIG_WITH_STATES, "closing", STATE_LOCKING), (CONFIG_WITH_STATES, "closing", STATE_LOCKING),
(CONFIG_WITH_STATES, "open", STATE_UNLOCKED), (CONFIG_WITH_STATES, "open", STATE_OPEN),
(CONFIG_WITH_STATES, "opening", STATE_UNLOCKING), (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( 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, "closed", STATE_LOCKED),
(CONFIG_WITH_STATES, "closing", STATE_LOCKING), (CONFIG_WITH_STATES, "closing", STATE_LOCKING),
(CONFIG_WITH_STATES, "open", STATE_UNLOCKED), (CONFIG_WITH_STATES, "open", STATE_OPEN),
(CONFIG_WITH_STATES, "opening", STATE_UNLOCKING), (CONFIG_WITH_STATES, "opening", STATE_OPENING),
(CONFIG_WITH_STATES, "unlocked", STATE_UNLOCKED),
(CONFIG_WITH_STATES, "unlocking", STATE_UNLOCKING),
(CONFIG_WITH_STATES, "None", STATE_UNKNOWN), (CONFIG_WITH_STATES, "None", STATE_UNKNOWN),
], ],
) )
@ -168,7 +176,7 @@ async def test_controlling_non_default_state_via_topic(
CONFIG_WITH_STATES, CONFIG_WITH_STATES,
({"value_template": "{{ value_json.val }}"},), ({"value_template": "{{ value_json.val }}"},),
), ),
'{"val":"opening"}', '{"val":"unlocking"}',
STATE_UNLOCKING, STATE_UNLOCKING,
), ),
( (
@ -178,6 +186,24 @@ async def test_controlling_non_default_state_via_topic(
({"value_template": "{{ value_json.val }}"},), ({"value_template": "{{ value_json.val }}"},),
), ),
'{"val":"open"}', '{"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, STATE_UNLOCKED,
), ),
( (
@ -237,7 +263,7 @@ async def test_controlling_state_via_topic_and_json_message(
({"value_template": "{{ value_json.val }}"},), ({"value_template": "{{ value_json.val }}"},),
), ),
'{"val":"open"}', '{"val":"open"}',
STATE_UNLOCKED, STATE_OPEN,
), ),
( (
help_custom_config( help_custom_config(
@ -246,6 +272,24 @@ async def test_controlling_state_via_topic_and_json_message(
({"value_template": "{{ value_json.val }}"},), ({"value_template": "{{ value_json.val }}"},),
), ),
'{"val":"opening"}', '{"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, 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.assert_called_once_with("command-topic", "OPEN", 0, False)
mqtt_mock.async_publish.reset_mock() mqtt_mock.async_publish.reset_mock()
state = hass.states.get("lock.test") 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) 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.assert_called_once_with("command-topic", "OPEN", 0, False)
mqtt_mock.async_publish.reset_mock() mqtt_mock.async_publish.reset_mock()
state = hass.states.get("lock.test") 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) assert state.attributes.get(ATTR_ASSUMED_STATE)