Skip to content

Add binary_sensor platform to Vistapool#172234

Open
fdebrus wants to merge 23 commits into
home-assistant:devfrom
fdebrus:vistapool-binary-sensor
Open

Add binary_sensor platform to Vistapool#172234
fdebrus wants to merge 23 commits into
home-assistant:devfrom
fdebrus:vistapool-binary-sensor

Conversation

@fdebrus
Copy link
Copy Markdown
Contributor

@fdebrus fdebrus commented May 26, 2026

Proposed change

Adds the binary_sensor platform to the Vistapool integration. This is the follow-up PR after the initial sensor-only integration merged as #168051, per the maintainers' guidance to limit each new-platform PR to a single platform.

Entities exposed (per pool, with appropriate device_class):

  • Equipment state (RUNNING): filtration, backwash, heating, pH acid pump, pH base pump, chlorine pump, redox pump, hidro cover reduction
  • Alarms (PROBLEM): pH pump, hidro flow, hidro FL2, electrolysis low / hydrolysis low (dynamic key based on hidro.is_electrolysis, mirroring the existing pattern in sensor.py), acid tank (true if any installed dosing tank reports low; returns unknown when no tank data has arrived yet)
  • Module-presence diagnostics (CONNECTIVITY, diagnostic, disabled by default): conductivity, chlorine, redox, pH, IO, hidro

The present ("Connected") entity from the upstream library / HACS source is intentionally not ported — Home Assistant already models per-entity availability via the coordinator.

Implementation notes

Aligned with the patterns already accepted on the sensor PR:

  • VistapoolBinarySensorEntityDescription extends BinarySensorEntityDescription, mirroring VistapoolSensorEntityDescription in sensor.py.
  • Conditional entities use an exists_path field on the description (a single path or a tuple of paths; all must be truthy) instead of separate if blocks. hidro_fl2 requires both hasHidro and hasCL.
  • Iterates entry.runtime_data.coordinators.values() for multi-pool support.
  • Constructor takes only the coordinator; pool_id / pool_name come from it.
  • AddConfigEntryEntitiesCallback, PARALLEL_UPDATES = 1, translation_key-only naming, unique IDs via build_unique_id(description.key).
  • description.key, translation_key, the strings.json key, the unique_id suffix, and the entity_id slug all line up.

PATH_HASIO added to const.py for the new IO module diagnostic. quality_scale.yaml exemption comments for action-setup, docs-actions, and action-exceptions were refreshed to drop the stale "sensor-only platform" wording — no rule statuses changed. No manifest or requirements changes.

Tests

tests/components/vistapool/test_binary_sensor.py covers:

  • Default fixture: always-on entities, module-gated entities present/absent, electrolysis branch chosen, acid tank present when any module is, diagnostic entities (hidro_module, io_module) registered as disabled-by-default.
  • Hydrolysis branch (is_electrolysis=False) creates hydrolysis_low instead of electrolysis_low.
  • All has* flags enabled: every module-gated entity created.
  • Acid tank reports on when a tank value is non-zero, and unknown when no tank data is available.
  • hasCL=1, hasHidro=0: hidro_fl2 is not created, but chlorine_pump still is.
  • Multi-pool setup creates entities for every pool.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • I understand the code I am submitting and can explain how it works.
  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.
  • Any generated code has been carefully reviewed for correctness and compliance with project standards.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies a diff between library versions and ideally a link to the changelog/release notes is added to the PR description.

To help with the load of incoming pull requests:

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a Vistapool binary_sensor platform and translations, plus test coverage to validate entity creation/state behavior across different module configurations.

Changes:

  • Introduce homeassistant/components/vistapool/binary_sensor.py with module-gated and diagnostic binary sensors.
  • Add binary_sensor entity translations to strings.json and register the new platform in __init__.py.
  • Add a new test module covering default, hydrolysis/electrolysis branching, all-modules, acid-tank, and multi-pool scenarios.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/components/vistapool/test_binary_sensor.py Adds tests validating binary sensor setup, gating, states, and registry defaults.
homeassistant/components/vistapool/strings.json Adds translation strings for the new binary sensor entities.
homeassistant/components/vistapool/quality_scale.yaml Updates exemption comments to reflect broader (non sensor-only) integration scope.
homeassistant/components/vistapool/const.py Adds PATH_HASIO constant used for IO module diagnostics.
homeassistant/components/vistapool/binary_sensor.py Implements binary sensor entities and setup logic (including hydrolysis/electrolysis and acid tank).
homeassistant/components/vistapool/init.py Registers the binary_sensor platform in PLATFORMS.

