From 30f73a49621f92b132e01502b3d38dba20319a70 Mon Sep 17 00:00:00 2001 From: Andreas Wrede Date: Sun, 6 Dec 2020 20:09:32 -0500 Subject: [PATCH] Add discovery of sensors on DS2409 MicroLan (#43599) * Add discovery of sensors on DS2409 MicroLan * Add tests for coupler * Update tests * Fix isort * Clean up * Move tests to test_sensor.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> Co-authored-by: Martin Hjelmare --- .../components/onewire/onewirehub.py | 30 +++-- .../components/onewire/test_binary_sensor.py | 3 + .../onewire/test_entity_owserver.py | 15 +++ tests/components/onewire/test_sensor.py | 126 +++++++++++++++++- tests/components/onewire/test_switch.py | 3 + 5 files changed, 161 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/onewire/onewirehub.py b/homeassistant/components/onewire/onewirehub.py index 3b715eed0dd..09a3235377d 100644 --- a/homeassistant/components/onewire/onewirehub.py +++ b/homeassistant/components/onewire/onewirehub.py @@ -11,6 +11,11 @@ from homeassistant.helpers.typing import HomeAssistantType from .const import CONF_MOUNT_DIR, CONF_TYPE_OWSERVER, CONF_TYPE_SYSBUS +DEVICE_COUPLERS = { + # Family : [branches] + "1F": ["aux", "main"] +} + class OneWireHub: """Hub to communicate with SysBus or OWServer.""" @@ -62,17 +67,24 @@ class OneWireHub: ) return self.devices - def _discover_devices_owserver(self): + def _discover_devices_owserver(self, path="/"): """Discover all owserver devices.""" devices = [] - for device_path in self.owproxy.dir(): - devices.append( - { - "path": device_path, - "family": self.owproxy.read(f"{device_path}family").decode(), - "type": self.owproxy.read(f"{device_path}type").decode(), - } - ) + for device_path in self.owproxy.dir(path): + device_family = self.owproxy.read(f"{device_path}family").decode() + device_type = self.owproxy.read(f"{device_path}type").decode() + device_branches = DEVICE_COUPLERS.get(device_family) + if device_branches: + for branch in device_branches: + devices += self._discover_devices_owserver(f"{device_path}{branch}") + else: + devices.append( + { + "path": device_path, + "family": device_family, + "type": device_type, + } + ) return devices diff --git a/tests/components/onewire/test_binary_sensor.py b/tests/components/onewire/test_binary_sensor.py index bc8cf1defc0..be740b13fb5 100644 --- a/tests/components/onewire/test_binary_sensor.py +++ b/tests/components/onewire/test_binary_sensor.py @@ -84,3 +84,6 @@ async def test_owserver_binary_sensor(owproxy, hass, device_id): assert registry_entry is not None state = hass.states.get(entity_id) assert state.state == expected_sensor["result"] + assert state.attributes["device_file"] == expected_sensor.get( + "device_file", registry_entry.unique_id + ) diff --git a/tests/components/onewire/test_entity_owserver.py b/tests/components/onewire/test_entity_owserver.py index a09808316c4..aee84f9fe2b 100644 --- a/tests/components/onewire/test_entity_owserver.py +++ b/tests/components/onewire/test_entity_owserver.py @@ -179,6 +179,18 @@ MOCK_DEVICE_SENSORS = { }, ], }, + "1F.111111111111": { + "inject_reads": [ + b"DS2409", # read device type + ], + "device_info": { + "identifiers": {(DOMAIN, "1F.111111111111")}, + "manufacturer": "Maxim Integrated", + "model": "DS2409", + "name": "1F.111111111111", + }, + SENSOR_DOMAIN: [], + }, "22.111111111111": { "inject_reads": [ b"DS1822", # read device type @@ -752,3 +764,6 @@ async def test_owserver_setup_valid_device(owproxy, hass, device_id, platform): assert state is None else: assert state.state == expected_sensor["result"] + assert state.attributes["device_file"] == expected_sensor.get( + "device_file", registry_entry.unique_id + ) diff --git a/tests/components/onewire/test_sensor.py b/tests/components/onewire/test_sensor.py index 751ef106147..ad9580f34ed 100644 --- a/tests/components/onewire/test_sensor.py +++ b/tests/components/onewire/test_sensor.py @@ -1,16 +1,66 @@ """Tests for 1-Wire sensor platform.""" -from homeassistant.components.onewire.const import DEFAULT_SYSBUS_MOUNT_DIR -import homeassistant.components.sensor as sensor +from pyownet.protocol import Error as ProtocolError +import pytest + +from homeassistant.components.onewire.const import DEFAULT_SYSBUS_MOUNT_DIR, DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.setup import async_setup_component -from tests.common import assert_setup_component +from . import setup_onewire_patched_owserver_integration + +from tests.async_mock import patch +from tests.common import assert_setup_component, mock_registry + +MOCK_COUPLERS = { + "1F.111111111111": { + "inject_reads": [ + b"DS2409", # read device type + ], + "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, + }, + ], + }, + }, + }, + } +} async def test_setup_minimum(hass): """Test old platform setup with minimum configuration.""" config = {"sensor": {"platform": "onewire"}} with assert_setup_component(1, "sensor"): - assert await async_setup_component(hass, sensor.DOMAIN, config) + assert await async_setup_component(hass, SENSOR_DOMAIN, config) await hass.async_block_till_done() @@ -23,7 +73,7 @@ async def test_setup_sysbus(hass): } } with assert_setup_component(1, "sensor"): - assert await async_setup_component(hass, sensor.DOMAIN, config) + assert await async_setup_component(hass, SENSOR_DOMAIN, config) await hass.async_block_till_done() @@ -31,7 +81,7 @@ async def test_setup_owserver(hass): """Test old platform setup with OWServer configuration.""" config = {"sensor": {"platform": "onewire", "host": "localhost"}} with assert_setup_component(1, "sensor"): - assert await async_setup_component(hass, sensor.DOMAIN, config) + assert await async_setup_component(hass, SENSOR_DOMAIN, config) await hass.async_block_till_done() @@ -39,5 +89,67 @@ async def test_setup_owserver_with_port(hass): """Test old platform setup with OWServer configuration.""" config = {"sensor": {"platform": "onewire", "host": "localhost", "port": "1234"}} with assert_setup_component(1, "sensor"): - assert await async_setup_component(hass, sensor.DOMAIN, config) + assert await async_setup_component(hass, SENSOR_DOMAIN, config) await hass.async_block_till_done() + + +@pytest.mark.parametrize("device_id", ["1F.111111111111"]) +@patch("homeassistant.components.onewire.onewirehub.protocol.proxy") +async def test_sensors_on_owserver_coupler(owproxy, hass, device_id): + """Test for 1-Wire sensors connected to DS2409 coupler.""" + await async_setup_component(hass, "persistent_notification", {}) + entity_registry = mock_registry(hass) + + mock_coupler = MOCK_COUPLERS[device_id] + + dir_side_effect = [] # List of lists of string + read_side_effect = [] # List of byte arrays + + dir_side_effect.append([f"/{device_id}/"]) # dir on root + read_side_effect.append(device_id[0:2].encode()) # read family on root + if "inject_reads" in mock_coupler: + read_side_effect += mock_coupler["inject_reads"] + + expected_sensors = [] + for branch, branch_details in mock_coupler["branches"].items(): + dir_side_effect.append( + [ # dir on branch + f"/{device_id}/{branch}/{sub_device_id}/" + for sub_device_id in branch_details + ] + ) + + for sub_device_id, sub_device in branch_details.items(): + read_side_effect.append(sub_device_id[0:2].encode()) + if "inject_reads" in sub_device: + read_side_effect.extend(sub_device["inject_reads"]) + + expected_sensors += sub_device[SENSOR_DOMAIN] + for expected_sensor in sub_device[SENSOR_DOMAIN]: + 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.side_effect = dir_side_effect + owproxy.return_value.read.side_effect = read_side_effect + + with patch("homeassistant.components.onewire.SUPPORTED_PLATFORMS", [SENSOR_DOMAIN]): + await setup_onewire_patched_owserver_integration(hass) + await hass.async_block_till_done() + + assert len(entity_registry.entities) == len(expected_sensors) + + 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"] + assert registry_entry.disabled == expected_sensor.get("disabled", False) + state = hass.states.get(entity_id) + if registry_entry.disabled: + assert state is None + else: + assert state.state == expected_sensor["result"] + assert state.attributes["device_file"] == expected_sensor["device_file"] diff --git a/tests/components/onewire/test_switch.py b/tests/components/onewire/test_switch.py index 3a1f2eb9f7a..0c70ad3c9fc 100644 --- a/tests/components/onewire/test_switch.py +++ b/tests/components/onewire/test_switch.py @@ -127,3 +127,6 @@ async def test_owserver_switch(owproxy, hass, device_id): state = hass.states.get(entity_id) assert state.state == expected_sensor["result"] + assert state.attributes["device_file"] == expected_sensor.get( + "device_file", registry_entry.unique_id + )