diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index 9498b597590..f1142baa37a 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -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)) diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index 742aafba8dc..6efaedd270b 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -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, {