---
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';

<Image
  src={wordmark}
  alt="OmniPro II Automation"
  loading="eager"
  style="max-width: 420px; width: 100%; height: auto; margin: 0.5rem 0 2rem; filter: invert(1); opacity: 0.9;"
/>

## 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.

<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 0.75rem; margin: 1.5rem 0;">
  <a href="/how-to/install-in-home-assistant/" style="display: block;">
    <Image src={shotIntegrations} alt="HA Devices and Services dashboard with HAI/Leviton Omni Panel listed alongside built-in integrations" style="width: 100%; height: auto; border: 1px solid color-mix(in srgb, currentColor 25%, transparent); border-radius: 6px;" />
    <div style="font-size: 0.85rem; opacity: 0.75; margin-top: 0.5rem;">Discoverable in the integrations list →</div>
  </a>
  <a href="/reference/ha-entities/" style="display: block;">
    <Image src={shotConfig} alt="omni_pca integration page showing Custom integration, version 2026.5.10, 1 device with 38 entities" style="width: 100%; height: auto; border: 1px solid color-mix(in srgb, currentColor 25%, transparent); border-radius: 6px;" />
    <div style="font-size: 0.85rem; opacity: 0.75; margin-top: 0.5rem;">Entity catalogue · 8 platforms →</div>
  </a>
  <a href="/reference/ha-entities/#what-it-looks-like-in-ha" style="display: block;">
    <Image src={shotDevice} alt="Omni Pro II device page in HA with lights, areas, thermostats, and panel buttons all live" style="width: 100%; height: auto; border: 1px solid color-mix(in srgb, currentColor 25%, transparent); border-radius: 6px;" />
    <div style="font-size: 0.85rem; opacity: 0.75; margin-top: 0.5rem;">Per-device controls grid →</div>
  </a>
  <a href="/how-to/automate-on-alarm/" style="display: block;">
    <Image src={shotStates} alt="HA Developer Tools showing alarm_control_panel.omni_pro_ii_main with full attribute payload" style="width: 100%; height: auto; border: 1px solid color-mix(in srgb, currentColor 25%, transparent); border-radius: 6px;" />
    <div style="font-size: 0.85rem; opacity: 0.75; margin-top: 0.5rem;">Real entity state from the panel →</div>
  </a>
</div>

## 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

<CardGrid>
  <LinkCard
    title="Install in Home Assistant"
    href="/how-to/install-in-home-assistant/"
    description="Two install paths (HACS + manual), config-flow walkthrough, common errors, reauth flow."
  />
  <LinkCard
    title="Spin up the dev stack"
    href="/tutorials/dev-stack/"
    description="Real HA + a faithful mock panel in Docker. Click around the integration without touching real hardware."
  />
  <LinkCard
    title="Omni-Link II protocol reference"
    href="/reference/protocol/"
    description="Byte-level handshake diagrams, every packet type visualized, key derivation, steady-state encryption."
  />
  <LinkCard
    title="The two non-public quirks, in depth"
    href="/explanation/quirks/"
    description="Why the session key isn't the ControllerKey, and the per-block whitening that breaks every naive AES-ECB client."
  />
  <LinkCard
    title=".pca file format"
    href="/reference/file-format/"
    description="Borland-Pascal LCG XOR cipher, three keys, and the on-disk layout so you can decode your config without PC Access."
  />
  <LinkCard
    title="The Journey"
    href="/journey/"
    description="Chronological retrospective from a pile of binaries off a Mac volume to 351 passing tests in a couple of days."
  />
</CardGrid>
