Skip to content

Realtime Zone Sensors #419

Description

@DAB-LABS

Hi sebr, first off the v4.1.0 work is great. PR #414's next_watering was a clean piece of architecture, and the data path it relies on made it natural to extend. This issue proposes building on top of that, and I'd appreciate your read before I open a PR.

Worth noting up front: the previously-shipped bhyve_next_watering.py python_script (retired in #400) was clearly tracking the same itch. It had a working Smart Watering path, with TODO placeholders for the manual programs. The work here picks up where that left off, fills in the manual program math, and surfaces it as first-class entities rather than a python_script.

The use case

I wanted a sensor that tells me when each individual zone is going to fire so I can move my lawn furniture out of the way before the wrong sprinkler hits it. I'm a Smart Watering user, so the controller's reported start time would have been good enough for my own use. But once I started exploring the data path, I wanted the per-zone resolution to be correct for everyone, including users on manual programs and one-off zone test runs from the Orbit app.

The gap

What I wanted was per-zone resolution: "when does Front - Street fire?" Not "when does the controller next start?" The two answers can differ by 30+ minutes for a mid-sequence zone, and the data to compute the per-zone time is already at program.watering_plan[*].run_times in the coordinator state.

Manual programs are similar. Without a per-zone sensor, there's no predictive valve open time at all for any zone past the first. The user is left to figure out the offset themselves from the program's start time and the run durations.

What we built

Per-zone calculation across all four manual program types plus Smart Watering:

  • Smart Watering (program e): zone's open time = cycle start + cumulative duration of preceding stations from watering_plan[*].run_times.
  • Manual days program: combines start_times[0] with the next configured weekday from frequency.days (B-hyve numbering: 0=Sunday, 1=Monday).
  • Manual interval program: advances from frequency.interval_start_time by frequency.interval days until the next future occurrence.
  • Manual odd / even programs: matches the next calendar date with the configured parity.

For multi-zone manual programs, the same per-station offset applies. Zones with multiple programs assigned return the earliest future run, with deterministic tie-break on program letter so the result doesn't depend on dict iteration order.

What we noticed during testing

While verifying the per-zone sensor on dev HA, I noticed something that surprised me: when I toggled a manual program switch directly in HA (visible in the video below) or within the Orbit App, the per-zone sensors updated within a second. But next_watering stayed stale until the next coordinator polling cycle or an integration reload. I have been using an automation to reload the integration every hour to make sure I had the latest start datetime.

The integration's websocket already updates coordinator.data["programs"] immediately on program_changed events, which is why the per-zone sensors are realtime. next_watering reads from device.status.next_start_time, which is on the polled refresh cadence. Different data path, different freshness.

Rather than touching next_watering, we built a sibling sensor that aggregates the new per-zone calculations and inherits their realtime freshness, coexisting with PR #414's sensor.

zone_bhyve.MP4

In the first part of the video, I'm toggling interval, odd, and even program switches directly in HA. The per-zone sensors and the new device-level summary update immediately, while next_watering stays stale. The toggles round-trip through Orbit's API and come back via websocket, so the same path that catches Orbit-app changes also catches HA-side changes.

In the last few seconds, I trigger a manual zone start from the Orbit app on Front - West. Watch Zone Now Watering flip from "All Valves Idle" to "Front - West" and the pump icon switch. The same data path catches both scheduled runs and ad-hoc test starts, which is what makes Zone Now Watering useful for automations.

That insight pulled in two more entities, both useful for automations:

  • Zone Now Watering (visible toggling in the video): friendly name of the zone currently watering, sourced from device.status.watering_status. Returns idle (translated to "All Valves Idle") when no zone is running. Catches scheduled runs and ad-hoc test starts.
  • Zone Next Watering Name: companion to Zone Next Watering, exposes the zone name as state. Lets users build single-tile dashboard views combining "Front - Street, in 2 minutes" without template logic.

The four sensors

Sensor Scope State
Zone Watering: {zone_name} Per zone TIMESTAMP, when this zone next fires
Zone Next Watering Per device TIMESTAMP, earliest future watering across all zones
Zone Next Watering Name Per device STRING, zone name responsible for Zone Next Watering
Zone Now Watering Per device STRING, currently watering zone or idle

All four are disabled by default. Users opt in via the entity registry, which avoids dropping six-plus new entities on existing installs.

Scope

Touches sensor.py, the two translation files, tests/test_sensor.py, and README.md. No changes to coordinator.py, valve.py, switch.py, config_flow.py, or the API client. Tests cover all five frequency types, multi-program priority, edge cases, entity registration, and the dynamic icon.

Side notes

Program letter behavior. B-hyve devices support up to four manual programs (A through D) plus one Smart program (E). The Orbit app surfaces the manual letters as user-facing labels in the Program tab. While testing, a manual program landed at letter d after a delete-and-recreate cycle. Orbit assigns letters by lowest available slot and recycles them. The README's existing line that "Slot d does not have a known use at this stage" is slightly outdated; slot d is the fourth user-fillable manual program slot. The PR currently includes a small one-paragraph correction. Happy to keep bundled or split, your preference.

Smart classification. The orchestrator uses program.is_smart_program is True as the canonical Smart marker (matching valve.py). Doesn't infer Smart from program == "e", since the letter is reusable.

Open questions

  1. Does the per-zone use case land for you? We were able to get fairly close predictions (as actual smart watering times can be last minute) to automate against.
  2. Are four new sensor types coexisting with 'next_watering' OK, or would you prefer different scoping? Curious whether you'd be open to 'next_watering' itself adopting the realtime data path internally, while keeping its current name and entity_id stable for backward compatibility?

Status

Built and verified on dev HA against a 6-zone Brookdale Timer. Ruff clean, pytest passing. Branch is local; not pushed to a fork yet, waiting on your read before opening any PR.

Thanks for taking a look. 🕵️‍♂️

~David

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions