Cleanup orphan devices in onewire integration (#48581)

* Cleanup orphan devices (https://github.com/home-assistant/core/issues/47438)

* Refactor unit testing

* Filter device entries for this config entry

* Update logging

* Cleanup check
This commit is contained in:
epenet 2021-04-01 15:06:47 +02:00 committed by GitHub
parent fdbef90a57
commit 81bdd41fdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 419 additions and 409 deletions

View File

@ -1,13 +1,17 @@
"""The 1-Wire component.""" """The 1-Wire component."""
import asyncio import asyncio
import logging
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from .const import DOMAIN, PLATFORMS from .const import DOMAIN, PLATFORMS
from .onewirehub import CannotConnect, OneWireHub from .onewirehub import CannotConnect, OneWireHub
_LOGGER = logging.getLogger(__name__)
async def async_setup(hass, config): async def async_setup(hass, config):
"""Set up 1-Wire integrations.""" """Set up 1-Wire integrations."""
@ -26,10 +30,43 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry):
hass.data[DOMAIN][config_entry.unique_id] = onewirehub hass.data[DOMAIN][config_entry.unique_id] = onewirehub
for platform in PLATFORMS: async def cleanup_registry() -> None:
hass.async_create_task( # Get registries
hass.config_entries.async_forward_entry_setup(config_entry, platform) device_registry, entity_registry = await asyncio.gather(
hass.helpers.device_registry.async_get_registry(),
hass.helpers.entity_registry.async_get_registry(),
) )
# Generate list of all device entries
registry_devices = [
entry.id
for entry in dr.async_entries_for_config_entry(
device_registry, config_entry.entry_id
)
]
# Remove devices that don't belong to any entity
for device_id in registry_devices:
if not er.async_entries_for_device(
entity_registry, device_id, include_disabled_entities=True
):
_LOGGER.debug(
"Removing device `%s` because it does not have any entities",
device_id,
)
device_registry.async_remove_device(device_id)
async def start_platforms() -> None:
"""Start platforms and cleanup devices."""
# wait until all required platforms are ready
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_setup(config_entry, platform)
for platform in PLATFORMS
]
)
await cleanup_registry()
hass.async_create_task(start_platforms())
return True return True

View File

