Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a96b335
Add binary_sensor platform to Vistapool
claude May 26, 2026
c43e73d
Vistapool: also require hasHidro for the hidro_fl2 binary sensor
claude May 26, 2026
89f223c
Vistapool: return None from acid_tank when no tank data is available
claude May 26, 2026
54839b1
Vistapool: use STATE_ON / STATE_OFF / STATE_UNKNOWN in binary_sensor …
claude May 26, 2026
24ee9c3
Vistapool: align binary_sensor description keys with entity name slugs
claude May 26, 2026
234bfaf
Vistapool: cover io_module disabled-by-default in binary_sensor tests
claude May 26, 2026
037ee6e
Vistapool: rename acid_tank binary sensor to dosing_tank
claude May 26, 2026
59da410
Vistapool: restore ph_pump_alarm naming on the PROBLEM-class entity
claude May 26, 2026
1aeffa8
Vistapool: cover every diagnostic module entity as disabled-by-default
claude May 26, 2026
628e548
Vistapool: clarify why module diagnostic entities exist regardless of…
claude May 26, 2026
6ddaa78
Vistapool: use snapshot_platform for the binary_sensor default-fixtur…
claude May 26, 2026
6bd904a
Vistapool: fold all-modules-enabled case into the parametrized snapsh…
claude May 26, 2026
6f03880
Vistapool: drop CONNECTIVITY device class from module-presence binary…
claude May 26, 2026
a040ed4
Vistapool: add binary_sensor snapshot
claude May 26, 2026
2fcc69a
Vistapool: coerce numeric-as-string API values before bool conversion
claude May 26, 2026
7cc58c8
Vistapool: coerce has* flags in entity-creation gates
claude May 26, 2026
a29fdd4
Vistapool: pin PLATFORMS to binary_sensor across the test module
claude May 26, 2026
2293d94
Vistapool: rewrite dosing_tank None filter as an explicit loop
claude May 26, 2026
ebf64e6
Vistapool: fix codespell finding (unparseable -> unparsable)
claude May 26, 2026
11702fd
Vistapool: switch to async_load_json_object_fixture in tests
claude May 26, 2026
c5f1308
Vistapool: simplify binary_sensor coercion per joostlek's review
claude May 26, 2026
019d2f9
Merge remote-tracking branch 'upstream/dev' into vistapool-binary-sensor
claude Jun 2, 2026
f4429fc
Vistapool: deepcopy _LED_DATA in button tests to prevent cross-test m…
claude Jun 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion homeassistant/components/vistapool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

_LOGGER = logging.getLogger(__name__)

PLATFORMS: list[Platform] = [Platform.BUTTON, Platform.SENSOR]
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR]


@dataclass
Expand Down
255 changes: 255 additions & 0 deletions homeassistant/components/vistapool/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
"""Vistapool Binary Sensor entities."""

from dataclasses import dataclass
Comment thread
fdebrus marked this conversation as resolved.
from typing import Any

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 VistapoolConfigEntry
from .const import (
PATH_HASCD,
PATH_HASCL,
PATH_HASHIDRO,
PATH_HASIO,
PATH_HASPH,
PATH_HASRX,
)
from .coordinator import VistapoolDataUpdateCoordinator
from .entity import VistapoolEntity

PARALLEL_UPDATES = 1

TANK_MODULE_PATHS = (
"modules.ph.tank",
"modules.rx.tank",
"modules.cl.tank",
"modules.cd.tank",
)


