mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Add locking and unlocking to MQTT lock (#85779)
* Implement locking, unlocking and jammed on MQTT lock Signed-off-by: Patrick ZAJDA <patrick@zajda.fr> * Add tests Signed-off-by: Patrick ZAJDA <patrick@zajda.fr> * Refactor condition Signed-off-by: Patrick ZAJDA <patrick@zajda.fr> * Parametrize tests Signed-off-by: Patrick ZAJDA <patrick@zajda.fr> * Manage only locking and unlocking Signed-off-by: Patrick ZAJDA <patrick@zajda.fr> * Remove jammed from abbreviations Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com> * set valid states in self._valid_states Signed-off-by: Patrick ZAJDA <patrick@zajda.fr> Signed-off-by: Patrick ZAJDA <patrick@zajda.fr> Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
This commit is contained in:
parent
d3c41bc31c
commit
0b02abf708
@ -204,7 +204,9 @@ ABBREVIATIONS = {
|
||||
"stat_opening": "state_opening",
|
||||
"stat_stopped": "state_stopped",
|
||||
"stat_locked": "state_locked",
|
||||
"stat_locking": "state_locking",
|
||||
"stat_unlocked": "state_unlocked",
|
||||
"stat_unlocking": "state_unlocking",
|
||||
"stat_t": "state_topic",
|
||||
"stat_tpl": "state_template",
|
||||
"stat_val_tpl": "state_value_template",
|
||||
|
@ -40,14 +40,18 @@ CONF_PAYLOAD_UNLOCK = "payload_unlock"
|
||||
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"
|
||||
|
||||
DEFAULT_NAME = "MQTT Lock"
|
||||
DEFAULT_PAYLOAD_LOCK = "LOCK"
|
||||
DEFAULT_PAYLOAD_UNLOCK = "UNLOCK"
|
||||
DEFAULT_PAYLOAD_OPEN = "OPEN"
|
||||
DEFAULT_STATE_LOCKED = "LOCKED"
|
||||
DEFAULT_STATE_LOCKING = "LOCKING"
|
||||
DEFAULT_STATE_UNLOCKED = "UNLOCKED"
|
||||
DEFAULT_STATE_UNLOCKING = "UNLOCKING"
|
||||
|
||||
MQTT_LOCK_ATTRIBUTES_BLOCKED = frozenset(
|
||||
{
|
||||
@ -63,7 +67,9 @@ PLATFORM_SCHEMA_MODERN = MQTT_RW_SCHEMA.extend(
|
||||
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_LOCKING, default=DEFAULT_STATE_LOCKING): 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,
|
||||
}
|
||||
).extend(MQTT_ENTITY_COMMON_SCHEMA.schema)
|
||||
@ -76,6 +82,13 @@ PLATFORM_SCHEMA = vol.All(
|
||||
|
||||
DISCOVERY_SCHEMA = PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA)
|
||||
|
||||
STATE_CONFIG_KEYS = [
|
||||
CONF_STATE_LOCKED,
|
||||
CONF_STATE_LOCKING,
|
||||
CONF_STATE_UNLOCKED,
|
||||
CONF_STATE_UNLOCKING,
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@ -107,6 +120,7 @@ class MqttLock(MqttEntity, LockEntity):
|
||||
_attributes_extra_blocked = MQTT_LOCK_ATTRIBUTES_BLOCKED
|
||||
|
||||
_optimistic: bool
|
||||
_valid_states: list[str]
|
||||
_value_template: Callable[[ReceivePayloadType], ReceivePayloadType]
|
||||
|
||||
def __init__(
|
||||
@ -138,6 +152,8 @@ class MqttLock(MqttEntity, LockEntity):
|
||||
if CONF_PAYLOAD_OPEN in config:
|
||||
self._attr_supported_features |= LockEntityFeature.OPEN
|
||||
|
||||
self._valid_states = [config[state] for state in STATE_CONFIG_KEYS]
|
||||
|
||||
def _prepare_subscribe_topics(self) -> None:
|
||||
"""(Re)Subscribe to topics."""
|
||||
|
||||
@ -146,10 +162,10 @@ class MqttLock(MqttEntity, LockEntity):
|
||||
def message_received(msg: ReceiveMessage) -> None:
|
||||
"""Handle new MQTT messages."""
|
||||
payload = self._value_template(msg.payload)
|
||||
if payload == self._config[CONF_STATE_LOCKED]:
|
||||
self._attr_is_locked = True
|
||||
elif payload == self._config[CONF_STATE_UNLOCKED]:
|
||||
self._attr_is_locked = False
|
||||
if 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_unlocking = payload == self._config[CONF_STATE_UNLOCKING]
|
||||
|
||||
get_mqtt_data(self.hass).state_write_requests.write_state_request(self)
|
||||
|
||||
|
@ -9,7 +9,9 @@ from homeassistant.components.lock import (
|
||||
SERVICE_OPEN,
|
||||
SERVICE_UNLOCK,
|
||||
STATE_LOCKED,
|
||||
STATE_LOCKING,
|
||||
STATE_UNLOCKED,
|
||||
STATE_UNLOCKING,
|
||||
LockEntityFeature,
|
||||
)
|
||||
from homeassistant.components.mqtt.lock import MQTT_LOCK_ATTRIBUTES_BLOCKED
|
||||
@ -65,7 +67,18 @@ def lock_platform_only():
|
||||
yield
|
||||
|
||||
|
||||
async def test_controlling_state_via_topic(hass, mqtt_mock_entry_with_yaml_config):
|
||||
@pytest.mark.parametrize(
|
||||
"payload,lock_state",
|
||||
[
|
||||
("LOCKED", STATE_LOCKED),
|
||||
("LOCKING", STATE_LOCKING),
|
||||
("UNLOCKED", STATE_UNLOCKED),
|
||||
("UNLOCKING", STATE_UNLOCKING),
|
||||
],
|
||||
)
|
||||
async def test_controlling_state_via_topic(
|
||||
hass, mqtt_mock_entry_with_yaml_config, payload, lock_state
|
||||
):
|
||||
"""Test the controlling state via topic."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
@ -79,7 +92,9 @@ async def test_controlling_state_via_topic(hass, mqtt_mock_entry_with_yaml_confi
|
||||
"payload_lock": "LOCK",
|
||||
"payload_unlock": "UNLOCK",
|
||||
"state_locked": "LOCKED",
|
||||
"state_locking": "LOCKING",
|
||||
"state_unlocked": "UNLOCKED",
|
||||
"state_unlocking": "UNLOCKING",
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -92,19 +107,23 @@ async def test_controlling_state_via_topic(hass, mqtt_mock_entry_with_yaml_confi
|
||||
assert not state.attributes.get(ATTR_ASSUMED_STATE)
|
||||
assert not state.attributes.get(ATTR_SUPPORTED_FEATURES)
|
||||
|
||||
async_fire_mqtt_message(hass, "state-topic", "LOCKED")
|
||||
async_fire_mqtt_message(hass, "state-topic", payload)
|
||||
|
||||
state = hass.states.get("lock.test")
|
||||
assert state.state is STATE_LOCKED
|
||||
|
||||
async_fire_mqtt_message(hass, "state-topic", "UNLOCKED")
|
||||
|
||||
state = hass.states.get("lock.test")
|
||||
assert state.state is STATE_UNLOCKED
|
||||
assert state.state is lock_state
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"payload,lock_state",
|
||||
[
|
||||
("closed", STATE_LOCKED),
|
||||
("closing", STATE_LOCKING),
|
||||
("open", STATE_UNLOCKED),
|
||||
("opening", STATE_UNLOCKING),
|
||||
],
|
||||
)
|
||||
async def test_controlling_non_default_state_via_topic(
|
||||
hass, mqtt_mock_entry_with_yaml_config
|
||||
hass, mqtt_mock_entry_with_yaml_config, payload, lock_state
|
||||
):
|
||||
"""Test the controlling state via topic."""
|
||||
assert await async_setup_component(
|
||||
@ -119,7 +138,9 @@ async def test_controlling_non_default_state_via_topic(
|
||||
"payload_lock": "LOCK",
|
||||
"payload_unlock": "UNLOCK",
|
||||
"state_locked": "closed",
|
||||
"state_locking": "closing",
|
||||
"state_unlocked": "open",
|
||||
"state_unlocking": "opening",
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -131,19 +152,23 @@ async def test_controlling_non_default_state_via_topic(
|
||||
assert state.state is STATE_UNLOCKED
|
||||
assert not state.attributes.get(ATTR_ASSUMED_STATE)
|
||||
|
||||
async_fire_mqtt_message(hass, "state-topic", "closed")
|
||||
async_fire_mqtt_message(hass, "state-topic", payload)
|
||||
|
||||
state = hass.states.get("lock.test")
|
||||
assert state.state is STATE_LOCKED
|
||||
|
||||
async_fire_mqtt_message(hass, "state-topic", "open")
|
||||
|
||||
state = hass.states.get("lock.test")
|
||||
assert state.state is STATE_UNLOCKED
|
||||
assert state.state is lock_state
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"payload,lock_state",
|
||||
[
|
||||
('{"val":"LOCKED"}', STATE_LOCKED),
|
||||
('{"val":"LOCKING"}', STATE_LOCKING),
|
||||
('{"val":"UNLOCKED"}', STATE_UNLOCKED),
|
||||
('{"val":"UNLOCKING"}', STATE_UNLOCKING),
|
||||
],
|
||||
)
|
||||
async def test_controlling_state_via_topic_and_json_message(
|
||||
hass, mqtt_mock_entry_with_yaml_config
|
||||
hass, mqtt_mock_entry_with_yaml_config, payload, lock_state
|
||||
):
|
||||
"""Test the controlling state via topic and JSON message."""
|
||||
assert await async_setup_component(
|
||||
@ -158,7 +183,9 @@ async def test_controlling_state_via_topic_and_json_message(
|
||||
"payload_lock": "LOCK",
|
||||
"payload_unlock": "UNLOCK",
|
||||
"state_locked": "LOCKED",
|
||||
"state_locking": "LOCKING",
|
||||
"state_unlocked": "UNLOCKED",
|
||||
"state_unlocking": "UNLOCKING",
|
||||
"value_template": "{{ value_json.val }}",
|
||||
}
|
||||
}
|
||||
@ -170,19 +197,23 @@ async def test_controlling_state_via_topic_and_json_message(
|
||||
state = hass.states.get("lock.test")
|
||||
assert state.state is STATE_UNLOCKED
|
||||
|
||||
async_fire_mqtt_message(hass, "state-topic", '{"val":"LOCKED"}')
|
||||
async_fire_mqtt_message(hass, "state-topic", payload)
|
||||
|
||||
state = hass.states.get("lock.test")
|
||||
assert state.state is STATE_LOCKED
|
||||
|
||||
async_fire_mqtt_message(hass, "state-topic", '{"val":"UNLOCKED"}')
|
||||
|
||||
state = hass.states.get("lock.test")
|
||||
assert state.state is STATE_UNLOCKED
|
||||
assert state.state is lock_state
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"payload,lock_state",
|
||||
[
|
||||
('{"val":"closed"}', STATE_LOCKED),
|
||||
('{"val":"closing"}', STATE_LOCKING),
|
||||
('{"val":"open"}', STATE_UNLOCKED),
|
||||
('{"val":"opening"}', STATE_UNLOCKING),
|
||||
],
|
||||
)
|
||||
async def test_controlling_non_default_state_via_topic_and_json_message(
|
||||
hass, mqtt_mock_entry_with_yaml_config
|
||||
hass, mqtt_mock_entry_with_yaml_config, payload, lock_state
|
||||
):
|
||||
"""Test the controlling state via topic and JSON message."""
|
||||
assert await async_setup_component(
|
||||
@ -197,7 +228,9 @@ async def test_controlling_non_default_state_via_topic_and_json_message(
|
||||
"payload_lock": "LOCK",
|
||||
"payload_unlock": "UNLOCK",
|
||||
"state_locked": "closed",
|
||||
"state_locking": "closing",
|
||||
"state_unlocked": "open",
|
||||
"state_unlocking": "opening",
|
||||
"value_template": "{{ value_json.val }}",
|
||||
}
|
||||
}
|
||||
@ -209,15 +242,10 @@ async def test_controlling_non_default_state_via_topic_and_json_message(
|
||||
state = hass.states.get("lock.test")
|
||||
assert state.state is STATE_UNLOCKED
|
||||
|
||||
async_fire_mqtt_message(hass, "state-topic", '{"val":"closed"}')
|
||||
async_fire_mqtt_message(hass, "state-topic", payload)
|
||||
|
||||
state = hass.states.get("lock.test")
|
||||
assert state.state is STATE_LOCKED
|
||||
|
||||
async_fire_mqtt_message(hass, "state-topic", '{"val":"open"}')
|
||||
|
||||
state = hass.states.get("lock.test")
|
||||
assert state.state is STATE_UNLOCKED
|
||||
assert state.state is lock_state
|
||||
|
||||
|
||||
async def test_sending_mqtt_commands_and_optimistic(
|
||||
|
Loading…
x
Reference in New Issue
Block a user