diff --git a/homeassistant/components/neopool/binary_sensor.py b/homeassistant/components/neopool/binary_sensor.py new file mode 100644 index 000000000000..b72e38f2730a --- /dev/null +++ b/homeassistant/components/neopool/binary_sensor.py @@ -0,0 +1,390 @@ +"""Binary sensor platform for the NeoPool integration.""" + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any, override + +from neopool_modbus.capabilities import ( + has_heating_relay, + is_chlorine_module_present, + is_conductivity_module_present, + is_hydrolysis_present, + is_ionization_present, + is_ph_module_present, + is_redox_module_present, +) +from neopool_modbus.registers import is_valid_relay_gpio + +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 .const import ( + CONF_USE_AUX1, + CONF_USE_AUX2, + CONF_USE_AUX3, + CONF_USE_AUX4, + CONF_USE_COVER_SENSOR, + CONF_USE_LIGHT, +) +from .coordinator import NeoPoolConfigEntry, NeoPoolCoordinator +from .entity import NeoPoolEntity + +PARALLEL_UPDATES = 0 + +type _SupportedFn = Callable[[dict[str, Any]], bool] + + +@dataclass(frozen=True, kw_only=True) +class NeoPoolBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes a NeoPool binary sensor entity.""" + + supported_fn: _SupportedFn | None = None + value_fn: Callable[[dict[str, Any], HomeAssistant], bool | None] | None = None + + +def _gpio_ok(gpio_key: str) -> _SupportedFn: + """Return a supported_fn that checks a relay GPIO key is valid.""" + return lambda data: gpio_key not in data or is_valid_relay_gpio(data[gpio_key] or 0) + + +def _pool_cover_open(data: dict[str, Any], hass: HomeAssistant) -> bool | None: + """Invert the raw cover state for the OPENING device class. + + The cover bit is only valid while filtration runs; otherwise report unknown. + """ + if data.get("Filtration Pump") is not True: + return None + value = data.get("Pool Cover") + if value is None: + return None + return not bool(value) + + +BINARY_SENSOR_DESCRIPTIONS: dict[str, NeoPoolBinarySensorEntityDescription] = { + "pH Acid Pump": NeoPoolBinarySensorEntityDescription( + key="pH Acid Pump", + translation_key="ph_acid_pump", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + supported_fn=_gpio_ok("MBF_PAR_PH_ACID_RELAY_GPIO"), + ), + "Filtration Pump": NeoPoolBinarySensorEntityDescription( + key="Filtration Pump", + translation_key="filtration_pump", + device_class=BinarySensorDeviceClass.RUNNING, + supported_fn=_gpio_ok("MBF_PAR_FILT_GPIO"), + ), + "Pool Light": NeoPoolBinarySensorEntityDescription( + key="Pool Light", + translation_key="pool_light", + device_class=BinarySensorDeviceClass.LIGHT, + supported_fn=_gpio_ok("MBF_PAR_LIGHTING_GPIO"), + ), + "AUX1": NeoPoolBinarySensorEntityDescription( + key="AUX1", + translation_key="aux", + translation_placeholders={"number": "1"}, + device_class=BinarySensorDeviceClass.POWER, + ), + "AUX2": NeoPoolBinarySensorEntityDescription( + key="AUX2", + translation_key="aux", + translation_placeholders={"number": "2"}, + device_class=BinarySensorDeviceClass.POWER, + ), + "AUX3": NeoPoolBinarySensorEntityDescription( + key="AUX3", + translation_key="aux", + translation_placeholders={"number": "3"}, + device_class=BinarySensorDeviceClass.POWER, + ), + "AUX4": NeoPoolBinarySensorEntityDescription( + key="AUX4", + translation_key="aux", + translation_placeholders={"number": "4"}, + device_class=BinarySensorDeviceClass.POWER, + ), + "pH module control status": NeoPoolBinarySensorEntityDescription( + key="pH module control status", + translation_key="ph_module_control_status", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + supported_fn=is_ph_module_present, + ), + "pH control module": NeoPoolBinarySensorEntityDescription( + key="pH control module", + translation_key="ph_control_module", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + supported_fn=is_ph_module_present, + ), + "pH measurement active": NeoPoolBinarySensorEntityDescription( + key="pH measurement active", + translation_key="ph_measurement_active", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + supported_fn=is_ph_module_present, + ), + "Redox pump active": NeoPoolBinarySensorEntityDescription( + key="Redox pump active", + translation_key="redox_pump_active", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + supported_fn=lambda data: ( + is_redox_module_present(data) + and ( + "MBF_PAR_RX_RELAY_GPIO" not in data + or is_valid_relay_gpio(data["MBF_PAR_RX_RELAY_GPIO"] or 0) + ) + ), + ), + "Redox control module": NeoPoolBinarySensorEntityDescription( + key="Redox control module", + translation_key="redox_control_module", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + supported_fn=is_redox_module_present, + ), + "Redox measurement active": NeoPoolBinarySensorEntityDescription( + key="Redox measurement active", + translation_key="redox_measurement_active", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + supported_fn=is_redox_module_present, + ), + "Chlorine flow sensor problem": NeoPoolBinarySensorEntityDescription( + key="Chlorine flow sensor problem", + translation_key="chlorine_flow_sensor_problem", + device_class=BinarySensorDeviceClass.PROBLEM, + entity_category=EntityCategory.DIAGNOSTIC, + supported_fn=is_chlorine_module_present, + ), + "Chlorine pump active": NeoPoolBinarySensorEntityDescription( + key="Chlorine pump active", + translation_key="chlorine_pump_active", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + supported_fn=lambda data: ( + is_chlorine_module_present(data) + and ( + "MBF_PAR_CL_RELAY_GPIO" not in data + or is_valid_relay_gpio(data["MBF_PAR_CL_RELAY_GPIO"] or 0) + ) + ), + ), + "Chlorine control module": NeoPoolBinarySensorEntityDescription( + key="Chlorine control module", + translation_key="chlorine_control_module", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + supported_fn=is_chlorine_module_present, + ), + "Chlorine measurement active": NeoPoolBinarySensorEntityDescription( + key="Chlorine measurement active", + translation_key="chlorine_measurement_active", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + supported_fn=is_chlorine_module_present, + ), + "Conductivity pump active": NeoPoolBinarySensorEntityDescription( + key="Conductivity pump active", + translation_key="conductivity_pump_active", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + supported_fn=lambda data: ( + is_conductivity_module_present(data) + and ( + "MBF_PAR_CD_RELAY_GPIO" not in data + or is_valid_relay_gpio(data["MBF_PAR_CD_RELAY_GPIO"] or 0) + ) + ), + ), + "Conductivity control module": NeoPoolBinarySensorEntityDescription( + key="Conductivity control module", + translation_key="conductivity_control_module", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + supported_fn=is_conductivity_module_present, + ), + "Conductivity measurement active": NeoPoolBinarySensorEntityDescription( + key="Conductivity measurement active", + translation_key="conductivity_measurement_active", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + supported_fn=is_conductivity_module_present, + ), + "ION On Target": NeoPoolBinarySensorEntityDescription( + key="ION On Target", + translation_key="ion_on_target", + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + supported_fn=is_ionization_present, # pragma: no cover + ), + "ION Low": NeoPoolBinarySensorEntityDescription( + key="ION Low", + translation_key="ion_low", + device_class=BinarySensorDeviceClass.PROBLEM, + entity_category=EntityCategory.DIAGNOSTIC, + supported_fn=is_ionization_present, # pragma: no cover + ), + "ION Program time exceeded": NeoPoolBinarySensorEntityDescription( + key="ION Program time exceeded", + translation_key="ion_program_time_exceeded", + device_class=BinarySensorDeviceClass.PROBLEM, + entity_category=EntityCategory.DIAGNOSTIC, + supported_fn=is_ionization_present, # pragma: no cover + ), + "HIDRO Low": NeoPoolBinarySensorEntityDescription( + key="HIDRO Low", + translation_key="hidro_low", + device_class=BinarySensorDeviceClass.PROBLEM, + entity_category=EntityCategory.DIAGNOSTIC, + supported_fn=is_hydrolysis_present, + ), + "Pool Cover": NeoPoolBinarySensorEntityDescription( + key="Pool Cover", + translation_key="pool_cover", + device_class=BinarySensorDeviceClass.OPENING, + value_fn=_pool_cover_open, + ), + "HIDRO Module active": NeoPoolBinarySensorEntityDescription( + key="HIDRO Module active", + translation_key="hidro_module_active", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + supported_fn=is_hydrolysis_present, + ), + "HIDRO Module regulated": NeoPoolBinarySensorEntityDescription( + key="HIDRO Module regulated", + translation_key="hidro_module_regulated", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + supported_fn=is_hydrolysis_present, + ), + "HIDRO Activated by the RX module": NeoPoolBinarySensorEntityDescription( + key="HIDRO Activated by the RX module", + translation_key="hidro_activated_by_the_rx_module", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + supported_fn=lambda data: ( + is_hydrolysis_present(data) and is_redox_module_present(data) + ), # pragma: no cover + ), + "HIDRO Chlorine shock mode": NeoPoolBinarySensorEntityDescription( + key="HIDRO Chlorine shock mode", + translation_key="hidro_chlorine_shock_mode", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + supported_fn=is_hydrolysis_present, + ), + "HIDRO Activated by the CL module": NeoPoolBinarySensorEntityDescription( + key="HIDRO Activated by the CL module", + translation_key="hidro_activated_by_the_cl_module", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + supported_fn=lambda data: ( + is_hydrolysis_present(data) and is_chlorine_module_present(data) + ), + ), + "Heating": NeoPoolBinarySensorEntityDescription( + key="Heating", + translation_key="heating", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + supported_fn=has_heating_relay, + ), + "UV Lamp": NeoPoolBinarySensorEntityDescription( + key="UV Lamp", + translation_key="uv_lamp", + device_class=BinarySensorDeviceClass.RUNNING, + entity_category=EntityCategory.DIAGNOSTIC, + supported_fn=lambda data: ( + "MBF_PAR_UV_RELAY_GPIO" not in data + or is_valid_relay_gpio(data["MBF_PAR_UV_RELAY_GPIO"] or 0) + ), + ), +} + + +# Entities gated on a config-entry option (in addition to their supported_fn). +# The controller cannot detect what is physically wired to the light or aux +# relays, nor whether a cover sensor is present, so these entities are opt-in +# per config entry rather than surfaced from a device capability bit. +_ENTITY_OPTION_KEY: dict[str, str] = { + "Pool Light": CONF_USE_LIGHT, + "AUX1": CONF_USE_AUX1, + "AUX2": CONF_USE_AUX2, + "AUX3": CONF_USE_AUX3, + "AUX4": CONF_USE_AUX4, + "Pool Cover": CONF_USE_COVER_SENSOR, +} + + +async def async_setup_entry( + hass: HomeAssistant, + entry: NeoPoolConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up NeoPool binary sensors from a config entry.""" + coordinator = entry.runtime_data + options = entry.options + + async_add_entities( + NeoPoolBinarySensor(coordinator, key, desc) + for key, desc in BINARY_SENSOR_DESCRIPTIONS.items() + if ( + (option_key := _ENTITY_OPTION_KEY.get(key)) is None + or bool(options.get(option_key)) + ) + and (desc.supported_fn is None or desc.supported_fn(coordinator.data)) + ) + + +class NeoPoolBinarySensor(NeoPoolEntity, BinarySensorEntity): + """Representation of a NeoPool binary sensor.""" + + _winter_mode_active = False + entity_description: NeoPoolBinarySensorEntityDescription + + def __init__( + self, + coordinator: NeoPoolCoordinator, + key: str, + description: NeoPoolBinarySensorEntityDescription, + ) -> None: + """Initialize the binary sensor.""" + super().__init__(coordinator) + self.entity_description = description + self._key = key + self._attr_unique_id = ( + f"{self.coordinator.config_entry.unique_id}_{key.lower()}" + ) + + @property + @override + def is_on(self) -> bool | None: + """Return True if the binary sensor is on.""" + if (value_fn := self.entity_description.value_fn) is not None: + value: bool | None = value_fn(self.coordinator.data, self.hass) + return value + value = self.coordinator.data.get(self._key) + return None if value is None else bool(value) diff --git a/homeassistant/components/neopool/const.py b/homeassistant/components/neopool/const.py index 33ab63a75ae9..c8c0ed3a102f 100644 --- a/homeassistant/components/neopool/const.py +++ b/homeassistant/components/neopool/const.py @@ -6,6 +6,7 @@ DOMAIN = "neopool" NAME = "NeoPool" PLATFORMS: list[Platform] = [ + Platform.BINARY_SENSOR, Platform.BUTTON, Platform.LIGHT, Platform.SENSOR, diff --git a/homeassistant/components/neopool/strings.json b/homeassistant/components/neopool/strings.json index c795fbfda197..3002e901e68c 100644 --- a/homeassistant/components/neopool/strings.json +++ b/homeassistant/components/neopool/strings.json @@ -44,6 +44,95 @@ } }, "entity": { + "binary_sensor": { + "aux": { + "name": "Auxiliary relay {number}" + }, + "chlorine_control_module": { + "name": "Chlorine regulation active" + }, + "chlorine_flow_sensor_problem": { + "name": "Chlorine flow sensor" + }, + "chlorine_measurement_active": { + "name": "Chlorine measurement" + }, + "chlorine_pump_active": { + "name": "Chlorine pump active" + }, + "conductivity_control_module": { + "name": "Conductivity regulation active" + }, + "conductivity_measurement_active": { + "name": "Conductivity measurement" + }, + "conductivity_pump_active": { + "name": "Conductivity pump active" + }, + "filtration_pump": { + "name": "Filtration" + }, + "heating": { + "name": "Heating" + }, + "hidro_activated_by_the_cl_module": { + "name": "Hydrolysis activated by chlorine module" + }, + "hidro_activated_by_the_rx_module": { + "name": "Hydrolysis activated by Redox module" + }, + "hidro_chlorine_shock_mode": { + "name": "Hydrolysis chlorine shock mode (boost)" + }, + "hidro_low": { + "name": "Hydrolysis production problem" + }, + "hidro_module_active": { + "name": "Hydrolysis enabled" + }, + "hidro_module_regulated": { + "name": "Hydrolysis regulation active" + }, + "ion_low": { + "name": "Ionizer production problem" + }, + "ion_on_target": { + "name": "Ionizer on target" + }, + "ion_program_time_exceeded": { + "name": "Ionizer program time exceeded" + }, + "ph_acid_pump": { + "name": "pH acid pump" + }, + "ph_control_module": { + "name": "pH regulation active" + }, + "ph_measurement_active": { + "name": "pH measurement" + }, + "ph_module_control_status": { + "name": "pH flow detection control" + }, + "pool_cover": { + "name": "Pool cover" + }, + "pool_light": { + "name": "Pool light" + }, + "redox_control_module": { + "name": "Redox regulation active" + }, + "redox_measurement_active": { + "name": "Redox measurement" + }, + "redox_pump_active": { + "name": "Redox pump active" + }, + "uv_lamp": { + "name": "UV lamp" + } + }, "button": { "escape": { "name": "Clear error messages" diff --git a/tests/components/neopool/conftest.py b/tests/components/neopool/conftest.py index 0ab265308bcc..2026f242468d 100644 --- a/tests/components/neopool/conftest.py +++ b/tests/components/neopool/conftest.py @@ -83,6 +83,15 @@ MOCK_POOL_DATA: dict[str, Any] = { "pH pump active": False, "pH acid pump active": False, "Filtration Pump": False, + "pH Acid Pump": False, + # Measurement / module "active" bits. The controller keeps measuring the + # probes regardless of filtration state, so these read True even though the + # filtration pump above is off. + "pH measurement active": True, + "Redox measurement active": True, + "Chlorine measurement active": True, + "Conductivity measurement active": True, + "HIDRO Module active": True, "MBF_PAR_HIDRO_COVER_REDUCTION": 0x0C19, "MBF_PAR_HIDRO_COVER_ENABLE": 0x0000, "Pool Cover": 0, @@ -200,6 +209,58 @@ def mock_config_entry_switch() -> MockConfigEntry: ) +@pytest.fixture +def mock_config_entry_binary_sensor() -> MockConfigEntry: + """Return a config entry with the options the binary_sensor platform gates on.""" + return MockConfigEntry( + domain=DOMAIN, + title=MOCK_NAME, + unique_id=MOCK_SERIAL, + version=CURRENT_VERSION, + data={ + CONF_HOST: MOCK_HOST, + CONF_PORT: MOCK_PORT, + CONF_NAME: MOCK_NAME, + "unit_id": DEFAULT_UNIT_ID, + "modbus_framer": "tcp", + }, + options={ + CONF_USE_LIGHT: True, + CONF_USE_COVER_SENSOR: True, + CONF_USE_AUX1: True, + CONF_USE_AUX2: True, + CONF_USE_AUX3: True, + CONF_USE_AUX4: True, + }, + ) + + +@pytest.fixture +def mock_config_entry_binary_sensor_no_options() -> MockConfigEntry: + """Return a config entry with every binary_sensor option disabled.""" + return MockConfigEntry( + domain=DOMAIN, + title=MOCK_NAME, + unique_id=MOCK_SERIAL, + version=CURRENT_VERSION, + data={ + CONF_HOST: MOCK_HOST, + CONF_PORT: MOCK_PORT, + CONF_NAME: MOCK_NAME, + "unit_id": DEFAULT_UNIT_ID, + "modbus_framer": "tcp", + }, + options={ + CONF_USE_LIGHT: False, + CONF_USE_COVER_SENSOR: False, + CONF_USE_AUX1: False, + CONF_USE_AUX2: False, + CONF_USE_AUX3: False, + CONF_USE_AUX4: False, + }, + ) + + @pytest.fixture def mock_neopool_client() -> Generator[MagicMock]: """Patch the NeoPoolModbusClient and return a configurable mock instance.""" diff --git a/tests/components/neopool/snapshots/test_binary_sensor.ambr b/tests/components/neopool/snapshots/test_binary_sensor.ambr new file mode 100644 index 000000000000..312e5018e21b --- /dev/null +++ b/tests/components/neopool/snapshots/test_binary_sensor.ambr @@ -0,0 +1,1836 @@ +# serializer version: 1 +# name: test_all_entities[binary_sensor.neopool_auxiliary_relay_1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Auxiliary relay 1', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Auxiliary relay 1', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'aux', + 'unique_id': '1234567890_aux1', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_auxiliary_relay_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'power', + : 'NeoPool Auxiliary relay 1', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_auxiliary_relay_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Auxiliary relay 2', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Auxiliary relay 2', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'aux', + 'unique_id': '1234567890_aux2', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_auxiliary_relay_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'power', + : 'NeoPool Auxiliary relay 2', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_auxiliary_relay_3-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Auxiliary relay 3', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Auxiliary relay 3', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'aux', + 'unique_id': '1234567890_aux3', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_auxiliary_relay_3-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'power', + : 'NeoPool Auxiliary relay 3', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_3', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_auxiliary_relay_4-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_4', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Auxiliary relay 4', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Auxiliary relay 4', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'aux', + 'unique_id': '1234567890_aux4', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_auxiliary_relay_4-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'power', + : 'NeoPool Auxiliary relay 4', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_4', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_chlorine_flow_sensor-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_chlorine_flow_sensor', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Chlorine flow sensor', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Chlorine flow sensor', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'chlorine_flow_sensor_problem', + 'unique_id': '1234567890_chlorine flow sensor problem', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_chlorine_flow_sensor-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'problem', + : 'NeoPool Chlorine flow sensor', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_chlorine_flow_sensor', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_chlorine_measurement-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_chlorine_measurement', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Chlorine measurement', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Chlorine measurement', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'chlorine_measurement_active', + 'unique_id': '1234567890_chlorine measurement active', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_chlorine_measurement-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool Chlorine measurement', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_chlorine_measurement', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_chlorine_pump_active-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_chlorine_pump_active', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Chlorine pump active', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Chlorine pump active', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'chlorine_pump_active', + 'unique_id': '1234567890_chlorine pump active', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_chlorine_pump_active-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool Chlorine pump active', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_chlorine_pump_active', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_chlorine_regulation_active-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_chlorine_regulation_active', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Chlorine regulation active', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Chlorine regulation active', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'chlorine_control_module', + 'unique_id': '1234567890_chlorine control module', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_chlorine_regulation_active-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool Chlorine regulation active', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_chlorine_regulation_active', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_conductivity_measurement-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_conductivity_measurement', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Conductivity measurement', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Conductivity measurement', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'conductivity_measurement_active', + 'unique_id': '1234567890_conductivity measurement active', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_conductivity_measurement-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool Conductivity measurement', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_conductivity_measurement', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_conductivity_regulation_active-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_conductivity_regulation_active', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Conductivity regulation active', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Conductivity regulation active', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'conductivity_control_module', + 'unique_id': '1234567890_conductivity control module', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_conductivity_regulation_active-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool Conductivity regulation active', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_conductivity_regulation_active', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_filtration-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.neopool_filtration', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Filtration', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Filtration', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'filtration_pump', + 'unique_id': '1234567890_filtration pump', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_filtration-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool Filtration', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_filtration', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_heating-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_heating', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Heating', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Heating', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'heating', + 'unique_id': '1234567890_heating', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_heating-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool Heating', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_heating', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_hydrolysis_activated_by_chlorine_module-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_hydrolysis_activated_by_chlorine_module', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Hydrolysis activated by chlorine module', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Hydrolysis activated by chlorine module', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'hidro_activated_by_the_cl_module', + 'unique_id': '1234567890_hidro activated by the cl module', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_hydrolysis_activated_by_chlorine_module-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool Hydrolysis activated by chlorine module', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_hydrolysis_activated_by_chlorine_module', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_hydrolysis_activated_by_redox_module-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_hydrolysis_activated_by_redox_module', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Hydrolysis activated by Redox module', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Hydrolysis activated by Redox module', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'hidro_activated_by_the_rx_module', + 'unique_id': '1234567890_hidro activated by the rx module', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_hydrolysis_activated_by_redox_module-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool Hydrolysis activated by Redox module', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_hydrolysis_activated_by_redox_module', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_hydrolysis_chlorine_shock_mode_boost-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_hydrolysis_chlorine_shock_mode_boost', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Hydrolysis chlorine shock mode (boost)', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Hydrolysis chlorine shock mode (boost)', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'hidro_chlorine_shock_mode', + 'unique_id': '1234567890_hidro chlorine shock mode', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_hydrolysis_chlorine_shock_mode_boost-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool Hydrolysis chlorine shock mode (boost)', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_hydrolysis_chlorine_shock_mode_boost', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_hydrolysis_enabled-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_hydrolysis_enabled', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Hydrolysis enabled', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Hydrolysis enabled', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'hidro_module_active', + 'unique_id': '1234567890_hidro module active', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_hydrolysis_enabled-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool Hydrolysis enabled', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_hydrolysis_enabled', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_hydrolysis_production_problem-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_hydrolysis_production_problem', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Hydrolysis production problem', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Hydrolysis production problem', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'hidro_low', + 'unique_id': '1234567890_hidro low', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_hydrolysis_production_problem-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'problem', + : 'NeoPool Hydrolysis production problem', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_hydrolysis_production_problem', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_hydrolysis_regulation_active-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_hydrolysis_regulation_active', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Hydrolysis regulation active', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Hydrolysis regulation active', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'hidro_module_regulated', + 'unique_id': '1234567890_hidro module regulated', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_hydrolysis_regulation_active-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool Hydrolysis regulation active', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_hydrolysis_regulation_active', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_ionizer_on_target-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_ionizer_on_target', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Ionizer on target', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Ionizer on target', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'ion_on_target', + 'unique_id': '1234567890_ion on target', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_ionizer_on_target-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'NeoPool Ionizer on target', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_ionizer_on_target', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_ionizer_production_problem-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_ionizer_production_problem', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Ionizer production problem', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Ionizer production problem', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'ion_low', + 'unique_id': '1234567890_ion low', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_ionizer_production_problem-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'problem', + : 'NeoPool Ionizer production problem', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_ionizer_production_problem', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_ionizer_program_time_exceeded-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_ionizer_program_time_exceeded', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Ionizer program time exceeded', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Ionizer program time exceeded', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'ion_program_time_exceeded', + 'unique_id': '1234567890_ion program time exceeded', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_ionizer_program_time_exceeded-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'problem', + : 'NeoPool Ionizer program time exceeded', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_ionizer_program_time_exceeded', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_ph_acid_pump-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_ph_acid_pump', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'pH acid pump', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'pH acid pump', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'ph_acid_pump', + 'unique_id': '1234567890_ph acid pump', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_ph_acid_pump-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool pH acid pump', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_ph_acid_pump', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_ph_flow_detection_control-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_ph_flow_detection_control', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'pH flow detection control', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'pH flow detection control', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'ph_module_control_status', + 'unique_id': '1234567890_ph module control status', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_ph_flow_detection_control-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool pH flow detection control', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_ph_flow_detection_control', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_ph_measurement-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_ph_measurement', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'pH measurement', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'pH measurement', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'ph_measurement_active', + 'unique_id': '1234567890_ph measurement active', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_ph_measurement-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool pH measurement', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_ph_measurement', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_ph_regulation_active-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_ph_regulation_active', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'pH regulation active', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'pH regulation active', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'ph_control_module', + 'unique_id': '1234567890_ph control module', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_ph_regulation_active-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool pH regulation active', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_ph_regulation_active', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_pool_cover-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.neopool_pool_cover', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Pool cover', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Pool cover', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'pool_cover', + 'unique_id': '1234567890_pool cover', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_pool_cover-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'opening', + : 'NeoPool Pool cover', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_pool_cover', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_pool_light-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.neopool_pool_light', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Pool light', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Pool light', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'pool_light', + 'unique_id': '1234567890_pool light', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_pool_light-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'light', + : 'NeoPool Pool light', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_pool_light', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_redox_measurement-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_redox_measurement', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Redox measurement', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Redox measurement', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'redox_measurement_active', + 'unique_id': '1234567890_redox measurement active', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_redox_measurement-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool Redox measurement', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_redox_measurement', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_redox_pump_active-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_redox_pump_active', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Redox pump active', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Redox pump active', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'redox_pump_active', + 'unique_id': '1234567890_redox pump active', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_redox_pump_active-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool Redox pump active', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_redox_pump_active', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_redox_regulation_active-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_redox_regulation_active', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Redox regulation active', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Redox regulation active', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'redox_control_module', + 'unique_id': '1234567890_redox control module', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_redox_regulation_active-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool Redox regulation active', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_redox_regulation_active', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_all_entities[binary_sensor.neopool_uv_lamp-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.neopool_uv_lamp', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'UV lamp', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'UV lamp', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'uv_lamp', + 'unique_id': '1234567890_uv lamp', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.neopool_uv_lamp-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'running', + : 'NeoPool UV lamp', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_uv_lamp', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_setup_when_modules_absent[binary_sensor.neopool_auxiliary_relay_1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Auxiliary relay 1', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Auxiliary relay 1', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'aux', + 'unique_id': '1234567890_aux1', + 'unit_of_measurement': None, + }) +# --- +# name: test_setup_when_modules_absent[binary_sensor.neopool_auxiliary_relay_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'power', + : 'NeoPool Auxiliary relay 1', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_setup_when_modules_absent[binary_sensor.neopool_auxiliary_relay_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Auxiliary relay 2', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Auxiliary relay 2', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'aux', + 'unique_id': '1234567890_aux2', + 'unit_of_measurement': None, + }) +# --- +# name: test_setup_when_modules_absent[binary_sensor.neopool_auxiliary_relay_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'power', + : 'NeoPool Auxiliary relay 2', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_setup_when_modules_absent[binary_sensor.neopool_auxiliary_relay_3-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Auxiliary relay 3', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Auxiliary relay 3', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'aux', + 'unique_id': '1234567890_aux3', + 'unit_of_measurement': None, + }) +# --- +# name: test_setup_when_modules_absent[binary_sensor.neopool_auxiliary_relay_3-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'power', + : 'NeoPool Auxiliary relay 3', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_3', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_setup_when_modules_absent[binary_sensor.neopool_auxiliary_relay_4-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_4', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Auxiliary relay 4', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Auxiliary relay 4', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'aux', + 'unique_id': '1234567890_aux4', + 'unit_of_measurement': None, + }) +# --- +# name: test_setup_when_modules_absent[binary_sensor.neopool_auxiliary_relay_4-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'power', + : 'NeoPool Auxiliary relay 4', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_auxiliary_relay_4', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_setup_when_modules_absent[binary_sensor.neopool_pool_cover-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.neopool_pool_cover', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Pool cover', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Pool cover', + 'platform': 'neopool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'pool_cover', + 'unique_id': '1234567890_pool cover', + 'unit_of_measurement': None, + }) +# --- +# name: test_setup_when_modules_absent[binary_sensor.neopool_pool_cover-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'opening', + : 'NeoPool Pool cover', + }), + 'context': , + 'entity_id': 'binary_sensor.neopool_pool_cover', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- diff --git a/tests/components/neopool/snapshots/test_diagnostics.ambr b/tests/components/neopool/snapshots/test_diagnostics.ambr index fdae906d02bc..c99e1315cd5d 100644 --- a/tests/components/neopool/snapshots/test_diagnostics.ambr +++ b/tests/components/neopool/snapshots/test_diagnostics.ambr @@ -31,9 +31,12 @@ 'CELL_RUNTIME_POLB': 1800, 'CELL_RUNTIME_POL_CHANGES': 7, 'CELL_RUNTIME_TOTAL': 65536, + 'Chlorine measurement active': True, 'Chlorine measurement module detected': True, + 'Conductivity measurement active': True, 'Conductivity measurement module detected': True, 'Filtration Pump': False, + 'HIDRO Module active': True, 'HIDRO in Pol1': False, 'HIDRO in Pol2': False, 'HIDRO in dead time': False, @@ -81,11 +84,14 @@ 'MBF_POWER_MODULE_VERSION': 4660, 'PH_PUMP_STATUS': 'off', 'Pool Cover': 0, + 'Redox measurement active': True, 'Redox measurement module detected': True, 'filtration_mode': 'manual', 'filtration_speed_state': 'off', + 'pH Acid Pump': False, 'pH acid pump active': False, 'pH control module': True, + 'pH measurement active': True, 'pH measurement module detected': True, 'pH pump active': False, }), diff --git a/tests/components/neopool/test_binary_sensor.py b/tests/components/neopool/test_binary_sensor.py new file mode 100644 index 000000000000..8875b1b06b7e --- /dev/null +++ b/tests/components/neopool/test_binary_sensor.py @@ -0,0 +1,255 @@ +"""Tests for the NeoPool binary_sensor platform value decoders.""" + +from datetime import timedelta +from typing import Any +from unittest.mock import MagicMock, patch + +from freezegun.api import FrozenDateTimeFactory +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import setup_integration +from .conftest import MOCK_POOL_DATA + +from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform + + +def _binary_state(hass: HomeAssistant, entry: MockConfigEntry, key: str): + """Return the HA state object of the binary_sensor for a coordinator key.""" + registry = er.async_get(hass) + suffix = f"_{key.lower()}" + entries = [ + e + for e in er.async_entries_for_config_entry(registry, entry.entry_id) + if e.domain == BINARY_SENSOR_DOMAIN and e.unique_id.endswith(suffix) + ] + if not entries: + return None + return hass.states.get(entries[0].entity_id) + + +async def test_direct_key_reflects_coordinator_value( + hass: HomeAssistant, + mock_config_entry_binary_sensor: MockConfigEntry, + mock_neopool_client: MagicMock, + freezer: FrozenDateTimeFactory, +) -> None: + """A simple boolean key from coordinator.data flows straight through is_on.""" + await setup_integration(hass, mock_config_entry_binary_sensor) + + mock_neopool_client.async_read_all.return_value = { + **MOCK_POOL_DATA, + "Filtration Pump": True, + } + freezer.tick(timedelta(seconds=60)) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + state = _binary_state(hass, mock_config_entry_binary_sensor, "Filtration Pump") + assert state is not None + assert state.state == STATE_ON + + mock_neopool_client.async_read_all.return_value = { + **MOCK_POOL_DATA, + "Filtration Pump": False, + } + freezer.tick(timedelta(seconds=60)) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + state = _binary_state(hass, mock_config_entry_binary_sensor, "Filtration Pump") + assert state is not None + assert state.state == STATE_OFF + + +async def test_pool_cover_inverts_hardware_value( + hass: HomeAssistant, + mock_config_entry_binary_sensor: MockConfigEntry, + mock_neopool_client: MagicMock, + freezer: FrozenDateTimeFactory, +) -> None: + """Pool Cover: hardware 1 (covered) → HA OFF; hardware 0 → HA ON. + + The OPENING device class needs the opposite polarity from the raw + register, so the entity inverts the value before returning is_on. + """ + await setup_integration(hass, mock_config_entry_binary_sensor) + + mock_neopool_client.async_read_all.return_value = { + **MOCK_POOL_DATA, + "Pool Cover": True, + "Filtration Pump": True, + } + freezer.tick(timedelta(seconds=60)) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + state = _binary_state(hass, mock_config_entry_binary_sensor, "Pool Cover") + assert state is not None + assert state.state == STATE_OFF + + mock_neopool_client.async_read_all.return_value = { + **MOCK_POOL_DATA, + "Pool Cover": False, + "Filtration Pump": True, + } + freezer.tick(timedelta(seconds=60)) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + state = _binary_state(hass, mock_config_entry_binary_sensor, "Pool Cover") + assert state is not None + assert state.state == STATE_ON + + +async def test_pool_cover_none_yields_unknown( + hass: HomeAssistant, + mock_config_entry_binary_sensor: MockConfigEntry, + mock_neopool_client: MagicMock, + freezer: FrozenDateTimeFactory, +) -> None: + """Missing Pool Cover key surfaces as STATE_UNKNOWN, not on/off.""" + await setup_integration(hass, mock_config_entry_binary_sensor) + + mock_neopool_client.async_read_all.return_value = { + **MOCK_POOL_DATA, + "Pool Cover": None, + "Filtration Pump": True, + } + freezer.tick(timedelta(seconds=60)) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + state = _binary_state(hass, mock_config_entry_binary_sensor, "Pool Cover") + assert state is not None + assert state.state == STATE_UNKNOWN + + +@pytest.mark.parametrize("pump_state", [False, None]) +async def test_pool_cover_unknown_when_filtration_not_running( + hass: HomeAssistant, + mock_config_entry_binary_sensor: MockConfigEntry, + mock_neopool_client: MagicMock, + freezer: FrozenDateTimeFactory, + pump_state: bool | None, +) -> None: + """Cover reads unknown unless the pump is confirmed running. + + The device only reports the cover bit while filtration runs, so an idle + (False) or unknown (None) pump state must not surface a stale open/closed. + """ + await setup_integration(hass, mock_config_entry_binary_sensor) + + mock_neopool_client.async_read_all.return_value = { + **MOCK_POOL_DATA, + "Pool Cover": False, + "Filtration Pump": pump_state, + } + freezer.tick(timedelta(seconds=60)) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + state = _binary_state(hass, mock_config_entry_binary_sensor, "Pool Cover") + assert state is not None + assert state.state == STATE_UNKNOWN + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_measurement_module_reads_raw_bit( + hass: HomeAssistant, + mock_config_entry_binary_sensor: MockConfigEntry, + mock_neopool_client: MagicMock, + freezer: FrozenDateTimeFactory, +) -> None: + """Measurement-module sensors report the raw device bit, even with filtration off. + + The controller keeps measuring the probes regardless of the filtration + pump state, so the entity must not force the value off. + """ + await setup_integration(hass, mock_config_entry_binary_sensor) + + mock_neopool_client.async_read_all.return_value = { + **MOCK_POOL_DATA, + "pH measurement active": True, + "Filtration Pump": False, + } + freezer.tick(timedelta(seconds=60)) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + state = _binary_state( + hass, mock_config_entry_binary_sensor, "pH measurement active" + ) + assert state is not None + assert state.state == STATE_ON + + mock_neopool_client.async_read_all.return_value = { + **MOCK_POOL_DATA, + "pH measurement active": False, + "Filtration Pump": False, + } + freezer.tick(timedelta(seconds=60)) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + state = _binary_state( + hass, mock_config_entry_binary_sensor, "pH measurement active" + ) + assert state is not None + assert state.state == STATE_OFF + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default", "mock_neopool_client") +async def test_all_entities( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, + mock_config_entry_binary_sensor: MockConfigEntry, +) -> None: + """Snapshot every entity registered by the binary_sensor platform.""" + with patch("homeassistant.components.neopool.PLATFORMS", [Platform.BINARY_SENSOR]): + await setup_integration(hass, mock_config_entry_binary_sensor) + await snapshot_platform( + hass, entity_registry, snapshot, mock_config_entry_binary_sensor.entry_id + ) + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_setup_when_modules_absent( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, + mock_config_entry_binary_sensor: MockConfigEntry, + mock_neopool_client: MagicMock, + minimal_pool_data: dict[str, Any], +) -> None: + """Snapshot the binary_sensor entities registered when no modules are present.""" + mock_neopool_client.async_read_all.return_value = minimal_pool_data + with patch("homeassistant.components.neopool.PLATFORMS", [Platform.BINARY_SENSOR]): + await setup_integration(hass, mock_config_entry_binary_sensor) + await snapshot_platform( + hass, entity_registry, snapshot, mock_config_entry_binary_sensor.entry_id + ) + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default", "mock_neopool_client") +async def test_opt_in_entities_absent_without_options( + hass: HomeAssistant, + mock_config_entry_binary_sensor_no_options: MockConfigEntry, +) -> None: + """Opt-in entities are not registered when their config option is off. + + Pool Light, the four auxiliary relays, and Pool Cover are gated on an + integration option in addition to any capability check. With every option + disabled they must not register, while an ungated relay sensor still does. + """ + with patch("homeassistant.components.neopool.PLATFORMS", [Platform.BINARY_SENSOR]): + await setup_integration(hass, mock_config_entry_binary_sensor_no_options) + + for key in ("Pool Light", "AUX1", "AUX2", "AUX3", "AUX4", "Pool Cover"): + assert ( + _binary_state(hass, mock_config_entry_binary_sensor_no_options, key) is None + ) + assert ( + _binary_state( + hass, mock_config_entry_binary_sensor_no_options, "Filtration Pump" + ) + is not None + )