Add MQTT tag scanner (#40709)
This commit is contained in:
parent
3bcd2b6f71
commit
e7f832a82f
4 changed files with 977 additions and 1 deletions
|
@ -1229,7 +1229,7 @@ async def cleanup_device_registry(hass, device_id):
|
|||
"""Remove device registry entry if there are no remaining entities or triggers."""
|
||||
# Local import to avoid circular dependencies
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from . import device_trigger
|
||||
from . import device_trigger, tag
|
||||
|
||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
||||
|
@ -1239,6 +1239,7 @@ async def cleanup_device_registry(hass, device_id):
|
|||
entity_registry, device_id
|
||||
)
|
||||
and not await device_trigger.async_get_triggers(hass, device_id)
|
||||
and not tag.async_has_tags(hass, device_id)
|
||||
):
|
||||
device_registry.async_remove_device(device_id)
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ SUPPORTED_COMPONENTS = [
|
|||
"lock",
|
||||
"sensor",
|
||||
"switch",
|
||||
"tag",
|
||||
"vacuum",
|
||||
]
|
||||
|
||||
|
@ -154,6 +155,12 @@ async def async_start(
|
|||
from . import device_automation
|
||||
|
||||
await device_automation.async_setup_entry(hass, config_entry)
|
||||
elif component == "tag":
|
||||
# Local import to avoid circular dependencies
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from . import tag
|
||||
|
||||
await tag.async_setup_entry(hass, config_entry)
|
||||
else:
|
||||
await hass.config_entries.async_forward_entry_setup(
|
||||
config_entry, component
|
||||
|
|
224
homeassistant/components/mqtt/tag.py
Normal file
224
homeassistant/components/mqtt/tag.py
Normal file
|
@ -0,0 +1,224 @@
|
|||
"""Provides tag scanning for MQTT."""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import mqtt
|
||||
from homeassistant.const import CONF_PLATFORM, CONF_VALUE_TEMPLATE
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from . import (
|
||||
ATTR_DISCOVERY_HASH,
|
||||
ATTR_DISCOVERY_TOPIC,
|
||||
CONF_CONNECTIONS,
|
||||
CONF_DEVICE,
|
||||
CONF_IDENTIFIERS,
|
||||
CONF_QOS,
|
||||
CONF_TOPIC,
|
||||
DOMAIN,
|
||||
cleanup_device_registry,
|
||||
subscription,
|
||||
)
|
||||
from .discovery import MQTT_DISCOVERY_NEW, MQTT_DISCOVERY_UPDATED, clear_discovery_hash
|
||||
from .util import valid_subscribe_topic
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
TAG = "tag"
|
||||
TAGS = "mqtt_tags"
|
||||
|
||||
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
|
||||
vol.Optional(CONF_PLATFORM): "mqtt",
|
||||
vol.Required(CONF_TOPIC): valid_subscribe_topic,
|
||||
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
||||
},
|
||||
mqtt.validate_device_has_at_least_one_identifier,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
"""Set up MQTT tag scan dynamically through MQTT discovery."""
|
||||
|
||||
async def async_discover(discovery_payload):
|
||||
"""Discover and add MQTT tag scan."""
|
||||
discovery_data = discovery_payload.discovery_data
|
||||
try:
|
||||
config = PLATFORM_SCHEMA(discovery_payload)
|
||||
await async_setup_tag(hass, config, config_entry, discovery_data)
|
||||
except Exception:
|
||||
clear_discovery_hash(hass, discovery_data[ATTR_DISCOVERY_HASH])
|
||||
raise
|
||||
|
||||
async_dispatcher_connect(
|
||||
hass, MQTT_DISCOVERY_NEW.format("tag", "mqtt"), async_discover
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_tag(hass, config, config_entry, discovery_data):
|
||||
"""Set up the MQTT tag scanner."""
|
||||
discovery_hash = discovery_data[ATTR_DISCOVERY_HASH]
|
||||
discovery_id = discovery_hash[1]
|
||||
|
||||
device_id = None
|
||||
if CONF_DEVICE in config:
|
||||
await _update_device(hass, config_entry, config)
|
||||
|
||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||
device = device_registry.async_get_device(
|
||||
{(DOMAIN, id_) for id_ in config[CONF_DEVICE][CONF_IDENTIFIERS]},
|
||||
{tuple(x) for x in config[CONF_DEVICE][CONF_CONNECTIONS]},
|
||||
)
|
||||
|
||||
if device is None:
|
||||
return
|
||||
device_id = device.id
|
||||
|
||||
if TAGS not in hass.data:
|
||||
hass.data[TAGS] = {}
|
||||
if device_id not in hass.data[TAGS]:
|
||||
hass.data[TAGS][device_id] = {}
|
||||
|
||||
tag_scanner = MQTTTagScanner(
|
||||
hass,
|
||||
config,
|
||||
device_id,
|
||||
discovery_data,
|
||||
config_entry,
|
||||
)
|
||||
|
||||
await tag_scanner.setup()
|
||||
|
||||
if device_id:
|
||||
hass.data[TAGS][device_id][discovery_id] = tag_scanner
|
||||
|
||||
|
||||
def async_has_tags(hass, device_id):
|
||||
"""Device has tag scanners."""
|
||||
if TAGS not in hass.data or device_id not in hass.data[TAGS]:
|
||||
return False
|
||||
return hass.data[TAGS][device_id] != {}
|
||||
|
||||
|
||||
class MQTTTagScanner:
|
||||
"""MQTT Tag scanner."""
|
||||
|
||||
def __init__(self, hass, config, device_id, discovery_data, config_entry):
|
||||
"""Initialize."""
|
||||
self._config = config
|
||||
self._config_entry = config_entry
|
||||
self.device_id = device_id
|
||||
self.discovery_data = discovery_data
|
||||
self.hass = hass
|
||||
self._remove_discovery = None
|
||||
self._remove_device_updated = None
|
||||
self._sub_state = None
|
||||
self._value_template = None
|
||||
|
||||
self._setup_from_config(config)
|
||||
|
||||
async def discovery_update(self, payload):
|
||||
"""Handle discovery update."""
|
||||
discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH]
|
||||
_LOGGER.info(
|
||||
"Got update for tag scanner with hash: %s '%s'", discovery_hash, payload
|
||||
)
|
||||
if not payload:
|
||||
# Empty payload: Remove tag scanner
|
||||
_LOGGER.info("Removing tag scanner: %s", discovery_hash)
|
||||
await self.tear_down()
|
||||
if self.device_id:
|
||||
await cleanup_device_registry(self.hass, self.device_id)
|
||||
else:
|
||||
# Non-empty payload: Update tag scanner
|
||||
_LOGGER.info("Updating tag scanner: %s", discovery_hash)
|
||||
config = PLATFORM_SCHEMA(payload)
|
||||
self._config = config
|
||||
if self.device_id:
|
||||
await _update_device(self.hass, self._config_entry, config)
|
||||
self._setup_from_config(config)
|
||||
await self.subscribe_topics()
|
||||
|
||||
def _setup_from_config(self, config):
|
||||
self._value_template = lambda value, error_value: value
|
||||
if CONF_VALUE_TEMPLATE in config:
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
value_template.hass = self.hass
|
||||
|
||||
self._value_template = value_template.async_render_with_possible_json_value
|
||||
|
||||
async def setup(self):
|
||||
"""Set up the MQTT tag scanner."""
|
||||
discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH]
|
||||
await self.subscribe_topics()
|
||||
if self.device_id:
|
||||
self._remove_device_updated = self.hass.bus.async_listen(
|
||||
EVENT_DEVICE_REGISTRY_UPDATED, self.device_removed
|
||||
)
|
||||
self._remove_discovery = async_dispatcher_connect(
|
||||
self.hass,
|
||||
MQTT_DISCOVERY_UPDATED.format(discovery_hash),
|
||||
self.discovery_update,
|
||||
)
|
||||
|
||||
async def subscribe_topics(self):
|
||||
"""Subscribe to MQTT topics."""
|
||||
|
||||
async def tag_scanned(msg):
|
||||
tag_id = self._value_template(msg.payload, error_value="").strip()
|
||||
if not tag_id: # No output from template, ignore
|
||||
return
|
||||
|
||||
await self.hass.components.tag.async_scan_tag(tag_id, self.device_id)
|
||||
|
||||
self._sub_state = await subscription.async_subscribe_topics(
|
||||
self.hass,
|
||||
self._sub_state,
|
||||
{
|
||||
"state_topic": {
|
||||
"topic": self._config[CONF_TOPIC],
|
||||
"msg_callback": tag_scanned,
|
||||
"qos": self._config[CONF_QOS],
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
async def device_removed(self, event):
|
||||
"""Handle the removal of a device."""
|
||||
device_id = event.data["device_id"]
|
||||
if event.data["action"] != "remove" or device_id != self.device_id:
|
||||
return
|
||||
|
||||
await self.tear_down()
|
||||
|
||||
async def tear_down(self):
|
||||
"""Cleanup tag scanner."""
|
||||
discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH]
|
||||
discovery_id = discovery_hash[1]
|
||||
discovery_topic = self.discovery_data[ATTR_DISCOVERY_TOPIC]
|
||||
|
||||
clear_discovery_hash(self.hass, discovery_hash)
|
||||
if self.device_id:
|
||||
self._remove_device_updated()
|
||||
self._remove_discovery()
|
||||
|
||||
mqtt.publish(self.hass, discovery_topic, "", retain=True)
|
||||
self._sub_state = await subscription.async_unsubscribe_topics(
|
||||
self.hass, self._sub_state
|
||||
)
|
||||
if self.device_id:
|
||||
self.hass.data[TAGS][self.device_id].pop(discovery_id)
|
||||
|
||||
|
||||
async def _update_device(hass, config_entry, config):
|
||||
"""Update device registry."""
|
||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||
config_entry_id = config_entry.entry_id
|
||||
device_info = mqtt.device_info_from_config(config[CONF_DEVICE])
|
||||
|
||||
if config_entry_id is not None and device_info is not None:
|
||||
device_info["config_entry_id"] = config_entry_id
|
||||
device_registry.async_get_or_create(**device_info)
|
744
tests/components/mqtt/test_tag.py
Normal file
744
tests/components/mqtt/test_tag.py
Normal file
|
@ -0,0 +1,744 @@
|
|||
"""The tests for MQTT tag scanner."""
|
||||
import copy
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.mqtt import DOMAIN
|
||||
from homeassistant.components.mqtt.discovery import async_start
|
||||
|
||||
from tests.async_mock import ANY, patch
|
||||
from tests.common import (
|
||||
async_fire_mqtt_message,
|
||||
async_get_device_automations,
|
||||
mock_device_registry,
|
||||
mock_registry,
|
||||
)
|
||||
|
||||
DEFAULT_CONFIG_DEVICE = {
|
||||
"device": {"identifiers": ["0AFFD2"]},
|
||||
"topic": "foobar/tag_scanned",
|
||||
}
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
"topic": "foobar/tag_scanned",
|
||||
}
|
||||
|
||||
DEFAULT_CONFIG_JSON = {
|
||||
"device": {"identifiers": ["0AFFD2"]},
|
||||
"topic": "foobar/tag_scanned",
|
||||
"value_template": "{{ value_json.PN532.UID }}",
|
||||
}
|
||||
|
||||
DEFAULT_TAG_ID = "E9F35959"
|
||||
|
||||
DEFAULT_TAG_SCAN = "E9F35959"
|
||||
|
||||
DEFAULT_TAG_SCAN_JSON = (
|
||||
'{"Time":"2020-09-28T17:02:10","PN532":{"UID":"E9F35959", "DATA":"ILOVETASMOTA"}}'
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def device_reg(hass):
|
||||
"""Return an empty, loaded, registry."""
|
||||
return mock_device_registry(hass)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def entity_reg(hass):
|
||||
"""Return an empty, loaded, registry."""
|
||||
return mock_registry(hass)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tag_mock():
|
||||
"""Fixture to mock tag."""
|
||||
with patch("homeassistant.components.tag.async_scan_tag") as mock_tag:
|
||||
yield mock_tag
|
||||
|
||||
|
||||
@pytest.mark.no_fail_on_log_exception
|
||||
async def test_discover_bad_tag(hass, device_reg, entity_reg, mqtt_mock, tag_mock):
|
||||
"""Test bad discovery message."""
|
||||
config1 = copy.deepcopy(DEFAULT_CONFIG_DEVICE)
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
# Test sending bad data
|
||||
data0 = '{ "device":{"identifiers":["0AFFD2"]}, "topics": "foobar/tag_scanned" }'
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla/config", data0)
|
||||
await hass.async_block_till_done()
|
||||
assert device_reg.async_get_device({("mqtt", "0AFFD2")}, set()) is None
|
||||
|
||||
# Test sending correct data
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla/config", json.dumps(config1))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}, set())
|
||||
# Fake tag scan.
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, device_entry.id)
|
||||
|
||||
|
||||
async def test_if_fires_on_mqtt_message_with_device(
|
||||
hass, device_reg, mqtt_mock, tag_mock
|
||||
):
|
||||
"""Test tag scanning, with device."""
|
||||
config = copy.deepcopy(DEFAULT_CONFIG_DEVICE)
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config))
|
||||
await hass.async_block_till_done()
|
||||
device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}, set())
|
||||
|
||||
# Fake tag scan.
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, device_entry.id)
|
||||
|
||||
|
||||
async def test_if_fires_on_mqtt_message_without_device(
|
||||
hass, device_reg, mqtt_mock, tag_mock
|
||||
):
|
||||
"""Test tag scanning, without device."""
|
||||
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Fake tag scan.
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, None)
|
||||
|
||||
|
||||
async def test_if_fires_on_mqtt_message_with_template(
|
||||
hass, device_reg, mqtt_mock, tag_mock
|
||||
):
|
||||
"""Test tag scanning, with device."""
|
||||
config = copy.deepcopy(DEFAULT_CONFIG_JSON)
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config))
|
||||
await hass.async_block_till_done()
|
||||
device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}, set())
|
||||
|
||||
# Fake tag scan.
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN_JSON)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, device_entry.id)
|
||||
|
||||
|
||||
async def test_strip_tag_id(hass, device_reg, mqtt_mock, tag_mock):
|
||||
"""Test strip whitespace from tag_id."""
|
||||
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Fake tag scan.
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", "123456 ")
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, "123456", None)
|
||||
|
||||
|
||||
async def test_if_fires_on_mqtt_message_after_update_with_device(
|
||||
hass, device_reg, mqtt_mock, tag_mock
|
||||
):
|
||||
"""Test tag scanning after update."""
|
||||
config1 = copy.deepcopy(DEFAULT_CONFIG_DEVICE)
|
||||
config2 = copy.deepcopy(DEFAULT_CONFIG_DEVICE)
|
||||
config2["topic"] = "foobar/tag_scanned2"
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config1))
|
||||
await hass.async_block_till_done()
|
||||
device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}, set())
|
||||
|
||||
# Fake tag scan.
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, device_entry.id)
|
||||
|
||||
# Update the tag scanner with different topic
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config2))
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.reset_mock()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_not_called()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned2", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, device_entry.id)
|
||||
|
||||
# Update the tag scanner with same topic
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config2))
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.reset_mock()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_not_called()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned2", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, device_entry.id)
|
||||
|
||||
|
||||
async def test_if_fires_on_mqtt_message_after_update_without_device(
|
||||
hass, device_reg, mqtt_mock, tag_mock
|
||||
):
|
||||
"""Test tag scanning after update."""
|
||||
config1 = copy.deepcopy(DEFAULT_CONFIG)
|
||||
config2 = copy.deepcopy(DEFAULT_CONFIG)
|
||||
config2["topic"] = "foobar/tag_scanned2"
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config1))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Fake tag scan.
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, None)
|
||||
|
||||
# Update the tag scanner with different topic
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config2))
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.reset_mock()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_not_called()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned2", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, None)
|
||||
|
||||
# Update the tag scanner with same topic
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config2))
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.reset_mock()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_not_called()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned2", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, None)
|
||||
|
||||
|
||||
async def test_if_fires_on_mqtt_message_after_update_with_template(
|
||||
hass, device_reg, mqtt_mock, tag_mock
|
||||
):
|
||||
"""Test tag scanning after update."""
|
||||
config1 = copy.deepcopy(DEFAULT_CONFIG_JSON)
|
||||
config2 = copy.deepcopy(DEFAULT_CONFIG_JSON)
|
||||
config2["value_template"] = "{{ value_json.RDM6300.UID }}"
|
||||
tag_scan_2 = '{"Time":"2020-09-28T17:02:10","RDM6300":{"UID":"E9F35959", "DATA":"ILOVETASMOTA"}}'
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config1))
|
||||
await hass.async_block_till_done()
|
||||
device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}, set())
|
||||
|
||||
# Fake tag scan.
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN_JSON)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, device_entry.id)
|
||||
|
||||
# Update the tag scanner with different template
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config2))
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.reset_mock()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN_JSON)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_not_called()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", tag_scan_2)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, device_entry.id)
|
||||
|
||||
# Update the tag scanner with same template
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config2))
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.reset_mock()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN_JSON)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_not_called()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", tag_scan_2)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, device_entry.id)
|
||||
|
||||
|
||||
async def test_no_resubscribe_same_topic(hass, device_reg, mqtt_mock):
|
||||
"""Test subscription to topics without change."""
|
||||
config = copy.deepcopy(DEFAULT_CONFIG_DEVICE)
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config))
|
||||
await hass.async_block_till_done()
|
||||
assert device_reg.async_get_device({("mqtt", "0AFFD2")}, set())
|
||||
|
||||
call_count = mqtt_mock.async_subscribe.call_count
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config))
|
||||
await hass.async_block_till_done()
|
||||
assert mqtt_mock.async_subscribe.call_count == call_count
|
||||
|
||||
|
||||
async def test_not_fires_on_mqtt_message_after_remove_by_mqtt_with_device(
|
||||
hass, device_reg, mqtt_mock, tag_mock
|
||||
):
|
||||
"""Test tag scanning after removal."""
|
||||
config = copy.deepcopy(DEFAULT_CONFIG_DEVICE)
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config))
|
||||
await hass.async_block_till_done()
|
||||
device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}, set())
|
||||
|
||||
# Fake tag scan.
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, device_entry.id)
|
||||
|
||||
# Remove the tag scanner
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", "")
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.reset_mock()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_not_called()
|
||||
|
||||
# Rediscover the tag scanner
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, device_entry.id)
|
||||
|
||||
|
||||
async def test_not_fires_on_mqtt_message_after_remove_by_mqtt_without_device(
|
||||
hass, device_reg, mqtt_mock, tag_mock
|
||||
):
|
||||
"""Test tag scanning not firing after removal."""
|
||||
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Fake tag scan.
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, None)
|
||||
|
||||
# Remove the tag scanner
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", "")
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.reset_mock()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_not_called()
|
||||
|
||||
# Rediscover the tag scanner
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, None)
|
||||
|
||||
|
||||
async def test_not_fires_on_mqtt_message_after_remove_from_registry(
|
||||
hass,
|
||||
device_reg,
|
||||
mqtt_mock,
|
||||
tag_mock,
|
||||
):
|
||||
"""Test tag scanning after removal."""
|
||||
config = copy.deepcopy(DEFAULT_CONFIG_DEVICE)
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config))
|
||||
await hass.async_block_till_done()
|
||||
device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}, set())
|
||||
|
||||
# Fake tag scan.
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, DEFAULT_TAG_ID, device_entry.id)
|
||||
|
||||
# Remove the device
|
||||
device_reg.async_remove_device(device_entry.id)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.reset_mock()
|
||||
|
||||
async_fire_mqtt_message(hass, "foobar/tag_scanned", DEFAULT_TAG_SCAN)
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_not_called()
|
||||
|
||||
|
||||
async def test_entity_device_info_with_connection(hass, mqtt_mock):
|
||||
"""Test MQTT device registry integration."""
|
||||
entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", entry)
|
||||
registry = await hass.helpers.device_registry.async_get_registry()
|
||||
|
||||
data = json.dumps(
|
||||
{
|
||||
"topic": "test-topic",
|
||||
"device": {
|
||||
"connections": [["mac", "02:5b:26:a8:dc:12"]],
|
||||
"manufacturer": "Whatever",
|
||||
"name": "Beer",
|
||||
"model": "Glass",
|
||||
"sw_version": "0.1-beta",
|
||||
},
|
||||
}
|
||||
)
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla/config", data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = registry.async_get_device(set(), {("mac", "02:5b:26:a8:dc:12")})
|
||||
assert device is not None
|
||||
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_device_info_with_identifier(hass, mqtt_mock):
|
||||
"""Test MQTT device registry integration."""
|
||||
entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", entry)
|
||||
registry = await hass.helpers.device_registry.async_get_registry()
|
||||
|
||||
data = json.dumps(
|
||||
{
|
||||
"topic": "test-topic",
|
||||
"device": {
|
||||
"identifiers": ["helloworld"],
|
||||
"manufacturer": "Whatever",
|
||||
"name": "Beer",
|
||||
"model": "Glass",
|
||||
"sw_version": "0.1-beta",
|
||||
},
|
||||
}
|
||||
)
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla/config", data)
|
||||
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.manufacturer == "Whatever"
|
||||
assert device.name == "Beer"
|
||||
assert device.model == "Glass"
|
||||
assert device.sw_version == "0.1-beta"
|
||||
|
||||
|
||||
async def test_entity_device_info_update(hass, mqtt_mock):
|
||||
"""Test device registry update."""
|
||||
entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", entry)
|
||||
registry = await hass.helpers.device_registry.async_get_registry()
|
||||
|
||||
config = {
|
||||
"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",
|
||||
},
|
||||
}
|
||||
|
||||
data = json.dumps(config)
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla/config", data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = registry.async_get_device({("mqtt", "helloworld")}, set())
|
||||
assert device is not None
|
||||
assert device.name == "Beer"
|
||||
|
||||
config["device"]["name"] = "Milk"
|
||||
data = json.dumps(config)
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla/config", data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = registry.async_get_device({("mqtt", "helloworld")}, set())
|
||||
assert device is not None
|
||||
assert device.name == "Milk"
|
||||
|
||||
|
||||
async def test_cleanup_tag(hass, device_reg, entity_reg, mqtt_mock):
|
||||
"""Test tag discovery topic is cleaned when device is removed from registry."""
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
config = {
|
||||
"topic": "test-topic",
|
||||
"device": {"identifiers": ["helloworld"]},
|
||||
}
|
||||
|
||||
data = json.dumps(config)
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla/config", data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify device registry entry is created
|
||||
device_entry = device_reg.async_get_device({("mqtt", "helloworld")}, set())
|
||||
assert device_entry is not None
|
||||
|
||||
device_reg.async_remove_device(device_entry.id)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify device registry entry is cleared
|
||||
device_entry = device_reg.async_get_device({("mqtt", "helloworld")}, set())
|
||||
assert device_entry is None
|
||||
|
||||
# Verify retained discovery topic has been cleared
|
||||
mqtt_mock.async_publish.assert_called_once_with(
|
||||
"homeassistant/tag/bla/config", "", 0, True
|
||||
)
|
||||
|
||||
|
||||
async def test_cleanup_device(hass, device_reg, entity_reg, mqtt_mock):
|
||||
"""Test removal from device registry when tag is removed."""
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
config = {
|
||||
"topic": "test-topic",
|
||||
"device": {"identifiers": ["helloworld"]},
|
||||
}
|
||||
|
||||
data = json.dumps(config)
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla/config", data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify device registry entry is created
|
||||
device_entry = device_reg.async_get_device({("mqtt", "helloworld")}, set())
|
||||
assert device_entry is not None
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla/config", "")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify device registry entry is cleared
|
||||
device_entry = device_reg.async_get_device({("mqtt", "helloworld")}, set())
|
||||
assert device_entry is None
|
||||
|
||||
|
||||
async def test_cleanup_device_several_tags(
|
||||
hass, device_reg, entity_reg, mqtt_mock, tag_mock
|
||||
):
|
||||
"""Test removal from device registry when the last tag is removed."""
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
config1 = {
|
||||
"topic": "test-topic1",
|
||||
"device": {"identifiers": ["helloworld"]},
|
||||
}
|
||||
|
||||
config2 = {
|
||||
"topic": "test-topic2",
|
||||
"device": {"identifiers": ["helloworld"]},
|
||||
}
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", json.dumps(config1))
|
||||
await hass.async_block_till_done()
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla2/config", json.dumps(config2))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify device registry entry is created
|
||||
device_entry = device_reg.async_get_device({("mqtt", "helloworld")}, set())
|
||||
assert device_entry is not None
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", "")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify device registry entry is not cleared
|
||||
device_entry = device_reg.async_get_device({("mqtt", "helloworld")}, set())
|
||||
assert device_entry is not None
|
||||
|
||||
# Fake tag scan.
|
||||
async_fire_mqtt_message(hass, "test-topic1", "12345")
|
||||
async_fire_mqtt_message(hass, "test-topic2", "23456")
|
||||
await hass.async_block_till_done()
|
||||
tag_mock.assert_called_once_with(ANY, "23456", device_entry.id)
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla2/config", "")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify device registry entry is cleared
|
||||
device_entry = device_reg.async_get_device({("mqtt", "helloworld")}, set())
|
||||
assert device_entry is None
|
||||
|
||||
|
||||
async def test_cleanup_device_with_entity_and_trigger_1(
|
||||
hass, device_reg, entity_reg, mqtt_mock
|
||||
):
|
||||
"""Test removal from device registry for device with tag, entity and trigger.
|
||||
|
||||
Tag removed first, then trigger and entity.
|
||||
"""
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
config1 = {
|
||||
"topic": "test-topic",
|
||||
"device": {"identifiers": ["helloworld"]},
|
||||
}
|
||||
|
||||
config2 = {
|
||||
"automation_type": "trigger",
|
||||
"topic": "test-topic",
|
||||
"type": "foo",
|
||||
"subtype": "bar",
|
||||
"device": {"identifiers": ["helloworld"]},
|
||||
}
|
||||
|
||||
config3 = {
|
||||
"name": "test_binary_sensor",
|
||||
"state_topic": "test-topic",
|
||||
"device": {"identifiers": ["helloworld"]},
|
||||
"unique_id": "veryunique",
|
||||
}
|
||||
|
||||
data1 = json.dumps(config1)
|
||||
data2 = json.dumps(config2)
|
||||
data3 = json.dumps(config3)
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", data1)
|
||||
await hass.async_block_till_done()
|
||||
async_fire_mqtt_message(hass, "homeassistant/device_automation/bla2/config", data2)
|
||||
await hass.async_block_till_done()
|
||||
async_fire_mqtt_message(hass, "homeassistant/binary_sensor/bla3/config", data3)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify device registry entry is created
|
||||
device_entry = device_reg.async_get_device({("mqtt", "helloworld")}, set())
|
||||
assert device_entry is not None
|
||||
|
||||
triggers = await async_get_device_automations(hass, "trigger", device_entry.id)
|
||||
assert len(triggers) == 3 # 2 binary_sensor triggers + device trigger
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", "")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify device registry entry is not cleared
|
||||
device_entry = device_reg.async_get_device({("mqtt", "helloworld")}, set())
|
||||
assert device_entry is not None
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/device_automation/bla2/config", "")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/binary_sensor/bla3/config", "")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify device registry entry is cleared
|
||||
device_entry = device_reg.async_get_device({("mqtt", "helloworld")}, set())
|
||||
assert device_entry is None
|
||||
|
||||
|
||||
async def test_cleanup_device_with_entity2(hass, device_reg, entity_reg, mqtt_mock):
|
||||
"""Test removal from device registry for device with tag, entity and trigger.
|
||||
|
||||
Trigger and entity removed first, then tag.
|
||||
"""
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
await async_start(hass, "homeassistant", config_entry)
|
||||
|
||||
config1 = {
|
||||
"topic": "test-topic",
|
||||
"device": {"identifiers": ["helloworld"]},
|
||||
}
|
||||
|
||||
config2 = {
|
||||
"automation_type": "trigger",
|
||||
"topic": "test-topic",
|
||||
"type": "foo",
|
||||
"subtype": "bar",
|
||||
"device": {"identifiers": ["helloworld"]},
|
||||
}
|
||||
|
||||
config3 = {
|
||||
"name": "test_binary_sensor",
|
||||
"state_topic": "test-topic",
|
||||
"device": {"identifiers": ["helloworld"]},
|
||||
"unique_id": "veryunique",
|
||||
}
|
||||
|
||||
data1 = json.dumps(config1)
|
||||
data2 = json.dumps(config2)
|
||||
data3 = json.dumps(config3)
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", data1)
|
||||
await hass.async_block_till_done()
|
||||
async_fire_mqtt_message(hass, "homeassistant/device_automation/bla2/config", data2)
|
||||
await hass.async_block_till_done()
|
||||
async_fire_mqtt_message(hass, "homeassistant/binary_sensor/bla3/config", data3)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify device registry entry is created
|
||||
device_entry = device_reg.async_get_device({("mqtt", "helloworld")}, set())
|
||||
assert device_entry is not None
|
||||
|
||||
triggers = await async_get_device_automations(hass, "trigger", device_entry.id)
|
||||
assert len(triggers) == 3 # 2 binary_sensor triggers + device trigger
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/device_automation/bla2/config", "")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/binary_sensor/bla3/config", "")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify device registry entry is not cleared
|
||||
device_entry = device_reg.async_get_device({("mqtt", "helloworld")}, set())
|
||||
assert device_entry is not None
|
||||
|
||||
async_fire_mqtt_message(hass, "homeassistant/tag/bla1/config", "")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify device registry entry is cleared
|
||||
device_entry = device_reg.async_get_device({("mqtt", "helloworld")}, set())
|
||||
assert device_entry is None
|
Loading…
Add table
Reference in a new issue