custom_components/omni_pca/coordinator.py — full rewrite:
- Long-lived OmniClient for entry lifetime
- One-shot discovery: system info + zone/unit/area/thermostat/button names
via list_*_names + per-index get_object_properties
- Periodic poll (30s default): get_extended_status for zones/units/thermostats,
get_object_status for areas, skip empty discoveries
- Background _run_event_listener task consuming client.events(), patches
state in-place and async_set_updated_data on push:
ZoneStateChanged -> patch zone_status raw byte
UnitStateChanged -> patch unit_status state, preserve brightness
ArmingChanged -> patch area_status mode + last_user
AlarmActivated/Cleared -> trigger refresh
AcLost/Restored, BatteryLow/Restored -> recorded for sensors
- InvalidEncryptionKeyError/HandshakeError -> ConfigEntryAuthFailed (HA reauth)
- OmniConnectionError/RequestTimeoutError -> UpdateFailed + drop client
- Event task cancelled in async_shutdown
custom_components/omni_pca/binary_sensor.py — full rewrite:
- OmniZoneBinarySensor per discovered zone (device class from zone type:
smoke/water/freeze use latched-alarm bit; doors/motion use current condition)
- OmniZoneBypassedBinarySensor per zone (DIAGNOSTIC, PROBLEM)
- OmniSystemAcBinarySensor (POWER, prefers AcLost/AcRestored push)
- OmniSystemBatteryBinarySensor (BATTERY)
- OmniSystemTroubleBinarySensor (PROBLEM)
custom_components/omni_pca/helpers.py — pure functions extracted for testing:
- device_class_for_zone_type, is_binary_zone_type, use_latched_alarm_for_zone,
prettify_name. 61 unit tests in tests/test_ha_helpers.py.
docs/JOURNEY.md — 4383-word raw chronological retrospective of the whole
arc from binary archive to working library. 18 dated sections including
the 2191-byte magic-number header validation moment, the two non-public
protocol quirks, the offline-panel comedy. Source material for future
writeups (intentionally raw, not polished).
264 tests pass (was 203, +61 helper tests). Ruff clean across all dirs.
36 lines
1.1 KiB
Python
36 lines
1.1 KiB
Python
"""Constants for the HAI/Leviton Omni Panel integration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import timedelta
|
|
from typing import Final
|
|
|
|
DOMAIN: Final = "omni_pca"
|
|
|
|
DEFAULT_PORT: Final = 4369
|
|
DEFAULT_TIMEOUT: Final = 5.0
|
|
|
|
CONF_CONTROLLER_KEY: Final = "controller_key"
|
|
|
|
MANUFACTURER: Final = "HAI / Leviton"
|
|
|
|
# Polling interval. Most state arrives via unsolicited push messages, so
|
|
# this is just a safety net that keeps `last_update_success` honest if the
|
|
# panel goes quiet.
|
|
SCAN_INTERVAL: Final = timedelta(seconds=30)
|
|
|
|
# Background event-listener task name, surfaced to ``asyncio.all_tasks()``
|
|
# for diagnostics.
|
|
EVENT_TASK_NAME: Final = "omni_pca-event-listener"
|
|
|
|
# Upper bound for the discovery walk. The protocol caps object indices at
|
|
# uint16, but Omni panels never approach that — most installs have <100
|
|
# zones / units / areas, so we stop early when discovery returns EOD.
|
|
MAX_OBJECT_INDEX: Final = 0xFFFF
|
|
|
|
# Length, in characters, of a hex-encoded 16-byte controller key.
|
|
CONTROLLER_KEY_HEX_LEN: Final = 32
|
|
|
|
LOGGER: Final = logging.getLogger(__package__)
|