Ryan Malloy 9dbe563aed Content + docker pin: 13-page Starlight site live behind Caddy
src/content/docs/ — twelve pages totalling ~18,800 words, ported from
the omni-pca repo's docs and reference material:

  index.mdx (377 w)               landing page with three CardGrid links
  start/quickstart.md (572 w)     three flows: decode .pca / talk to
                                  panel / install in HA

  reference/protocol.md (2525 w)  byte-level Omni-Link II spec, full
                                  packet+message layouts, the two
                                  non-public quirks, opcode tables
  reference/file-format.md (1593 w)  XOR-LCG cipher, key derivation,
                                  PCA01.CFG schema, .pca PCA03 header
  reference/library-api.md (1170 w)  module-by-module Python API summary
  reference/ha-entities.md (1070 w)  per-platform entity catalogue
  reference/ha-services.md (567 w)   seven services + automation YAML

  explanation/quirks.md (1448 w)  the headline RE essay — session-key
                                  XOR mix + per-block whitening, why
                                  no public client documents them
  explanation/architecture.md (1123 w)  library + HA + mock + tests
  explanation/pc-access-bug.md (1131 w)  LargeVocabulary off-by-N

  journey.md (6194 w)             chronological retrospective ported
                                  from omni-pca/docs/JOURNEY.md
  changelog.md (1213 w)           full 2026.5.10 release notes

Dockerfile — pinned node:lts-alpine and caddy:latest (registry-1
.docker.io was returning 'tls: internal error' on node:25-alpine and
caddy:2-alpine pulls; the pinned tags are cached locally and work).
TODO comment notes to bump back to node:25 once registry stabilises.

.gitignore — added .env / .env.local just in case.

Build: 13 pages built clean in 1.83s, sitemap + Pagefind search index
emitted. Container runs at hai-omni-docs-docs (caddy network), accepts
requests with Host: hai-omni-pro-ii.warehack.ing, returns rendered
Starlight HTML with title/description meta intact. Once DNS for
hai-omni-pro-ii.warehack.ing points at the host, the site is live.
2026-05-10 17:05:25 -06:00

7.9 KiB

title description
Home Assistant entity catalogue One device per panel plus typed entities for every named object — alarm panels, lights, binary sensors, climates, sensors, buttons, switches, and a typed event relay.

The integration creates one HA device per Omni panel. Every named object on the controller (zones, units, areas, thermostats, buttons) is materialised as one or more typed entities below. Discovery happens once at first refresh; live state propagates over the panel's unsolicited push channel within one TCP round-trip, with a 30-second poll backstopping anything that didn't push.

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

alarm_control_panel

One per discovered area (OmniAreaAlarmPanel). Surfaces the area's current SecurityMode translated into HA's AlarmControlPanelState enum:

  • OFFdisarmed
  • DAYarmed_home
  • NIGHTarmed_night
  • AWAYarmed_away
  • VACATIONarmed_vacation
  • DAY_INSTANTarmed_custom_bypass
  • ARMING_* (security modes 9..14) → arming
  • entry-timer running → pending
  • alarms != 0triggered

Supported features: ARM_HOME, ARM_NIGHT, ARM_AWAY, ARM_VACATION, ARM_CUSTOM_BYPASS. Code validation is enforced server-side: the user's PIN is sent through ExecuteSecurityCommand (opcode 74). Wrong code raises an HA ServiceValidationError.

Attributes: area_index, mode_name, entry_timer_secs, exit_timer_secs, alarm_active, alarm_bitfield.

binary_sensor

Three flavours.

Per binary zone — open/tripped (OmniZoneBinarySensor). device_class is derived from the zone's ZoneType:

ZoneType HA BinarySensorDeviceClass
ENTRY_EXIT, PERIMETER, NIGHT_INTERIOR, AWAY_INTERIOR, DELAY, LATCHING door / window (opening)
FIRE, FIRE_EMERGENCY, FIRE_TAMPER smoke
GAS gas
WATER moisture
FREEZE cold
TAMPER, LATCHING_TAMPER tamper
TROUBLE problem
temperature/humidity types (handled by sensor instead)

State: on when the zone is open / not-secure / tripped. Attributes: zone_index, zone_type, area, current_state, latched_state, arming_state, is_in_alarm, is_trouble.

Per binary zone — bypass diagnostic (OmniZoneBypassBinarySensor). entity_category = DIAGNOSTIC, on iff the zone is currently bypassed (user or auto-bypass).