@dataclass(frozen=True, kw_only=True)
class VistapoolBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describes a Vistapool binary sensor entity."""

value_path: str
exists_path: str | tuple[str, ...] | None = None


BINARY_SENSOR_DESCRIPTIONS: tuple[VistapoolBinarySensorEntityDescription, ...] = (
VistapoolBinarySensorEntityDescription(
key="filtration",
translation_key="filtration",
device_class=BinarySensorDeviceClass.RUNNING,
value_path="filtration.status",
),
VistapoolBinarySensorEntityDescription(
key="backwash",
translation_key="backwash",
device_class=BinarySensorDeviceClass.RUNNING,
value_path="backwash.status",
),
VistapoolBinarySensorEntityDescription(
key="heating",
translation_key="heating",
device_class=BinarySensorDeviceClass.RUNNING,
value_path="relays.filtration.heating.status",
),
VistapoolBinarySensorEntityDescription(
key="hidro_flow",
translation_key="hidro_flow",
device_class=BinarySensorDeviceClass.PROBLEM,
value_path="hidro.fl1",
exists_path=PATH_HASHIDRO,
),
Comment thread
fdebrus marked this conversation as resolved.
VistapoolBinarySensorEntityDescription(
key="hidro_cover_reduction",
translation_key="hidro_cover_reduction",
device_class=BinarySensorDeviceClass.RUNNING,
value_path="hidro.cover",
exists_path=PATH_HASHIDRO,
),
VistapoolBinarySensorEntityDescription(
key="hidro_fl2",
translation_key="hidro_fl2",
device_class=BinarySensorDeviceClass.PROBLEM,
value_path="hidro.fl2",
exists_path=(PATH_HASHIDRO, PATH_HASCL),
),
Comment thread
fdebrus marked this conversation as resolved.
VistapoolBinarySensorEntityDescription(
key="chlorine_pump",
translation_key="chlorine_pump",
device_class=BinarySensorDeviceClass.RUNNING,
value_path="modules.cl.pump_status",
exists_path=PATH_HASCL,
),
VistapoolBinarySensorEntityDescription(
key="redox_pump",
translation_key="redox_pump",
device_class=BinarySensorDeviceClass.RUNNING,
value_path="modules.rx.pump_status",
exists_path=PATH_HASRX,
),
VistapoolBinarySensorEntityDescription(
key="ph_pump_alarm",
translation_key="ph_pump_alarm",
device_class=BinarySensorDeviceClass.PROBLEM,
value_path="modules.ph.al3",
exists_path=PATH_HASPH,
),
Comment thread
fdebrus marked this conversation as resolved.
Comment thread
fdebrus marked this conversation as resolved.
VistapoolBinarySensorEntityDescription(
key="ph_acid_pump",
translation_key="ph_acid_pump",
device_class=BinarySensorDeviceClass.RUNNING,
value_path="modules.ph.pump_high_on",
exists_path=PATH_HASPH,
),
VistapoolBinarySensorEntityDescription(
key="ph_base_pump",
translation_key="ph_base_pump",
device_class=BinarySensorDeviceClass.RUNNING,
value_path="modules.ph.pump_low_on",
exists_path=PATH_HASPH,
),
VistapoolBinarySensorEntityDescription(
key="conductivity_module",
translation_key="conductivity_module",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_path=PATH_HASCD,
Comment thread
fdebrus marked this conversation as resolved.
),
VistapoolBinarySensorEntityDescription(
key="chlorine_module",
translation_key="chlorine_module",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_path=PATH_HASCL,
),
VistapoolBinarySensorEntityDescription(
key="redox_module",
translation_key="redox_module",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_path=PATH_HASRX,
),
VistapoolBinarySensorEntityDescription(
key="ph_module",
translation_key="ph_module",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_path=PATH_HASPH,
),
VistapoolBinarySensorEntityDescription(
key="io_module",
translation_key="io_module",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_path=PATH_HASIO,
),
VistapoolBinarySensorEntityDescription(
key="hidro_module",
translation_key="hidro_module",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_path=PATH_HASHIDRO,
),
Comment thread
fdebrus marked this conversation as resolved.
)


async def async_setup_entry(
hass: HomeAssistant,
entry: VistapoolConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Vistapool binary sensors for every pool on the account."""
entities: list[BinarySensorEntity] = []

for coordinator in entry.runtime_data.coordinators.values():
for description in BINARY_SENSOR_DESCRIPTIONS:
if description.exists_path is not None:
required = (
(description.exists_path,)
if isinstance(description.exists_path, str)
else description.exists_path
)
if not all(coordinator.get_value(path) for path in required):
continue
Comment thread
fdebrus marked this conversation as resolved.
Comment thread
fdebrus marked this conversation as resolved.
entities.append(VistapoolBinarySensor(coordinator, description))

if coordinator.get_value(PATH_HASHIDRO):
is_electrolysis = coordinator.get_value("hidro.is_electrolysis")
entities.append(
VistapoolBinarySensor(
coordinator,
VistapoolBinarySensorEntityDescription(
key="electrolysis_low" if is_electrolysis else "hydrolysis_low",
translation_key=(
"electrolysis_low" if is_electrolysis else "hydrolysis_low"
),
device_class=BinarySensorDeviceClass.PROBLEM,
value_path="hidro.low",
),
)
)

