Skip to content

Add a custom management panel for Irrigation Unlimited - #239

Open
uspass wants to merge 106 commits into
rgc99:masterfrom
uspass:master
Open

Add a custom management panel for Irrigation Unlimited#239
uspass wants to merge 106 commits into
rgc99:masterfrom
uspass:master

Conversation

@uspass

@uspass uspass commented Jun 12, 2026

Copy link
Copy Markdown

Add a custom management panel for Irrigation Unlimited

Summary

This PR adds a full custom sidebar panel ("Irrigation") that lets users manage all Irrigation Unlimited configuration — controllers, zones, sequences, schedules, adjustments, all-zones defaults, and global settings — directly from the Home Assistant UI, with no YAML editing required.

The upstream config-entry setup wizard / options flow / subentry flow (~760 lines in config_flow.py) is replaced by an instant, zero-step config entry. All CRUD that wizard used to handle is now done through the new panel, backed by a persistent JSON store and a dedicated WebSocket API.

The panel is fully multilingual (English / French / Italian / Romanian / Spanish, easy to extend) and shows a live "running" indicator on zones currently irrigating.


What's new

Backend

  • storage.py (new) — IUStore: persistent, panel-managed source of truth for all controller configuration (Store(STORAGE_VERSION, STORAGE_KEY)). Provides:

    • Controller / Zone / Sequence / Schedule / Adjustment / Sequence-Zone / Sequence-Schedule CRUD
    • All-Zones-Config (5.2) and global config (5: granularity, refresh_interval, rename_entities, history, clock) CRUD
    • to_iu_config_multi() / generate_yaml() — converts panel storage into the full multi-controller IU config (and equivalent YAML), applying:
      • IU-schema default dropping (omits untouched fields, e.g. enabled: true, allow_manual: false, future_span: 3, anchor: "start", preamble/postamble: 00:00:00, ...)
      • HH:MM / HH:MM:SS time normalization (accepts 8:2 → stores/exports 08:02:00)
      • future_span as int (avoids 3.0 in YAML)
      • entity_id always wrapped as a list (required by IU's iteration)
      • weekday / month / zone_id rendered as YAML flow-style lists ([mon, wed, sat])
    • One-time migration from the old flat {"zones": [...], "sequences": [...]} format
  • websocket_api.py (new) — WebSocket commands consumed by the panel for every CRUD operation (irrigation_unlimited/config, save_controller, save_zone, save_schedule, save_adjustment, save_sequence, save_sequence_zone, save_sequence_schedule, save_all_zones_config, save_global_config, generate_yaml, and matching delete_*). Every save/delete schedules a debounced (4s) reload of the config entry so the IU coordinator picks up changes without a HA restart.

    • One-time auto-import: if a panel controller has no zones/sequences yet, and a YAML-configured controller with the same name exists, its zones/sequences are imported automatically.
  • config_flow.py (rewritten, 762 → ~30 lines) — single-step, no-form setup. async_set_unique_id ensures only one config entry can exist (the panel manages multiple physical controllers internally).

  • __init__.py (rewritten):

    • Registers a static file route (/irrigation_unlimited_static → bundled frontend/) and a custom sidebar panel ("Irrigation", mdi:water).
    • Loads/shares IUStore and registers the WebSocket commands.
    • One-shot migration of any pre-existing entry.data / HA-native subentries into IUStore.
    • async_unload_entry now preserves store / yaml_config / panel & static-path / WS registration across reloads (previously hass.data[DOMAIN] was fully wiped on every reload, forcing re-registration on every panel save).
    • New async_remove_entry for full cleanup (removes the sidebar panel) when the integration is deleted.
  • manifest.json — added "integration_type": "hub".

Frontend (new frontend/ directory)

A vanilla-JS Web Component (ES modules, no build step), split for maintainability:

frontend/
├── irrigation-unlimited-panel.js   – class skeleton, lifecycle, finders
├── render-mixin.js                 – panel HTML (cards, lists)
├── forms-mixin.js                  – modal forms
├── actions-mixin.js                – event handling, save/delete, validation, YAML export
└── lib/
    ├── constants.js, translations.js, helpers.js, form-fields.js, styles.js
    └── translations/en.json, es.json, fr.json, it.json, ro.json

Features:

  • Full CRUD UI for controllers, zones, sequences, sequence-zones, schedules, sequence-schedules, adjustments, all-zones-config and global config
  • Multilingual UI (English / French / Italian / Romanian / Spanish), auto-detected from hass.language; adding another language is just a new translations/<lang>.json.
  • Live "running" indicator: a pulsing dot + highlighted row on any zone whose entity_id is currently on, updated on every hass update without a full re-render (no flicker).
  • Inline field validation (time formats, numbers, dates) before save.
  • Entity picker with autocomplete for switch / input_boolean / valve entities.
  • "View YAML" button — renders the exact config IU would load, generated server-side by IUStore.generate_yaml().

Documentation

All new files and all modified sections (vs. upstream master) are documented with English docstrings/comments explaining what changed and why, to make future diffs against upstream easier.


Testing

  • Manually tested: add/edit/delete controller, zone, sequence, sequence-zone, schedule, sequence-schedule, adjustment, all-zones-config, global config; YAML export; language switch ; live running indicator; HH:MM normalization edge cases.

Breaking changes

None for new installs (instant setup). Existing installs with controllers configured via the old options/subentry flow or via YAML are migrated automatically into the new panel storage on first load.

uspass added 30 commits June 12, 2026 16:59
NEW FILE -- part of the custom frontend panel, not present in upstream master.
NEW FILE -- part of the custom frontend panel, not present in upstream master.
NEW FILE -- part of the custom frontend panel, not present in upstream master (upstream ships no frontend assets).
NEW FILE (and entire frontend/ directory) -- not present in upstream master.
NEW FILE -- part of the custom frontend panel, not present in upstream master.
NEW FILE -- part of the custom frontend panel, not present in upstream master.
NEW FILE -- part of the custom frontend panel, not present in upstream master.
NEW FILE -- part of the custom frontend panel, not present in upstream master.
NEW FILE -- part of the custom frontend panel, not present in upstream master.
NEW FILE -- not present in upstream master.
uspass added 9 commits June 30, 2026 15:15
Add _fmtTime helper to format schedule's time field for display, since time can now be a string (HH:MM:SS), a sun-event dict {sun, before?, after?}, or a cron dict {cron}. Previously the raw value was
interpolated directly into the template, producing "[object Object]"
for sun/cron schedules. Applied in both _schedList (zone schedules)
and _sqSchedList (sequence schedules).
Add optional `required` parameter (default false) to fText and
fEntityPicker. When true, renders a red asterisk span next to the
field's label, matching the visual convention used for fields that
are vol.Required in IU's schema.
- Add .req: red asterisk style for required field labels (used by
  fText/fEntityPicker's new `required` parameter).

- Add layout styles for the zone's multi-entity picker:
  * .ep-header: label + add button on one row (flex space-between)
  * .ep-header .ep-add:disabled: dimmed state when the add button is disabled (last row empty)
  * .ep-multi / .ep-row: vertical stack of entity rows, each row
    flex-aligned with its delete button
Add 10 new translation keys used by recent UI additions:

Entity picker validation:
- err.entity_id_req, err.entity_id_domain, err.entity_id_not_found
- btn.add_entity (currently unused -- superseded by the inline +
  button in .ep-header; removed)

Schedule validation:
- err.sched_time_req, err.duration_req

YAML export:
- btn.copy, btn.copied, btn.download, err.copy_failed
Entity picker validation:
- err.entity_id_req, err.entity_id_domain, err.entity_id_not_found
- btn.add_entity (currently unused -- superseded by the inline +
  button in .ep-header; removed)

Schedule validation:
- err.sched_time_req, err.duration_req

YAML export:
- btn.copy, btn.copied, btn.download, err.copy_failed
Add 10 new translation keys used by recent UI additions:

Entity picker validation:
- err.entity_id_req, err.entity_id_domain, err.entity_id_not_found
- btn.add_entity (currently unused -- superseded by the inline +
  button in .ep-header; removed)

Schedule validation:
- err.sched_time_req, err.duration_req

YAML export:
- btn.copy, btn.copied, btn.download, err.copy_failed
Add 10 new translation keys used by recent UI additions:

Entity picker validation:
- err.entity_id_req, err.entity_id_domain, err.entity_id_not_found
- btn.add_entity (currently unused -- superseded by the inline +
  button in .ep-header; removed)

Schedule validation:
- err.sched_time_req, err.duration_req

YAML export:
- btn.copy, btn.copied, btn.download, err.copy_failed
Entity picker validation:
- err.entity_id_req, err.entity_id_domain, err.entity_id_not_found
- btn.add_entity (currently unused -- superseded by the inline +
  button in .ep-header; removed)

Schedule validation:
- err.sched_time_req, err.duration_req

YAML export:
- btn.copy, btn.copied, btn.download, err.copy_failed
Add 10 new translation keys used by recent UI additions:

Entity picker validation:
- err.entity_id_req, err.entity_id_domain, err.entity_id_not_found
- btn.add_entity (currently unused -- superseded by the inline +
  button in .ep-header; removed)

Schedule validation:
- err.sched_time_req, err.duration_req

YAML export:
- btn.copy, btn.copied, btn.download, err.copy_failed
@rgc99

rgc99 commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Hi @uspass, thank you for all your good work on this. I am currently travelling through the Red Centre slowly heading north. I have not had the time to review the changes. It's a little difficult given the resources at my disposal. In fact, there is a bit of a backlog at the moment as didn't expect to receive this many PR's all at once. I am happy for all the support I have been receiving recently.

Thanks for everything.
Cheers Robert

@smoki3

smoki3 commented Jul 6, 2026

Copy link
Copy Markdown

Hi uspass,

Found one minor bug:
When you open the UI on mobile the sidebar of HA is not visible anymore:

Screenshot_20260706-095053

uspass added 10 commits July 8, 2026 00:40
- Add set narrow(val) setter that reflects the narrow state as a host
  attribute ("narrow") so that CSS :host([narrow]) ha-menu-button
  can show the sidebar toggle on mobile.

- Detect HA Companion app (Android/iOS) in the constructor via
  window.externalBus, window.webkit.messageHandlers.externalBus,
  and io.robbie.HomeAssistant UA string. The Companion app always
  passes narrow=false (it has native navigation), so we force
  narrow=true to keep the hamburger visible.

- Set hass and narrow properties on ha-menu-button before appending
  to the shadow root (avoids null-hass crash on LitElement's first
  render cycle).

- Add set panel(val) setter stub for future use.
Replace the <a href="..." target="_top"> Integration button with a
nav-integration action that uses history.pushState() +
window.dispatchEvent(new Event("location-changed")).

target="_top" caused the HA Companion app to open an external browser
instead of navigating within the app. The location-changed event is
HA's standard routing mechanism and works correctly in both the
browser and the Companion app WebView.
Hide ha-menu-button by default; show it via :host([narrow]) when the panel host element carries the narrow attribute (set by the narrow setter in irrigation-unlimited-panel.js when HA signals mobile mode).
…ion app

- Add ha-menu-button to the toolbar. Hidden by default via CSS;
  visible when the panel host carries the narrow attribute
  (set by the narrow setter in irrigation-unlimited-panel.js).

- Replace <a href="..." target="_top"> Integration link with a plain
  button (data-a="nav-integration"). target="_top" caused the HA
  Companion app to open an external browser. The new handler in
  actions-mixin uses history.pushState() + location-changed event.
@uspass

uspass commented Jul 7, 2026

Copy link
Copy Markdown
Author

Hi @smoki3,

Thank you for reporting this!
I have fixed the bug.

For any future issues, please open them directly on my GitHub repository — it makes it much easier for me to track, reference and follow up on bug reports.

Thanks again!

uspass and others added 6 commits July 13, 2026 10:32
Restructure the panel layout so the toolbar is a sibling of the content wrapper instead of nested inside it: _html() returns toolbar + .wrap as siblings inside a .page flex column. Toolbar uses HA header variables with fallbacks (--header-height, --app-header-background-color, --app-header-text-color, --app-header-border-bottom) plus safe-area insets for the Companion app notch/status bar. Native layout order (menu button, title flex:1, actions right) with new .bhdr button style. Content scrolls independently under the sticky header. Direction credit: #10
The toolbar used box-sizing: content-box, so the 1px border-bottom was added on top of --header-height — 57px total vs HA's native 56px header, leaving a visible 1px gap at the sidebar boundary.

Switch to border-box with the safe-area folded into the height:
height: calc(var(--header-height, 56px) + var(--safe-area-inset-top, 0px)). Desktop aligns exactly (inset = 0); on notched phones the padding-top consumes the inset while content keeps the full header height.

Reported in issue #10.
Omit 'enabled' key from YAML output for cleaner format.
Updated error handling for schedule time and duration requirements.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants