Add IrrigationSystem accessory support to HomeKit integration - #177434
Add IrrigationSystem accessory support to HomeKit integration#177434donavanbecker wants to merge 26 commits into
Conversation
|
Hey there @bdraco, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
There was a problem hiding this comment.
Pull request overview
Adds HomeKit Irrigation System accessories that group multiple Home Assistant valves.
Changes:
- Adds grouped valve configuration.
- Implements irrigation services and command routing.
- Updates accessory selection and translations.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
type_switches.py |
Implements irrigation accessories. |
config_flow.py |
Adds valve grouping options. |
accessories.py |
Routes grouped valves. |
const.py |
Adds irrigation constants. |
strings.json |
Adds option-step text. |
Comments suppressed due to low confidence (1)
homeassistant/components/homekit/type_switches.py:756
- Implement the duration lifecycle before exposing
SetDuration/RemainingDuration. The selected duration is only stored in an otherwise-unused dictionary entry: opening a zone never schedules its close or updates either remaining-duration characteristic, so a five-minute HomeKit run can stay open indefinitely while reporting zero seconds remaining. Start/cancel a per-zone timer and update the zone/system countdowns, or omit these characteristics until real duration state is available.
def _set_valve_duration(self, entity_id: str, value: int) -> None:
"""Store the HomeKit-requested run duration for a valve."""
chars = self._valve_chars.get(entity_id)
if chars:
chars["duration"] = max(int(value), 0)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (3)
homeassistant/components/homekit/type_switches.py:754
- Handle primary-valve unavailable transitions before the base callback filters them out.
HomeAccessory.async_update_state_callbackreturns for unavailable/unknown states, so this override never sets the primary zone or systemStatusFaultand can leave priorActive/InUsevalues latched; override event handling for those states or subscribe the primary through the same multi-valve path.
@callback
@override
def async_update_state_callback(self, new_state: State | None) -> None:
"""Handle primary valve state changes, including unavailable/unknown."""
homeassistant/components/homekit/type_switches.py:775
- Preserve locally selected durations across Home Assistant state updates.
_duration_from_statereturns 300 when a valve has no duration attribute, so the next open/close event overwrites any value saved by_set_valve_duration; return a sentinel for no device duration and only replace the cached local value when an attribute is actually present.
if chars is None:
return
has_fault = state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN)
tests/components/homekit/test_type_switches.py:1261
- Assert the fault characteristic this test is meant to cover. The test only checks
Active/InUse, which would also pass ifStatusFaultremainedNO_FAULT, so the unavailable-zone behavior can regress undetected.
front_chars = acc._valve_chars["valve.front_lawn"]
assert front_chars["duration"] == 900
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
homeassistant/components/homekit/type_switches.py:695
- Implement the
SetDurationcountdown before exposing this characteristic. HomeKit's duration contract requires an active valve to transition back to inactive when the countdown expires, but this implementation only stores the value and leavesRemainingDurationstatic, so a five-minute zone can remain open indefinitely; track a deadline/timer per zone, close it on expiry, and derive remaining time from that deadline.
char_set_duration = serv_valve.configure_char(
CHAR_SET_DURATION,
value=initial_duration,
properties=IRRIGATION_DURATION_PROPERTIES,
setter_callback=lambda v, eid=entity_id: self._set_valve_duration(
homeassistant/components/homekit/type_switches.py:586
- Report
ProgramModeas “No Program Scheduled.” HAP defines that value as0; value1means “Program Scheduled,” so the current constant advertises a schedule that this accessory does not implement.
HK_PROGRAM_MODE_NO_SCHEDULE_ACTIVE = 1
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
homeassistant/components/homekit/type_switches.py:835
- Enforce the selected duration when opening a zone.
SetDurationis only stored/displayed here: theopen_valvecall is never followed byclose_valve, and without device duration attributesRemainingDurationstays at the original value, so a five-minute HomeKit run can remain open indefinitely. Track a per-zone deadline, schedule/cancel its close callback, and derive remaining time from that deadline (or do not expose these duration characteristics without a backing implementation).
def _set_system_active(self, value: int) -> None:
"""Close all valves when HomeKit deactivates the irrigation system."""
if not value:
for entity_id in self._valve_entity_ids:
self.hass.async_create_task(
homeassistant/components/homekit/type_switches.py:757
- Keep accessory availability independent of the arbitrary primary zone. Before this override runs,
HomeAccessory.async_update_event_state_callback()setsavailablesolely fromself.entity_id; when the first selected valve becomes unavailable, the entire multi-zone accessory becomes unavailable even though the other linked zones are usable and this code advertises per-zone faults. Override availability for this grouped accessory to remain available while any zone exists, relying on the zone/systemStatusFaultcharacteristics for individual failures.
self._update_system_state()
return
self._sync_valve_chars(new_state.entity_id, new_state)
self._update_system_state()
homeassistant/components/homekit/type_switches.py:747
- Mark a removed linked valve as faulted and inactive instead of dropping the event. A state-removal event has
new_state=None; returning here preserves the zone's previousActive,InUse, and remaining-duration values indefinitely and can leave the whole irrigation system shown as running. Add shared missing-state handling that clears the zone and sets its/systemStatusFault(also usable when a linked state is absent during initialization).
def _async_linked_valve_state_changed(
self, event: Event[EventStateChangedData]
) -> None:
d70f308 to
8aeefba
Compare
Add support for grouping multiple valve entities into a single HomeKit IrrigationSystem accessory instead of individual Valve accessories. Changes: - Add IrrigationSystem accessory class in type_switches.py that creates a primary IrrigationSystem service with linked child Valve services, one per grouped valve entity. Supports Active, InUse, ProgramMode, RemainingDuration on the system level and Active, InUse, ValveType, SetDuration, RemainingDuration per zone. Subscribes to state changes for both the primary and linked valve entities. - Add async_step_valves options flow step that lets users select two or more valve entities to group as one Irrigation System. Fits into the existing flow after the climate step. Stores config via CONF_TYPE and CONF_LINKED_IRRIGATION_VALVES on the primary valve entity config entry and CONF_IRRIGATION_CONTROLLER on each linked valve. - Update accessories.py to route valve entities to the IrrigationSystem or Valve type based on entity_config, and skip linked valve entities that are already managed by an irrigation system group. - Add new constants: SERV_IRRIGATION_SYSTEM, CHAR_PROGRAM_MODE, TYPE_IRRIGATION_SYSTEM, CONF_LINKED_IRRIGATION_VALVES, CONF_IRRIGATION_CONTROLLER. - Add strings.json entry for the new valves options step. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Filter grouped valve defaults to included valves in options flow - Resync commanded irrigation valves when service calls fail - Add irrigation accessory mapping and config-flow regression tests - Add irrigation switch behavior tests for durations and failure resync Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use type: ignore[untyped-decorator] on the IrrigationSystem run method decorator so mypy suppresses the correct error code. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Replace temporary list used only for iteration with an in-place tuple (and tuple default) to satisfy pylint consider-using-tuple. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Replace list concatenation with iterable unpacking to satisfy Ruff RUF005 in IrrigationSystem initialization. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Run ruff-format and ruff-check hooks and commit the resulting auto-fixes so CI working tree remains clean. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Clear stale irrigation entity metadata globally before regrouping - Preserve HomeKit-selected duration when valve has no duration attrs - Handle primary unavailable/unknown updates in irrigation accessory - Extend irrigation tests for status fault and duration persistence Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Run prettier on homeassistant/components/homekit/strings.json to match CI hook output and keep prek working tree clean. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…and invalid attrs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Deduplicate grouped valves with dict.fromkeys before length check to prevent self-referential controllers and duplicate Valve services - Use dt_util.as_utc() on parsed end_time to normalize naive/aware datetimes before subtracting utcnow(), avoiding TypeError Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Track runtime updates for device-provided remaining/end_time values without scheduling local auto-close service calls - Reuse a normalized end_time helper for remaining calculation - Add test coverage ensuring end_time-based remaining counts down without extra state events Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
8aeefba to
89dccb6
Compare
Proposed change
Add native support for grouping multiple
valveentities into a single HomeKit Irrigation System accessory. Previously the HomeKit integration exposed each valve entity as a separate HomeKitValveaccessory.With this change, users can optionally group two or more valve entities into one HomeKit
IrrigationSystemaccessory — presenting them as a sprinkler tile in Apple Home with individual zone controls. When valve entities are included in the bridge, a new "Valve configuration" options step appears (after the climate configuration step) where users can multi-select which valves to group as an irrigation system. Selecting fewer than two leaves the valves as individual accessories.How it works:
IrrigationSystemservice; remaining selected valves are linked childValveservices (zones)Active/InUsecharacteristics and the overall systemActive/InUsestatevalve.open_valve/valve.close_valveon the corresponding HA entitySetDuration(stored locally; defaults 5 min) andRemainingDurationType of change
Additional information
Files changed:
const.pyTYPE_IRRIGATION_SYSTEM,CONF_LINKED_IRRIGATION_VALVES,CONF_IRRIGATION_CONTROLLER,SERV_IRRIGATION_SYSTEM,CHAR_PROGRAM_MODEtype_switches.pyIrrigationSystemaccessory class (registered as"IrrigationSystem") with primary service + linked child Valve services, state sync, and HomeKit ↔ HA command routingaccessories.pyTYPE_IRRIGATION_SYSTEMconfig →IrrigationSystem;CONF_IRRIGATION_CONTROLLER(linked valve) → skip; else →Valveconfig_flow.pyasync_step_valvesoptions flow step with multi-select EntitySelector; wired into existing step chain after climate stepstrings.json"valves"step stringsConfig flow mockup — new "Valve configuration" step:
Result in Apple Home:
Checklist
ruff format homeassistant tests)If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running:
python3 -m script.hassfest.requirements_all.txt.Updated by running
python3 -m script.gen_requirements_all.To help with the load of incoming pull requests: