1
0
mirror of https://github.com/home-assistant/core.git synced 2026-06-06 23:46:56 +01:00

Add binary_sensor platform to pooldose integration (#156894)

This commit is contained in:
Lukas
2025-11-21 23:50:30 +01:00
committed by GitHub
parent e0778c8e2e
commit ac7b063c2c
8 changed files with 987 additions and 5 deletions
@@ -17,7 +17,7 @@ from .coordinator import PooldoseConfigEntry, PooldoseCoordinator
_LOGGER = logging.getLogger(__name__)
PLATFORMS: list[Platform] = [Platform.SENSOR]
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
async def async_migrate_entry(hass: HomeAssistant, entry: PooldoseConfigEntry) -> bool:
@@ -0,0 +1,129 @@
"""Binary sensors for the Seko PoolDose integration."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, cast
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import PooldoseConfigEntry
from .entity import PooldoseEntity
_LOGGER = logging.getLogger(__name__)
BINARY_SENSOR_DESCRIPTIONS: tuple[BinarySensorEntityDescription, ...] = (
BinarySensorEntityDescription(
key="pump_alarm",
translation_key="pump_alarm",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
BinarySensorEntityDescription(
key="ph_level_alarm",
translation_key="ph_level_alarm",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
BinarySensorEntityDescription(
key="orp_level_alarm",
translation_key="orp_level_alarm",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
BinarySensorEntityDescription(
key="flow_rate_alarm",
translation_key="flow_rate_alarm",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
BinarySensorEntityDescription(
key="alarm_ofa_ph",
translation_key="alarm_ofa_ph",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
BinarySensorEntityDescription(
key="alarm_ofa_orp",
translation_key="alarm_ofa_orp",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
BinarySensorEntityDescription(
key="alarm_ofa_cl",
translation_key="alarm_ofa_cl",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
BinarySensorEntityDescription(
key="relay_alarm",
translation_key="relay_alarm",
device_class=BinarySensorDeviceClass.POWER,
entity_category=EntityCategory.DIAGNOSTIC,
),
BinarySensorEntityDescription(
key="relay_aux1",
translation_key="relay_aux1",
device_class=BinarySensorDeviceClass.POWER,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
BinarySensorEntityDescription(
key="relay_aux2",
translation_key="relay_aux2",
device_class=BinarySensorDeviceClass.POWER,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
BinarySensorEntityDescription(
key="relay_aux3",
translation_key="relay_aux3",
device_class=BinarySensorDeviceClass.POWER,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: PooldoseConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up PoolDose binary sensor entities from a config entry."""
if TYPE_CHECKING:
assert config_entry.unique_id is not None
coordinator = config_entry.runtime_data
binary_sensor_data = coordinator.data["binary_sensor"]
serial_number = config_entry.unique_id
async_add_entities(
PooldoseBinarySensor(
coordinator,
serial_number,
coordinator.device_info,
description,
"binary_sensor",
)
for description in BINARY_SENSOR_DESCRIPTIONS
if description.key in binary_sensor_data
)
class PooldoseBinarySensor(PooldoseEntity, BinarySensorEntity):
"""Binary sensor entity for the Seko PoolDose Python API."""
@property
def is_on(self) -> bool:
"""Return true if the binary sensor is on."""
data = cast(dict, self.get_data())
return cast(bool, data["value"])
@@ -68,6 +68,11 @@ class PooldoseEntity(CoordinatorEntity[PooldoseCoordinator]):
coordinator.config_entry.data.get(CONF_MAC),
)
@property
def available(self) -> bool:
"""Return if entity is available."""
return super().available and self.get_data() is not None
def get_data(self) -> ValueDict | None:
"""Get data for this entity, only if available."""
platform_data = self.coordinator.data[self.platform_name]
+71 -3
View File
@@ -1,11 +1,79 @@
{
"entity": {
"binary_sensor": {
"alarm_ofa_cl": {
"default": "mdi:clock-alert-outline",
"state": {
"on": "mdi:clock-alert"
}
},
"alarm_ofa_orp": {
"default": "mdi:clock-alert-outline",
"state": {
"on": "mdi:clock-alert"
}
},
"alarm_ofa_ph": {
"default": "mdi:clock-alert-outline",
"state": {
"on": "mdi:clock-alert"
}
},
"flow_rate_alarm": {
"default": "mdi:autorenew",
"state": {
"on": "mdi:autorenew-off"
}
},
"orp_level_alarm": {
"default": "mdi:flask",
"state": {
"on": "mdi:flask-empty"
}
},
"ph_level_alarm": {
"default": "mdi:flask",
"state": {
"on": "mdi:flask-empty"
}
},
"pump_alarm": {
"default": "mdi:pump",
"state": {
"on": "mdi:pump-off"
}
},
"relay_alarm": {
"default": "mdi:electric-switch-closed",
"state": {
"on": "mdi:electric-switch"
}
},
"relay_aux1": {
"default": "mdi:electric-switch-closed",
"state": {
"on": "mdi:electric-switch"
}
},
"relay_aux2": {
"default": "mdi:electric-switch-closed",
"state": {
"on": "mdi:electric-switch"
}
},
"relay_aux3": {
"default": "mdi:electric-switch-closed",
"state": {
"on": "mdi:electric-switch"
}
}
},
"sensor": {
"cl": {
"default": "mdi:pool"
},
"cl_type_dosing": {
"default": "mdi:flask"
"default": "mdi:beaker"
},
"flow_rate": {
"default": "mdi:pipe-valve"
@@ -29,7 +97,7 @@
"default": "mdi:form-select"
},
"orp_type_dosing": {
"default": "mdi:flask"
"default": "mdi:beaker"
},
"peristaltic_cl_dosing": {
"default": "mdi:pump"
@@ -50,7 +118,7 @@
"default": "mdi:form-select"
},
"ph_type_dosing": {
"default": "mdi:flask"
"default": "mdi:beaker"
}
}
}
@@ -33,6 +33,41 @@
}
},
"entity": {
"binary_sensor": {
"alarm_ofa_cl": {
"name": "Chlorine tank level"
},
"alarm_ofa_orp": {
"name": "ORP overfeed"
},
"alarm_ofa_ph": {
"name": "pH overfeed"
},
"flow_rate_alarm": {
"name": "Flow rate"
},
"orp_level_alarm": {
"name": "ORP tank level"
},
"ph_level_alarm": {
"name": "pH tank level"
},
"pump_alarm": {
"name": "Recirculation"
},
"relay_alarm": {
"name": "Alarm relay status"
},
"relay_aux1": {
"name": "Auxiliary relay 1 status"
},
"relay_aux2": {
"name": "Auxiliary relay 2 status"
},
"relay_aux3": {
"name": "Auxiliary relay 3 status"
}
},
"sensor": {
"cl": {
"name": "Chlorine"
@@ -90,7 +90,7 @@
"flow_rate_alarm": {
"value": false
},
"alarm_relay": {
"relay_alarm": {
"value": true
},
"relay_aux1": {
@@ -98,6 +98,18 @@
},
"relay_aux2": {
"value": false
},
"relay_aux3": {
"value": false
},
"alarm_ofa_ph": {
"value": false
},
"alarm_ofa_orp": {
"value": false
},
"alarm_ofa_cl": {
"value": false
}
},
"number": {
@@ -0,0 +1,540 @@
# serializer version: 1
# name: test_all_binary_sensors[binary_sensor.pool_device_alarm_relay_status-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': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.pool_device_alarm_relay_status',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.POWER: 'power'>,
'original_icon': None,
'original_name': 'Alarm relay status',
'platform': 'pooldose',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'relay_alarm',
'unique_id': 'TEST123456789_relay_alarm',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_alarm_relay_status-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'power',
'friendly_name': 'Pool Device Alarm relay status',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.pool_device_alarm_relay_status',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_auxiliary_relay_1_status-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': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.pool_device_auxiliary_relay_1_status',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.POWER: 'power'>,
'original_icon': None,
'original_name': 'Auxiliary relay 1 status',
'platform': 'pooldose',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'relay_aux1',
'unique_id': 'TEST123456789_relay_aux1',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_auxiliary_relay_1_status-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'power',
'friendly_name': 'Pool Device Auxiliary relay 1 status',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.pool_device_auxiliary_relay_1_status',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_auxiliary_relay_2_status-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': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.pool_device_auxiliary_relay_2_status',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.POWER: 'power'>,
'original_icon': None,
'original_name': 'Auxiliary relay 2 status',
'platform': 'pooldose',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'relay_aux2',
'unique_id': 'TEST123456789_relay_aux2',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_auxiliary_relay_2_status-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'power',
'friendly_name': 'Pool Device Auxiliary relay 2 status',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.pool_device_auxiliary_relay_2_status',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_auxiliary_relay_3_status-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': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.pool_device_auxiliary_relay_3_status',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.POWER: 'power'>,
'original_icon': None,
'original_name': 'Auxiliary relay 3 status',
'platform': 'pooldose',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'relay_aux3',
'unique_id': 'TEST123456789_relay_aux3',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_auxiliary_relay_3_status-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'power',
'friendly_name': 'Pool Device Auxiliary relay 3 status',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.pool_device_auxiliary_relay_3_status',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_chlorine_tank_level-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': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.pool_device_chlorine_tank_level',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'Chlorine tank level',
'platform': 'pooldose',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'alarm_ofa_cl',
'unique_id': 'TEST123456789_alarm_ofa_cl',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_chlorine_tank_level-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'problem',
'friendly_name': 'Pool Device Chlorine tank level',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.pool_device_chlorine_tank_level',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_flow_rate-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': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.pool_device_flow_rate',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'Flow rate',
'platform': 'pooldose',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'flow_rate_alarm',
'unique_id': 'TEST123456789_flow_rate_alarm',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_flow_rate-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'problem',
'friendly_name': 'Pool Device Flow rate',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.pool_device_flow_rate',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_orp_overfeed-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': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.pool_device_orp_overfeed',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'ORP overfeed',
'platform': 'pooldose',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'alarm_ofa_orp',
'unique_id': 'TEST123456789_alarm_ofa_orp',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_orp_overfeed-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'problem',
'friendly_name': 'Pool Device ORP overfeed',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.pool_device_orp_overfeed',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_orp_tank_level-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': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.pool_device_orp_tank_level',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'ORP tank level',
'platform': 'pooldose',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'orp_level_alarm',
'unique_id': 'TEST123456789_orp_level_alarm',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_orp_tank_level-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'problem',
'friendly_name': 'Pool Device ORP tank level',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.pool_device_orp_tank_level',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_ph_overfeed-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': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.pool_device_ph_overfeed',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'pH overfeed',
'platform': 'pooldose',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'alarm_ofa_ph',
'unique_id': 'TEST123456789_alarm_ofa_ph',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_ph_overfeed-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'problem',
'friendly_name': 'Pool Device pH overfeed',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.pool_device_ph_overfeed',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_ph_tank_level-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': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.pool_device_ph_tank_level',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'pH tank level',
'platform': 'pooldose',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'ph_level_alarm',
'unique_id': 'TEST123456789_ph_level_alarm',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_ph_tank_level-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'problem',
'friendly_name': 'Pool Device pH tank level',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.pool_device_ph_tank_level',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_recirculation-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': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.pool_device_recirculation',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
'original_icon': None,
'original_name': 'Recirculation',
'platform': 'pooldose',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'pump_alarm',
'unique_id': 'TEST123456789_pump_alarm',
'unit_of_measurement': None,
})
# ---
# name: test_all_binary_sensors[binary_sensor.pool_device_recirculation-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'problem',
'friendly_name': 'Pool Device Recirculation',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.pool_device_recirculation',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
@@ -0,0 +1,193 @@
"""Test the PoolDose binary sensor platform."""
from datetime import timedelta
from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory
from pooldose.request_status import RequestStatus
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_all_binary_sensors(
hass: HomeAssistant,
mock_pooldose_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test the Pooldose binary sensors."""
with patch("homeassistant.components.pooldose.PLATFORMS", [Platform.BINARY_SENSOR]):
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize("exception", [TimeoutError, ConnectionError, OSError])
async def test_exception_raising(
hass: HomeAssistant,
mock_pooldose_client: AsyncMock,
mock_config_entry: MockConfigEntry,
exception: Exception,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test the Pooldose binary sensors."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("binary_sensor.pool_device_recirculation").state == STATE_ON
mock_pooldose_client.instant_values_structured.side_effect = exception
freezer.tick(timedelta(minutes=10))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
hass.states.get("binary_sensor.pool_device_recirculation").state
== STATE_UNAVAILABLE
)
async def test_no_data(
hass: HomeAssistant,
mock_pooldose_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test the Pooldose binary sensors."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("binary_sensor.pool_device_recirculation").state == STATE_ON
mock_pooldose_client.instant_values_structured.return_value = (
RequestStatus.SUCCESS,
None,
)
freezer.tick(timedelta(minutes=10))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
hass.states.get("binary_sensor.pool_device_recirculation").state
== STATE_UNAVAILABLE
)
async def test_binary_sensor_entity_unavailable_no_coordinator_data(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_pooldose_client: AsyncMock,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test binary sensor entity becomes unavailable when coordinator has no data."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
# Verify initial working state
pump_state = hass.states.get("binary_sensor.pool_device_recirculation")
assert pump_state.state == STATE_ON
# Set coordinator data to None by making API return empty
mock_pooldose_client.instant_values_structured.return_value = (
RequestStatus.HOST_UNREACHABLE,
None,
)
freezer.tick(timedelta(minutes=10))
async_fire_time_changed(hass)
await hass.async_block_till_done()
# Check binary sensor becomes unavailable
pump_state = hass.states.get("binary_sensor.pool_device_recirculation")
assert pump_state.state == STATE_UNAVAILABLE
async def test_binary_sensor_state_changes(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_pooldose_client: AsyncMock,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test binary sensor state changes."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
# Verify initial states
pump_state = hass.states.get("binary_sensor.pool_device_recirculation")
assert pump_state.state == STATE_ON
ph_level_state = hass.states.get("binary_sensor.pool_device_ph_tank_level")
assert ph_level_state.state == STATE_OFF
# Update data with changed values
current_data = mock_pooldose_client.instant_values_structured.return_value[1]
updated_data = current_data.copy()
updated_data["binary_sensor"]["pump_alarm"]["value"] = False
updated_data["binary_sensor"]["ph_level_alarm"]["value"] = True
mock_pooldose_client.instant_values_structured.return_value = (
RequestStatus.SUCCESS,
updated_data,
)
freezer.tick(timedelta(minutes=10))
async_fire_time_changed(hass)
await hass.async_block_till_done()
# Check states have changed
pump_state = hass.states.get("binary_sensor.pool_device_recirculation")
assert pump_state.state == STATE_OFF
ph_level_state = hass.states.get("binary_sensor.pool_device_ph_tank_level")
assert ph_level_state.state == STATE_ON
async def test_binary_sensor_missing_from_data(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_pooldose_client: AsyncMock,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test binary sensor becomes unavailable when missing from coordinator data."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
# Verify initial working state
flow_alarm_state = hass.states.get("binary_sensor.pool_device_flow_rate")
assert flow_alarm_state.state == STATE_OFF
# Update data with missing sensor
current_data = mock_pooldose_client.instant_values_structured.return_value[1]
updated_data = current_data.copy()
del updated_data["binary_sensor"]["flow_rate_alarm"]
mock_pooldose_client.instant_values_structured.return_value = (
RequestStatus.SUCCESS,
updated_data,
)
freezer.tick(timedelta(minutes=10))
async_fire_time_changed(hass)
await hass.async_block_till_done()
# Check sensor becomes unavailable when not in coordinator data
flow_alarm_state = hass.states.get("binary_sensor.pool_device_flow_rate")
assert flow_alarm_state.state == STATE_UNAVAILABLE