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:
Kevin Cooper 2019-03-30 06:36:10 +00:00 committed by emontnemery
parent 1bfe86b30d
commit 4b9e3258dc
2 changed files with 120 additions and 26 deletions

View file

@ -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))

View file

@ -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, {