Add switch platform to bosch alarm (#142157)

* add switch platform to bosch alarm

* fix tests

* one device per output

* add switch for door

* add switch entities for door

* fix switch devices

* apply changes from review

* update identifiers

* add missing entity

* use base entity for switch

* rename var

* fix icons

* give user a nice error if they try to lock or secure a door that is in the process of being cycled

* fix test

* Update homeassistant/components/bosch_alarm/switch.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Update homeassistant/components/bosch_alarm/switch.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* use service constants

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Sanjay Govind 2025-05-10 04:17:26 +12:00 committed by GitHub
parent a7afeb078c
commit c18b6d736a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 958 additions and 2 deletions

View File

@ -14,7 +14,11 @@ from homeassistant.helpers import device_registry as dr
from .const import CONF_INSTALLER_CODE, CONF_USER_CODE, DOMAIN
PLATFORMS: list[Platform] = [Platform.ALARM_CONTROL_PANEL, Platform.SENSOR]
PLATFORMS: list[Platform] = [
Platform.ALARM_CONTROL_PANEL,
Platform.SENSOR,
Platform.SWITCH,
]
type BoschAlarmConfigEntry = ConfigEntry[Panel]

View File

@ -86,3 +86,57 @@ class BoschAlarmAreaEntity(BoschAlarmEntity):
self._area.ready_observer.detach(self.schedule_update_ha_state)
if self._observe_status:
self._area.status_observer.detach(self.schedule_update_ha_state)
class BoschAlarmDoorEntity(BoschAlarmEntity):
"""A base entity for area related entities within a bosch alarm panel."""
def __init__(self, panel: Panel, door_id: int, unique_id: str) -> None:
"""Set up a area related entity for a bosch alarm panel."""
super().__init__(panel, unique_id)
self._door_id = door_id
self._door = panel.doors[door_id]
self._door_unique_id = f"{unique_id}_door_{door_id}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._door_unique_id)},
name=self._door.name,
manufacturer="Bosch Security Systems",
via_device=(DOMAIN, unique_id),
)
async def async_added_to_hass(self) -> None:
"""Observe state changes."""
await super().async_added_to_hass()
self._door.status_observer.attach(self.schedule_update_ha_state)
async def async_will_remove_from_hass(self) -> None:
"""Stop observing state changes."""
await super().async_added_to_hass()
self._door.status_observer.detach(self.schedule_update_ha_state)
class BoschAlarmOutputEntity(BoschAlarmEntity):
"""A base entity for area related entities within a bosch alarm panel."""
def __init__(self, panel: Panel, output_id: int, unique_id: str) -> None:
"""Set up a output related entity for a bosch alarm panel."""
super().__init__(panel, unique_id)
self._output_id = output_id
self._output = panel.outputs[output_id]
self._output_unique_id = f"{unique_id}_output_{output_id}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._output_unique_id)},
name=self._output.name,
manufacturer="Bosch Security Systems",
via_device=(DOMAIN, unique_id),
)
async def async_added_to_hass(self) -> None:
"""Observe state changes."""
await super().async_added_to_hass()
self._output.status_observer.attach(self.schedule_update_ha_state)
async def async_will_remove_from_hass(self) -> None:
"""Stop observing state changes."""
await super().async_added_to_hass()
self._output.status_observer.detach(self.schedule_update_ha_state)

View File

@ -2,7 +2,27 @@
"entity": {
"sensor": {
"faulting_points": {
"default": "mdi:alert-circle-outline"
"default": "mdi:alert-circle"
}
},
"switch": {
"locked": {
"default": "mdi:lock",
"state": {
"off": "mdi:lock-open"
}
},
"secured": {
"default": "mdi:lock",
"state": {
"off": "mdi:lock-open"
}
},
"cycling": {
"default": "mdi:lock",
"state": {
"on": "mdi:lock-open"
}
}
}
}

View File

@ -54,9 +54,23 @@
},
"authentication_failed": {
"message": "Incorrect credentials for panel."
},
"incorrect_door_state": {
"message": "Door cannot be manipulated while it is being cycled."
}
},
"entity": {
"switch": {
"secured": {
"name": "Secured"
},
"cycling": {
"name": "Cycling"
},
"locked": {
"name": "Locked"
}
},
"sensor": {
"faulting_points": {
"name": "Faulting points",

View File

@ -0,0 +1,150 @@
"""Support for Bosch Alarm Panel outputs and doors as switches."""
from __future__ import annotations
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any
from bosch_alarm_mode2 import Panel
from bosch_alarm_mode2.panel import Door
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import BoschAlarmConfigEntry
from .const import DOMAIN
from .entity import BoschAlarmDoorEntity, BoschAlarmOutputEntity
@dataclass(kw_only=True, frozen=True)
class BoschAlarmSwitchEntityDescription(SwitchEntityDescription):
"""Describes Bosch Alarm door entity."""
value_fn: Callable[[Door], bool]
on_fn: Callable[[Panel, int], Coroutine[Any, Any, None]]
off_fn: Callable[[Panel, int], Coroutine[Any, Any, None]]
DOOR_SWITCH_TYPES: list[BoschAlarmSwitchEntityDescription] = [
BoschAlarmSwitchEntityDescription(
key="locked",
translation_key="locked",
value_fn=lambda door: door.is_locked(),
on_fn=lambda panel, door_id: panel.door_relock(door_id),
off_fn=lambda panel, door_id: panel.door_unlock(door_id),
),
BoschAlarmSwitchEntityDescription(
key="secured",
translation_key="secured",
value_fn=lambda door: door.is_secured(),
on_fn=lambda panel, door_id: panel.door_secure(door_id),
off_fn=lambda panel, door_id: panel.door_unsecure(door_id),
),
BoschAlarmSwitchEntityDescription(
key="cycling",
translation_key="cycling",
value_fn=lambda door: door.is_cycling(),
on_fn=lambda panel, door_id: panel.door_cycle(door_id),
off_fn=lambda panel, door_id: panel.door_relock(door_id),
),
]
async def async_setup_entry(
hass: HomeAssistant,
config_entry: BoschAlarmConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up switch entities for outputs."""
panel = config_entry.runtime_data
entities: list[SwitchEntity] = [
PanelOutputEntity(
panel, output_id, config_entry.unique_id or config_entry.entry_id
)
for output_id in panel.outputs
]
entities.extend(
PanelDoorEntity(
panel,
door_id,
config_entry.unique_id or config_entry.entry_id,
entity_description,
)
for door_id in panel.doors
for entity_description in DOOR_SWITCH_TYPES
)
async_add_entities(entities)
PARALLEL_UPDATES = 0
class PanelDoorEntity(BoschAlarmDoorEntity, SwitchEntity):
"""A switch entity for a door on a bosch alarm panel."""
entity_description: BoschAlarmSwitchEntityDescription
def __init__(
self,
panel: Panel,
door_id: int,
unique_id: str,
entity_description: BoschAlarmSwitchEntityDescription,
) -> None:
"""Set up a switch entity for a door on a bosch alarm panel."""
super().__init__(panel, door_id, unique_id)
self.entity_description = entity_description
self._attr_unique_id = f"{self._door_unique_id}_{entity_description.key}"
@property
def is_on(self) -> bool:
"""Return the value function."""
return self.entity_description.value_fn(self._door)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Run the on function."""
# If the door is currently cycling, we can't send it any other commands until it is done
if self._door.is_cycling():
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="incorrect_door_state"
)
await self.entity_description.on_fn(self.panel, self._door_id)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Run the off function."""
# If the door is currently cycling, we can't send it any other commands until it is done
if self._door.is_cycling():
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="incorrect_door_state"
)
await self.entity_description.off_fn(self.panel, self._door_id)
class PanelOutputEntity(BoschAlarmOutputEntity, SwitchEntity):
"""An output entity for a bosch alarm panel."""
_attr_name = None
def __init__(self, panel: Panel, output_id: int, unique_id: str) -> None:
"""Set up an output entity for a bosch alarm panel."""
super().__init__(panel, output_id, unique_id)
self._attr_unique_id = self._output_unique_id
@property
def is_on(self) -> bool:
"""Check if this entity is on."""
return self._output.is_active()
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on this output."""
await self.panel.set_output_active(self._output_id)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off this output."""
await self.panel.set_output_inactive(self._output_id)

View File

@ -118,6 +118,8 @@ def door() -> Generator[Door]:
mock.name = "Main Door"
mock.status_observer = AsyncMock(spec=Observable)
mock.is_open.return_value = False
mock.is_cycling.return_value = False
mock.is_secured.return_value = False
mock.is_locked.return_value = True
return mock

View File

@ -0,0 +1,565 @@
# serializer version: 1
# name: test_switch[amax_3000][switch.main_door_cycling-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.main_door_cycling',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Cycling',
'platform': 'bosch_alarm',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'cycling',
'unique_id': '01JQ917ACKQ33HHM7YCFXYZX51_door_1_cycling',
'unit_of_measurement': None,
})
# ---
# name: test_switch[amax_3000][switch.main_door_cycling-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Main Door Cycling',
}),
'context': <ANY>,
'entity_id': 'switch.main_door_cycling',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch[amax_3000][switch.main_door_locked-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.main_door_locked',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Locked',
'platform': 'bosch_alarm',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'locked',
'unique_id': '01JQ917ACKQ33HHM7YCFXYZX51_door_1_locked',
'unit_of_measurement': None,
})
# ---
# name: test_switch[amax_3000][switch.main_door_locked-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Main Door Locked',
}),
'context': <ANY>,
'entity_id': 'switch.main_door_locked',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch[amax_3000][switch.main_door_secured-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.main_door_secured',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Secured',
'platform': 'bosch_alarm',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'secured',
'unique_id': '01JQ917ACKQ33HHM7YCFXYZX51_door_1_secured',
'unit_of_measurement': None,
})
# ---
# name: test_switch[amax_3000][switch.main_door_secured-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Main Door Secured',
}),
'context': <ANY>,
'entity_id': 'switch.main_door_secured',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch[amax_3000][switch.output_a-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.output_a',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'bosch_alarm',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '01JQ917ACKQ33HHM7YCFXYZX51_output_1',
'unit_of_measurement': None,
})
# ---
# name: test_switch[amax_3000][switch.output_a-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Output A',
}),
'context': <ANY>,
'entity_id': 'switch.output_a',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch[b5512][switch.main_door_cycling-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.main_door_cycling',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Cycling',
'platform': 'bosch_alarm',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'cycling',
'unique_id': '01JQ917ACKQ33HHM7YCFXYZX51_door_1_cycling',
'unit_of_measurement': None,
})
# ---
# name: test_switch[b5512][switch.main_door_cycling-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Main Door Cycling',
}),
'context': <ANY>,
'entity_id': 'switch.main_door_cycling',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch[b5512][switch.main_door_locked-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.main_door_locked',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Locked',
'platform': 'bosch_alarm',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'locked',
'unique_id': '01JQ917ACKQ33HHM7YCFXYZX51_door_1_locked',
'unit_of_measurement': None,
})
# ---
# name: test_switch[b5512][switch.main_door_locked-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Main Door Locked',
}),
'context': <ANY>,
'entity_id': 'switch.main_door_locked',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch[b5512][switch.main_door_secured-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.main_door_secured',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Secured',
'platform': 'bosch_alarm',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'secured',
'unique_id': '01JQ917ACKQ33HHM7YCFXYZX51_door_1_secured',
'unit_of_measurement': None,
})
# ---
# name: test_switch[b5512][switch.main_door_secured-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Main Door Secured',
}),
'context': <ANY>,
'entity_id': 'switch.main_door_secured',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch[b5512][switch.output_a-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.output_a',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'bosch_alarm',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '01JQ917ACKQ33HHM7YCFXYZX51_output_1',
'unit_of_measurement': None,
})
# ---
# name: test_switch[b5512][switch.output_a-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Output A',
}),
'context': <ANY>,
'entity_id': 'switch.output_a',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch[solution_3000][switch.main_door_cycling-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.main_door_cycling',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Cycling',
'platform': 'bosch_alarm',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'cycling',
'unique_id': '1234567890_door_1_cycling',
'unit_of_measurement': None,
})
# ---
# name: test_switch[solution_3000][switch.main_door_cycling-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Main Door Cycling',
}),
'context': <ANY>,
'entity_id': 'switch.main_door_cycling',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch[solution_3000][switch.main_door_locked-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.main_door_locked',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Locked',
'platform': 'bosch_alarm',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'locked',
'unique_id': '1234567890_door_1_locked',
'unit_of_measurement': None,
})
# ---
# name: test_switch[solution_3000][switch.main_door_locked-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Main Door Locked',
}),
'context': <ANY>,
'entity_id': 'switch.main_door_locked',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch[solution_3000][switch.main_door_secured-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.main_door_secured',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Secured',
'platform': 'bosch_alarm',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'secured',
'unique_id': '1234567890_door_1_secured',
'unit_of_measurement': None,
})
# ---
# name: test_switch[solution_3000][switch.main_door_secured-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Main Door Secured',
}),
'context': <ANY>,
'entity_id': 'switch.main_door_secured',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch[solution_3000][switch.output_a-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': None,
'entity_id': 'switch.output_a',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'bosch_alarm',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '1234567890_output_1',
'unit_of_measurement': None,
})
# ---
# name: test_switch[solution_3000][switch.output_a-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Output A',
}),
'context': <ANY>,
'entity_id': 'switch.output_a',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---

View File

@ -0,0 +1,147 @@
"""Tests for Bosch Alarm component."""
from collections.abc import AsyncGenerator
from unittest.mock import AsyncMock, patch
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_OFF,
STATE_ON,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import call_observable, setup_integration
from tests.common import MockConfigEntry, snapshot_platform
@pytest.fixture(autouse=True)
async def platforms() -> AsyncGenerator[None]:
"""Return the platforms to be loaded for this test."""
with patch("homeassistant.components.bosch_alarm.PLATFORMS", [Platform.SWITCH]):
yield
async def test_update_switch_device(
hass: HomeAssistant,
mock_panel: AsyncMock,
output: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test that output state changes after turning on the output."""
await setup_integration(hass, mock_config_entry)
entity_id = "switch.output_a"
assert hass.states.get(entity_id).state == STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
output.is_active.return_value = True
await call_observable(hass, output.status_observer)
assert hass.states.get(entity_id).state == STATE_ON
async def test_unlock_door(
hass: HomeAssistant,
mock_panel: AsyncMock,
door: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test that door state changes after unlocking the door."""
await setup_integration(hass, mock_config_entry)
entity_id = "switch.main_door_locked"
assert hass.states.get(entity_id).state == STATE_ON
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
door.is_locked.return_value = False
door.is_open.return_value = True
await call_observable(hass, door.status_observer)
assert hass.states.get(entity_id).state == STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
door.is_locked.return_value = True
door.is_open.return_value = False
await call_observable(hass, door.status_observer)
assert hass.states.get(entity_id).state == STATE_ON
async def test_secure_door(
hass: HomeAssistant,
mock_panel: AsyncMock,
door: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test that door state changes after unlocking the door."""
await setup_integration(hass, mock_config_entry)
entity_id = "switch.main_door_secured"
assert hass.states.get(entity_id).state == STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
door.is_secured.return_value = True
await call_observable(hass, door.status_observer)
assert hass.states.get(entity_id).state == STATE_ON
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
door.is_secured.return_value = False
await call_observable(hass, door.status_observer)
assert hass.states.get(entity_id).state == STATE_OFF
async def test_cycle_door(
hass: HomeAssistant,
mock_panel: AsyncMock,
door: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test that door state changes after unlocking the door."""
await setup_integration(hass, mock_config_entry)
entity_id = "switch.main_door_cycling"
assert hass.states.get(entity_id).state == STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
door.is_cycling.return_value = True
await call_observable(hass, door.status_observer)
assert hass.states.get(entity_id).state == STATE_ON
async def test_switch(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
mock_panel: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the switch state."""
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)