Per panel — system trouble triplet. Three diagnostic sensors per panel:

  • binary_sensor.{name}_ac_power — on iff AC is OK (inverted from AcLost)
  • binary_sensor.{name}_backup_battery — on iff battery OK
  • binary_sensor.{name}_system_trouble — on iff any current trouble

These mirror the typed events AcLost/AcRestored, BatteryLow/ BatteryRestored, and DcmTrouble/DcmOk.

button

One OmniButton per discovered panel button macro. Pressing the HA button dispatches a Command.EXECUTE_BUTTON (parameter2 = button index). No state.

This is the only entity for "scenes" — Omni "scenes" are user-named button macros, so adding a parallel scene platform would just double-count.

climate

One OmniClimate per discovered thermostat. Maps the panel's HvacMode

  • FanMode + HoldMode triple onto HA's HVACMode, fan modes, and preset modes. Setpoints are translated through the omni_temp_to_* helpers so HA can display whatever unit the user prefers.

Supported features: TARGET_TEMPERATURE (single setpoint), or TARGET_TEMPERATURE_RANGE when in HEAT_COOL/auto mode. Fan: auto, on, cycle. Preset: none, hold, vacation.

Attributes: thermostat_index, humidity_percent, outdoor_temperature_*, humidify_setpoint_raw, dehumidify_setpoint_raw, horc_status (1=heating active, 2=cooling active).

event

One OmniPanelEvent per panel. Surfaces the typed push-event stream as a single HA event entity with event_types:

zone_state_changed       unit_state_changed       arming_changed
alarm_activated          alarm_cleared
ac_lost                  ac_restored
battery_low              battery_restored
user_macro_button        phone_line_dead          phone_line_restored

Plus an unknown catch-all for the 14 less-common SystemEvent subclasses.

Each event carries the originating dataclass's fields in event_data (zone index, area, alarm type, etc.), plus a raw_word for debugging.

Automations key on platform: state filtered by attributes.event_type. See HA services → automation example for a worked snippet.

light

One OmniLight per discovered unit. Dimmer state is a single byte that encodes a lot:

State byte Meaning
0 Off
1 On (relay; no level info — exposed as 100% brightness)
2..13 Scene A..L (state - 63 → ASCII)
17..25 Dim 1..9 (state - 16)
26 Blink
33..41 Brighten 1..9 (state - 32)
100..200 Brightness 0..100% (state - 100)

Non-dimmable relays silently ignore brightness. Conversion is done in pure helpers (omni_state_to_ha_brightness, ha_brightness_to_omni_percent) unit-tested without HA in the venv.

Attributes: unit_index, time_remaining_secs (panel-side timer for auto-off; 0 = indefinite).

sensor

Three flavours.

Per analog zone. Zones with ZoneType in {TEMPERATURE, OUTDOOR_TEMP, HUMIDITY, TEMP_ALARM, ENERGY_SAVER, FREEZE} get a sensor entity instead of (or in addition to) a binary sensor. Unit is inferred from the zone type — temperature in °F/°C, humidity as %, power-related as W.

Per thermostat. Three sub-sensors per thermostat:

  • sensor.{name}_temperature — current measured temp
  • sensor.{name}_humidity — humidity percent
  • sensor.{name}_outdoor_temperature — outdoor temp (if reported)

Per panel. Two info sensors:

  • sensor.{name}_panel_model — model name + firmware version (state)
  • sensor.{name}_last_event — class name of the most recent SystemEvent (ZoneStateChanged, ArmingChanged, etc.) with the raw word as an attribute

switch

One OmniZoneBypassSwitch per binary zone, entity_category = CONFIG. Toggling the switch dispatches Command.BYPASS_ZONE / Command.RESTORE_ZONE. State mirrors the bypass diagnostic binary sensor above.

What gets discovered

Only objects with a name set on the panel are discovered — that's the panel's own definition of "this slot is in use". To populate names, use PC Access's "Names" page (or any other Omni programmer). On a fresh factory-default panel you'll see zero entities; configure object names first, then reload the integration.

Where to look in source

Class File
OmniAreaAlarmPanel custom_components/omni_pca/alarm_control_panel.py
OmniZoneBinarySensor, OmniZoneBypassBinarySensor binary_sensor.py
OmniButton button.py
OmniClimate climate.py
OmniPanelEvent event.py
OmniLight light.py
sensor classes sensor.py
OmniZoneBypassSwitch switch.py

All of them sit on top of OmniDataUpdateCoordinator (coordinator.py), which keeps a long-lived OmniClient, runs one-time discovery on first refresh, and patches state in-place from the typed event stream.