if any(
coordinator.get_value(path)
for path in (PATH_HASCD, PATH_HASCL, PATH_HASPH, PATH_HASRX)
):
Comment thread
fdebrus marked this conversation as resolved.
entities.append(VistapoolDosingTankBinarySensor(coordinator))
Comment thread
fdebrus marked this conversation as resolved.

async_add_entities(entities)


class VistapoolBinarySensor(VistapoolEntity, BinarySensorEntity):
"""Generic Vistapool binary sensor driven by an entity description."""

entity_description: VistapoolBinarySensorEntityDescription

def __init__(
self,
coordinator: VistapoolDataUpdateCoordinator,
description: VistapoolBinarySensorEntityDescription,
) -> None:
"""Initialize the binary sensor."""
super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = self.build_unique_id(description.key)

@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
value = self.coordinator.get_value(self.entity_description.value_path)
if value is None:
return None
return value in (True, "1")
Comment thread
fdebrus marked this conversation as resolved.
Comment thread
fdebrus marked this conversation as resolved.


class VistapoolDosingTankBinarySensor(VistapoolEntity, BinarySensorEntity):
"""Dosing-tank low-level sensor: on if any installed dosing module reports low."""

_attr_device_class = BinarySensorDeviceClass.PROBLEM
_attr_translation_key = "dosing_tank"

def __init__(self, coordinator: VistapoolDataUpdateCoordinator) -> None:
"""Initialize the dosing-tank binary sensor."""
super().__init__(coordinator)
self._attr_unique_id = self.build_unique_id("dosing_tank")

@property
def is_on(self) -> bool | None:
"""Return true if any tank is low, or None if no tank data is available."""
Comment thread
fdebrus marked this conversation as resolved.
values: list[Any] = []
for path in TANK_MODULE_PATHS:
value = self.coordinator.get_value(path)
if value is not None:
values.append(value)
if not values:
return None
return any(value in (True, "1") for value in values)
Comment thread
fdebrus marked this conversation as resolved.
Comment thread
fdebrus marked this conversation as resolved.
1 change: 1 addition & 0 deletions homeassistant/components/vistapool/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
PATH_PREFIX = "main."
PATH_HASCD = f"{PATH_PREFIX}hasCD"
PATH_HASCL = f"{PATH_PREFIX}hasCL"
PATH_HASIO = f"{PATH_PREFIX}hasIO"
PATH_HASPH = f"{PATH_PREFIX}hasPH"
PATH_HASRX = f"{PATH_PREFIX}hasRX"
PATH_HASUV = f"{PATH_PREFIX}hasUV"
Expand Down
62 changes: 62 additions & 0 deletions homeassistant/components/vistapool/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,68 @@
}
},
"entity": {
"binary_sensor": {
"backwash": {
"name": "Backwash"
},
"chlorine_module": {
"name": "Chlorine module"
},
"chlorine_pump": {
"name": "Chlorine pump"
},
"conductivity_module": {
"name": "Conductivity module"
},
"dosing_tank": {
"name": "Dosing tank"
},
"electrolysis_low": {
"name": "Electrolysis low"
},
"filtration": {
"name": "Filtration"
},
"heating": {
"name": "Heating"
},
"hidro_cover_reduction": {
"name": "Hidro cover reduction"
},
"hidro_fl2": {
"name": "Hidro FL2"
},
"hidro_flow": {
"name": "Hidro flow"
},
"hidro_module": {
"name": "Hidro module"
},
"hydrolysis_low": {
"name": "Hydrolysis low"
},
"io_module": {
"name": "IO module"
},
"ph_acid_pump": {
"name": "pH acid pump"
},
"ph_base_pump": {
"name": "pH base pump"
},
"ph_module": {
"name": "pH module"
},
"ph_pump_alarm": {
"name": "pH pump alarm"
},
"redox_module": {
"name": "Redox module"
},
"redox_pump": {
"name": "Redox pump"
}
},
"button": {
"led_pulse": {
"name": "LED next color"
Expand Down
Loading
Loading