@ -2,6 +2,8 @@
from unittest.mock import patch from unittest.mock import patch
from pyownet.protocol import ProtocolError
from homeassistant.components.onewire.const import ( from homeassistant.components.onewire.const import (
CONF_MOUNT_DIR, CONF_MOUNT_DIR,
CONF_NAMES, CONF_NAMES,
@ -13,6 +15,8 @@ from homeassistant.components.onewire.const import (
from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE
from .const import MOCK_OWPROXY_DEVICES
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -89,3 +93,35 @@ async def setup_onewire_patched_owserver_integration(hass):
await hass.async_block_till_done() await hass.async_block_till_done()
return config_entry return config_entry
def setup_owproxy_mock_devices(owproxy, domain, device_ids) -> None:
"""Set up mock for owproxy."""
dir_return_value = []
main_read_side_effect = []
sub_read_side_effect = []
for device_id in device_ids:
mock_device = MOCK_OWPROXY_DEVICES[device_id]
# Setup directory listing
dir_return_value += [f"/{device_id}/"]
# Setup device reads
main_read_side_effect += [device_id[0:2].encode()]
if "inject_reads" in mock_device:
main_read_side_effect += mock_device["inject_reads"]
# Setup sub-device reads
device_sensors = mock_device.get(domain, [])
for expected_sensor in device_sensors:
sub_read_side_effect.append(expected_sensor["injected_value"])
# Ensure enough read side effect
read_side_effect = (
main_read_side_effect
+ sub_read_side_effect
+ [ProtocolError("Missing injected value")] * 20
)
owproxy.return_value.dir.return_value = dir_return_value
owproxy.return_value.read.side_effect = read_side_effect

View File

@ -1,11 +1,10 @@
"""Tests for 1-Wire devices connected on OWServer.""" """Constants for 1-Wire integration."""
from unittest.mock import patch
from pi1wire import InvalidCRCException, UnsupportResponseException
from pyownet.protocol import Error as ProtocolError from pyownet.protocol import Error as ProtocolError
import pytest
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.onewire.const import DOMAIN, PLATFORMS, PRESSURE_CBAR from homeassistant.components.onewire.const import DOMAIN, PRESSURE_CBAR
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.const import ( from homeassistant.const import (
@ -24,13 +23,8 @@ from homeassistant.const import (
TEMP_CELSIUS, TEMP_CELSIUS,
VOLT, VOLT,
) )
from homeassistant.setup import async_setup_component
from . import setup_onewire_patched_owserver_integration MOCK_OWPROXY_DEVICES = {
from tests.common import mock_device_registry, mock_registry
MOCK_DEVICE_SENSORS = {
"00.111111111111": { "00.111111111111": {
"inject_reads": [ "inject_reads": [
b"", # read device type b"", # read device type
@ -186,7 +180,42 @@ MOCK_DEVICE_SENSORS = {
"model": "DS2409", "model": "DS2409",
"name": "1F.111111111111", "name": "1F.111111111111",
}, },
SENSOR_DOMAIN: [], "branches": {
"aux": {},
"main": {
"1D.111111111111": {
"inject_reads": [
b"DS2423", # read device type
],
"device_info": {
"identifiers": {(DOMAIN, "1D.111111111111")},
"manufacturer": "Maxim Integrated",
"model": "DS2423",
"name": "1D.111111111111",
},
SENSOR_DOMAIN: [
{
"entity_id": "sensor.1d_111111111111_counter_a",
"device_file": "/1F.111111111111/main/1D.111111111111/counter.A",
"unique_id": "/1D.111111111111/counter.A",
"injected_value": b" 251123",
"result": "251123",
"unit": "count",
"class": None,
},
{
"entity_id": "sensor.1d_111111111111_counter_b",
"device_file": "/1F.111111111111/main/1D.111111111111/counter.B",
"unique_id": "/1D.111111111111/counter.B",
"injected_value": b" 248125",
"result": "248125",
"unit": "count",
"class": None,
},
],
},
},
},
}, },
"22.111111111111": { "22.111111111111": {
"inject_reads": [ "inject_reads": [
@ -748,65 +777,106 @@ MOCK_DEVICE_SENSORS = {
}, },
} }
MOCK_SYSBUS_DEVICES = {
@pytest.mark.parametrize("device_id", MOCK_DEVICE_SENSORS.keys()) "00-111111111111": {"sensors": []},
@pytest.mark.parametrize("platform", PLATFORMS) "10-111111111111": {
@patch("homeassistant.components.onewire.onewirehub.protocol.proxy") "device_info": {
async def test_owserver_setup_valid_device(owproxy, hass, device_id, platform): "identifiers": {(DOMAIN, "10-111111111111")},
"""Test for 1-Wire device. "manufacturer": "Maxim Integrated",
"model": "10",
As they would be on a clean setup: all binary-sensors and switches disabled. "name": "10-111111111111",
""" },
await async_setup_component(hass, "persistent_notification", {}) "sensors": [
entity_registry = mock_registry(hass) {
device_registry = mock_device_registry(hass) "entity_id": "sensor.my_ds18b20_temperature",
"unique_id": "/sys/bus/w1/devices/10-111111111111/w1_slave",
mock_device_sensor = MOCK_DEVICE_SENSORS[device_id] "injected_value": 25.123,
"result": "25.1",
device_family = device_id[0:2] "unit": TEMP_CELSIUS,
dir_return_value = [f"/{device_id}/"] "class": DEVICE_CLASS_TEMPERATURE,
read_side_effect = [device_family.encode()] },
if "inject_reads" in mock_device_sensor: ],
read_side_effect += mock_device_sensor["inject_reads"] },
"12-111111111111": {"sensors": []},
expected_sensors = mock_device_sensor.get(platform, []) "1D-111111111111": {"sensors": []},
for expected_sensor in expected_sensors: "22-111111111111": {
read_side_effect.append(expected_sensor["injected_value"]) "device_info": {
"identifiers": {(DOMAIN, "22-111111111111")},
# Ensure enough read side effect "manufacturer": "Maxim Integrated",
read_side_effect.extend([ProtocolError("Missing injected value")] * 20) "model": "22",
owproxy.return_value.dir.return_value = dir_return_value "name": "22-111111111111",
owproxy.return_value.read.side_effect = read_side_effect },
"sensors": [
with patch("homeassistant.components.onewire.PLATFORMS", [platform]): {
await setup_onewire_patched_owserver_integration(hass) "entity_id": "sensor.22_111111111111_temperature",
await hass.async_block_till_done() "unique_id": "/sys/bus/w1/devices/22-111111111111/w1_slave",
"injected_value": FileNotFoundError,
assert len(entity_registry.entities) == len(expected_sensors) "result": "unknown",
"unit": TEMP_CELSIUS,
if len(expected_sensors) > 0: "class": DEVICE_CLASS_TEMPERATURE,
device_info = mock_device_sensor["device_info"] },
assert len(device_registry.devices) == 1 ],
registry_entry = device_registry.async_get_device({(DOMAIN, device_id)}) },
assert registry_entry is not None "26-111111111111": {"sensors": []},
assert registry_entry.identifiers == {(DOMAIN, device_id)} "28-111111111111": {
assert registry_entry.manufacturer == device_info["manufacturer"] "device_info": {
assert registry_entry.name == device_info["name"] "identifiers": {(DOMAIN, "28-111111111111")},
assert registry_entry.model == device_info["model"] "manufacturer": "Maxim Integrated",
"model": "28",
for expected_sensor in expected_sensors: "name": "28-111111111111",
entity_id = expected_sensor["entity_id"] },
registry_entry = entity_registry.entities.get(entity_id) "sensors": [
assert registry_entry is not None {
assert registry_entry.unique_id == expected_sensor["unique_id"] "entity_id": "sensor.28_111111111111_temperature",
assert registry_entry.unit_of_measurement == expected_sensor["unit"] "unique_id": "/sys/bus/w1/devices/28-111111111111/w1_slave",
assert registry_entry.device_class == expected_sensor["class"] "injected_value": InvalidCRCException,
assert registry_entry.disabled == expected_sensor.get("disabled", False) "result": "unknown",
state = hass.states.get(entity_id) "unit": TEMP_CELSIUS,
if registry_entry.disabled: "class": DEVICE_CLASS_TEMPERATURE,
assert state is None },
else: ],
assert state.state == expected_sensor["result"] },
assert state.attributes["device_file"] == expected_sensor.get( "29-111111111111": {"sensors": []},
"device_file", registry_entry.unique_id "3B-111111111111": {
) "device_info": {
"identifiers": {(DOMAIN, "3B-111111111111")},
"manufacturer": "Maxim Integrated",
"model": "3B",
"name": "3B-111111111111",
},
"sensors": [
{
"entity_id": "sensor.3b_111111111111_temperature",
"unique_id": "/sys/bus/w1/devices/3B-111111111111/w1_slave",
"injected_value": 29.993,
"result": "30.0",
"unit": TEMP_CELSIUS,
"class": DEVICE_CLASS_TEMPERATURE,
},
],
},
"42-111111111111": {
"device_info": {
"identifiers": {(DOMAIN, "42-111111111111")},
"manufacturer": "Maxim Integrated",
"model": "42",
"name": "42-111111111111",
},
"sensors": [
{
"entity_id": "sensor.42_111111111111_temperature",
"unique_id": "/sys/bus/w1/devices/42-111111111111/w1_slave",
"injected_value": UnsupportResponseException,
"result": "unknown",
"unit": TEMP_CELSIUS,
"class": DEVICE_CLASS_TEMPERATURE,
},
],
},
"EF-111111111111": {
"sensors": [],
},
"EF-111111111112": {
"sensors": [],
},
}

View File

@ -2,40 +2,25 @@
import copy import copy
from unittest.mock import patch from unittest.mock import patch
from pyownet.protocol import Error as ProtocolError
import pytest import pytest
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.onewire.binary_sensor import DEVICE_BINARY_SENSORS from homeassistant.components.onewire.binary_sensor import DEVICE_BINARY_SENSORS
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from . import setup_onewire_patched_owserver_integration from . import setup_onewire_patched_owserver_integration, setup_owproxy_mock_devices
from .const import MOCK_OWPROXY_DEVICES
from tests.common import mock_registry from tests.common import mock_registry
MOCK_DEVICE_SENSORS = { MOCK_BINARY_SENSORS = {
"12.111111111111": { key: value
"inject_reads": [ for (key, value) in MOCK_OWPROXY_DEVICES.items()
b"DS2406", # read device type if BINARY_SENSOR_DOMAIN in value
],
BINARY_SENSOR_DOMAIN: [
{
"entity_id": "binary_sensor.12_111111111111_sensed_a",
"injected_value": b" 1",
"result": STATE_ON,
},
{
"entity_id": "binary_sensor.12_111111111111_sensed_b",
"injected_value": b" 0",
"result": STATE_OFF,
},
],
},
} }
@pytest.mark.parametrize("device_id", MOCK_DEVICE_SENSORS.keys()) @pytest.mark.parametrize("device_id", MOCK_BINARY_SENSORS.keys())
@patch("homeassistant.components.onewire.onewirehub.protocol.proxy") @patch("homeassistant.components.onewire.onewirehub.protocol.proxy")
async def test_owserver_binary_sensor(owproxy, hass, device_id): async def test_owserver_binary_sensor(owproxy, hass, device_id):
"""Test for 1-Wire binary sensor. """Test for 1-Wire binary sensor.
@ -45,26 +30,14 @@ async def test_owserver_binary_sensor(owproxy, hass, device_id):
await async_setup_component(hass, "persistent_notification", {}) await async_setup_component(hass, "persistent_notification", {})
entity_registry = mock_registry(hass) entity_registry = mock_registry(hass)
mock_device_sensor = MOCK_DEVICE_SENSORS[device_id] setup_owproxy_mock_devices(owproxy, BINARY_SENSOR_DOMAIN, [device_id])
device_family = device_id[0:2] mock_device = MOCK_BINARY_SENSORS[device_id]
dir_return_value = [f"/{device_id}/"] expected_entities = mock_device[BINARY_SENSOR_DOMAIN]
read_side_effect = [device_family.encode()]
if "inject_reads" in mock_device_sensor:
read_side_effect += mock_device_sensor["inject_reads"]
expected_sensors = mock_device_sensor[BINARY_SENSOR_DOMAIN]
for expected_sensor in expected_sensors:
read_side_effect.append(expected_sensor["injected_value"])
# Ensure enough read side effect
read_side_effect.extend([ProtocolError("Missing injected value")] * 10)
owproxy.return_value.dir.return_value = dir_return_value
owproxy.return_value.read.side_effect = read_side_effect
# Force enable binary sensors # Force enable binary sensors
patch_device_binary_sensors = copy.deepcopy(DEVICE_BINARY_SENSORS) patch_device_binary_sensors = copy.deepcopy(DEVICE_BINARY_SENSORS)
for item in patch_device_binary_sensors[device_family]: for item in patch_device_binary_sensors[device_id[0:2]]:
item["default_disabled"] = False item["default_disabled"] = False
with patch( with patch(
@ -76,14 +49,14 @@ async def test_owserver_binary_sensor(owproxy, hass, device_id):
await setup_onewire_patched_owserver_integration(hass) await setup_onewire_patched_owserver_integration(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(entity_registry.entities) == len(expected_sensors) assert len(entity_registry.entities) == len(expected_entities)
for expected_sensor in expected_sensors: for expected_entity in expected_entities:
entity_id = expected_sensor["entity_id"] entity_id = expected_entity["entity_id"]
registry_entry = entity_registry.entities.get(entity_id) registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry is not None assert registry_entry is not None
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state.state == expected_sensor["result"] assert state.state == expected_entity["result"]
assert state.attributes["device_file"] == expected_sensor.get( assert state.attributes["device_file"] == expected_entity.get(
"device_file", registry_entry.unique_id "device_file", registry_entry.unique_id
) )

View File

@ -1,175 +0,0 @@
"""Tests for 1-Wire devices connected on SysBus."""
from unittest.mock import patch
from pi1wire import InvalidCRCException, UnsupportResponseException
import pytest
from homeassistant.components.onewire.const import DEFAULT_SYSBUS_MOUNT_DIR, DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS
from homeassistant.setup import async_setup_component
from tests.common import mock_device_registry, mock_registry
MOCK_CONFIG = {
SENSOR_DOMAIN: {
"platform": DOMAIN,
"mount_dir": DEFAULT_SYSBUS_MOUNT_DIR,
"names": {
"10-111111111111": "My DS18B20",
},
}
}
MOCK_DEVICE_SENSORS = {
"00-111111111111": {"sensors": []},
"10-111111111111": {
"device_info": {
"identifiers": {(DOMAIN, "10-111111111111")},
"manufacturer": "Maxim Integrated",
"model": "10",
"name": "10-111111111111",
},
"sensors": [
{
"entity_id": "sensor.my_ds18b20_temperature",
"unique_id": "/sys/bus/w1/devices/10-111111111111/w1_slave",
"injected_value": 25.123,
"result": "25.1",
"unit": TEMP_CELSIUS,
"class": DEVICE_CLASS_TEMPERATURE,
},
],
},
"12-111111111111": {"sensors": []},
"1D-111111111111": {"sensors": []},
"22-111111111111": {
"device_info": {
"identifiers": {(DOMAIN, "22-111111111111")},
"manufacturer": "Maxim Integrated",
"model": "22",
"name": "22-111111111111",
},
"sensors": [
{
"entity_id": "sensor.22_111111111111_temperature",
"unique_id": "/sys/bus/w1/devices/22-111111111111/w1_slave",
"injected_value": FileNotFoundError,
"result": "unknown",
"unit": TEMP_CELSIUS,
"class": DEVICE_CLASS_TEMPERATURE,
},
],
},
"26-111111111111": {"sensors": []},
"28-111111111111": {
"device_info": {
"identifiers": {(DOMAIN, "28-111111111111")},
"manufacturer": "Maxim Integrated",
"model": "28",
"name": "28-111111111111",
},
"sensors": [
{
"entity_id": "sensor.28_111111111111_temperature",
"unique_id": "/sys/bus/w1/devices/28-111111111111/w1_slave",
"injected_value": InvalidCRCException,
"result": "unknown",
"unit": TEMP_CELSIUS,
"class": DEVICE_CLASS_TEMPERATURE,
},
],
},
"29-111111111111": {"sensors": []},
"3B-111111111111": {
"device_info": {
"identifiers": {(DOMAIN, "3B-111111111111")},
"manufacturer": "Maxim Integrated",
"model": "3B",
"name": "3B-111111111111",
},
"sensors": [
{
"entity_id": "sensor.3b_111111111111_temperature",
"unique_id": "/sys/bus/w1/devices/3B-111111111111/w1_slave",
"injected_value": 29.993,
"result": "30.0",
"unit": TEMP_CELSIUS,
"class": DEVICE_CLASS_TEMPERATURE,
},
],
},
"42-111111111111": {
"device_info": {
"identifiers": {(DOMAIN, "42-111111111111")},
"manufacturer": "Maxim Integrated",
"model": "42",
"name": "42-111111111111",
},
"sensors": [
{
"entity_id": "sensor.42_111111111111_temperature",
"unique_id": "/sys/bus/w1/devices/42-111111111111/w1_slave",
"injected_value": UnsupportResponseException,
"result": "unknown",
"unit": TEMP_CELSIUS,
"class": DEVICE_CLASS_TEMPERATURE,
},
],
},
"EF-111111111111": {
"sensors": [],
},
"EF-111111111112": {
"sensors": [],
},
}
@pytest.mark.parametrize("device_id", MOCK_DEVICE_SENSORS.keys())
async def test_onewiredirect_setup_valid_device(hass, device_id):
"""Test that sysbus config entry works correctly."""
entity_registry = mock_registry(hass)
device_registry = mock_device_registry(hass)
mock_device_sensor = MOCK_DEVICE_SENSORS[device_id]
glob_result = [f"/{DEFAULT_SYSBUS_MOUNT_DIR}/{device_id}"]
read_side_effect = []
expected_sensors = mock_device_sensor["sensors"]
for expected_sensor in expected_sensors:
read_side_effect.append(expected_sensor["injected_value"])
# Ensure enough read side effect
read_side_effect.extend([FileNotFoundError("Missing injected value")] * 20)
with patch(
"homeassistant.components.onewire.onewirehub.os.path.isdir", return_value=True
), patch("pi1wire._finder.glob.glob", return_value=glob_result,), patch(
"pi1wire.OneWire.get_temperature",
side_effect=read_side_effect,
):
assert await async_setup_component(hass, SENSOR_DOMAIN, MOCK_CONFIG)
await hass.async_block_till_done()
assert len(entity_registry.entities) == len(expected_sensors)
if len(expected_sensors) > 0:
device_info = mock_device_sensor["device_info"]
assert len(device_registry.devices) == 1
registry_entry = device_registry.async_get_device({(DOMAIN, device_id)})
assert registry_entry is not None
assert registry_entry.identifiers == {(DOMAIN, device_id)}
assert registry_entry.manufacturer == device_info["manufacturer"]
assert registry_entry.name == device_info["name"]
assert registry_entry.model == device_info["model"]
for expected_sensor in expected_sensors:
entity_id = expected_sensor["entity_id"]
registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry is not None
assert registry_entry.unique_id == expected_sensor["unique_id"]
assert registry_entry.unit_of_measurement == expected_sensor["unit"]
assert registry_entry.device_class == expected_sensor["class"]
state = hass.states.get(entity_id)
assert state.state == expected_sensor["result"]

View File

@ -4,6 +4,7 @@ from unittest.mock import patch
from pyownet.protocol import ConnError, OwnetError from pyownet.protocol import ConnError, OwnetError
from homeassistant.components.onewire.const import CONF_TYPE_OWSERVER, DOMAIN from homeassistant.components.onewire.const import CONF_TYPE_OWSERVER, DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.config_entries import ( from homeassistant.config_entries import (
CONN_CLASS_LOCAL_POLL, CONN_CLASS_LOCAL_POLL,
ENTRY_STATE_LOADED, ENTRY_STATE_LOADED,
@ -11,10 +12,17 @@ from homeassistant.config_entries import (
ENTRY_STATE_SETUP_RETRY, ENTRY_STATE_SETUP_RETRY,
) )
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.setup import async_setup_component
from . import setup_onewire_owserver_integration, setup_onewire_sysbus_integration from . import (
setup_onewire_owserver_integration,
setup_onewire_patched_owserver_integration,
setup_onewire_sysbus_integration,
setup_owproxy_mock_devices,
)
from tests.common import MockConfigEntry from tests.common import MockConfigEntry, mock_device_registry, mock_registry
async def test_owserver_connect_failure(hass): async def test_owserver_connect_failure(hass):
@ -87,3 +95,41 @@ async def test_unload_entry(hass):
assert config_entry_owserver.state == ENTRY_STATE_NOT_LOADED assert config_entry_owserver.state == ENTRY_STATE_NOT_LOADED
assert config_entry_sysbus.state == ENTRY_STATE_NOT_LOADED assert config_entry_sysbus.state == ENTRY_STATE_NOT_LOADED
assert not hass.data.get(DOMAIN) assert not hass.data.get(DOMAIN)
@patch("homeassistant.components.onewire.onewirehub.protocol.proxy")
async def test_registry_cleanup(owproxy, hass):
"""Test for 1-Wire device.
As they would be on a clean setup: all binary-sensors and switches disabled.
"""
await async_setup_component(hass, "persistent_notification", {})
entity_registry = mock_registry(hass)
device_registry = mock_device_registry(hass)
# Initialise with two components
setup_owproxy_mock_devices(
owproxy, SENSOR_DOMAIN, ["10.111111111111", "28.111111111111"]
)
with patch("homeassistant.components.onewire.PLATFORMS", [SENSOR_DOMAIN]):
await setup_onewire_patched_owserver_integration(hass)
await hass.async_block_till_done()
assert len(dr.async_entries_for_config_entry(device_registry, "2")) == 2
assert len(er.async_entries_for_config_entry(entity_registry, "2")) == 2
# Second item has disappeared from bus, and was removed manually from the front-end
setup_owproxy_mock_devices(owproxy, SENSOR_DOMAIN, ["10.111111111111"])
entity_registry.async_remove("sensor.28_111111111111_temperature")
await hass.async_block_till_done()
assert len(er.async_entries_for_config_entry(entity_registry, "2")) == 1
assert len(dr.async_entries_for_config_entry(device_registry, "2")) == 2
# Second item has disappeared from bus, and was removed manually from the front-end
with patch("homeassistant.components.onewire.PLATFORMS", [SENSOR_DOMAIN]):
await hass.config_entries.async_reload("2")
await hass.async_block_till_done()
assert len(er.async_entries_for_config_entry(entity_registry, "2")) == 1
assert len(dr.async_entries_for_config_entry(device_registry, "2")) == 1

View File

@ -4,54 +4,29 @@ from unittest.mock import patch
from pyownet.protocol import Error as ProtocolError from pyownet.protocol import Error as ProtocolError
import pytest import pytest
from homeassistant.components.onewire.const import DEFAULT_SYSBUS_MOUNT_DIR, DOMAIN from homeassistant.components.onewire.const import (
DEFAULT_SYSBUS_MOUNT_DIR,
DOMAIN,
PLATFORMS,
)
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from . import setup_onewire_patched_owserver_integration from . import setup_onewire_patched_owserver_integration, setup_owproxy_mock_devices
from .const import MOCK_OWPROXY_DEVICES, MOCK_SYSBUS_DEVICES
from tests.common import assert_setup_component, mock_registry from tests.common import assert_setup_component, mock_device_registry, mock_registry
MOCK_COUPLERS = { MOCK_COUPLERS = {
"1F.111111111111": { key: value for (key, value) in MOCK_OWPROXY_DEVICES.items() if "branches" in value
"inject_reads": [ }
b"DS2409", # read device type
], MOCK_SYSBUS_CONFIG = {
"branches": { SENSOR_DOMAIN: {
"aux": {}, "platform": DOMAIN,
"main": { "mount_dir": DEFAULT_SYSBUS_MOUNT_DIR,
"1D.111111111111": { "names": {
"inject_reads": [ "10-111111111111": "My DS18B20",
b"DS2423", # read device type
],
"device_info": {
"identifiers": {(DOMAIN, "1D.111111111111")},
"manufacturer": "Maxim Integrated",
"model": "DS2423",
"name": "1D.111111111111",
},
SENSOR_DOMAIN: [
{
"entity_id": "sensor.1d_111111111111_counter_a",
"device_file": "/1F.111111111111/main/1D.111111111111/counter.A",
"unique_id": "/1D.111111111111/counter.A",
"injected_value": b" 251123",
"result": "251123",
"unit": "count",
"class": None,
},
{
"entity_id": "sensor.1d_111111111111_counter_b",
"device_file": "/1F.111111111111/main/1D.111111111111/counter.B",
"unique_id": "/1D.111111111111/counter.B",
"injected_value": b" 248125",
"result": "248125",
"unit": "count",
"class": None,
},
],
},
},
}, },
} }
} }
@ -154,3 +129,103 @@ async def test_sensors_on_owserver_coupler(owproxy, hass, device_id):
else: else:
assert state.state == expected_sensor["result"] assert state.state == expected_sensor["result"]
assert state.attributes["device_file"] == expected_sensor["device_file"] assert state.attributes["device_file"] == expected_sensor["device_file"]
@pytest.mark.parametrize("device_id", MOCK_OWPROXY_DEVICES.keys())
@pytest.mark.parametrize("platform", PLATFORMS)
@patch("homeassistant.components.onewire.onewirehub.protocol.proxy")
async def test_owserver_setup_valid_device(owproxy, hass, device_id, platform):
"""Test for 1-Wire device.
As they would be on a clean setup: all binary-sensors and switches disabled.
"""
await async_setup_component(hass, "persistent_notification", {})
entity_registry = mock_registry(hass)
device_registry = mock_device_registry(hass)
setup_owproxy_mock_devices(owproxy, platform, [device_id])
mock_device = MOCK_OWPROXY_DEVICES[device_id]
expected_entities = mock_device.get(platform, [])
with patch("homeassistant.components.onewire.PLATFORMS", [platform]):
await setup_onewire_patched_owserver_integration(hass)
await hass.async_block_till_done()
assert len(entity_registry.entities) == len(expected_entities)
if len(expected_entities) > 0:
device_info = mock_device["device_info"]
assert len(device_registry.devices) == 1
registry_entry = device_registry.async_get_device({(DOMAIN, device_id)})
assert registry_entry is not None
assert registry_entry.identifiers == {(DOMAIN, device_id)}
assert registry_entry.manufacturer == device_info["manufacturer"]
assert registry_entry.name == device_info["name"]
assert registry_entry.model == device_info["model"]
for expected_entity in expected_entities:
entity_id = expected_entity["entity_id"]
registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry is not None
assert registry_entry.unique_id == expected_entity["unique_id"]
assert registry_entry.unit_of_measurement == expected_entity["unit"]
assert registry_entry.device_class == expected_entity["class"]
assert registry_entry.disabled == expected_entity.get("disabled", False)
state = hass.states.get(entity_id)
if registry_entry.disabled:
assert state is None
else:
assert state.state == expected_entity["result"]
assert state.attributes["device_file"] == expected_entity.get(
"device_file", registry_entry.unique_id
)
@pytest.mark.parametrize("device_id", MOCK_SYSBUS_DEVICES.keys())
async def test_onewiredirect_setup_valid_device(hass, device_id):
"""Test that sysbus config entry works correctly."""
entity_registry = mock_registry(hass)
device_registry = mock_device_registry(hass)
mock_device_sensor = MOCK_SYSBUS_DEVICES[device_id]
glob_result = [f"/{DEFAULT_SYSBUS_MOUNT_DIR}/{device_id}"]
read_side_effect = []
expected_sensors = mock_device_sensor["sensors"]
for expected_sensor in expected_sensors:
read_side_effect.append(expected_sensor["injected_value"])
# Ensure enough read side effect
read_side_effect.extend([FileNotFoundError("Missing injected value")] * 20)
with patch(
"homeassistant.components.onewire.onewirehub.os.path.isdir", return_value=True
), patch("pi1wire._finder.glob.glob", return_value=glob_result,), patch(
"pi1wire.OneWire.get_temperature",
side_effect=read_side_effect,
):
assert await async_setup_component(hass, SENSOR_DOMAIN, MOCK_SYSBUS_CONFIG)
await hass.async_block_till_done()
assert len(entity_registry.entities) == len(expected_sensors)
if len(expected_sensors) > 0:
device_info = mock_device_sensor["device_info"]
assert len(device_registry.devices) == 1
registry_entry = device_registry.async_get_device({(DOMAIN, device_id)})
assert registry_entry is not None
assert registry_entry.identifiers == {(DOMAIN, device_id)}
assert registry_entry.manufacturer == device_info["manufacturer"]
assert registry_entry.name == device_info["name"]
assert registry_entry.model == device_info["model"]
for expected_sensor in expected_sensors:
entity_id = expected_sensor["entity_id"]
registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry is not None
assert registry_entry.unique_id == expected_sensor["unique_id"]
assert registry_entry.unit_of_measurement == expected_sensor["unit"]
assert registry_entry.device_class == expected_sensor["class"]
state = hass.states.get(entity_id)
assert state.state == expected_sensor["result"]

View File

@ -2,7 +2,6 @@
import copy import copy
from unittest.mock import patch from unittest.mock import patch
from pyownet.protocol import Error as ProtocolError
import pytest import pytest
from homeassistant.components.onewire.switch import DEVICE_SWITCHES from homeassistant.components.onewire.switch import DEVICE_SWITCHES
@ -10,58 +9,19 @@ from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TOGGLE, STATE_OFF, STATE_ON from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TOGGLE, STATE_OFF, STATE_ON
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from . import setup_onewire_patched_owserver_integration from . import setup_onewire_patched_owserver_integration, setup_owproxy_mock_devices
from .const import MOCK_OWPROXY_DEVICES
from tests.common import mock_registry from tests.common import mock_registry
MOCK_DEVICE_SENSORS = { MOCK_SWITCHES = {
"12.111111111111": { key: value
"inject_reads": [ for (key, value) in MOCK_OWPROXY_DEVICES.items()
b"DS2406", # read device type if SWITCH_DOMAIN in value
],
SWITCH_DOMAIN: [
{
"entity_id": "switch.12_111111111111_pio_a",
"unique_id": "/12.111111111111/PIO.A",
"injected_value": b" 1",
"result": STATE_ON,
"unit": None,
"class": None,
"disabled": True,
},
{
"entity_id": "switch.12_111111111111_pio_b",
"unique_id": "/12.111111111111/PIO.B",
"injected_value": b" 0",
"result": STATE_OFF,
"unit": None,
"class": None,
"disabled": True,
},
{
"entity_id": "switch.12_111111111111_latch_a",
"unique_id": "/12.111111111111/latch.A",
"injected_value": b" 1",
"result": STATE_ON,
"unit": None,
"class": None,
"disabled": True,
},
{
"entity_id": "switch.12_111111111111_latch_b",
"unique_id": "/12.111111111111/latch.B",
"injected_value": b" 0",
"result": STATE_OFF,
"unit": None,
"class": None,
"disabled": True,
},
],
}
} }
@pytest.mark.parametrize("device_id", ["12.111111111111"]) @pytest.mark.parametrize("device_id", MOCK_SWITCHES.keys())
@patch("homeassistant.components.onewire.onewirehub.protocol.proxy") @patch("homeassistant.components.onewire.onewirehub.protocol.proxy")
async def test_owserver_switch(owproxy, hass, device_id): async def test_owserver_switch(owproxy, hass, device_id):
"""Test for 1-Wire switch. """Test for 1-Wire switch.
@ -71,26 +31,14 @@ async def test_owserver_switch(owproxy, hass, device_id):
await async_setup_component(hass, "persistent_notification", {}) await async_setup_component(hass, "persistent_notification", {})
entity_registry = mock_registry(hass) entity_registry = mock_registry(hass)
mock_device_sensor = MOCK_DEVICE_SENSORS[device_id] setup_owproxy_mock_devices(owproxy, SWITCH_DOMAIN, [device_id])
device_family = device_id[0:2] mock_device = MOCK_SWITCHES[device_id]
dir_return_value = [f"/{device_id}/"] expected_entities = mock_device[SWITCH_DOMAIN]
read_side_effect = [device_family.encode()]
if "inject_reads" in mock_device_sensor:
read_side_effect += mock_device_sensor["inject_reads"]
expected_sensors = mock_device_sensor[SWITCH_DOMAIN]
for expected_sensor in expected_sensors:
read_side_effect.append(expected_sensor["injected_value"])
# Ensure enough read side effect
read_side_effect.extend([ProtocolError("Missing injected value")] * 10)
owproxy.return_value.dir.return_value = dir_return_value
owproxy.return_value.read.side_effect = read_side_effect
# Force enable switches # Force enable switches
patch_device_switches = copy.deepcopy(DEVICE_SWITCHES) patch_device_switches = copy.deepcopy(DEVICE_SWITCHES)
for item in patch_device_switches[device_family]: for item in patch_device_switches[device_id[0:2]]:
item["default_disabled"] = False item["default_disabled"] = False
with patch( with patch(
@ -101,21 +49,21 @@ async def test_owserver_switch(owproxy, hass, device_id):
await setup_onewire_patched_owserver_integration(hass) await setup_onewire_patched_owserver_integration(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(entity_registry.entities) == len(expected_sensors) assert len(entity_registry.entities) == len(expected_entities)
for expected_sensor in expected_sensors: for expected_entity in expected_entities:
entity_id = expected_sensor["entity_id"] entity_id = expected_entity["entity_id"]
registry_entry = entity_registry.entities.get(entity_id) registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry is not None assert registry_entry is not None
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state.state == expected_sensor["result"] assert state.state == expected_entity["result"]
if state.state == STATE_ON: if state.state == STATE_ON:
owproxy.return_value.read.side_effect = [b" 0"] owproxy.return_value.read.side_effect = [b" 0"]
expected_sensor["result"] = STATE_OFF expected_entity["result"] = STATE_OFF
elif state.state == STATE_OFF: elif state.state == STATE_OFF:
owproxy.return_value.read.side_effect = [b" 1"] owproxy.return_value.read.side_effect = [b" 1"]
expected_sensor["result"] = STATE_ON expected_entity["result"] = STATE_ON
await hass.services.async_call( await hass.services.async_call(
SWITCH_DOMAIN, SWITCH_DOMAIN,
@ -126,7 +74,7 @@ async def test_owserver_switch(owproxy, hass, device_id):
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state.state == expected_sensor["result"] assert state.state == expected_entity["result"]
assert state.attributes["device_file"] == expected_sensor.get( assert state.attributes["device_file"] == expected_entity.get(
"device_file", registry_entry.unique_id "device_file", registry_entry.unique_id
) )