Ryan Malloy 486258a034
Some checks are pending
Validate / HACS validation (push) Waiting to run
Validate / Hassfest (push) Waiting to run
panel: structured-OP AND record editor (TEMP > 70 etc.)
Replaces the read-only "structured comparison" banner with a real
editor. Structured AND records encode ``Arg1 OP Arg2`` where Arg1 is
a typed reference (Zone / Unit / Thermostat / Area / TimeDate) plus a
per-type field selector, and Arg2 is either another typed reference
or a literal constant.

I1 — TS types + decoders:

Wire layout (programs.py decoders, clsProgram.cs):
  cond  high byte  = and_op           (CondOP: 1=EQ, 2=NE, 3=LT,
                                       4=GT, 5=ODD, 6=EVEN, 7=MULT,
                                       8=IN, 9=NOT_IN)
  cond  low byte   = and_arg1_argtype (CondArgType)
  cond2 (whole)    = and_arg1_ix      (object idx; 0 for TimeDate)
  cmd              = and_arg1_field   (per-type field selector)
  par              = and_arg2_argtype (Constant most common)
  pr2              = and_arg2_ix      (constant value or 2nd obj idx)
  month            = and_arg2_field
  day,days         = and_compconst    (BE u16; usually 0)

decodeStructuredAnd / encodeStructuredAnd handle both directions;
round-trip exact.

Per-Arg1Type field menus in FIELDS_BY_TYPE — exact 1:1 with the
Python enuZoneField / enuUnitField / enuThermostatField /
enuTimeDateField enums in omni_pca.programs and the field handling
in StateEvaluator. Areas only expose "Security mode" (single useful
field). TimeDate exposes Year / Month / Day / DoW / Time / Hour /
Minute (skips the rarely-used Date / DST / SunriseSunset fields).

I2 — editor UI:

isEditableStructuredAnd guard: only opens the editor for records
matching the editor's scope (Arg1 in supported types, Arg2=Constant,
compConst=0). Out-of-scope structured records render with a
"read-only" tag — preserved on save, still removable.

Structured rows render with a "structured" tag and an orange-tinted
background to distinguish them from Traditional rows. Layout:

  Arg1 type ▸ object picker ▸ Field ▸ Operator ▸ Compare against

Unary operators (ODD / EVEN) hide the Arg2 input. Changing Arg1 type
resets the Arg1 index + field to defaults so the form stays self-
consistent (no stale picker values from a previous type).

Arg2 is locked to Constant in this pass. Editing record-vs-record
comparisons (e.g. "Thermostat 1 temp > Thermostat 2 temp") is a
future cut — current real-world programs use the Constant form
exclusively per my homeowner-panel sample.

_pickBucket gains the missing "thermostat" branch (was missed in
earlier passes; only mattered now that thermostat is an Arg1Type).

Live screenshot 12-structured-and.png shows an injected chain with
both a Traditional AND (CTRL UNIT 1 ON) and a Structured AND
(Thermostat(1).Temperature > 70) — both editable end-to-end.

Frontend bundle: 88 KB minified (up from 82 KB).
Full suite: 653 passed, 1 skipped (no test changes).
2026-05-17 02:24:59 -06:00
..
2026-05-16 01:29:25 -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.