--- title: HAI Omni Pro II — omni-pca description: Reverse-engineered Python library and Home Assistant integration for HAI/Leviton Omni Pro II home automation panels. template: doc hero: tagline: | Async Python library and a drop-in Home Assistant integration for the HAI/Leviton Omni-Link II protocol — clean-room reverse-engineered from PC Access 3.17, complete with two non-public crypto quirks no other public client implements. actions: - text: Add to Home Assistant link: /how-to/install-in-home-assistant/ icon: right-arrow variant: primary - text: Decode your .pca file link: /tutorials/decrypt-your-pca/ icon: external variant: secondary - text: Source on Gitea link: https://git.supported.systems/warehack.ing/omni-pca icon: external variant: minimal --- import { Card, CardGrid, LinkCard } from '@astrojs/starlight/components'; import { Image } from 'astro:assets'; import wordmark from '../../assets/manual/omnipro-ii-wordmark.png'; import shotIntegrations from '../../assets/screenshots/02-integrations-list.png'; import shotConfig from '../../assets/screenshots/03-omni-pca-config.png'; import shotDevice from '../../assets/screenshots/04-panel-device.png'; import shotStates from '../../assets/screenshots/06-developer-states.png'; OmniPro II Automation ## In Home Assistant One device per panel. Typed entities for every named object the controller knows about — alarm areas, lights and outputs, binary zones with bypass, thermostats with HVAC modes, programs and panel-button macros, plus a single `event` entity that relays the panel's typed push-event stream into HA automations. Push updates arrive within one TCP round-trip; a 30-second poll backstops anything that didn't push.
HA Devices and Services dashboard with HAI/Leviton Omni Panel listed alongside built-in integrations
Discoverable in the integrations list →
omni_pca integration page showing Custom integration, version 2026.5.10, 1 device with 38 entities
Entity catalogue · 8 platforms →
Omni Pro II device page in HA with lights, areas, thermostats, and panel buttons all live
Per-device controls grid →
HA Developer Tools showing alarm_control_panel.omni_pro_ii_main with full attribute payload
Real entity state from the panel →
## In Python The library underneath is intentionally async-first and typed end-to-end: ```python import asyncio from omni_pca import OmniClient async def main() -> None: async with OmniClient( host="192.168.1.9", port=4369, controller_key=bytes.fromhex("6ba7b4e9b4656de3cd7edd4c650cdb09"), ) as panel: info = await panel.get_system_information() print(info.model_name, info.firmware_version) async for event in panel.events(): print(event) # ZoneStateChanged, ArmingChanged, AlarmActivated, … asyncio.run(main()) ``` What you get from the library: - **Full opcode coverage** — 104 v1 + 83 v2 message types, byte-exact to the decompiled C# enums. - **21 typed status/properties dataclasses**, 26 typed `SystemEvent` subclasses, no untyped bytes leaking past the framing layer. - **Stateful mock controller** for offline development. The same `MockPanel` class powers the integration tests and the docker dev stack. - **Async-first** — `OmniClient` is an async context manager, `events()` is an async iterator, no callback soup. ## Two non-public protocol quirks The wire protocol — as actually implemented in PC Access 3.17 — has two quirks that public Omni-Link clients miss. Without them the panel will accept your TCP connection, complete the unencrypted handshake, and then silently drop you on the first encrypted message: 1. **Session key XOR mix.** The AES-128 session key is *not* the panel's `ControllerKey` directly. Bytes `[11..16)` of the ControllerKey are XORed with a 5-byte `SessionID` nonce that the controller sends in `ControllerAckNewSession`. Bytes `[0..11)` are the ControllerKey verbatim. 2. **Per-block XOR pre-whitening before AES.** Before each 16-byte block is AES-encrypted, its first two bytes are XORed with the packet's 16-bit sequence number (high byte first). The same mask is applied to *every* block of the packet. Decrypt reverses it. Both are unambiguous in the decompiled C# (`clsOmniLinkConnection.cs:1886-1892` and `:396-401`). Neither appears in `jomnilinkII`, `pyomnilink`, or any third-party Omni-Link writeup we found. See [the quirks explainer](/explanation/quirks/) for why they exist and the full visual breakdown. ## Read the in-depth content