Comment thread homeassistant/components/vistapool/binary_sensor.py
Comment thread homeassistant/components/vistapool/binary_sensor.py Outdated
Comment thread tests/components/vistapool/test_binary_sensor.py Outdated
Comment on lines +186 to +200
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",
),
)
)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Holding off here for consistency: the merged sensor.py constructs electrolysis/hydrolysis inline in async_setup_entry the same way (https://github.com/home-assistant/core/blob/dev/homeassistant/components/vistapool/sensor.py search for is_electrolysis). Lifting only the binary_sensor version into module-level constants would split the convention across two files. Happy to do both in a follow-up PR if maintainers prefer that pattern integration-wide.

claude added 2 commits May 26, 2026 08:12
Without this, the entity is created on controllers where hasCL=1 but
hasHidro=0 and reports unknown permanently because the hidro.* subtree
isn't present at all.
Treating missing tank readings as off was misleading — it looked like "all
tanks OK" when the integration actually had no data to evaluate. Now the
sensor ignores None values and reports unknown only when every checked
tank path is unset.
Copilot AI review requested due to automatic review settings May 26, 2026 08:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Comment thread homeassistant/components/vistapool/binary_sensor.py
Comment thread homeassistant/components/vistapool/binary_sensor.py
Comment thread tests/components/vistapool/test_binary_sensor.py Outdated
Comment thread tests/components/vistapool/test_binary_sensor.py Outdated
Comment thread tests/components/vistapool/test_binary_sensor.py Outdated
claude added 2 commits May 26, 2026 08:21
The previous keys carried suffixes (_status, _alarm, _installed) that
didn't appear in the entity_id (which is derived from the translated
entity name) or in the strings.json names. The unique_id, which uses
description.key, ended up with cruft that didn't match either. Renaming
the keys to match the slugified entity name keeps key, translation_key,
unique_id suffix, and entity_id slug in sync.
Copilot AI review requested due to automatic review settings May 26, 2026 08:24
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Comment thread homeassistant/components/vistapool/binary_sensor.py Outdated
Comment thread homeassistant/components/vistapool/binary_sensor.py
Comment thread tests/components/vistapool/test_binary_sensor.py Outdated
Comment thread tests/components/vistapool/test_binary_sensor.py Outdated
Comment thread homeassistant/components/vistapool/binary_sensor.py
Comment on lines +22 to +63
"""Test binary sensors come up with the expected states for the default fixture."""
mock_vistapool_client.fetch_pool_data.return_value = mock_pool_data
mock_config_entry.add_to_hass(hass)

assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()

# Always-on entities.
assert hass.states.get("binary_sensor.my_pool_filtration").state == STATE_ON
assert hass.states.get("binary_sensor.my_pool_backwash").state == STATE_OFF
assert hass.states.get("binary_sensor.my_pool_heating").state == STATE_OFF

# Module-gated; the fixture sets hasHidro=1, hasPH=1, hasRX=1.
assert hass.states.get("binary_sensor.my_pool_hidro_flow").state == STATE_OFF
assert (
hass.states.get("binary_sensor.my_pool_hidro_cover_reduction").state
== STATE_OFF
)
assert hass.states.get("binary_sensor.my_pool_ph_pump").state == STATE_OFF
assert hass.states.get("binary_sensor.my_pool_ph_acid_pump").state == STATE_OFF
assert hass.states.get("binary_sensor.my_pool_ph_base_pump").state == STATE_OFF
assert hass.states.get("binary_sensor.my_pool_redox_pump").state == STATE_OFF

# is_electrolysis=True in the fixture, so electrolysis_low is exposed.
assert hass.states.get("binary_sensor.my_pool_electrolysis_low").state == STATE_OFF
assert hass.states.get("binary_sensor.my_pool_hydrolysis_low") is None

# Acid tank: any-of CD/CL/PH/RX — present because hasPH and hasRX.
assert hass.states.get("binary_sensor.my_pool_acid_tank").state == STATE_OFF

# Module-gated and absent in the fixture (hasCD=0, hasCL=0).
assert hass.states.get("binary_sensor.my_pool_hidro_fl2") is None
assert hass.states.get("binary_sensor.my_pool_chlorine_pump") is None

