Add command_template and value_template for MQTT alarm (#21438)
* Option to send pin code with the MQTT payload for MQTT alarm * publish code via json Add publish code via json add code_disarm_required * publish code via json Add publish code via json add code_disarm_required * implemented command_template * Fix issue with night arm and add template test * implemented value_template for mqtt alarm * Fixed merge errors * Requested changes * Resolve lint errors * Resolve hound issues * Fix test formatting
This commit is contained in:
parent
1bfe86b30d
commit
4b9e3258dc
2 changed files with 120 additions and 26 deletions
|
@ -12,9 +12,9 @@ import voluptuous as vol
|
|||
from homeassistant.components import mqtt
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.const import (
|
||||
CONF_CODE, CONF_DEVICE, CONF_NAME, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
|
||||
CONF_CODE, CONF_DEVICE, CONF_NAME, CONF_VALUE_TEMPLATE,
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
@ -29,11 +29,14 @@ from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_CODE_ARM_REQUIRED = 'code_arm_required'
|
||||
CONF_CODE_DISARM_REQUIRED = 'code_disarm_required'
|
||||
CONF_PAYLOAD_DISARM = 'payload_disarm'
|
||||
CONF_PAYLOAD_ARM_HOME = 'payload_arm_home'
|
||||
CONF_PAYLOAD_ARM_AWAY = 'payload_arm_away'
|
||||
CONF_PAYLOAD_ARM_NIGHT = 'payload_arm_night'
|
||||
CONF_COMMAND_TEMPLATE = 'command_template'
|
||||
|
||||
DEFAULT_COMMAND_TEMPLATE = '{{action}}'
|
||||
DEFAULT_ARM_NIGHT = 'ARM_NIGHT'
|
||||
DEFAULT_ARM_AWAY = 'ARM_AWAY'
|
||||
DEFAULT_ARM_HOME = 'ARM_HOME'
|
||||
|
@ -51,9 +54,13 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
|
|||
vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
|
||||
vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean,
|
||||
vol.Optional(CONF_CODE_DISARM_REQUIRED, default=True): cv.boolean,
|
||||
vol.Optional(CONF_COMMAND_TEMPLATE,
|
||||
default=DEFAULT_COMMAND_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
|
||||
vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean,
|
||||
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend(
|
||||
mqtt.MQTT_JSON_ATTRS_SCHEMA.schema)
|
||||
|
||||
|
@ -125,10 +132,21 @@ class MqttAlarm(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
|
||||
async def _subscribe_topics(self):
|
||||
"""(Re)Subscribe to topics."""
|
||||
value_template = self._config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template.hass = self.hass
|
||||
command_template = self._config.get(CONF_COMMAND_TEMPLATE)
|
||||
if command_template is not None:
|
||||
command_template.hass = self.hass
|
||||
|
||||
@callback
|
||||
def message_received(msg):
|
||||
"""Run when new MQTT message has been received."""
|
||||
if msg.payload not in (
|
||||
payload = msg.payload
|
||||
if value_template is not None:
|
||||
payload = value_template.async_render_with_possible_json_value(
|
||||
msg.payload, self._state)
|
||||
if payload not in (
|
||||
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_NIGHT,
|
||||
|
@ -136,7 +154,7 @@ class MqttAlarm(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
STATE_ALARM_TRIGGERED):
|
||||
_LOGGER.warning("Received unexpected payload: %s", msg.payload)
|
||||
return
|
||||
self._state = msg.payload
|
||||
self._state = payload
|
||||
self.async_write_ha_state()
|
||||
|
||||
self._sub_state = await subscription.async_subscribe_topics(
|
||||
|
@ -187,13 +205,11 @@ class MqttAlarm(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
|
||||
This method is a coroutine.
|
||||
"""
|
||||
if not self._validate_code(code, 'disarming'):
|
||||
code_required = self._config.get(CONF_CODE_DISARM_REQUIRED)
|
||||
if code_required and not self._validate_code(code, 'disarming'):
|
||||
return
|
||||
mqtt.async_publish(
|
||||
self.hass, self._config.get(CONF_COMMAND_TOPIC),
|
||||
self._config.get(CONF_PAYLOAD_DISARM),
|
||||
self._config.get(CONF_QOS),
|
||||
self._config.get(CONF_RETAIN))
|
||||
payload = self._config.get(CONF_PAYLOAD_DISARM)
|
||||
self._publish(code, payload)
|
||||
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command.
|
||||
|
@ -203,11 +219,8 @@ class MqttAlarm(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
code_required = self._config.get(CONF_CODE_ARM_REQUIRED)
|
||||
if code_required and not self._validate_code(code, 'arming home'):
|
||||
return
|
||||
mqtt.async_publish(
|
||||
self.hass, self._config.get(CONF_COMMAND_TOPIC),
|
||||
self._config.get(CONF_PAYLOAD_ARM_HOME),
|
||||
self._config.get(CONF_QOS),
|
||||
self._config.get(CONF_RETAIN))
|
||||
action = self._config.get(CONF_PAYLOAD_ARM_HOME)
|
||||
self._publish(code, action)
|
||||
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command.
|
||||
|
@ -217,11 +230,8 @@ class MqttAlarm(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
code_required = self._config.get(CONF_CODE_ARM_REQUIRED)
|
||||
if code_required and not self._validate_code(code, 'arming away'):
|
||||
return
|
||||
mqtt.async_publish(
|
||||
self.hass, self._config.get(CONF_COMMAND_TOPIC),
|
||||
self._config.get(CONF_PAYLOAD_ARM_AWAY),
|
||||
self._config.get(CONF_QOS),
|
||||
self._config.get(CONF_RETAIN))
|
||||
action = self._config.get(CONF_PAYLOAD_ARM_AWAY)
|
||||
self._publish(code, action)
|
||||
|
||||
async def async_alarm_arm_night(self, code=None):
|
||||
"""Send arm night command.
|
||||
|
@ -231,9 +241,17 @@ class MqttAlarm(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
|
|||
code_required = self._config.get(CONF_CODE_ARM_REQUIRED)
|
||||
if code_required and not self._validate_code(code, 'arming night'):
|
||||
return
|
||||
action = self._config.get(CONF_PAYLOAD_ARM_NIGHT)
|
||||
self._publish(code, action)
|
||||
|
||||
def _publish(self, code, action):
|
||||
"""Publish via mqtt."""
|
||||
command_template = self._config.get(CONF_COMMAND_TEMPLATE)
|
||||
values = {'action': action, 'code': code}
|
||||
payload = command_template.async_render(**values)
|
||||
mqtt.async_publish(
|
||||
self.hass, self._config.get(CONF_COMMAND_TOPIC),
|
||||
self._config.get(CONF_PAYLOAD_ARM_NIGHT),
|
||||
payload,
|
||||
self._config.get(CONF_QOS),
|
||||
self._config.get(CONF_RETAIN))
|
||||
|
||||
|
|
|
@ -288,15 +288,64 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
|
|||
self.mock_publish.async_publish.assert_called_once_with(
|
||||
'alarm/command', 'DISARM', 0, False)
|
||||
|
||||
def test_disarm_not_publishes_mqtt_with_invalid_code(self):
|
||||
"""Test not publishing of MQTT messages with invalid code."""
|
||||
def test_disarm_publishes_mqtt_with_template(self):
|
||||
"""Test publishing of MQTT messages while disarmed.
|
||||
|
||||
When command_template set to output json
|
||||
"""
|
||||
assert setup_component(self.hass, alarm_control_panel.DOMAIN, {
|
||||
alarm_control_panel.DOMAIN: {
|
||||
'platform': 'mqtt',
|
||||
'name': 'test',
|
||||
'state_topic': 'alarm/state',
|
||||
'command_topic': 'alarm/command',
|
||||
'code': '1234'
|
||||
'code': '1234',
|
||||
'command_template': '{\"action\":\"{{ action }}\",'
|
||||
'\"code\":\"{{ code }}\"}',
|
||||
}
|
||||
})
|
||||
|
||||
common.alarm_disarm(self.hass, 1234)
|
||||
self.hass.block_till_done()
|
||||
self.mock_publish.async_publish.assert_called_once_with(
|
||||
'alarm/command', '{\"action\":\"DISARM\",\"code\":\"1234\"}',
|
||||
0,
|
||||
False)
|
||||
|
||||
def test_disarm_publishes_mqtt_when_code_not_req(self):
|
||||
"""Test publishing of MQTT messages while disarmed.
|
||||
|
||||
When code_disarm_required = False
|
||||
"""
|
||||
assert setup_component(self.hass, alarm_control_panel.DOMAIN, {
|
||||
alarm_control_panel.DOMAIN: {
|
||||
'platform': 'mqtt',
|
||||
'name': 'test',
|
||||
'state_topic': 'alarm/state',
|
||||
'command_topic': 'alarm/command',
|
||||
'code': '1234',
|
||||
'code_disarm_required': False
|
||||
}
|
||||
})
|
||||
|
||||
common.alarm_disarm(self.hass)
|
||||
self.hass.block_till_done()
|
||||
self.mock_publish.async_publish.assert_called_once_with(
|
||||
'alarm/command', 'DISARM', 0, False)
|
||||
|
||||
def test_disarm_not_publishes_mqtt_with_invalid_code_when_req(self):
|
||||
"""Test not publishing of MQTT messages with invalid code.
|
||||
|
||||
When code_disarm_required = True
|
||||
"""
|
||||
assert setup_component(self.hass, alarm_control_panel.DOMAIN, {
|
||||
alarm_control_panel.DOMAIN: {
|
||||
'platform': 'mqtt',
|
||||
'name': 'test',
|
||||
'state_topic': 'alarm/state',
|
||||
'command_topic': 'alarm/command',
|
||||
'code': '1234',
|
||||
'code_disarm_required': True
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -373,6 +422,33 @@ async def test_setting_attribute_via_mqtt_json_message(hass, mqtt_mock):
|
|||
assert '100' == state.attributes.get('val')
|
||||
|
||||
|
||||
async def test_update_state_via_state_topic_template(hass, mqtt_mock):
|
||||
"""Test updating with template_value via state topic."""
|
||||
assert await async_setup_component(hass, alarm_control_panel.DOMAIN, {
|
||||
alarm_control_panel.DOMAIN: {
|
||||
'platform': 'mqtt',
|
||||
'name': 'test',
|
||||
'command_topic': 'test-topic',
|
||||
'state_topic': 'test-topic',
|
||||
'value_template': '\
|
||||
{% if (value | int) == 100 %}\
|
||||
armed_away\
|
||||
{% else %}\
|
||||
disarmed\
|
||||
{% endif %}'
|
||||
}
|
||||
})
|
||||
|
||||
state = hass.states.get('alarm_control_panel.test')
|
||||
assert STATE_UNKNOWN == state.state
|
||||
|
||||
async_fire_mqtt_message(hass, 'test-topic', '100')
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get('alarm_control_panel.test')
|
||||
assert STATE_ALARM_ARMED_AWAY == state.state
|
||||
|
||||
|
||||
async def test_update_with_json_attrs_not_dict(hass, mqtt_mock, caplog):
|
||||
"""Test attributes get extracted from a JSON result."""
|
||||
assert await async_setup_component(hass, alarm_control_panel.DOMAIN, {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue