mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 07:37:34 +00:00
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:
parent
a7afeb078c
commit
c18b6d736a
@ -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]
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
150
homeassistant/components/bosch_alarm/switch.py
Normal file
150
homeassistant/components/bosch_alarm/switch.py
Normal 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)
|
@ -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
|
||||
|
||||
|
565
tests/components/bosch_alarm/snapshots/test_switch.ambr
Normal file
565
tests/components/bosch_alarm/snapshots/test_switch.ambr
Normal 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',
|
||||
})
|
||||
# ---
|
147
tests/components/bosch_alarm/test_switch.py
Normal file
147
tests/components/bosch_alarm/test_switch.py
Normal 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)
|
Loading…
x
Reference in New Issue
Block a user