From 52e1d744902be09bebbf3562791254eebb280ac3 Mon Sep 17 00:00:00 2001 From: Emanuele Palazzetti Date: Sat, 17 Feb 2024 17:12:45 +0100 Subject: [PATCH] feat(experimental): add managed sectors to control only part of available areas (#146) --- custom_components/econnect_metronet/__init__.py | 9 ++++++++- custom_components/econnect_metronet/const.py | 1 + custom_components/econnect_metronet/devices.py | 11 +++++++++++ tests/test_devices.py | 17 +++++++++++++++++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/custom_components/econnect_metronet/__init__.py b/custom_components/econnect_metronet/__init__.py index c77ff02..266fec2 100644 --- a/custom_components/econnect_metronet/__init__.py +++ b/custom_components/econnect_metronet/__init__.py @@ -13,6 +13,7 @@ from . import services from .const import ( CONF_DOMAIN, + CONF_EXPERIMENTAL, CONF_SCAN_INTERVAL, CONF_SYSTEM_URL, DOMAIN, @@ -102,9 +103,15 @@ async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool: Raises: Any exceptions raised by the coordinator or the setup process will be propagated up to the caller. """ + # Enable experimental settings from the configuration file + # NOTE: While it's discouraged to use YAML configurations for integrations, this approach + # ensures that we can experiment with new settings we can break without notice. + experimental = hass.data[DOMAIN].get(CONF_EXPERIMENTAL, {}) + + # Initialize Components scan_interval = config.options.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL_DEFAULT) client = ElmoClient(config.data[CONF_SYSTEM_URL], config.data[CONF_DOMAIN]) - device = AlarmDevice(client, config.options) + device = AlarmDevice(client, {**config.options, **experimental}) coordinator = AlarmCoordinator(hass, device, scan_interval) await coordinator.async_config_entry_first_refresh() diff --git a/custom_components/econnect_metronet/const.py b/custom_components/econnect_metronet/const.py index 0abc096..ea74747 100644 --- a/custom_components/econnect_metronet/const.py +++ b/custom_components/econnect_metronet/const.py @@ -14,6 +14,7 @@ CONF_AREAS_ARM_NIGHT = "areas_arm_night" CONF_AREAS_ARM_VACATION = "areas_arm_vacation" CONF_SCAN_INTERVAL = "scan_interval" +CONF_MANAGE_SECTORS = "managed_sectors" DEVICE_CLASS_SECTORS = "sector" DOMAIN = "econnect_metronet" NOTIFICATION_MESSAGE = ( diff --git a/custom_components/econnect_metronet/devices.py b/custom_components/econnect_metronet/devices.py index b8a9580..77c2b43 100644 --- a/custom_components/econnect_metronet/devices.py +++ b/custom_components/econnect_metronet/devices.py @@ -24,6 +24,7 @@ CONF_AREAS_ARM_HOME, CONF_AREAS_ARM_NIGHT, CONF_AREAS_ARM_VACATION, + CONF_MANAGE_SECTORS, NOTIFICATION_MESSAGE, ) from .helpers import split_code @@ -60,6 +61,7 @@ def __init__(self, connection, config=None): # Load user configuration config = config or {} + self._managed_sectors = config.get(CONF_MANAGE_SECTORS) or [] self._sectors_away = config.get(CONF_AREAS_ARM_AWAY) or [] self._sectors_home = config.get(CONF_AREAS_ARM_HOME) or [] self._sectors_night = config.get(CONF_AREAS_ARM_NIGHT) or [] @@ -291,6 +293,15 @@ def update(self): self._last_ids[q.ALERTS] = alerts.get("last_id", 0) self._last_ids[q.PANEL] = panel.get("last_id", 0) + # Filter out the sectors that are not managed + # NOTE: this change is internal and not exposed to users as the feature is experimental. Further + # development requires that users can register multiple devices and alarm panels to control + # sectors in a more granular way. See: https://github.com/palazzem/ha-econnect-alarm/issues/95 + if self._managed_sectors: + self._inventory[q.SECTORS] = { + k: v for k, v in self._inventory[q.SECTORS].items() if v["element"] in self._managed_sectors + } + # Update the internal state machine (mapping state) self.state = self.get_state() diff --git a/tests/test_devices.py b/tests/test_devices.py index 01491d8..f5a7f9b 100644 --- a/tests/test_devices.py +++ b/tests/test_devices.py @@ -18,6 +18,7 @@ CONF_AREAS_ARM_HOME, CONF_AREAS_ARM_NIGHT, CONF_AREAS_ARM_VACATION, + CONF_MANAGE_SECTORS, ) from custom_components.econnect_metronet.devices import AlarmDevice @@ -30,6 +31,7 @@ def test_device_constructor(client): assert device._inventory == {} assert device._sectors == {} assert device._last_ids == {10: 0, 9: 0, 11: 0, 12: 0} + assert device._managed_sectors == [] assert device._sectors_away == [] assert device._sectors_home == [] assert device._sectors_night == [] @@ -44,6 +46,7 @@ def test_device_constructor_with_config(client): CONF_AREAS_ARM_HOME: [3, 4], CONF_AREAS_ARM_NIGHT: [1, 2, 3], CONF_AREAS_ARM_VACATION: [5, 3], + CONF_MANAGE_SECTORS: [1, 2, 3, 4, 5], } device = AlarmDevice(client, config=config) # Test @@ -51,6 +54,7 @@ def test_device_constructor_with_config(client): assert device._inventory == {} assert device._sectors == {} assert device._last_ids == {10: 0, 9: 0, 11: 0, 12: 0} + assert device._managed_sectors == [1, 2, 3, 4, 5] assert device._sectors_away == [1, 2, 3, 4, 5] assert device._sectors_home == [3, 4] assert device._sectors_night == [1, 2, 3] @@ -65,6 +69,7 @@ def test_device_constructor_with_config_empty(client): CONF_AREAS_ARM_HOME: None, CONF_AREAS_ARM_NIGHT: None, CONF_AREAS_ARM_VACATION: None, + CONF_MANAGE_SECTORS: None, } device = AlarmDevice(client, config=config) # Test @@ -72,6 +77,7 @@ def test_device_constructor_with_config_empty(client): assert device._inventory == {} assert device._sectors == {} assert device._last_ids == {10: 0, 9: 0, 11: 0, 12: 0} + assert device._managed_sectors == [] assert device._sectors_away == [] assert device._sectors_home == [] assert device._sectors_night == [] @@ -578,6 +584,17 @@ def test_device_inventory_update_success(client, mocker): assert device._inventory == inventory +def test_device_inventory_update_managed_sectors(alarm_device): + # Ensure that only managed sectors are updated + alarm_device._managed_sectors = [2, 3] + # Test + alarm_device.update() + assert alarm_device._inventory[q.SECTORS] == { + 1: {"element": 2, "activable": True, "id": 2, "index": 1, "name": "S2 Bedroom", "status": True}, + 2: {"element": 3, "activable": False, "id": 3, "index": 2, "name": "S3 Outdoor", "status": False}, + } + + class TestInputsView: def test_property_populated(self, alarm_device): """Should check if the device property is correctly populated"""