Homepage redesign: lead with the HA integration, then the library
The previous homepage opened with prose ('What it is') and listed three
generic 'where to start' cards. New version is more concrete:
- Hero block with three call-to-action buttons (Add to HA, Decode .pca,
Source on Gitea) using Starlight's hero frontmatter
- '## In Home Assistant' section opens with what users actually get,
followed by a 2x2 grid of clickable HA screenshots (integrations
list, integration page, device controls, dev-tools states) — each
thumbnail links to the relevant detail page with a short caption
- '## In Python' section: 15-line OmniClient + events() example so
the library shape is immediately legible, plus four bullets
highlighting opcode coverage / typed dataclasses / mock controller
/ async-first
- '## Two non-public protocol quirks' kept verbatim — still the
headline RE finding
- '## Read the in-depth content' replaces the three generic cards
with six specific ones routing to install how-to, dev-stack
tutorial, protocol reference, quirks explainer, file format,
and journey
Build clean, 23 pages. Local container rebuilt + recreated. The
new sections render: In Home Assistant, In Python, Two non-public
protocol quirks, Read the in-depth content.
This commit is contained in:
parent
17cd57f1bf
commit
f7be0f7b18
@ -2,69 +2,159 @@
|
|||||||
title: HAI Omni Pro II — omni-pca
|
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.
|
description: Reverse-engineered Python library and Home Assistant integration for HAI/Leviton Omni Pro II home automation panels.
|
||||||
template: doc
|
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 { Card, CardGrid, LinkCard } from '@astrojs/starlight/components';
|
||||||
import { Image } from 'astro:assets';
|
import { Image } from 'astro:assets';
|
||||||
|
|
||||||
import wordmark from '../../assets/manual/omnipro-ii-wordmark.png';
|
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
|
<Image
|
||||||
src={wordmark}
|
src={wordmark}
|
||||||
alt="OmniPro II Automation"
|
alt="OmniPro II Automation"
|
||||||
loading="eager"
|
loading="eager"
|
||||||
style="max-width: 480px; width: 100%; height: auto; margin: 1rem 0 2rem; filter: invert(1); opacity: 0.9;"
|
style="max-width: 420px; width: 100%; height: auto; margin: 0.5rem 0 2rem; filter: invert(1); opacity: 0.9;"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
## What it is
|
## In Home Assistant
|
||||||
|
|
||||||
`omni-pca` is an async Python library and a matching Home Assistant custom
|
One device per panel. Typed entities for every named object the controller
|
||||||
component for HAI / Leviton **Omni Pro II**, **Omni IIe**, **Omni LTe**, and
|
knows about — alarm areas, lights and outputs, binary zones with bypass,
|
||||||
**Lumina** panels. It speaks Omni-Link II straight to the controller over TCP,
|
thermostats with HVAC modes, programs and panel-button macros, plus a
|
||||||
opens an encrypted session, and surfaces the panel's typed object model — zones,
|
single `event` entity that relays the panel's typed push-event stream
|
||||||
units, areas, thermostats, buttons, programs, codes, messages — plus the
|
into HA automations. Push updates arrive within one TCP round-trip; a
|
||||||
unsolicited push-event stream the panel emits on state changes.
|
30-second poll backstops anything that didn't push.
|
||||||
|
|
||||||
The protocol layer was built from a clean-room decompilation of HAI's PC Access
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 0.75rem; margin: 1.5rem 0;">
|
||||||
3.17. Every opcode, byte layout, and crypto step is cited back to the source
|
<a href="/how-to/install-in-home-assistant/" style="display: block;">
|
||||||
line in the decompiled C# (`clsOmniLinkConnection.cs`, `clsHAC.cs`, etc.).
|
<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
|
## Two non-public protocol quirks
|
||||||
|
|
||||||
The wire protocol — as actually implemented in PC Access 3.17 — has two
|
The wire protocol — as actually implemented in PC Access 3.17 — has two
|
||||||
non-public quirks that public Omni-Link clients miss. Without them the panel
|
quirks that public Omni-Link clients miss. Without them the panel will
|
||||||
will accept your TCP connection, complete the unencrypted handshake, and then
|
accept your TCP connection, complete the unencrypted handshake, and then
|
||||||
silently drop you on the first encrypted message:
|
silently drop you on the first encrypted message:
|
||||||
|
|
||||||
1. **Session key XOR mix.** The AES-128 session key is *not* the panel's
|
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
|
`ControllerKey` directly. Bytes `[11..16)` of the ControllerKey are
|
||||||
with a 5-byte `SessionID` nonce that the controller sends in
|
XORed with a 5-byte `SessionID` nonce that the controller sends in
|
||||||
`ControllerAckNewSession`. Bytes `[0..11)` are the ControllerKey verbatim.
|
`ControllerAckNewSession`. Bytes `[0..11)` are the ControllerKey
|
||||||
2. **Per-block XOR pre-whitening before AES.** Before each 16-byte block is
|
verbatim.
|
||||||
AES-encrypted, its first two bytes are XORed with the packet's 16-bit
|
2. **Per-block XOR pre-whitening before AES.** Before each 16-byte block
|
||||||
sequence number (high byte first). The same mask is applied to *every*
|
is AES-encrypted, its first two bytes are XORed with the packet's
|
||||||
block of the packet. Decrypt reverses it.
|
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`
|
Both are unambiguous in the decompiled C# (`clsOmniLinkConnection.cs:1886-1892`
|
||||||
and `:396-401`). Neither appears in `jomnilinkII`, `pyomnilink`, or any
|
and `:396-401`). Neither appears in `jomnilinkII`, `pyomnilink`, or any
|
||||||
third-party Omni-Link writeup we found. See [the quirks
|
third-party Omni-Link writeup we found. See
|
||||||
explainer](/explanation/quirks/) for the full story.
|
[the quirks explainer](/explanation/quirks/) for why they exist and the
|
||||||
|
full visual breakdown.
|
||||||
|
|
||||||
## Where to start
|
## Read the in-depth content
|
||||||
|
|
||||||
<CardGrid>
|
<CardGrid>
|
||||||
<LinkCard
|
<LinkCard
|
||||||
title="Decode your .pca file"
|
title="Install in Home Assistant"
|
||||||
href="/start/quickstart/"
|
href="/how-to/install-in-home-assistant/"
|
||||||
description="Pull the panel's IP, port, and AES-128 ControllerKey out of an encrypted .pca config export — no panel hardware required."
|
description="Two install paths (HACS + manual), config-flow walkthrough, common errors, reauth flow."
|
||||||
/>
|
/>
|
||||||
<LinkCard
|
<LinkCard
|
||||||
title="Protocol reference"
|
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/"
|
href="/reference/protocol/"
|
||||||
description="Byte-level Omni-Link II handshake, packet layouts, key derivation, steady-state encryption, sequence numbers, and teardown."
|
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
|
<LinkCard
|
||||||
title="The Journey"
|
title="The Journey"
|
||||||
href="/journey/"
|
href="/journey/"
|
||||||
description="Chronological retrospective of the reverse-engineering work — pile of binaries to 351 passing tests in a few days."
|
description="Chronological retrospective from a pile of binaries off a Mac volume to 351 passing tests in a couple of days."
|
||||||
/>
|
/>
|
||||||
</CardGrid>
|
</CardGrid>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user