mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 11:47:06 +00:00
Add command template and code_format support for MQTT lock (#85830)
* Add command template for MQTT lock * Fix tests
This commit is contained in:
parent
00e5f23249
commit
f719ecf086
@ -42,6 +42,7 @@ ABBREVIATIONS = {
|
|||||||
"cmd_tpl": "command_template",
|
"cmd_tpl": "command_template",
|
||||||
"cod_arm_req": "code_arm_required",
|
"cod_arm_req": "code_arm_required",
|
||||||
"cod_dis_req": "code_disarm_required",
|
"cod_dis_req": "code_disarm_required",
|
||||||
|
"cod_form": "code_format",
|
||||||
"cod_trig_req": "code_trigger_required",
|
"cod_trig_req": "code_trigger_required",
|
||||||
"curr_hum_t": "current_humidity_topic",
|
"curr_hum_t": "current_humidity_topic",
|
||||||
"curr_hum_tpl": "current_humidity_template",
|
"curr_hum_tpl": "current_humidity_template",
|
||||||
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
import functools
|
import functools
|
||||||
|
import re
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -14,11 +15,12 @@ from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE
|
|||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, TemplateVarsType
|
||||||
|
|
||||||
from . import subscription
|
from . import subscription
|
||||||
from .config import MQTT_RW_SCHEMA
|
from .config import MQTT_RW_SCHEMA
|
||||||
from .const import (
|
from .const import (
|
||||||
|
CONF_COMMAND_TEMPLATE,
|
||||||
CONF_COMMAND_TOPIC,
|
CONF_COMMAND_TOPIC,
|
||||||
CONF_ENCODING,
|
CONF_ENCODING,
|
||||||
CONF_QOS,
|
CONF_QOS,
|
||||||
@ -32,9 +34,17 @@ from .mixins import (
|
|||||||
async_setup_entry_helper,
|
async_setup_entry_helper,
|
||||||
warn_for_legacy_schema,
|
warn_for_legacy_schema,
|
||||||
)
|
)
|
||||||
from .models import MqttValueTemplate, ReceiveMessage, ReceivePayloadType
|
from .models import (
|
||||||
|
MqttCommandTemplate,
|
||||||
|
MqttValueTemplate,
|
||||||
|
PublishPayloadType,
|
||||||
|
ReceiveMessage,
|
||||||
|
ReceivePayloadType,
|
||||||
|
)
|
||||||
from .util import get_mqtt_data
|
from .util import get_mqtt_data
|
||||||
|
|
||||||
|
CONF_CODE_FORMAT = "code_format"
|
||||||
|
|
||||||
CONF_PAYLOAD_LOCK = "payload_lock"
|
CONF_PAYLOAD_LOCK = "payload_lock"
|
||||||
CONF_PAYLOAD_UNLOCK = "payload_unlock"
|
CONF_PAYLOAD_UNLOCK = "payload_unlock"
|
||||||
CONF_PAYLOAD_OPEN = "payload_open"
|
CONF_PAYLOAD_OPEN = "payload_open"
|
||||||
@ -64,6 +74,8 @@ MQTT_LOCK_ATTRIBUTES_BLOCKED = frozenset(
|
|||||||
|
|
||||||
PLATFORM_SCHEMA_MODERN = MQTT_RW_SCHEMA.extend(
|
PLATFORM_SCHEMA_MODERN = MQTT_RW_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
|
vol.Optional(CONF_CODE_FORMAT): cv.is_regex,
|
||||||
|
vol.Optional(CONF_COMMAND_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
vol.Optional(CONF_PAYLOAD_LOCK, default=DEFAULT_PAYLOAD_LOCK): cv.string,
|
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_UNLOCK, default=DEFAULT_PAYLOAD_UNLOCK): cv.string,
|
||||||
@ -123,8 +135,12 @@ class MqttLock(MqttEntity, LockEntity):
|
|||||||
_entity_id_format = lock.ENTITY_ID_FORMAT
|
_entity_id_format = lock.ENTITY_ID_FORMAT
|
||||||
_attributes_extra_blocked = MQTT_LOCK_ATTRIBUTES_BLOCKED
|
_attributes_extra_blocked = MQTT_LOCK_ATTRIBUTES_BLOCKED
|
||||||
|
|
||||||
|
_compiled_pattern: re.Pattern[Any] | None
|
||||||
_optimistic: bool
|
_optimistic: bool
|
||||||
_valid_states: list[str]
|
_valid_states: list[str]
|
||||||
|
_command_template: Callable[
|
||||||
|
[PublishPayloadType, TemplateVarsType], PublishPayloadType
|
||||||
|
]
|
||||||
_value_template: Callable[[ReceivePayloadType], ReceivePayloadType]
|
_value_template: Callable[[ReceivePayloadType], ReceivePayloadType]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -145,7 +161,18 @@ class MqttLock(MqttEntity, LockEntity):
|
|||||||
|
|
||||||
def _setup_from_config(self, config: ConfigType) -> None:
|
def _setup_from_config(self, config: ConfigType) -> None:
|
||||||
"""(Re)Setup the entity."""
|
"""(Re)Setup the entity."""
|
||||||
self._optimistic = config[CONF_OPTIMISTIC]
|
self._optimistic = (
|
||||||
|
config[CONF_OPTIMISTIC] or self._config.get(CONF_STATE_TOPIC) is None
|
||||||
|
)
|
||||||
|
|
||||||
|
self._compiled_pattern = config.get(CONF_CODE_FORMAT)
|
||||||
|
self._attr_code_format = (
|
||||||
|
self._compiled_pattern.pattern if self._compiled_pattern else None
|
||||||
|
)
|
||||||
|
|
||||||
|
self._command_template = MqttCommandTemplate(
|
||||||
|
config.get(CONF_COMMAND_TEMPLATE), entity=self
|
||||||
|
).async_render
|
||||||
|
|
||||||
self._value_template = MqttValueTemplate(
|
self._value_template = MqttValueTemplate(
|
||||||
config.get(CONF_VALUE_TEMPLATE),
|
config.get(CONF_VALUE_TEMPLATE),
|
||||||
@ -209,9 +236,10 @@ class MqttLock(MqttEntity, LockEntity):
|
|||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
"""
|
"""
|
||||||
|
payload = self._command_template(self._config[CONF_PAYLOAD_LOCK], kwargs)
|
||||||
await self.async_publish(
|
await self.async_publish(
|
||||||
self._config[CONF_COMMAND_TOPIC],
|
self._config[CONF_COMMAND_TOPIC],
|
||||||
self._config[CONF_PAYLOAD_LOCK],
|
payload,
|
||||||
self._config[CONF_QOS],
|
self._config[CONF_QOS],
|
||||||
self._config[CONF_RETAIN],
|
self._config[CONF_RETAIN],
|
||||||
self._config[CONF_ENCODING],
|
self._config[CONF_ENCODING],
|
||||||
@ -226,9 +254,10 @@ class MqttLock(MqttEntity, LockEntity):
|
|||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
"""
|
"""
|
||||||
|
payload = self._command_template(self._config[CONF_PAYLOAD_UNLOCK], kwargs)
|
||||||
await self.async_publish(
|
await self.async_publish(
|
||||||
self._config[CONF_COMMAND_TOPIC],
|
self._config[CONF_COMMAND_TOPIC],
|
||||||
self._config[CONF_PAYLOAD_UNLOCK],
|
payload,
|
||||||
self._config[CONF_QOS],
|
self._config[CONF_QOS],
|
||||||
self._config[CONF_RETAIN],
|
self._config[CONF_RETAIN],
|
||||||
self._config[CONF_ENCODING],
|
self._config[CONF_ENCODING],
|
||||||
@ -243,9 +272,10 @@ class MqttLock(MqttEntity, LockEntity):
|
|||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
"""
|
"""
|
||||||
|
payload = self._command_template(self._config[CONF_PAYLOAD_OPEN], kwargs)
|
||||||
await self.async_publish(
|
await self.async_publish(
|
||||||
self._config[CONF_COMMAND_TOPIC],
|
self._config[CONF_COMMAND_TOPIC],
|
||||||
self._config[CONF_PAYLOAD_OPEN],
|
payload,
|
||||||
self._config[CONF_QOS],
|
self._config[CONF_QOS],
|
||||||
self._config[CONF_RETAIN],
|
self._config[CONF_RETAIN],
|
||||||
self._config[CONF_ENCODING],
|
self._config[CONF_ENCODING],
|
||||||
|
@ -18,6 +18,7 @@ from homeassistant.components.lock import (
|
|||||||
from homeassistant.components.mqtt.lock import MQTT_LOCK_ATTRIBUTES_BLOCKED
|
from homeassistant.components.mqtt.lock import MQTT_LOCK_ATTRIBUTES_BLOCKED
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ASSUMED_STATE,
|
ATTR_ASSUMED_STATE,
|
||||||
|
ATTR_CODE,
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
ATTR_SUPPORTED_FEATURES,
|
ATTR_SUPPORTED_FEATURES,
|
||||||
Platform,
|
Platform,
|
||||||
@ -298,6 +299,67 @@ async def test_sending_mqtt_commands_and_optimistic(
|
|||||||
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sending_mqtt_commands_with_template(
|
||||||
|
hass, mqtt_mock_entry_with_yaml_config
|
||||||
|
):
|
||||||
|
"""Test sending commands with template."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
mqtt.DOMAIN,
|
||||||
|
{
|
||||||
|
mqtt.DOMAIN: {
|
||||||
|
lock.DOMAIN: {
|
||||||
|
"name": "test",
|
||||||
|
"code_format": "^\\d{4}$",
|
||||||
|
"command_topic": "command-topic",
|
||||||
|
"command_template": '{ "{{ value }}": "{{ code }}" }',
|
||||||
|
"payload_lock": "LOCK",
|
||||||
|
"payload_unlock": "UNLOCK",
|
||||||
|
"payload_open": "OPEN",
|
||||||
|
"state_locked": "LOCKED",
|
||||||
|
"state_unlocked": "UNLOCKED",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
mqtt_mock = await mqtt_mock_entry_with_yaml_config()
|
||||||
|
|
||||||
|
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_LOCK,
|
||||||
|
{ATTR_ENTITY_ID: "lock.test", ATTR_CODE: "1234"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mqtt_mock.async_publish.assert_called_once_with(
|
||||||
|
"command-topic", '{ "LOCK": "1234" }', 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", ATTR_CODE: "1234"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mqtt_mock.async_publish.assert_called_once_with(
|
||||||
|
"command-topic", '{ "UNLOCK": "1234" }', 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(
|
async def test_sending_mqtt_commands_and_explicit_optimistic(
|
||||||
hass, mqtt_mock_entry_with_yaml_config
|
hass, mqtt_mock_entry_with_yaml_config
|
||||||
):
|
):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user