1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-02 08:26:41 +01:00

Bump satel_integra to 1.0.0 (#164257)

This commit is contained in:
Tom Matheussen
2026-03-25 12:05:46 +01:00
committed by GitHub
parent 78871e1766
commit 00cd07736e
14 changed files with 95 additions and 92 deletions

View File

@@ -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

View File

@@ -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,

View File

@@ -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()

View File

@@ -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):

View File

@@ -17,5 +17,3 @@ CONF_SWITCHABLE_OUTPUT_NUMBER = "switchable_output_number"
CONF_ARM_HOME_MODE = "arm_home_mode"
CONF_ZONE_TYPE = "type"
ZONES = "zones"

View File

@@ -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)

View File

@@ -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

View File

@@ -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"]
}

2
requirements_all.txt generated
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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