Ryan Malloy 9cdb312baf
Some checks are pending
Validate / HACS validation (push) Waiting to run
Validate / Hassfest (push) Waiting to run
program writeback: DownloadProgram wire + HA write API + Clear/Clone UI
The program viewer goes from read-only to write-capable. Three layers
land together because a partial implementation isn't actionable.

D1 — wire path:

* OmniClient.download_program(slot, program) — sends opcode 8
  (clsOLMsg2DownloadProgram, clsHAC.cs:1133-1140) with the 2-byte BE
  slot + Program.encode_wire_bytes(). Validates slot range 1..1500
  client-side. Maps Ack → success, Nak → CommandFailedError, any
  other opcode → OmniConnectionError.
* OmniClient.clear_program(slot) — convenience that writes an all-zero
  body. Mock treats this as deletion (removes the slot from
  state.programs) so subsequent reads see it as undefined.
* MockPanel handles DownloadProgram on the v2 dispatch path —
  receive 2-byte slot + 14-byte body, store in state.programs, ack.
* OmniClientV1.download_program raises NotImplementedError. v1 only
  has the bulk DownloadPrograms flow which clears everything before
  rewriting — destructive for HA's edit-one-program use case.
  Documented in the docstring so callers know to route v1 users to
  a v2 connection.

Tests cover: write-then-read round-trip, overwrite of existing slot,
clear deletes the slot, range validation, v1 not-implemented.

D2 — HA websocket commands:

* omni_pca/programs/clear — writes zero body, updates coordinator.
  data.programs immediately so the next list call shows the deletion.
  Returns ``{slot, cleared: true}``. Maps NotImplementedError on v1
  panels to the ``not_supported`` error code.
* omni_pca/programs/clone — copies source_slot → target_slot, with
  the slot field re-stamped. Refuses identical source/target,
  refuses missing source. Same coordinator update pattern.

5 new HA-integration tests covering clear, clone happy path, clone
to same slot, clone from missing source.

D3 — Clear/Clone UI in the side panel:

* "Clone…" button reveals an inline target-slot input (number,
  1..1500). Enter or "Clone" button calls the WS command, then
  navigates the detail panel to the new clone so the user sees the
  result.
* "Clear" button shows an inline confirmation row ("Clear slot N?
  This deletes the program from the panel.") with Yes/Cancel. Yes
  closes the detail panel and refreshes the list — the slot is gone.
* Both surface feedback via the same _writeFeedback state used by
  Fire now (auto-clears after 4 seconds).
* Three new button styles (.primary, .secondary, .danger) and the
  .action-row composite used for both inline prompts.

What's NOT shipped here: a real visual editor for trigger/condition/
action fields. That's a follow-up (~600 lines of new TS + careful
validation work). The current "Cut 1" UX is enough for the common
"I accidentally created a program, clear it" and "I want a variant
of this program, give me a copy in an empty slot" workflows.

Full suite: 643 passed, 1 skipped (up from 634).
Frontend bundle: 38 KB minified (up from 34 KB with the write UI).
2026-05-16 01:14:54 -06:00
..

HAI / Leviton Omni Panel — Home Assistant Integration

Native HA integration that talks Omni-Link II directly to your Omni Pro II / Omni IIe / Omni LTe / Lumina controller over TCP. No middleware — HA opens an encrypted session straight to the panel and listens for unsolicited push messages.

This integration is the HA-facing wrapper around the omni-pca Python library; the library handles the wire protocol, this component surfaces it as HA entities.

Install

HACS

  1. HACS → Integrations → search HAI / Leviton Omni Panel.
  2. Install, then restart Home Assistant.

(If not yet in the HACS default catalog: HACS → Integrations → custom repository → add https://github.com/rsp2k/omni-pca, category Integration.)

Manual

Copy the custom_components/omni_pca/ directory into your HA config/custom_components/ directory and restart HA.

Configure

  1. Settings → Devices & Services → Add Integration → search for HAI/Leviton Omni Panel.
  2. Enter:
    • Host — IP or hostname of the panel (e.g. 192.168.1.50)
    • Port — defaults to 4369 (HAI's reserved port)
    • Controller Key — 32 hex characters, the panel's NVRAM key
  3. Save. The panel appears as a single device with entities per object.

Where do I get the Controller Key?

If you have a .pca configuration export from PC Access, the included CLI extracts the key for you:

uvx omni-pca decode-pca '/path/to/My House.pca' --field controller_key

Otherwise, find it in PC Access under the panel's Setup → Misc → Network page (HAI labels it "Encryption Key 1").

Entities created

One device per panel, plus per-object entities below.

Platform Entity Per
alarm_control_panel Area arm/disarm with code discovered area
binary_sensor Zone open/tripped binary zone
binary_sensor Zone bypassed (diagnostic) binary zone
binary_sensor AC power, backup battery, system trouble panel
button Panel button macro discovered button
climate Thermostat (heat/cool/auto, fan, hold) discovered thermostat
event Typed push event relay panel
light Unit on/off + brightness discovered unit
sensor Analog zone (temp/humidity/power) analog zone
sensor Thermostat current temp / humidity / outdoor temp thermostat
sensor Panel model + firmware, last event class panel
switch Zone bypass toggle binary zone

State propagates via the panel's unsolicited push messages: zone changes, arming changes, AC/battery troubles, etc. all arrive within one TCP round- trip. A 30-second background poll backstops anything that didn't push.

Services

Service Purpose
omni_pca.bypass_zone Bypass a zone by 1-based index
omni_pca.restore_zone Restore a previously-bypassed zone
omni_pca.execute_program Run a stored program by index
omni_pca.show_message Display a stored message on consoles
omni_pca.clear_message Clear a displayed message
omni_pca.acknowledge_alerts Clear all outstanding troubles/alerts
omni_pca.send_command Power-user escape hatch (raw Command opcode)

Every service takes an entry_id so it picks the right panel when you have multiple configured.

Automation example

React to any alarm activation in real time:

automation:
  - alias: Notify on alarm
    trigger:
      - platform: event
        event_type: state_changed
        event_data:
          entity_id: event.panel_events
    condition: >
      {{ trigger.event.data.new_state.attributes.event_type ==
         "alarm_activated" }}
    action:
      - service: notify.mobile_app
        data:
          title: ALARM
          message: >
            Area {{ trigger.event.data.new_state.attributes.area_index }}

Diagnostics

Settings → Devices & Services → HAI/Leviton Omni Panel → ⋮ → Download diagnostics dumps a redacted snapshot (controller key removed, zone names hashed) — useful for bug reports.

Troubleshooting

  • Won't connect: confirm port 4369 is open on the panel. The Omni Pro II's network module ships off by default; enable it under Setup → Misc → Network on a console.
  • Authentication failed: re-check the Controller Key. The integration triggers HA's reauth flow when the panel rejects the key.
  • No entities for X: only objects with a name configured on the panel are discovered. PC Access's "Names" page is where they live.

See the parent README for protocol / library details. Detailed reverse-engineering notes are in docs/JOURNEY.md.