Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions homeassistant/components/alarm_control_panel/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components import mqtt
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN,
CONF_NAME, CONF_CODE)
CONF_CODE, CONF_DEVICE, CONF_NAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_PENDING,
STATE_ALARM_TRIGGERED, STATE_UNKNOWN)
from homeassistant.components.mqtt import (
ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC,
CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
CONF_QOS, CONF_RETAIN, MqttAvailability, MqttDiscoveryUpdate, subscription)
ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_COMMAND_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
CONF_STATE_TOPIC, MqttAvailability, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, subscription)
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
Expand All @@ -30,6 +31,7 @@
CONF_PAYLOAD_DISARM = 'payload_disarm'
CONF_PAYLOAD_ARM_HOME = 'payload_arm_home'
CONF_PAYLOAD_ARM_AWAY = 'payload_arm_away'
CONF_UNIQUE_ID = 'unique_id'

DEFAULT_ARM_AWAY = 'ARM_AWAY'
DEFAULT_ARM_HOME = 'ARM_HOME'
Expand All @@ -45,6 +47,8 @@
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_UNIQUE_ID): cv.string,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)


Expand Down Expand Up @@ -73,25 +77,28 @@ async def _async_setup_entity(config, async_add_entities,
async_add_entities([MqttAlarm(config, discovery_hash)])


class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate,
class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
alarm.AlarmControlPanel):
"""Representation of a MQTT alarm status."""

def __init__(self, config, discovery_hash):
"""Init the MQTT Alarm Control Panel."""
self._state = STATE_UNKNOWN
self._config = config
self._unique_id = config.get(CONF_UNIQUE_ID)
self._sub_state = None

availability_topic = config.get(CONF_AVAILABILITY_TOPIC)
payload_available = config.get(CONF_PAYLOAD_AVAILABLE)
payload_not_available = config.get(CONF_PAYLOAD_NOT_AVAILABLE)
qos = config.get(CONF_QOS)
device_config = config.get(CONF_DEVICE)

MqttAvailability.__init__(self, availability_topic, qos,
payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash,
self.discovery_update)
MqttEntityDeviceInfo.__init__(self, device_config)

async def async_added_to_hass(self):
"""Subscribe mqtt events."""
Expand Down Expand Up @@ -141,6 +148,11 @@ def name(self):
"""Return the name of the device."""
return self._config.get(CONF_NAME)

@property
def unique_id(self):
"""Return a unique ID."""
return self._unique_id

@property
def state(self):
"""Return the state of the device."""
Expand Down
106 changes: 104 additions & 2 deletions tests/components/alarm_control_panel/test_mqtt.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""The tests the MQTT alarm control panel component."""
import json
import unittest
from unittest.mock import ANY

from homeassistant.setup import setup_component
from homeassistant.const import (
Expand All @@ -10,8 +12,9 @@
from homeassistant.components.mqtt.discovery import async_start

from tests.common import (
mock_mqtt_component, async_fire_mqtt_message, fire_mqtt_message,
get_test_home_assistant, assert_setup_component, MockConfigEntry)
assert_setup_component, async_fire_mqtt_message, async_mock_mqtt_component,
async_setup_component, fire_mqtt_message, get_test_home_assistant,
mock_mqtt_component, MockConfigEntry, mock_registry)
from tests.components.alarm_control_panel import common

CODE = 'HELLO_CODE'
Expand Down Expand Up @@ -243,6 +246,29 @@ def test_custom_availability_payload(self):
fire_mqtt_message(self.hass, 'availability-topic', 'good')


async def test_unique_id(hass):
"""Test unique id option only creates one alarm per unique_id."""
await async_mock_mqtt_component(hass)
assert await async_setup_component(hass, alarm_control_panel.DOMAIN, {
alarm_control_panel.DOMAIN: [{
'platform': 'mqtt',
'name': 'Test 1',
'state_topic': 'test-topic',
'command_topic': 'test_topic',
'unique_id': 'TOTALLY_UNIQUE'
}, {
'platform': 'mqtt',
'name': 'Test 2',
'state_topic': 'test-topic',
'command_topic': 'test_topic',
'unique_id': 'TOTALLY_UNIQUE'
}]
})
async_fire_mqtt_message(hass, 'test-topic', 'payload')
await hass.async_block_till_done()
assert len(hass.states.async_entity_ids(alarm_control_panel.DOMAIN)) == 1


async def test_discovery_removal_alarm(hass, mqtt_mock, caplog):
"""Test removal of discovered alarm_control_panel."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)
Expand Down Expand Up @@ -310,3 +336,79 @@ async def test_discovery_update_alarm(hass, mqtt_mock, caplog):

state = hass.states.get('alarm_control_panel.milk')
assert state is None


async def test_entity_device_info_with_identifier(hass, mqtt_mock):
"""Test MQTT alarm control panel device registry integration."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)
entry.add_to_hass(hass)
await async_start(hass, 'homeassistant', {}, entry)
registry = await hass.helpers.device_registry.async_get_registry()

data = json.dumps({
'platform': 'mqtt',
'name': 'Test 1',
'state_topic': 'test-topic',
'command_topic': 'test-topic',
'device': {
'identifiers': ['helloworld'],
'connections': [
["mac", "02:5b:26:a8:dc:12"],
],
'manufacturer': 'Whatever',
'name': 'Beer',
'model': 'Glass',
'sw_version': '0.1-beta',
},
'unique_id': 'veryunique'
})
async_fire_mqtt_message(
hass, 'homeassistant/alarm_control_panel/bla/config', data)
await hass.async_block_till_done()
await hass.async_block_till_done()

device = registry.async_get_device({('mqtt', 'helloworld')}, set())
assert device is not None
assert device.identifiers == {('mqtt', 'helloworld')}
assert device.connections == {('mac', "02:5b:26:a8:dc:12")}
assert device.manufacturer == 'Whatever'
assert device.name == 'Beer'
assert device.model == 'Glass'
assert device.sw_version == '0.1-beta'


async def test_entity_id_update(hass, mqtt_mock):
"""Test MQTT subscriptions are managed when entity_id is updated."""
registry = mock_registry(hass, {})
mock_mqtt = await async_mock_mqtt_component(hass)
assert await async_setup_component(hass, alarm_control_panel.DOMAIN, {
alarm_control_panel.DOMAIN: [{
'platform': 'mqtt',
'name': 'beer',
'state_topic': 'test-topic',
'command_topic': 'command-topic',
'availability_topic': 'avty-topic',
'unique_id': 'TOTALLY_UNIQUE'
}]
})

state = hass.states.get('alarm_control_panel.beer')
assert state is not None
assert mock_mqtt.async_subscribe.call_count == 2
mock_mqtt.async_subscribe.assert_any_call('test-topic', ANY, 0, 'utf-8')
mock_mqtt.async_subscribe.assert_any_call('avty-topic', ANY, 0, 'utf-8')
mock_mqtt.async_subscribe.reset_mock()

registry.async_update_entity(
'alarm_control_panel.beer', new_entity_id='alarm_control_panel.milk')
await hass.async_block_till_done()
await hass.async_block_till_done()

state = hass.states.get('alarm_control_panel.beer')
assert state is None

state = hass.states.get('alarm_control_panel.milk')
assert state is not None
assert mock_mqtt.async_subscribe.call_count == 2
mock_mqtt.async_subscribe.assert_any_call('test-topic', ANY, 0, 'utf-8')
mock_mqtt.async_subscribe.assert_any_call('avty-topic', ANY, 0, 'utf-8')