Bundles the program viewer side panel (Lit/TS), program writeback API
(DownloadProgram + Clear/Clone), and the manifest.json documentation
URL fix (now points at hai-omni-pro-ii.warehack.ing instead of the
repo, matching pyproject.toml).
Phase C of the program viewer. Replaces the "panel coming soon" stub
with a real Lit-based side panel that consumes the Phase-B websocket
commands.
Layout (top to bottom):
* Header — title + total/filtered count
* Filter bar — search box (substring match), trigger-type chips
(TIMED / EVENT / YEARLY / WHEN / AT / EVERY / REMARK), a clearable
"filtering on <ref>" pill when an entity filter is active
* Two-column body: program list on the left, slide-in detail panel
on the right. Collapses to single-column on `narrow` view.
The program list renders one row per program (or per chain head, for
clausal multi-record programs). Each row carries the slot number,
the rendered one-line summary token stream, and meta pills for
trigger type / condition count / multi-action count.
The detail panel renders the full structured-English token stream
inside a styled <pre>. A "Fire now" button calls
``omni_pca/programs/fire`` over the wire — the panel actually
runs the program. For chain detail the spanned slot range is shown
underneath.
REF tokens are rendered as `<button>` elements that click to filter
the list to "programs that mention this entity" — the most useful
navigational affordance for the "why is this happening?" use case.
Live-state badges (SECURE / NOT READY / ON 60% / Away / 72°F / …) are
appended to REF tokens via the Phase-B coordinator-backed
StateResolver. The panel polls ``programs/list`` every 5 seconds to
refresh badges; switching to push-event subscriptions is a follow-up
when polling overhead becomes visible.
Theming uses HA's standard CSS variables (--primary-color,
--card-background-color, --divider-color, etc.) so the panel inherits
the user's HA theme automatically.
Build pipeline:
* TypeScript source under ``custom_components/omni_pca/frontend/src/``
* esbuild bundles entry → ESM in one self-contained file
* Output at ``custom_components/omni_pca/www/panel.js`` (~34 KB
minified) is committed so end-users don't need Node installed
* ``npm run watch`` for HA-dev-time iteration
* tsconfig has strict mode + noUnusedLocals; bundle currently
type-checks clean
Manifest declares deps on ``http`` and ``websocket_api``; ``frontend``
and ``panel_custom`` are loaded opportunistically (they require
``hass_frontend`` which the test harness doesn't ship — keeping them
out of the manifest deps keeps tests green).
Full suite: 634 passed, 1 skipped (no test changes; the integration
side hasn't moved since Phase B).
Re-tags from a tree that includes the brand assets — v2026.5.11 was
tagged before brand/icon.png landed, so the HACS submission-side
validator saw the tag as brand-less.
Library + integration version bumped to 2026.5.14; manifest requirement
pinned to the matching PyPI build. CHANGELOG entry covers everything
since 2026.5.10: SetupData decoding sweep, AND/OR evaluator, EVENT
taxonomy, WHEN/AT/EVERY clausal chains, brand inline, HACS+hassfest
workflow, GitHub URL switch, websocket side-panel, program_renderer.
Phase B of the program viewer. Three websocket commands and a stub
side-panel registration wire the HA integration to consume the
program_renderer library.
Websocket commands (all namespaced ``omni_pca/programs/``):
* ``list`` — paginated, filterable summaries. Filters: trigger_types
(TIMED / EVENT / YEARLY / WHEN / AT / EVERY), references_entity
(e.g. ``"unit:7"``), case-insensitive substring search. Each row
carries summary tokens + a flat ``references`` list for filter UI.
* ``get`` — full structured-English detail for a slot. Clausal
chains return as one logical unit even when the user clicked an
interior slot.
* ``fire`` — sends ``Command.EXECUTE_PROGRAM`` over the wire so the
panel runs the program now. Returns ``{slot, fired: true}`` on
success or a structured error.
Token serialisation uses short keys (k/t/ek/ei/s) for compact wire
format — the panel's 1500-slot table on a busy install fits in a few
hundred KB of JSON.
Coordinator-backed resolvers:
* ``_CoordinatorNameResolver`` — pulls names from data.zones / units /
areas / thermostats / buttons (HA-side ZoneProperties etc.)
* ``_CoordinatorStateResolver`` — pulls live state from *_status maps
so every websocket call sees the freshest available overlay without
round-tripping the panel. SECURE / NOT READY / BYPASSED for zones,
OFF / ON / ON 60% for units, Day / Night / Away for areas,
°F for thermostats.
Side-panel registration: ``async_register_side_panel`` registers a
custom panel under ``Omni Programs`` in HA's sidebar with a
``mdi:script-text-outline`` icon. Bundle is served at
``/api/omni_pca/panel.js`` via a static-path registration. A
working stub panel.js ships now so the wiring is exercisable;
Phase C will drop the real Lit/TS bundle into the same path.
Panel registration is wrapped in a try/except + a once-per-HA-boot
guard so test environments without ``hass_frontend`` installed don't
break the rest of the integration. The manifest only lists ``http``
and ``websocket_api`` as hard dependencies for the same reason —
panel_custom is opportunistic.
10 new HA-integration tests cover list/get/fire end-to-end plus
filters, pagination, search, live-state overlay, and structured-error
returns for bad entry_id / missing slot.
Full suite: 634 passed, 1 skipped (up from 624).
- manifest.json: keys reordered to domain, name, then alphabetical
- strings.json + translations/en.json: rephrase user-step description
without backticks/angle-brackets (hassfest rejects HTML in i18n strings)
- __init__.py: add CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
since async_setup exists but the integration is config-entry-only
First PyPI release of the v1 wire path. Wheel published from local
source 2026-05-11 with omni_pca/v1/ subpackage included.
What's in 2026.5.11 vs 2026.5.10 (already on PyPI):
* New omni_pca.v1 subpackage -- OmniConnectionV1, OmniClientV1,
OmniClientV1Adapter -- for panels that listen on UDP only and
speak the legacy OmniLink (not OmniLink2) wire dialect.
* HA integration wires the adapter into the coordinator when
Transport=UDP is selected at config-flow time; v2/TCP path is
unchanged.
* Streaming UploadNames discovery (bare opcode + lock-step
Acknowledge until EOD/NAK).
* Long-form RequestUnitStatus for unit indices > 255 (sprinklers,
named flags, expansion-enclosure outputs).
* Chunked status polls -- firmware 2.12 NAKs at ~63 records per
request, so we batch in groups of 40.
* OmniConnection.close() now sends ClientSessionTerminated so the
panel frees our session slot immediately on disconnect.
Verified end-to-end against a firmware 2.12 OmniPro II panel at
192.168.1.9: discovery (16 zones, 44 units, 16 buttons, 8 codes,
2 thermostats, 8 messages) + status polling + execute_command
round-trip all working under HA, side-by-side with the existing
TCP mock-panel path in the dev stack.
README: new "Two wire dialects" section explaining when to pick
TCP/OmniClient vs UDP/OmniClientV1.
manifest.json: requirements bump to omni-pca==2026.5.11.
Project moved to a self-hosted Gitea at git.supported.systems under the
warehack.ing org. Updated:
pyproject.toml project.urls.Repository
custom_components/omni_pca/manifest.json documentation, issue_tracker
custom_components/omni_pca/README.md every link
CHANGELOG.md release tag URL
Tests still 351 + 1 skip. No code changed.
custom_components/omni_pca/ — drop-in HA integration:
- manifest.json (HA 2026.x, iot_class=local_push, requires omni-pca lib)
- config_flow.py — host/port/controller_key with auth + reauth steps,
parse_controller_key() extracted as pure testable function
- coordinator.py — OmniDataUpdateCoordinator with long-lived OmniClient,
unsolicited push wiring, ConfigEntryAuthFailed on bad key, reconnect on err
- binary_sensor.py — one entity per named zone, zone_type -> device_class map
(OPENING/MOTION/SMOKE/etc), is_on derived from ZoneProperties.status
- const.py, strings.json, translations/en.json, README.md
- hacs.json at root for HACS distribution
tests: 97 pass + 2 skip (HA harness not installed; importorskip in
test_ha_imports.py). 12 cases for parse_controller_key validation.
Ruff clean across src/ tests/ custom_components/. Status of HA component
itself NOT validated against a running HA — needs that next.