mirror of
https://github.com/Electric-Special/ha-core.git
synced 2026-03-21 05:06:13 +01:00
Add coordinator for Satel Integra (#158533)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
This commit is contained in:
@@ -2,30 +2,21 @@
|
||||
|
||||
import logging
|
||||
|
||||
from satel_integra.satel_integra import AsyncSatel
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import (
|
||||
CONF_CODE,
|
||||
CONF_HOST,
|
||||
CONF_NAME,
|
||||
CONF_PORT,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.const import CONF_CODE, CONF_HOST, CONF_NAME, CONF_PORT, Platform
|
||||
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
issue_registry as ir,
|
||||
)
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .client import SatelClient
|
||||
from .const import (
|
||||
CONF_ARM_HOME_MODE,
|
||||
CONF_DEVICE_PARTITIONS,
|
||||
@@ -41,15 +32,17 @@ from .const import (
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_ZONE_TYPE,
|
||||
DOMAIN,
|
||||
SIGNAL_OUTPUTS_UPDATED,
|
||||
SIGNAL_PANEL_MESSAGE,
|
||||
SIGNAL_ZONES_UPDATED,
|
||||
SUBENTRY_TYPE_OUTPUT,
|
||||
SUBENTRY_TYPE_PARTITION,
|
||||
SUBENTRY_TYPE_SWITCHABLE_OUTPUT,
|
||||
SUBENTRY_TYPE_ZONE,
|
||||
ZONES,
|
||||
)
|
||||
from .coordinator import (
|
||||
SatelConfigEntry,
|
||||
SatelIntegraData,
|
||||
SatelIntegraOutputsCoordinator,
|
||||
SatelIntegraPartitionsCoordinator,
|
||||
SatelIntegraZonesCoordinator,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -159,51 +152,25 @@ async def _async_import(hass: HomeAssistant, config: ConfigType) -> None:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: SatelConfigEntry) -> bool:
|
||||
"""Set up Satel Integra from a config entry."""
|
||||
|
||||
host = entry.data[CONF_HOST]
|
||||
port = entry.data[CONF_PORT]
|
||||
client = SatelClient(hass, entry)
|
||||
|
||||
# Make sure we initialize the Satel controller with the configured entries to monitor
|
||||
partitions = [
|
||||
subentry.data[CONF_PARTITION_NUMBER]
|
||||
for subentry in entry.subentries.values()
|
||||
if subentry.subentry_type == SUBENTRY_TYPE_PARTITION
|
||||
]
|
||||
coordinator_zones = SatelIntegraZonesCoordinator(hass, entry, client)
|
||||
coordinator_outputs = SatelIntegraOutputsCoordinator(hass, entry, client)
|
||||
coordinator_partitions = SatelIntegraPartitionsCoordinator(hass, entry, client)
|
||||
|
||||
zones = [
|
||||
subentry.data[CONF_ZONE_NUMBER]
|
||||
for subentry in entry.subentries.values()
|
||||
if subentry.subentry_type == SUBENTRY_TYPE_ZONE
|
||||
]
|
||||
|
||||
outputs = [
|
||||
subentry.data[CONF_OUTPUT_NUMBER]
|
||||
for subentry in entry.subentries.values()
|
||||
if subentry.subentry_type == SUBENTRY_TYPE_OUTPUT
|
||||
]
|
||||
|
||||
switchable_outputs = [
|
||||
subentry.data[CONF_SWITCHABLE_OUTPUT_NUMBER]
|
||||
for subentry in entry.subentries.values()
|
||||
if subentry.subentry_type == SUBENTRY_TYPE_SWITCHABLE_OUTPUT
|
||||
]
|
||||
|
||||
monitored_outputs = outputs + switchable_outputs
|
||||
|
||||
controller = AsyncSatel(host, port, hass.loop, zones, monitored_outputs, partitions)
|
||||
|
||||
result = await controller.connect()
|
||||
|
||||
if not result:
|
||||
raise ConfigEntryNotReady("Controller failed to connect")
|
||||
|
||||
entry.runtime_data = controller
|
||||
|
||||
@callback
|
||||
def _close(*_):
|
||||
controller.close()
|
||||
await client.async_connect(
|
||||
coordinator_zones.zones_update_callback,
|
||||
coordinator_outputs.outputs_update_callback,
|
||||
coordinator_partitions.partitions_update_callback,
|
||||
)
|
||||
|
||||
entry.runtime_data = SatelIntegraData(
|
||||
client=client,
|
||||
coordinator_zones=coordinator_zones,
|
||||
coordinator_outputs=coordinator_outputs,
|
||||
coordinator_partitions=coordinator_partitions,
|
||||
)
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
entry.async_on_unload(hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _close))
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
device_registry.async_get_or_create(
|
||||
@@ -214,33 +181,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: SatelConfigEntry) -> boo
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
@callback
|
||||
def alarm_status_update_callback():
|
||||
"""Send status update received from alarm to Home Assistant."""
|
||||
_LOGGER.debug("Sending request to update panel state")
|
||||
async_dispatcher_send(hass, SIGNAL_PANEL_MESSAGE)
|
||||
|
||||
@callback
|
||||
def zones_update_callback(status):
|
||||
"""Update zone objects as per notification from the alarm."""
|
||||
_LOGGER.debug("Zones callback, status: %s", status)
|
||||
async_dispatcher_send(hass, SIGNAL_ZONES_UPDATED, status[ZONES])
|
||||
|
||||
@callback
|
||||
def outputs_update_callback(status):
|
||||
"""Update zone objects as per notification from the alarm."""
|
||||
_LOGGER.debug("Outputs updated callback , status: %s", status)
|
||||
async_dispatcher_send(hass, SIGNAL_OUTPUTS_UPDATED, status["outputs"])
|
||||
|
||||
# Create a task instead of adding a tracking job, since this task will
|
||||
# run until the connection to satel_integra is closed.
|
||||
hass.loop.create_task(controller.keep_alive())
|
||||
hass.loop.create_task(
|
||||
controller.monitor_status(
|
||||
alarm_status_update_callback, zones_update_callback, outputs_update_callback
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -248,8 +188,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: SatelConfigEntry) -> bo
|
||||
"""Unloading the Satel platforms."""
|
||||
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
controller = entry.runtime_data
|
||||
controller.close()
|
||||
runtime_data = entry.runtime_data
|
||||
runtime_data.client.close()
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from satel_integra.satel_integra import AlarmState, AsyncSatel
|
||||
from satel_integra.satel_integra import AlarmState
|
||||
|
||||
from homeassistant.components.alarm_control_panel import (
|
||||
AlarmControlPanelEntity,
|
||||
@@ -15,16 +15,10 @@ from homeassistant.components.alarm_control_panel import (
|
||||
)
|
||||
from homeassistant.config_entries import ConfigSubentry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import (
|
||||
CONF_ARM_HOME_MODE,
|
||||
CONF_PARTITION_NUMBER,
|
||||
SIGNAL_PANEL_MESSAGE,
|
||||
SUBENTRY_TYPE_PARTITION,
|
||||
SatelConfigEntry,
|
||||
)
|
||||
from .const import CONF_ARM_HOME_MODE, CONF_PARTITION_NUMBER, SUBENTRY_TYPE_PARTITION
|
||||
from .coordinator import SatelConfigEntry, SatelIntegraPartitionsCoordinator
|
||||
from .entity import SatelIntegraEntity
|
||||
|
||||
ALARM_STATE_MAP = {
|
||||
@@ -49,7 +43,7 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up for Satel Integra alarm panels."""
|
||||
|
||||
controller = config_entry.runtime_data
|
||||
runtime_data = config_entry.runtime_data
|
||||
|
||||
partition_subentries = filter(
|
||||
lambda entry: entry.subentry_type == SUBENTRY_TYPE_PARTITION,
|
||||
@@ -63,7 +57,7 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
[
|
||||
SatelIntegraAlarmPanel(
|
||||
controller,
|
||||
runtime_data.coordinator_partitions,
|
||||
config_entry.entry_id,
|
||||
subentry,
|
||||
partition_num,
|
||||
@@ -74,8 +68,10 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
|
||||
class SatelIntegraAlarmPanel(SatelIntegraEntity, AlarmControlPanelEntity):
|
||||
"""Representation of an AlarmDecoder-based alarm panel."""
|
||||
class SatelIntegraAlarmPanel(
|
||||
SatelIntegraEntity[SatelIntegraPartitionsCoordinator], AlarmControlPanelEntity
|
||||
):
|
||||
"""Representation of a Satel Integra-based alarm panel."""
|
||||
|
||||
_attr_code_format = CodeFormat.NUMBER
|
||||
_attr_supported_features = (
|
||||
@@ -85,7 +81,7 @@ class SatelIntegraAlarmPanel(SatelIntegraEntity, AlarmControlPanelEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
controller: AsyncSatel,
|
||||
coordinator: SatelIntegraPartitionsCoordinator,
|
||||
config_entry_id: str,
|
||||
subentry: ConfigSubentry,
|
||||
device_number: int,
|
||||
@@ -93,7 +89,7 @@ class SatelIntegraAlarmPanel(SatelIntegraEntity, AlarmControlPanelEntity):
|
||||
) -> None:
|
||||
"""Initialize the alarm panel."""
|
||||
super().__init__(
|
||||
controller,
|
||||
coordinator,
|
||||
config_entry_id,
|
||||
subentry,
|
||||
device_number,
|
||||
@@ -101,19 +97,11 @@ class SatelIntegraAlarmPanel(SatelIntegraEntity, AlarmControlPanelEntity):
|
||||
|
||||
self._arm_home_mode = arm_home_mode
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Update alarm status and register callbacks for future updates."""
|
||||
self._attr_alarm_state = self._read_alarm_state()
|
||||
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_PANEL_MESSAGE, self._update_alarm_status
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
def _update_alarm_status(self) -> None:
|
||||
"""Handle alarm status update."""
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
state = self._read_alarm_state()
|
||||
|
||||
if state != self._attr_alarm_state:
|
||||
@@ -123,14 +111,14 @@ class SatelIntegraAlarmPanel(SatelIntegraEntity, AlarmControlPanelEntity):
|
||||
def _read_alarm_state(self) -> AlarmControlPanelState | None:
|
||||
"""Read current status of the alarm and translate it into HA status."""
|
||||
|
||||
if not self._satel.connected:
|
||||
if not self._controller.connected:
|
||||
_LOGGER.debug("Alarm panel not connected")
|
||||
return None
|
||||
|
||||
for satel_state, ha_state in ALARM_STATE_MAP.items():
|
||||
if (
|
||||
satel_state in self._satel.partition_states
|
||||
and self._device_number in self._satel.partition_states[satel_state]
|
||||
satel_state in self.coordinator.data
|
||||
and self._device_number in self.coordinator.data[satel_state]
|
||||
):
|
||||
return ha_state
|
||||
|
||||
@@ -146,21 +134,21 @@ class SatelIntegraAlarmPanel(SatelIntegraEntity, AlarmControlPanelEntity):
|
||||
self._attr_alarm_state == AlarmControlPanelState.TRIGGERED
|
||||
)
|
||||
|
||||
await self._satel.disarm(code, [self._device_number])
|
||||
await self._controller.disarm(code, [self._device_number])
|
||||
|
||||
if clear_alarm_necessary:
|
||||
# Wait 1s before clearing the alarm
|
||||
await asyncio.sleep(1)
|
||||
await self._satel.clear_alarm(code, [self._device_number])
|
||||
await self._controller.clear_alarm(code, [self._device_number])
|
||||
|
||||
async def async_alarm_arm_away(self, code: str | None = None) -> None:
|
||||
"""Send arm away command."""
|
||||
|
||||
if code:
|
||||
await self._satel.arm(code, [self._device_number])
|
||||
await self._controller.arm(code, [self._device_number])
|
||||
|
||||
async def async_alarm_arm_home(self, code: str | None = None) -> None:
|
||||
"""Send arm home command."""
|
||||
|
||||
if code:
|
||||
await self._satel.arm(code, [self._device_number], self._arm_home_mode)
|
||||
await self._controller.arm(code, [self._device_number], self._arm_home_mode)
|
||||
|
||||
@@ -2,27 +2,22 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from satel_integra.satel_integra import AsyncSatel
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
BinarySensorEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigSubentry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import (
|
||||
CONF_OUTPUT_NUMBER,
|
||||
CONF_ZONE_NUMBER,
|
||||
CONF_ZONE_TYPE,
|
||||
SIGNAL_OUTPUTS_UPDATED,
|
||||
SIGNAL_ZONES_UPDATED,
|
||||
SUBENTRY_TYPE_OUTPUT,
|
||||
SUBENTRY_TYPE_ZONE,
|
||||
SatelConfigEntry,
|
||||
)
|
||||
from .coordinator import SatelConfigEntry, SatelIntegraBaseCoordinator
|
||||
from .entity import SatelIntegraEntity
|
||||
|
||||
|
||||
@@ -33,7 +28,7 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up the Satel Integra binary sensor devices."""
|
||||
|
||||
controller = config_entry.runtime_data
|
||||
runtime_data = config_entry.runtime_data
|
||||
|
||||
zone_subentries = filter(
|
||||
lambda entry: entry.subentry_type == SUBENTRY_TYPE_ZONE,
|
||||
@@ -47,12 +42,11 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
[
|
||||
SatelIntegraBinarySensor(
|
||||
controller,
|
||||
runtime_data.coordinator_zones,
|
||||
config_entry.entry_id,
|
||||
subentry,
|
||||
zone_num,
|
||||
zone_type,
|
||||
SIGNAL_ZONES_UPDATED,
|
||||
)
|
||||
],
|
||||
config_subentry_id=subentry.subentry_id,
|
||||
@@ -70,59 +64,50 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
[
|
||||
SatelIntegraBinarySensor(
|
||||
controller,
|
||||
runtime_data.coordinator_outputs,
|
||||
config_entry.entry_id,
|
||||
subentry,
|
||||
output_num,
|
||||
ouput_type,
|
||||
SIGNAL_OUTPUTS_UPDATED,
|
||||
)
|
||||
],
|
||||
config_subentry_id=subentry.subentry_id,
|
||||
)
|
||||
|
||||
|
||||
class SatelIntegraBinarySensor(SatelIntegraEntity, BinarySensorEntity):
|
||||
"""Representation of an Satel Integra binary sensor."""
|
||||
class SatelIntegraBinarySensor[_CoordinatorT: SatelIntegraBaseCoordinator](
|
||||
SatelIntegraEntity[_CoordinatorT], BinarySensorEntity
|
||||
):
|
||||
"""Base binary sensor for Satel Integra."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
controller: AsyncSatel,
|
||||
coordinator: _CoordinatorT,
|
||||
config_entry_id: str,
|
||||
subentry: ConfigSubentry,
|
||||
device_number: int,
|
||||
device_class: BinarySensorDeviceClass,
|
||||
react_to_signal: str,
|
||||
) -> None:
|
||||
"""Initialize the binary_sensor."""
|
||||
super().__init__(
|
||||
controller,
|
||||
coordinator,
|
||||
config_entry_id,
|
||||
subentry,
|
||||
device_number,
|
||||
)
|
||||
|
||||
self._attr_device_class = device_class
|
||||
self._react_to_signal = react_to_signal
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callbacks."""
|
||||
if self._react_to_signal == SIGNAL_OUTPUTS_UPDATED:
|
||||
self._attr_is_on = self._device_number in self._satel.violated_outputs
|
||||
else:
|
||||
self._attr_is_on = self._device_number in self._satel.violated_zones
|
||||
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, self._react_to_signal, self._devices_updated
|
||||
)
|
||||
)
|
||||
self._attr_is_on = self._get_state_from_coordinator()
|
||||
|
||||
@callback
|
||||
def _devices_updated(self, zones: dict[int, int]):
|
||||
"""Update the zone's state, if needed."""
|
||||
if self._device_number in zones:
|
||||
new_state = zones[self._device_number] == 1
|
||||
if new_state != self._attr_is_on:
|
||||
self._attr_is_on = new_state
|
||||
self.async_write_ha_state()
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
new_state = self._get_state_from_coordinator()
|
||||
if new_state != self._attr_is_on:
|
||||
self._attr_is_on = new_state
|
||||
self.async_write_ha_state()
|
||||
|
||||
def _get_state_from_coordinator(self) -> bool | None:
|
||||
"""Method to get binary sensor state from coordinator data."""
|
||||
return self.coordinator.data.get(self._device_number)
|
||||
|
||||
105
homeassistant/components/satel_integra/client.py
Normal file
105
homeassistant/components/satel_integra/client.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""Satel Integra client."""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from satel_integra.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.exceptions import ConfigEntryNotReady
|
||||
|
||||
from .const import (
|
||||
CONF_OUTPUT_NUMBER,
|
||||
CONF_PARTITION_NUMBER,
|
||||
CONF_SWITCHABLE_OUTPUT_NUMBER,
|
||||
CONF_ZONE_NUMBER,
|
||||
SUBENTRY_TYPE_OUTPUT,
|
||||
SUBENTRY_TYPE_PARTITION,
|
||||
SUBENTRY_TYPE_SWITCHABLE_OUTPUT,
|
||||
SUBENTRY_TYPE_ZONE,
|
||||
)
|
||||
|
||||
|
||||
class SatelClient:
|
||||
"""Client to connect to Satel Integra."""
|
||||
|
||||
controller: AsyncSatel
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Initialize the client wrapper."""
|
||||
self.hass = hass
|
||||
self.config_entry = entry
|
||||
|
||||
host = entry.data[CONF_HOST]
|
||||
port = entry.data[CONF_PORT]
|
||||
|
||||
# Make sure we initialize the Satel controller with the configured entries to monitor
|
||||
partitions = [
|
||||
subentry.data[CONF_PARTITION_NUMBER]
|
||||
for subentry in entry.subentries.values()
|
||||
if subentry.subentry_type == SUBENTRY_TYPE_PARTITION
|
||||
]
|
||||
|
||||
zones = [
|
||||
subentry.data[CONF_ZONE_NUMBER]
|
||||
for subentry in entry.subentries.values()
|
||||
if subentry.subentry_type == SUBENTRY_TYPE_ZONE
|
||||
]
|
||||
|
||||
outputs = [
|
||||
subentry.data[CONF_OUTPUT_NUMBER]
|
||||
for subentry in entry.subentries.values()
|
||||
if subentry.subentry_type == SUBENTRY_TYPE_OUTPUT
|
||||
]
|
||||
|
||||
switchable_outputs = [
|
||||
subentry.data[CONF_SWITCHABLE_OUTPUT_NUMBER]
|
||||
for subentry in entry.subentries.values()
|
||||
if subentry.subentry_type == SUBENTRY_TYPE_SWITCHABLE_OUTPUT
|
||||
]
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
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],
|
||||
partitions_update_callback: Callable[[], None],
|
||||
) -> None:
|
||||
"""Start controller connection."""
|
||||
result = await self.controller.connect()
|
||||
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.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,
|
||||
)
|
||||
|
||||
@callback
|
||||
def close(self, *args, **kwargs) -> None:
|
||||
"""Close the connection."""
|
||||
|
||||
self.controller.close()
|
||||
@@ -40,8 +40,8 @@ from .const import (
|
||||
SUBENTRY_TYPE_PARTITION,
|
||||
SUBENTRY_TYPE_SWITCHABLE_OUTPUT,
|
||||
SUBENTRY_TYPE_ZONE,
|
||||
SatelConfigEntry,
|
||||
)
|
||||
from .coordinator import SatelConfigEntry
|
||||
|
||||
_LOGGER = logging.getLogger(__package__)
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
"""Constants for the Satel Integra integration."""
|
||||
|
||||
from satel_integra.satel_integra import AsyncSatel
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
||||
DEFAULT_CONF_ARM_HOME_MODE = 1
|
||||
DEFAULT_PORT = 7094
|
||||
DEFAULT_ZONE_TYPE = "motion"
|
||||
@@ -28,11 +24,3 @@ CONF_OUTPUTS = "outputs"
|
||||
CONF_SWITCHABLE_OUTPUTS = "switchable_outputs"
|
||||
|
||||
ZONES = "zones"
|
||||
|
||||
|
||||
SIGNAL_PANEL_MESSAGE = "satel_integra.panel_message"
|
||||
|
||||
SIGNAL_ZONES_UPDATED = "satel_integra.zones_updated"
|
||||
SIGNAL_OUTPUTS_UPDATED = "satel_integra.outputs_updated"
|
||||
|
||||
type SatelConfigEntry = ConfigEntry[AsyncSatel]
|
||||
|
||||
114
homeassistant/components/satel_integra/coordinator.py
Normal file
114
homeassistant/components/satel_integra/coordinator.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""Coordinator for Satel Integra."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
from satel_integra.satel_integra import AlarmState
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .client import SatelClient
|
||||
from .const import ZONES
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SatelIntegraData:
|
||||
"""Data for the satel_integra integration."""
|
||||
|
||||
client: SatelClient
|
||||
coordinator_zones: SatelIntegraZonesCoordinator
|
||||
coordinator_outputs: SatelIntegraOutputsCoordinator
|
||||
coordinator_partitions: SatelIntegraPartitionsCoordinator
|
||||
|
||||
|
||||
type SatelConfigEntry = ConfigEntry[SatelIntegraData]
|
||||
|
||||
|
||||
class SatelIntegraBaseCoordinator[_DataT](DataUpdateCoordinator[_DataT]):
|
||||
"""DataUpdateCoordinator base class for Satel Integra."""
|
||||
|
||||
config_entry: SatelConfigEntry
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, entry: SatelConfigEntry, client: SatelClient
|
||||
) -> None:
|
||||
"""Initialize the base coordinator."""
|
||||
self.client = client
|
||||
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
config_entry=entry,
|
||||
name=f"{entry.entry_id} {self.__class__.__name__}",
|
||||
)
|
||||
|
||||
|
||||
class SatelIntegraZonesCoordinator(SatelIntegraBaseCoordinator[dict[int, bool]]):
|
||||
"""DataUpdateCoordinator to handle zone updates."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, entry: SatelConfigEntry, client: SatelClient
|
||||
) -> None:
|
||||
"""Initialize the coordinator."""
|
||||
super().__init__(hass, entry, client)
|
||||
|
||||
self.data = {}
|
||||
|
||||
@callback
|
||||
def zones_update_callback(self, status: dict[str, 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()}
|
||||
|
||||
self.async_set_updated_data(update_data)
|
||||
|
||||
|
||||
class SatelIntegraOutputsCoordinator(SatelIntegraBaseCoordinator[dict[int, bool]]):
|
||||
"""DataUpdateCoordinator to handle output updates."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, entry: SatelConfigEntry, client: SatelClient
|
||||
) -> None:
|
||||
"""Initialize the coordinator."""
|
||||
super().__init__(hass, entry, client)
|
||||
|
||||
self.data = {}
|
||||
|
||||
@callback
|
||||
def outputs_update_callback(self, status: dict[str, 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()
|
||||
}
|
||||
|
||||
self.async_set_updated_data(update_data)
|
||||
|
||||
|
||||
class SatelIntegraPartitionsCoordinator(
|
||||
SatelIntegraBaseCoordinator[dict[AlarmState, list[int]]]
|
||||
):
|
||||
"""DataUpdateCoordinator to handle partition state updates."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, entry: SatelConfigEntry, client: SatelClient
|
||||
) -> None:
|
||||
"""Initialize the coordinator."""
|
||||
super().__init__(hass, entry, client)
|
||||
|
||||
self.data = {}
|
||||
|
||||
@callback
|
||||
def partitions_update_callback(self) -> None:
|
||||
"""Update partition objects as per notification from the alarm."""
|
||||
_LOGGER.debug("Sending request to update panel state")
|
||||
|
||||
self.async_set_updated_data(self.client.controller.partition_states)
|
||||
@@ -9,7 +9,7 @@ from satel_integra.satel_integra import AsyncSatel
|
||||
from homeassistant.config_entries import ConfigSubentry
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
@@ -18,6 +18,7 @@ from .const import (
|
||||
SUBENTRY_TYPE_SWITCHABLE_OUTPUT,
|
||||
SUBENTRY_TYPE_ZONE,
|
||||
)
|
||||
from .coordinator import SatelIntegraBaseCoordinator
|
||||
|
||||
SubentryTypeToEntityType: dict[str, str] = {
|
||||
SUBENTRY_TYPE_PARTITION: "alarm_panel",
|
||||
@@ -27,23 +28,29 @@ SubentryTypeToEntityType: dict[str, str] = {
|
||||
}
|
||||
|
||||
|
||||
class SatelIntegraEntity(Entity):
|
||||
class SatelIntegraEntity[_CoordinatorT: SatelIntegraBaseCoordinator](
|
||||
CoordinatorEntity[_CoordinatorT]
|
||||
):
|
||||
"""Defines a base Satel Integra entity."""
|
||||
|
||||
_attr_should_poll = False
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
_controller: AsyncSatel
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
controller: AsyncSatel,
|
||||
coordinator: _CoordinatorT,
|
||||
config_entry_id: str,
|
||||
subentry: ConfigSubentry,
|
||||
device_number: int,
|
||||
) -> None:
|
||||
"""Initialize the Satel Integra entity."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self._controller = coordinator.client.controller
|
||||
|
||||
self._satel = controller
|
||||
self._device_number = device_number
|
||||
|
||||
entity_type = SubentryTypeToEntityType[subentry.subentry_type]
|
||||
|
||||
@@ -4,21 +4,14 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from satel_integra.satel_integra import AsyncSatel
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.config_entries import ConfigSubentry
|
||||
from homeassistant.const import CONF_CODE
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import (
|
||||
CONF_SWITCHABLE_OUTPUT_NUMBER,
|
||||
SIGNAL_OUTPUTS_UPDATED,
|
||||
SUBENTRY_TYPE_SWITCHABLE_OUTPUT,
|
||||
SatelConfigEntry,
|
||||
)
|
||||
from .const import CONF_SWITCHABLE_OUTPUT_NUMBER, SUBENTRY_TYPE_SWITCHABLE_OUTPUT
|
||||
from .coordinator import SatelConfigEntry, SatelIntegraOutputsCoordinator
|
||||
from .entity import SatelIntegraEntity
|
||||
|
||||
|
||||
@@ -29,7 +22,7 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up the Satel Integra switch devices."""
|
||||
|
||||
controller = config_entry.runtime_data
|
||||
runtime_data = config_entry.runtime_data
|
||||
|
||||
switchable_output_subentries = filter(
|
||||
lambda entry: entry.subentry_type == SUBENTRY_TYPE_SWITCHABLE_OUTPUT,
|
||||
@@ -42,7 +35,7 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
[
|
||||
SatelIntegraSwitch(
|
||||
controller,
|
||||
runtime_data.coordinator_outputs,
|
||||
config_entry.entry_id,
|
||||
subentry,
|
||||
switchable_output_num,
|
||||
@@ -53,12 +46,14 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
|
||||
class SatelIntegraSwitch(SatelIntegraEntity, SwitchEntity):
|
||||
class SatelIntegraSwitch(
|
||||
SatelIntegraEntity[SatelIntegraOutputsCoordinator], SwitchEntity
|
||||
):
|
||||
"""Representation of an Satel Integra switch."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
controller: AsyncSatel,
|
||||
coordinator: SatelIntegraOutputsCoordinator,
|
||||
config_entry_id: str,
|
||||
subentry: ConfigSubentry,
|
||||
device_number: int,
|
||||
@@ -66,7 +61,7 @@ class SatelIntegraSwitch(SatelIntegraEntity, SwitchEntity):
|
||||
) -> None:
|
||||
"""Initialize the switch."""
|
||||
super().__init__(
|
||||
controller,
|
||||
coordinator,
|
||||
config_entry_id,
|
||||
subentry,
|
||||
device_number,
|
||||
@@ -74,33 +69,28 @@ class SatelIntegraSwitch(SatelIntegraEntity, SwitchEntity):
|
||||
|
||||
self._code = code
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callbacks."""
|
||||
self._attr_is_on = self._device_number in self._satel.violated_outputs
|
||||
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_OUTPUTS_UPDATED, self._devices_updated
|
||||
)
|
||||
)
|
||||
self._attr_is_on = self._get_state_from_coordinator()
|
||||
|
||||
@callback
|
||||
def _devices_updated(self, outputs: dict[int, int]) -> None:
|
||||
"""Update switch state, if needed."""
|
||||
if self._device_number in outputs:
|
||||
new_state = outputs[self._device_number] == 1
|
||||
if new_state != self._attr_is_on:
|
||||
self._attr_is_on = new_state
|
||||
self.async_write_ha_state()
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
new_state = self._get_state_from_coordinator()
|
||||
if new_state != self._attr_is_on:
|
||||
self._attr_is_on = new_state
|
||||
self.async_write_ha_state()
|
||||
|
||||
def _get_state_from_coordinator(self) -> bool | None:
|
||||
"""Method to get switch state from coordinator data."""
|
||||
return self.coordinator.data.get(self._device_number)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the device on."""
|
||||
await self._satel.set_output(self._code, self._device_number, True)
|
||||
await self._controller.set_output(self._code, self._device_number, True)
|
||||
self._attr_is_on = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the device off."""
|
||||
await self._satel.set_output(self._code, self._device_number, False)
|
||||
await self._controller.set_output(self._code, self._device_number, False)
|
||||
self._attr_is_on = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
"""The tests for Satel Integra integration."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
from homeassistant.components.satel_integra import (
|
||||
CONF_ARM_HOME_MODE,
|
||||
@@ -80,3 +85,19 @@ async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry):
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
def get_monitor_callbacks(
|
||||
mock_satel: AsyncMock,
|
||||
) -> tuple[
|
||||
Callable[[], None],
|
||||
Callable[[dict[str, dict[int, int]]], None],
|
||||
Callable[[dict[str, 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")
|
||||
|
||||
call = mock_satel.monitor_status.call_args_list[-1]
|
||||
partitions_cb, zones_cb, outputs_cb = call.args
|
||||
return partitions_cb, zones_cb, outputs_cb
|
||||
|
||||
@@ -36,7 +36,7 @@ def mock_satel() -> Generator[AsyncMock]:
|
||||
"""Override the satel test."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.satel_integra.AsyncSatel",
|
||||
"homeassistant.components.satel_integra.client.AsyncSatel",
|
||||
autospec=True,
|
||||
) as mock_client,
|
||||
patch(
|
||||
@@ -45,12 +45,22 @@ def mock_satel() -> Generator[AsyncMock]:
|
||||
),
|
||||
):
|
||||
client = mock_client.return_value
|
||||
|
||||
client.partition_states = {}
|
||||
client.violated_outputs = []
|
||||
client.violated_zones = []
|
||||
|
||||
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.monitor_status = AsyncMock(side_effect=_monitor_status)
|
||||
|
||||
yield client
|
||||
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceRegistry
|
||||
from homeassistant.helpers.entity_registry import EntityRegistry
|
||||
|
||||
from . import MOCK_CODE, MOCK_ENTRY_ID, setup_integration
|
||||
from . import MOCK_CODE, MOCK_ENTRY_ID, get_monitor_callbacks, setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
@@ -59,7 +59,7 @@ async def test_alarm_control_panel(
|
||||
assert device_entry == snapshot(name="device")
|
||||
|
||||
|
||||
async def test_alarm_control_panel_initial_state_on(
|
||||
async def test_alarm_control_panel_initial_state(
|
||||
hass: HomeAssistant,
|
||||
mock_satel: AsyncMock,
|
||||
mock_config_entry_with_subentries: MockConfigEntry,
|
||||
@@ -104,8 +104,7 @@ async def test_alarm_status_callback(
|
||||
== AlarmControlPanelState.DISARMED
|
||||
)
|
||||
|
||||
monitor_status_call = mock_satel.monitor_status.call_args_list[0][0]
|
||||
alarm_panel_update_method = monitor_status_call[0]
|
||||
alarm_panel_update_method, _, _ = get_monitor_callbacks(mock_satel)
|
||||
|
||||
mock_satel.partition_states = {source_state: [1]}
|
||||
|
||||
|
||||
@@ -6,15 +6,14 @@ from unittest.mock import AsyncMock, patch
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.binary_sensor import STATE_OFF, STATE_ON
|
||||
from homeassistant.components.satel_integra.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceRegistry
|
||||
from homeassistant.helpers.entity_registry import EntityRegistry
|
||||
|
||||
from . import setup_integration
|
||||
from . import get_monitor_callbacks, setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
@@ -58,19 +57,36 @@ async def test_binary_sensors(
|
||||
assert device_entry == snapshot(name="device-output")
|
||||
|
||||
|
||||
async def test_binary_sensor_initial_state_on(
|
||||
@pytest.mark.parametrize(
|
||||
("violated_entries", "expected_state"),
|
||||
[
|
||||
({2: 1}, STATE_UNKNOWN),
|
||||
({1: 0}, STATE_OFF),
|
||||
({1: 1}, STATE_ON),
|
||||
],
|
||||
)
|
||||
async def test_binary_sensor_initial_state(
|
||||
hass: HomeAssistant,
|
||||
mock_satel: AsyncMock,
|
||||
mock_config_entry_with_subentries: MockConfigEntry,
|
||||
violated_entries: dict[int, int],
|
||||
expected_state: str,
|
||||
) -> None:
|
||||
"""Test binary sensors have a correct initial state ON after initialization."""
|
||||
mock_satel.violated_zones = [1]
|
||||
mock_satel.violated_outputs = [1]
|
||||
"""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})
|
||||
|
||||
mock_satel.monitor_status = AsyncMock(side_effect=mock_monitor_callback)
|
||||
|
||||
await setup_integration(hass, mock_config_entry_with_subentries)
|
||||
|
||||
assert hass.states.get("binary_sensor.zone").state == STATE_ON
|
||||
assert hass.states.get("binary_sensor.output").state == STATE_ON
|
||||
assert hass.states.get("binary_sensor.zone").state == expected_state
|
||||
assert hass.states.get("binary_sensor.output").state == expected_state
|
||||
|
||||
|
||||
async def test_binary_sensor_callback(
|
||||
@@ -84,19 +100,20 @@ async def test_binary_sensor_callback(
|
||||
assert hass.states.get("binary_sensor.zone").state == STATE_OFF
|
||||
assert hass.states.get("binary_sensor.output").state == STATE_OFF
|
||||
|
||||
monitor_status_call = mock_satel.monitor_status.call_args_list[0][0]
|
||||
output_update_method = monitor_status_call[2]
|
||||
zone_update_method = monitor_status_call[1]
|
||||
|
||||
# Should do nothing, only react to it's own number
|
||||
output_update_method({"outputs": {2: 1}})
|
||||
zone_update_method({"zones": {2: 1}})
|
||||
|
||||
assert hass.states.get("binary_sensor.zone").state == STATE_OFF
|
||||
assert hass.states.get("binary_sensor.output").state == STATE_OFF
|
||||
_, zone_update_method, output_update_method = get_monitor_callbacks(mock_satel)
|
||||
|
||||
output_update_method({"outputs": {1: 1}})
|
||||
zone_update_method({"zones": {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}})
|
||||
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}})
|
||||
assert hass.states.get("binary_sensor.zone").state == STATE_UNKNOWN
|
||||
assert hass.states.get("binary_sensor.output").state == STATE_UNKNOWN
|
||||
|
||||
@@ -6,19 +6,24 @@ from unittest.mock import AsyncMock, patch
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.binary_sensor import STATE_OFF, STATE_ON
|
||||
from homeassistant.components.satel_integra.const import DOMAIN
|
||||
from homeassistant.components.switch import (
|
||||
DOMAIN as SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNKNOWN,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceRegistry
|
||||
from homeassistant.helpers.entity_registry import EntityRegistry
|
||||
|
||||
from . import MOCK_CODE, MOCK_ENTRY_ID, setup_integration
|
||||
from . import MOCK_CODE, MOCK_ENTRY_ID, get_monitor_callbacks, setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
@@ -53,17 +58,34 @@ async def test_switches(
|
||||
assert device_entry == snapshot(name="device")
|
||||
|
||||
|
||||
async def test_switch_initial_state_on(
|
||||
@pytest.mark.parametrize(
|
||||
("violated_outputs", "expected_state"),
|
||||
[
|
||||
({2: 1}, STATE_UNKNOWN),
|
||||
({1: 0}, STATE_OFF),
|
||||
({1: 1}, STATE_ON),
|
||||
],
|
||||
)
|
||||
async def test_switch_initial_state(
|
||||
hass: HomeAssistant,
|
||||
mock_satel: AsyncMock,
|
||||
mock_config_entry_with_subentries: MockConfigEntry,
|
||||
violated_outputs: dict[int, int],
|
||||
expected_state: str,
|
||||
) -> None:
|
||||
"""Test switch has a correct initial state ON after initialization."""
|
||||
mock_satel.violated_outputs = [1]
|
||||
"""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})
|
||||
|
||||
mock_satel.monitor_status = AsyncMock(side_effect=mock_monitor_callback)
|
||||
|
||||
await setup_integration(hass, mock_config_entry_with_subentries)
|
||||
|
||||
assert hass.states.get("switch.switchable_output").state == STATE_ON
|
||||
assert hass.states.get("switch.switchable_output").state == expected_state
|
||||
|
||||
|
||||
async def test_switch_callback(
|
||||
@@ -76,16 +98,18 @@ async def test_switch_callback(
|
||||
|
||||
assert hass.states.get("switch.switchable_output").state == STATE_OFF
|
||||
|
||||
monitor_status_call = mock_satel.monitor_status.call_args_list[0][0]
|
||||
output_update_method = monitor_status_call[2]
|
||||
|
||||
# Should do nothing, only react to it's own number
|
||||
output_update_method({"outputs": {2: 1}})
|
||||
assert hass.states.get("switch.switchable_output").state == STATE_OFF
|
||||
_, _, output_update_method = get_monitor_callbacks(mock_satel)
|
||||
|
||||
output_update_method({"outputs": {1: 1}})
|
||||
assert hass.states.get("switch.switchable_output").state == STATE_ON
|
||||
|
||||
output_update_method({"outputs": {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}})
|
||||
assert hass.states.get("switch.switchable_output").state == STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_switch_change_state(
|
||||
hass: HomeAssistant,
|
||||
|
||||
Reference in New Issue
Block a user