From 00cd07736e3da91c4039d82494ceb961e728767b Mon Sep 17 00:00:00 2001 From: Tom Matheussen <13683094+Tommatheussen@users.noreply.github.com> Date: Wed, 25 Mar 2026 12:05:46 +0100 Subject: [PATCH] Bump satel_integra to 1.0.0 (#164257) --- .../components/satel_integra/__init__.py | 14 ++++-- .../satel_integra/alarm_control_panel.py | 2 +- .../components/satel_integra/client.py | 43 ++++++------------- .../components/satel_integra/config_flow.py | 21 +++++---- .../components/satel_integra/const.py | 2 - .../components/satel_integra/coordinator.py | 13 +++--- .../components/satel_integra/entity.py | 2 +- .../components/satel_integra/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/satel_integra/__init__.py | 20 ++++++--- tests/components/satel_integra/conftest.py | 18 +++++--- .../satel_integra/test_binary_sensor.py | 28 ++++++------ tests/components/satel_integra/test_switch.py | 18 ++++---- 14 files changed, 95 insertions(+), 92 deletions(-) diff --git a/homeassistant/components/satel_integra/__init__.py b/homeassistant/components/satel_integra/__init__.py index b81cf9b8e86..4c695a26561 100644 --- a/homeassistant/components/satel_integra/__init__.py +++ b/homeassistant/components/satel_integra/__init__.py @@ -2,8 +2,8 @@ import logging -from homeassistant.const import Platform -from homeassistant.core import HomeAssistant, callback +from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries @@ -55,7 +55,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: SatelConfigEntry) -> boo coordinator_outputs=coordinator_outputs, coordinator_partitions=coordinator_partitions, ) + + async def async_close_connection(event: Event) -> None: + """Close Satel Integra connection on HA Stop.""" + await client.async_close() + entry.async_on_unload(entry.add_update_listener(update_listener)) + entry.async_on_unload( + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_close_connection) + ) device_registry = dr.async_get(hass) device_registry.async_get_or_create( @@ -74,7 +82,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: SatelConfigEntry) -> bo if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): runtime_data = entry.runtime_data - runtime_data.client.close() + await runtime_data.client.async_close() return unload_ok diff --git a/homeassistant/components/satel_integra/alarm_control_panel.py b/homeassistant/components/satel_integra/alarm_control_panel.py index 549ddcca9a2..36258155a51 100644 --- a/homeassistant/components/satel_integra/alarm_control_panel.py +++ b/homeassistant/components/satel_integra/alarm_control_panel.py @@ -5,7 +5,7 @@ from __future__ import annotations import asyncio import logging -from satel_integra.satel_integra import AlarmState +from satel_integra import AlarmState from homeassistant.components.alarm_control_panel import ( AlarmControlPanelEntity, diff --git a/homeassistant/components/satel_integra/client.py b/homeassistant/components/satel_integra/client.py index 6950583f173..db66d8af6fa 100644 --- a/homeassistant/components/satel_integra/client.py +++ b/homeassistant/components/satel_integra/client.py @@ -2,11 +2,11 @@ from collections.abc import Callable -from satel_integra.satel_integra import AsyncSatel +from satel_integra import AsyncSatel from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP -from homeassistant.core import HomeAssistant, callback +from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from .const import ( @@ -61,18 +61,12 @@ class SatelClient: monitored_outputs = outputs + switchable_outputs - self.controller = AsyncSatel( - host, port, hass.loop, zones, monitored_outputs, partitions - ) - - entry.async_on_unload( - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.close) - ) + self.controller = AsyncSatel(host, port, zones, monitored_outputs, partitions) async def async_connect( self, - zones_update_callback: Callable[[dict[str, dict[int, int]]], None], - outputs_update_callback: Callable[[dict[str, dict[int, int]]], None], + zones_update_callback: Callable[[dict[int, int]], None], + outputs_update_callback: Callable[[dict[int, int]], None], partitions_update_callback: Callable[[], None], ) -> None: """Start controller connection.""" @@ -80,26 +74,15 @@ class SatelClient: if not result: raise ConfigEntryNotReady("Controller failed to connect") - self.config_entry.async_create_background_task( - self.hass, - self.controller.keep_alive(), - f"satel_integra.{self.config_entry.entry_id}.keep_alive", - eager_start=False, + self.controller.register_callbacks( + alarm_status_callback=partitions_update_callback, + zone_changed_callback=zones_update_callback, + output_changed_callback=outputs_update_callback, ) - self.config_entry.async_create_background_task( - self.hass, - self.controller.monitor_status( - partitions_update_callback, - zones_update_callback, - outputs_update_callback, - ), - f"satel_integra.{self.config_entry.entry_id}.monitor_status", - eager_start=False, - ) + await self.controller.start(enable_monitoring=True) - @callback - def close(self, *args, **kwargs) -> None: + async def async_close(self) -> None: """Close the connection.""" - self.controller.close() + await self.controller.close() diff --git a/homeassistant/components/satel_integra/config_flow.py b/homeassistant/components/satel_integra/config_flow.py index 10042a9253c..b70beaf6ba9 100644 --- a/homeassistant/components/satel_integra/config_flow.py +++ b/homeassistant/components/satel_integra/config_flow.py @@ -5,7 +5,7 @@ from __future__ import annotations import logging from typing import Any -from satel_integra.satel_integra import AsyncSatel +from satel_integra import AsyncSatel import voluptuous as vol from homeassistant.components.binary_sensor import BinarySensorDeviceClass @@ -191,14 +191,19 @@ class SatelConfigFlow(ConfigFlow, domain=DOMAIN): async def test_connection(self, host: str, port: int) -> bool: """Test a connection to the Satel alarm.""" - controller = AsyncSatel(host, port, self.hass.loop) + controller = AsyncSatel(host, port) - result = await controller.connect() - - # Make sure we close the connection again - controller.close() - - return result + try: + return await controller.connect(check_busy=False) + except Exception: + _LOGGER.exception( + "Unexpected error during connection test to %s:%s", + host, + port, + ) + return False + finally: + await controller.close() class SatelOptionsFlow(OptionsFlow): diff --git a/homeassistant/components/satel_integra/const.py b/homeassistant/components/satel_integra/const.py index 8a2f7bc5239..33e9c7a9572 100644 --- a/homeassistant/components/satel_integra/const.py +++ b/homeassistant/components/satel_integra/const.py @@ -17,5 +17,3 @@ CONF_SWITCHABLE_OUTPUT_NUMBER = "switchable_output_number" CONF_ARM_HOME_MODE = "arm_home_mode" CONF_ZONE_TYPE = "type" - -ZONES = "zones" diff --git a/homeassistant/components/satel_integra/coordinator.py b/homeassistant/components/satel_integra/coordinator.py index 0805ab94ed5..19101ba3ec4 100644 --- a/homeassistant/components/satel_integra/coordinator.py +++ b/homeassistant/components/satel_integra/coordinator.py @@ -5,7 +5,7 @@ from __future__ import annotations from dataclasses import dataclass import logging -from satel_integra.satel_integra import AlarmState +from satel_integra import AlarmState from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -13,7 +13,6 @@ from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .client import SatelClient -from .const import ZONES _LOGGER = logging.getLogger(__name__) @@ -64,11 +63,11 @@ class SatelIntegraZonesCoordinator(SatelIntegraBaseCoordinator[dict[int, bool]]) self.data = {} @callback - def zones_update_callback(self, status: dict[str, dict[int, int]]) -> None: + def zones_update_callback(self, status: dict[int, int]) -> None: """Update zone objects as per notification from the alarm.""" _LOGGER.debug("Zones callback, status: %s", status) - update_data = {zone: value == 1 for zone, value in status[ZONES].items()} + update_data = {zone: value == 1 for zone, value in status.items()} self.async_set_updated_data(update_data) @@ -85,13 +84,11 @@ class SatelIntegraOutputsCoordinator(SatelIntegraBaseCoordinator[dict[int, bool] self.data = {} @callback - def outputs_update_callback(self, status: dict[str, dict[int, int]]) -> None: + def outputs_update_callback(self, status: dict[int, int]) -> None: """Update output objects as per notification from the alarm.""" _LOGGER.debug("Outputs callback, status: %s", status) - update_data = { - output: value == 1 for output, value in status["outputs"].items() - } + update_data = {output: value == 1 for output, value in status.items()} self.async_set_updated_data(update_data) diff --git a/homeassistant/components/satel_integra/entity.py b/homeassistant/components/satel_integra/entity.py index a3733914718..ac8e391aa96 100644 --- a/homeassistant/components/satel_integra/entity.py +++ b/homeassistant/components/satel_integra/entity.py @@ -4,7 +4,7 @@ from __future__ import annotations from typing import TYPE_CHECKING -from satel_integra.satel_integra import AsyncSatel +from satel_integra import AsyncSatel from homeassistant.config_entries import ConfigSubentry from homeassistant.const import CONF_NAME diff --git a/homeassistant/components/satel_integra/manifest.json b/homeassistant/components/satel_integra/manifest.json index c94d3924db5..a9af410506a 100644 --- a/homeassistant/components/satel_integra/manifest.json +++ b/homeassistant/components/satel_integra/manifest.json @@ -7,5 +7,5 @@ "integration_type": "device", "iot_class": "local_push", "loggers": ["satel_integra"], - "requirements": ["satel-integra==0.3.7"] + "requirements": ["satel-integra==1.0.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 3527a275a51..43f0038fe98 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2886,7 +2886,7 @@ samsungtvws[async,encrypted]==2.7.2 sanix==1.0.6 # homeassistant.components.satel_integra -satel-integra==0.3.7 +satel-integra==1.0.0 # homeassistant.components.screenlogic screenlogicpy==0.10.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6d3ca320e5e..7d6ace27b03 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2446,7 +2446,7 @@ samsungtvws[async,encrypted]==2.7.2 sanix==1.0.6 # homeassistant.components.satel_integra -satel-integra==0.3.7 +satel-integra==1.0.0 # homeassistant.components.screenlogic screenlogicpy==0.10.2 diff --git a/tests/components/satel_integra/__init__.py b/tests/components/satel_integra/__init__.py index d046f9618fe..94683453676 100644 --- a/tests/components/satel_integra/__init__.py +++ b/tests/components/satel_integra/__init__.py @@ -94,13 +94,19 @@ def get_monitor_callbacks( mock_satel: AsyncMock, ) -> tuple[ Callable[[], None], - Callable[[dict[str, dict[int, int]]], None], - Callable[[dict[str, dict[int, int]]], None], + Callable[[dict[int, int]], None], + Callable[[dict[int, int]], None], ]: - """Return (partitions_cb, zones_cb, outputs_cb) passed to monitor_status.""" - if not mock_satel.monitor_status.call_args_list: - pytest.fail("monitor_status was not called") + """Return callbacks passed to `register_callbacks`.""" + if not mock_satel.register_callbacks.call_args_list: + pytest.fail("register_callbacks was not called") + + call = mock_satel.register_callbacks.call_args_list[-1] + if call.kwargs: + partitions_cb = call.kwargs["alarm_status_callback"] + zones_cb = call.kwargs["zone_changed_callback"] + outputs_cb = call.kwargs["output_changed_callback"] + else: + partitions_cb, zones_cb, outputs_cb = call.args - call = mock_satel.monitor_status.call_args_list[-1] - partitions_cb, zones_cb, outputs_cb = call.args return partitions_cb, zones_cb, outputs_cb diff --git a/tests/components/satel_integra/conftest.py b/tests/components/satel_integra/conftest.py index decd30de2fb..10327a5b976 100644 --- a/tests/components/satel_integra/conftest.py +++ b/tests/components/satel_integra/conftest.py @@ -16,6 +16,7 @@ from . import ( MOCK_PARTITION_SUBENTRY, MOCK_SWITCHABLE_OUTPUT_SUBENTRY, MOCK_ZONE_SUBENTRY, + get_monitor_callbacks, ) from tests.common import MockConfigEntry @@ -63,13 +64,18 @@ def mock_satel() -> Generator[AsyncMock]: client.connect = AsyncMock(return_value=True) client.set_output = AsyncMock() - # Immediately push baseline values so entities have stable states for snapshots - async def _monitor_status(partitions_cb, zones_cb, outputs_cb): - partitions_cb() - zones_cb({"zones": {1: 0}}) - outputs_cb({"outputs": {1: 0}}) + client.register_callbacks = MagicMock() - client.monitor_status = AsyncMock(side_effect=_monitor_status) + # Immediately push baseline values so entities have stable states for snapshots + async def _start(**_: object) -> None: + alarm_status_callback, zone_changed_callback, output_changed_callback = ( + get_monitor_callbacks(client) + ) + alarm_status_callback() + zone_changed_callback({1: 0}) + output_changed_callback({1: 0}) + + client.start = AsyncMock(side_effect=_start) yield client diff --git a/tests/components/satel_integra/test_binary_sensor.py b/tests/components/satel_integra/test_binary_sensor.py index 42435968146..2f0d4854c34 100644 --- a/tests/components/satel_integra/test_binary_sensor.py +++ b/tests/components/satel_integra/test_binary_sensor.py @@ -81,13 +81,13 @@ async def test_binary_sensor_initial_state( """Test binary sensors have a correct initial state after initialization.""" # Instantly call callback to ensure we have initial data set - async def mock_monitor_callback( - alarm_status_callback, zones_callback, outputs_callback - ): - outputs_callback({"outputs": violated_entries}) - zones_callback({"zones": violated_entries}) + async def mock_start(**_: object) -> None: + _, zones_callback, outputs_callback = get_monitor_callbacks(mock_satel) - mock_satel.monitor_status = AsyncMock(side_effect=mock_monitor_callback) + outputs_callback(violated_entries) + zones_callback(violated_entries) + + mock_satel.start = AsyncMock(side_effect=mock_start) await setup_integration(hass, mock_config_entry_with_subentries) @@ -108,19 +108,19 @@ async def test_binary_sensor_callback( _, zone_update_method, output_update_method = get_monitor_callbacks(mock_satel) - output_update_method({"outputs": {1: 1}}) - zone_update_method({"zones": {1: 1}}) + output_update_method({1: 1}) + zone_update_method({1: 1}) assert hass.states.get("binary_sensor.zone").state == STATE_ON assert hass.states.get("binary_sensor.output").state == STATE_ON - output_update_method({"outputs": {1: 0}}) - zone_update_method({"zones": {1: 0}}) + output_update_method({1: 0}) + zone_update_method({1: 0}) assert hass.states.get("binary_sensor.zone").state == STATE_OFF assert hass.states.get("binary_sensor.output").state == STATE_OFF # The client library should always report all entries, but test that we set the status correctly if it doesn't - output_update_method({"outputs": {2: 1}}) - zone_update_method({"zones": {2: 1}}) + output_update_method({2: 1}) + zone_update_method({2: 1}) assert hass.states.get("binary_sensor.zone").state == STATE_UNKNOWN assert hass.states.get("binary_sensor.output").state == STATE_UNKNOWN @@ -145,8 +145,8 @@ async def test_binary_sensor_last_reported( # Run callbacks with same payload _, zone_update_method, output_update_method = get_monitor_callbacks(mock_satel) - output_update_method({"outputs": {1: 0}}) - zone_update_method({"zones": {1: 0}}) + output_update_method({1: 0}) + zone_update_method({1: 0}) assert first_reported != hass.states.get("binary_sensor.zone").last_reported assert len(events) == 2 # last_reported shall not fire state_changed diff --git a/tests/components/satel_integra/test_switch.py b/tests/components/satel_integra/test_switch.py index ec74103624f..2717041ada0 100644 --- a/tests/components/satel_integra/test_switch.py +++ b/tests/components/satel_integra/test_switch.py @@ -84,12 +84,12 @@ async def test_switch_initial_state( """Test switch has a correct initial state after initialization.""" # Instantly call callback to ensure we have initial data set - async def mock_monitor_callback( - alarm_status_callback, zones_callback, outputs_callback - ): - outputs_callback({"outputs": violated_outputs}) + async def mock_start(**_: object) -> None: + _, _, outputs_callback = get_monitor_callbacks(mock_satel) - mock_satel.monitor_status = AsyncMock(side_effect=mock_monitor_callback) + outputs_callback(violated_outputs) + + mock_satel.start = AsyncMock(side_effect=mock_start) await setup_integration(hass, mock_config_entry_with_subentries) @@ -108,14 +108,14 @@ async def test_switch_callback( _, _, output_update_method = get_monitor_callbacks(mock_satel) - output_update_method({"outputs": {1: 1}}) + output_update_method({1: 1}) assert hass.states.get("switch.switchable_output").state == STATE_ON - output_update_method({"outputs": {1: 0}}) + output_update_method({1: 0}) assert hass.states.get("switch.switchable_output").state == STATE_OFF # The client library should always report all entries, but test that we set the status correctly if it doesn't - output_update_method({"outputs": {2: 1}}) + output_update_method({2: 1}) assert hass.states.get("switch.switchable_output").state == STATE_UNKNOWN @@ -174,7 +174,7 @@ async def test_switch_last_reported( # Run callbacks with same payload _, _, output_update_method = get_monitor_callbacks(mock_satel) - output_update_method({"outputs": {1: 0}}) + output_update_method({1: 0}) assert first_reported != hass.states.get("switch.switchable_output").last_reported assert len(events) == 1 # last_reported shall not fire state_changed