custom_components/omni_pca/ — six new platform modules wrapping the
v1.0 client surface. Every command method catches CommandFailedError
and re-raises HomeAssistantError so panel rejections (bad code, etc.)
become user-friendly HA errors instead of silent failures.
alarm_control_panel.py — OmniAreaAlarmPanel per discovered area.
Supports ARM_HOME (Day) / ARM_NIGHT / ARM_AWAY / ARM_VACATION /
ARM_CUSTOM_BYPASS (Day-Instant). State derives from area_status via
pure helpers.security_mode_to_alarm_state which handles arming-in-
progress, entry/exit timers, and active-alarm overrides.
light.py — OmniUnitLight per discovered unit (every unit; non-dimmable
units silently ignore brightness, no harm done). Brightness conversion
via helpers.omni_state_to_ha_brightness / ha_brightness_to_omni_percent
(Omni state byte: 0=off, 1=on, 100..200=brightness percent).
switch.py — OmniZoneBypassSwitch per binary zone. CONFIG entity_category;
pairs with the existing diagnostic 'zone bypassed' binary_sensor.
climate.py — OmniThermostatClimate per discovered thermostat.
Supports OFF / HEAT / COOL / HEAT_COOL hvac_modes; auto / on / diffuse
fan_modes; none / hold / vacation preset_modes. Single-setpoint and
range setpoint via TARGET_TEMPERATURE_RANGE. Fahrenheit native (Omni
panels are F-native; HA handles unit conversion downstream).
sensor.py — analog zones (temperature/humidity/power) + per-thermostat
diagnostic temp/humidity/outdoor sensors + OmniSystemModelSensor
+ OmniLastEventSensor (event_class + parsed event fields as attrs).
button.py — OmniPanelButton per discovered button macro. Programs not
yet exposed because the library lacks RequestProperties for Programs.
event.py — single OmniPanelEvent per panel relaying typed SystemEvents
via _trigger_event. event_types: zone_state_changed, unit_state_changed,
arming_changed, alarm_activated/cleared, ac_lost/restored,
battery_low/restored, user_macro_button, phone_line_dead/restored.
Automations key off platform: event + event_type filter.
helpers.py — extended with security_mode_to_alarm_state,
ARM_SERVICE_TO_SECURITY_MODE, omni_state_to_ha_brightness +
ha_brightness_to_omni_percent, omni/ha_{hvac,fan,hold} round-trips,
fahrenheit_to_omni_raw / celsius_to_omni_raw, analog_zone_device_class,
EVENT_TYPES tuple, event_type_for(class_name).
__init__.py — PLATFORMS extended to all 8 entity types.
scene.py intentionally NOT created — Omni 'scenes' are user-defined
button macros, already covered by the button platform. Documented in
README; revisit if/when the library gains scene-discovery opcodes.
tests/test_ha_helpers.py: +67 unit tests covering every new helper.
331 tests pass (was 264). Ruff clean across src/ tests/ custom_components/.
omni-pca
Async Python client for HAI/Leviton Omni-Link II home automation panels — Omni Pro II, Omni IIe, Omni LTe, Lumina.
Includes a Home Assistant custom component (custom_components/omni_pca/).
Status
Alpha. Built from a full reverse-engineering of HAI's PC Access 3.17 (the Windows installer/programmer app). The protocol layer captures two non-public quirks that public Omni-Link clients miss:
- Session key is not the ControllerKey. Last 5 bytes are XORed with a controller-supplied SessionID nonce.
- Per-block XOR pre-whitening before AES. First two bytes of every 16-byte block are XORed with the packet's sequence number.
See docs/PROTOCOL.md for the full byte-level spec.
Quick start (library)
uv add omni-pca
import asyncio
from omni_pca import OmniClient
async def main():
async with OmniClient(
host="192.168.1.9",
port=4369,
controller_key=bytes.fromhex("6ba7b4e9b4656de3cd7edd4c650cdb09"),
) as panel:
info = await panel.get_system_info()
print(info.model_name, info.firmware_version)
asyncio.run(main())
Quick start (Home Assistant)
Copy custom_components/omni_pca/ into your HA config/custom_components/, restart HA, then add the integration via Settings → Devices & Services. You'll need:
- Panel IP / hostname
- TCP port (default 4369)
- ControllerKey as 32 hex chars
Get the ControllerKey from your .pca file using the included parser:
uvx --from omni-pca omni-pca decode-pca path/to/Your.pca --field controller_key
Without a panel — mock controller
For testing, the library ships a minimal Omni controller emulator:
from omni_pca.mock_panel import MockPanel
async with MockPanel(controller_key=...).serve(port=14369):
# connect a real OmniClient to localhost:14369 — works end-to-end
...
Versioning
Date-based (CalVer): YYYY.M.D. Bumped on backwards-incompatible changes.
Acknowledgments
This client is independent and not affiliated with Leviton or HAI. Protocol details derived from clean-room analysis of the publicly-distributed PC Access installer.