# Diagnostic module-installed entities are disabled by default.
for entity_id in (
"binary_sensor.my_pool_hidro_module",
"binary_sensor.my_pool_io_module",
):
entry = entity_registry.async_get(entity_id)
assert entry is not None
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead I would recommend to enable all entities by default and use snapshot_platform

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default-modules test now runs under entity_registry_enabled_by_default and pins the registry via snapshot_platform, so the disabled_by flag on every diagnostic entity, plus every key/translation_key/unique_id/device_class, is now captured in the .ambr snapshot rather than spread across per-entity assertions.

Kept the targeted tests for genuinely dynamic behavior (electrolysis vs. hydrolysis branch, multi-gate hidro_fl2, dosing_tank low/unknown, all-modules-enabled positive case, multi-pool fan-out) since those exercise code paths the default-fixture snapshot can't reach.

Comment on lines +90 to +111
"""Test every module-gated binary sensor comes up when all `has*` flags are set."""
mock_vistapool_client.fetch_pool_data.return_value = {
"main": {
"hasCD": 1,
"hasCL": 1,
"hasIO": 1,
"hasPH": 1,
"hasRX": 1,
"hasHidro": 1,
"version": 1,
},
"modules": {
"ph": {"tank": 0, "pump_high_on": 0, "pump_low_on": 0, "al3": 0},
"rx": {"tank": 0, "pump_status": 0},
"cl": {"tank": 0, "pump_status": 0},
"cd": {"tank": 0},
},
"hidro": {"is_electrolysis": True, "fl1": 0, "fl2": 0, "cover": 0, "low": 0},
"filtration": {"status": 0},
"backwash": {"status": 0},
"relays": {"filtration": {"heating": {"status": 0}}},
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have this combined with that snapshot test, we can then test that entities are not created when some things are false

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default: pool_data.json (hasCD=hasCL=hasUV=hasIO=0, hasPH=hasRX=hasHidro=1); snapshot pins that chlorine_pump, hidro_fl2, conductivity_module, etc. are not created (or are disabled-by-default for the diagnostic ones).
all_modules_enabled: new pool_data_all_modules.json with every has* flag set; snapshot pins that every module-gated entity is created, including the multi-gate hidro_fl2.

@home-assistant home-assistant Bot marked this pull request as draft May 26, 2026 09:12
@home-assistant
Copy link
Copy Markdown
Contributor

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

claude added 2 commits May 26, 2026 11:35
The entity ORs the tank-low flags of every installed dosing module
(pH / Rx / Cl / CD), not only the pH/acid tank, so "Acid tank" was
misleading. Renamed translation_key, strings.json key, unique_id
suffix, and class to dosing_tank / VistapoolDosingTankBinarySensor;
behavior is unchanged.
The entity reads an alarm flag (modules.ph.al3) with device_class
PROBLEM, but ph_pump alongside RUNNING-class ph_acid_pump and
ph_base_pump was ambiguous. Renamed key, translation_key, strings.json
key, and the test assertions to ph_pump_alarm / "pH pump alarm".
Copilot AI review requested due to automatic review settings May 26, 2026 11:45
claude added 3 commits May 26, 2026 11:45
…e test

Per joostlek's review on home-assistant#172234, replace the per-
entity assertions on the default fixture with snapshot_platform run
under entity_registry_enabled_by_default so the snapshot also pins the
disabled-by-default flag on every diagnostic entity. Dynamic-behavior
tests (hydrolysis branch, dosing tank low/unknown, fl2 multi-gate,
multi-pool, and the all-modules-enabled positive case for the multi-
gate) remain explicit.

The .ambr snapshot needs to be generated locally with
  uv run pytest tests/components/vistapool/test_binary_sensor.py --snapshot-update
and committed in a follow-up commit before this PR can pass CI.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread homeassistant/components/vistapool/binary_sensor.py
Comment thread homeassistant/components/vistapool/binary_sensor.py
Copilot AI review requested due to automatic review settings May 26, 2026 13:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment thread tests/components/vistapool/test_binary_sensor.py
Comment thread homeassistant/components/vistapool/binary_sensor.py Outdated
HA's home-assistant-async-load-fixtures pylint rule rejects the sync
load_json_object_fixture from async test bodies.
Copilot AI review requested due to automatic review settings May 26, 2026 13:30
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comment thread homeassistant/components/vistapool/binary_sensor.py Outdated
@fdebrus fdebrus marked this pull request as ready for review May 26, 2026 13:36
@home-assistant home-assistant Bot requested a review from joostlek May 26, 2026 13:36
fdebrus pushed a commit to fdebrus/home-assistant.io that referenced this pull request May 26, 2026
The code-PR review on home-assistant/core#172234 renamed two entities:
- acid_tank -> dosing_tank (the aggregator covers every dosing module,
  not only the acid tank)
- ph_pump -> ph_pump_alarm (the entity is a PROBLEM-class alarm, not
  the pH pump's running state)

Updates the binary_sensors list on the docs page to match.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 11702fded8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread homeassistant/components/vistapool/binary_sensor.py
Comment on lines +37 to +52
def _coerce_to_bool(value: Any) -> bool | None:
"""Coerce a coordinator value to bool.

The Vistapool API returns some numeric fields as strings (for example
`"0"` / `"1"`), so a plain `bool(value)` would treat the string `"0"` as
truthy. Strings are normalized via `int` first; anything unparsable is
treated as missing data and reported as unknown.
"""
if value is None:
return None
if isinstance(value, str):
try:
value = int(value)
except ValueError:
return None
return bool(value)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we just do if value in (True, "1") in is_on?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to simplify. Two questions before I push:

Do you also want me to drop the None → unknown semantic in is_on and VistapoolDosingTankBinarySensor.is_on? value in (True, "1") always returns a bool, so the entity reads "Off" (i.e. "OK" for PROBLEM class) when data is genuinely missing instead of "Unknown". Currently a Copilot review earlier in this batch pushed me toward returning None for missing data happy to revert that if you'd rather have the simpler behavior.

Same defensive concern was raised on the setup-time gates (exists_path truthiness, is_electrolysis, dosing-module any(...)). Want me to apply the same simplification there (drop _coerce_to_bool entirely, trust the API returns ints), or just inline the in (True, "1") check for is_on and keep the helper for the gates?

Thanks as always for your review and helpful hints 👍

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay so

  1. If data is genuinely missing, we should indeed return None. If data was never expected there, we should not have created the entity to begin with
  2. I think you can just do this login in is_on. I think ideally this kind of data is pre-processed in the library and the library acts as a nice abstraction between the mess of the API. This way the consumers of the library don't have to deal with this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, so

  1. Simplify is_on to keep the None → unknown semantic but drop the helper: if value is None: return None; return value in (True, "1").
  2. Same shape for VistapoolDosingTankBinarySensor.is_on (filter None over the four tank paths, return None if all missing, otherwise any(v in (True, "1") for v in present)).
  3. Remove _coerce_to_bool entirely and let the setup-time gates use plain truthiness those have an exists_path/entity-creation contract that already handles "data was never expected here".
  4. Open a follow-up issue/PR on aioaquarite to normalize the numeric-as-string values once at the library boundary, so the integration stays a thin wrapper.

Drops the _coerce_to_bool helper. is_on now keeps the None -> unknown
semantic for missing data but uses a one-liner equality check
(value in (True, "1")) for the runtime state, matching joostlek's
suggestion. The setup-time gates (exists_path, PATH_HASHIDRO,
is_electrolysis, dosing-module any()) go back to plain truthiness,
matching the pattern used in sensor.py.

The numeric-string-to-bool defensiveness moves to the aioaquarite
library boundary as a follow-up, so the integration stays a thin
wrapper instead of duplicating the normalization. The
test_binary_sensors_string_has_flags case was pinning the in-integration
defensiveness on the gates and is removed; the runtime string case is
still covered by test_binary_sensors_string_values.
# Conflicts:
#	homeassistant/components/vistapool/__init__.py
#	homeassistant/components/vistapool/quality_scale.yaml
#	homeassistant/components/vistapool/strings.json
Copilot AI review requested due to automatic review settings June 2, 2026 11:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment thread homeassistant/components/vistapool/binary_sensor.py
Comment thread homeassistant/components/vistapool/binary_sensor.py
Comment thread tests/components/vistapool/test_binary_sensor.py
…utation

Same fix as the parallel work on vistapool-number and vistapool-select.
The merge from upstream/dev pulled in the button platform's tests, which
mutate _LED_DATA via the optimistic update. Wrap each use site in
deepcopy() so each test starts from a fresh dict and pytest-xdist
ordering doesn't cause flakes.
@fdebrus fdebrus marked this pull request as draft June 2, 2026 13:01
@fdebrus fdebrus marked this pull request as ready for review June 2, 2026 13:56
Copilot AI review requested due to automatic review settings June 2, 2026 13:56
@home-assistant home-assistant Bot requested a review from joostlek June 2, 2026 13:56
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Comment thread homeassistant/components/vistapool/binary_sensor.py
Comment thread homeassistant/components/vistapool/binary_sensor.py
Comment thread homeassistant/components/vistapool/binary_sensor.py
Comment thread tests/components/vistapool/test_binary_sensor.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants