diff --git a/homeassistant/components/bosch_alarm/icons.json b/homeassistant/components/bosch_alarm/icons.json index 43f6f33e066..b13822fa711 100644 --- a/homeassistant/components/bosch_alarm/icons.json +++ b/homeassistant/components/bosch_alarm/icons.json @@ -1,6 +1,15 @@ { "entity": { "sensor": { + "alarms_gas": { + "default": "mdi:alert-circle" + }, + "alarms_fire": { + "default": "mdi:alert-circle" + }, + "alarms_burglary": { + "default": "mdi:alert-circle" + }, "faulting_points": { "default": "mdi:alert-circle" } diff --git a/homeassistant/components/bosch_alarm/sensor.py b/homeassistant/components/bosch_alarm/sensor.py index 3d61c72a883..479aaa03049 100644 --- a/homeassistant/components/bosch_alarm/sensor.py +++ b/homeassistant/components/bosch_alarm/sensor.py @@ -6,6 +6,7 @@ from collections.abc import Callable from dataclasses import dataclass from bosch_alarm_mode2 import Panel +from bosch_alarm_mode2.const import ALARM_MEMORY_PRIORITIES from bosch_alarm_mode2.panel import Area from homeassistant.components.sensor import SensorEntity, SensorEntityDescription @@ -15,18 +16,53 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from . import BoschAlarmConfigEntry from .entity import BoschAlarmAreaEntity +ALARM_TYPES = { + "burglary": { + ALARM_MEMORY_PRIORITIES.BURGLARY_SUPERVISORY: "supervisory", + ALARM_MEMORY_PRIORITIES.BURGLARY_TROUBLE: "trouble", + ALARM_MEMORY_PRIORITIES.BURGLARY_ALARM: "alarm", + }, + "gas": { + ALARM_MEMORY_PRIORITIES.GAS_SUPERVISORY: "supervisory", + ALARM_MEMORY_PRIORITIES.GAS_TROUBLE: "trouble", + ALARM_MEMORY_PRIORITIES.GAS_ALARM: "alarm", + }, + "fire": { + ALARM_MEMORY_PRIORITIES.FIRE_SUPERVISORY: "supervisory", + ALARM_MEMORY_PRIORITIES.FIRE_TROUBLE: "trouble", + ALARM_MEMORY_PRIORITIES.FIRE_ALARM: "alarm", + }, +} + @dataclass(kw_only=True, frozen=True) class BoschAlarmSensorEntityDescription(SensorEntityDescription): """Describes Bosch Alarm sensor entity.""" - value_fn: Callable[[Area], int] + value_fn: Callable[[Area], str | int] observe_alarms: bool = False observe_ready: bool = False observe_status: bool = False +def priority_value_fn(priority_info: dict[int, str]) -> Callable[[Area], str]: + """Build a value_fn for a given priority type.""" + return lambda area: next( + (key for priority, key in priority_info.items() if priority in area.alarms_ids), + "no_issues", + ) + + SENSOR_TYPES: list[BoschAlarmSensorEntityDescription] = [ + *[ + BoschAlarmSensorEntityDescription( + key=f"alarms_{key}", + translation_key=f"alarms_{key}", + value_fn=priority_value_fn(priority_type), + observe_alarms=True, + ) + for key, priority_type in ALARM_TYPES.items() + ], BoschAlarmSensorEntityDescription( key="faulting_points", translation_key="faulting_points", @@ -81,6 +117,6 @@ class BoschAreaSensor(BoschAlarmAreaEntity, SensorEntity): self._attr_unique_id = f"{self._area_unique_id}_{entity_description.key}" @property - def native_value(self) -> int: + def native_value(self) -> str | int: """Return the state of the sensor.""" return self.entity_description.value_fn(self._area) diff --git a/homeassistant/components/bosch_alarm/strings.json b/homeassistant/components/bosch_alarm/strings.json index 3a6604c2634..b9176c41a08 100644 --- a/homeassistant/components/bosch_alarm/strings.json +++ b/homeassistant/components/bosch_alarm/strings.json @@ -118,6 +118,33 @@ } }, "sensor": { + "alarms_gas": { + "name": "Gas alarm issues", + "state": { + "supervisory": "Supervisory", + "trouble": "Trouble", + "alarm": "Alarm", + "no_issues": "No issues" + } + }, + "alarms_fire": { + "name": "Fire alarm issues", + "state": { + "supervisory": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::supervisory%]", + "trouble": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::trouble%]", + "alarm": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::alarm%]", + "no_issues": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::no_issues%]" + } + }, + "alarms_burglary": { + "name": "Burglary alarm issues", + "state": { + "supervisory": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::supervisory%]", + "trouble": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::trouble%]", + "alarm": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::alarm%]", + "no_issues": "[%key:component::bosch_alarm::entity::sensor::alarms_gas::state::no_issues%]" + } + }, "faulting_points": { "name": "Faulting points", "unit_of_measurement": "points" diff --git a/tests/components/bosch_alarm/snapshots/test_sensor.ambr b/tests/components/bosch_alarm/snapshots/test_sensor.ambr index def2c503a6a..64a02e730f6 100644 --- a/tests/components/bosch_alarm/snapshots/test_sensor.ambr +++ b/tests/components/bosch_alarm/snapshots/test_sensor.ambr @@ -1,4 +1,51 @@ # serializer version: 1 +# name: test_sensor[amax_3000][sensor.area1_burglary_alarm_issues-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.area1_burglary_alarm_issues', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Burglary alarm issues', + 'platform': 'bosch_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'alarms_burglary', + 'unique_id': '01JQ917ACKQ33HHM7YCFXYZX51_area_1_alarms_burglary', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[amax_3000][sensor.area1_burglary_alarm_issues-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Area1 Burglary alarm issues', + }), + 'context': , + 'entity_id': 'sensor.area1_burglary_alarm_issues', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'no_issues', + }) +# --- # name: test_sensor[amax_3000][sensor.area1_faulting_points-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -47,6 +94,147 @@ 'state': '0', }) # --- +# name: test_sensor[amax_3000][sensor.area1_fire_alarm_issues-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.area1_fire_alarm_issues', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Fire alarm issues', + 'platform': 'bosch_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'alarms_fire', + 'unique_id': '01JQ917ACKQ33HHM7YCFXYZX51_area_1_alarms_fire', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[amax_3000][sensor.area1_fire_alarm_issues-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Area1 Fire alarm issues', + }), + 'context': , + 'entity_id': 'sensor.area1_fire_alarm_issues', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'no_issues', + }) +# --- +# name: test_sensor[amax_3000][sensor.area1_gas_alarm_issues-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.area1_gas_alarm_issues', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Gas alarm issues', + 'platform': 'bosch_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'alarms_gas', + 'unique_id': '01JQ917ACKQ33HHM7YCFXYZX51_area_1_alarms_gas', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[amax_3000][sensor.area1_gas_alarm_issues-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Area1 Gas alarm issues', + }), + 'context': , + 'entity_id': 'sensor.area1_gas_alarm_issues', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'no_issues', + }) +# --- +# name: test_sensor[b5512][sensor.area1_burglary_alarm_issues-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.area1_burglary_alarm_issues', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Burglary alarm issues', + 'platform': 'bosch_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'alarms_burglary', + 'unique_id': '01JQ917ACKQ33HHM7YCFXYZX51_area_1_alarms_burglary', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[b5512][sensor.area1_burglary_alarm_issues-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Area1 Burglary alarm issues', + }), + 'context': , + 'entity_id': 'sensor.area1_burglary_alarm_issues', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'no_issues', + }) +# --- # name: test_sensor[b5512][sensor.area1_faulting_points-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -95,6 +283,147 @@ 'state': '0', }) # --- +# name: test_sensor[b5512][sensor.area1_fire_alarm_issues-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.area1_fire_alarm_issues', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Fire alarm issues', + 'platform': 'bosch_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'alarms_fire', + 'unique_id': '01JQ917ACKQ33HHM7YCFXYZX51_area_1_alarms_fire', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[b5512][sensor.area1_fire_alarm_issues-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Area1 Fire alarm issues', + }), + 'context': , + 'entity_id': 'sensor.area1_fire_alarm_issues', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'no_issues', + }) +# --- +# name: test_sensor[b5512][sensor.area1_gas_alarm_issues-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.area1_gas_alarm_issues', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Gas alarm issues', + 'platform': 'bosch_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'alarms_gas', + 'unique_id': '01JQ917ACKQ33HHM7YCFXYZX51_area_1_alarms_gas', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[b5512][sensor.area1_gas_alarm_issues-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Area1 Gas alarm issues', + }), + 'context': , + 'entity_id': 'sensor.area1_gas_alarm_issues', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'no_issues', + }) +# --- +# name: test_sensor[solution_3000][sensor.area1_burglary_alarm_issues-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.area1_burglary_alarm_issues', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Burglary alarm issues', + 'platform': 'bosch_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'alarms_burglary', + 'unique_id': '1234567890_area_1_alarms_burglary', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[solution_3000][sensor.area1_burglary_alarm_issues-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Area1 Burglary alarm issues', + }), + 'context': , + 'entity_id': 'sensor.area1_burglary_alarm_issues', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'no_issues', + }) +# --- # name: test_sensor[solution_3000][sensor.area1_faulting_points-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -143,3 +472,97 @@ 'state': '0', }) # --- +# name: test_sensor[solution_3000][sensor.area1_fire_alarm_issues-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.area1_fire_alarm_issues', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Fire alarm issues', + 'platform': 'bosch_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'alarms_fire', + 'unique_id': '1234567890_area_1_alarms_fire', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[solution_3000][sensor.area1_fire_alarm_issues-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Area1 Fire alarm issues', + }), + 'context': , + 'entity_id': 'sensor.area1_fire_alarm_issues', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'no_issues', + }) +# --- +# name: test_sensor[solution_3000][sensor.area1_gas_alarm_issues-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.area1_gas_alarm_issues', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Gas alarm issues', + 'platform': 'bosch_alarm', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'alarms_gas', + 'unique_id': '1234567890_area_1_alarms_gas', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[solution_3000][sensor.area1_gas_alarm_issues-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Area1 Gas alarm issues', + }), + 'context': , + 'entity_id': 'sensor.area1_gas_alarm_issues', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'no_issues', + }) +# --- diff --git a/tests/components/bosch_alarm/test_sensor.py b/tests/components/bosch_alarm/test_sensor.py index 02153a9656e..c986fdab733 100644 --- a/tests/components/bosch_alarm/test_sensor.py +++ b/tests/components/bosch_alarm/test_sensor.py @@ -3,6 +3,7 @@ from collections.abc import AsyncGenerator from unittest.mock import AsyncMock, patch +from bosch_alarm_mode2.const import ALARM_MEMORY_PRIORITIES import pytest from syrupy.assertion import SnapshotAssertion @@ -48,5 +49,21 @@ async def test_faulting_points( area.faults = 1 await call_observable(hass, area.ready_observer) - assert hass.states.get(entity_id).state == "1" + + +async def test_alarm_faults( + hass: HomeAssistant, + mock_panel: AsyncMock, + area: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test that alarm state changes after arming the panel.""" + await setup_integration(hass, mock_config_entry) + entity_id = "sensor.area1_fire_alarm_issues" + assert hass.states.get(entity_id).state == "no_issues" + + area.alarms_ids = [ALARM_MEMORY_PRIORITIES.FIRE_TROUBLE] + await call_observable(hass, area.alarm_observer) + + assert hass.states.get(entity_id).state == "trouble"