3 Commits

Author SHA1 Message Date
30b482a8cb HA integration: wire v1+UDP into the coordinator + config flow
OmniClientV1Adapter (src/omni_pca/v1/adapter.py)
  V2-shape facade over OmniClientV1. Exposes the OmniClient surface the
  HA coordinator was written against — get_system_information,
  list_*_names, get_object_properties (synthesized from streamed names),
  get_extended_status (chunked, routed to v1 typed status opcodes),
  get_object_status(AREA, ...) (derived from SystemStatus.area_alarms),
  events() (EventStream on v1 SystemEvents opcode 35), plus all the
  write-method shims.

  Chunks unit/zone/thermostat/aux polls per-type because firmware 2.12
  NAKs Request*Status with >~62 records in one shot (verified live).
  Falls back to "Area 1".."Area 8" when the UploadNames stream returns
  zero areas — common on panels where the installer didn't name them.

custom_components/omni_pca/coordinator.py
  _ensure_connected picks OmniClientV1Adapter for transport=udp. New
  _walk_properties_v1 replaces the v2 RequestProperties walk with a
  name-stream + synthesized-Properties pass.

custom_components/omni_pca/config_flow.py
  _probe routes to OmniClientV1Adapter for transport=udp instead of
  trying to drive v2 OmniClient over UDP (which silently dropped after
  handshake, per the earlier diagnosis).

src/omni_pca/events.py
  parse_events / _ensure_system_events / EventStream now take an
  expected_opcode arg (default v2 SystemEvents=55, v1 callers pass 35).
  Word format is byte-identical between v1 and v2, so the typed-event
  decoder is unchanged.

src/omni_pca/v1/client.py
  _range_status supports the long-form RequestUnitStatus (BE u16
  start/end) so panels with unit indices > 255 (sprinklers, flags) work.

Verified end-to-end against firmware 2.12 panel at 192.168.1.9:
  config entries:
    state=loaded  Omni Pro II (host.docker.internal)  (mock)
    state=loaded  Omni Pro II (192.168.1.9)           (real, v1+UDP)
  real-panel entities created in HA: 96 (30 binary_sensor, 26 light,
  15 switch, 13 button, 9 sensor, 3 climate)
  cross-check: light.omni_pro_ii_front_porch_2 = on  (matches live
  probe: unit #2 'FRONT PORCH' state=0x01 brightness=100)

dev/probe_v1_coordinator.py
  Coordinator-shaped end-to-end smoke test against the real panel
  without HA — drives the full discovery + poll cycle through the
  adapter. Useful for regression-checking the v1 wire path.

dev/add_real_panel.py
  Programmatically adds the real-panel config entry to the dev HA
  stack via the REST config-flow endpoints. Idempotent.
2026-05-11 01:30:49 -06:00
81725b4dbf HA config_flow: transport dropdown (TCP/UDP) for the new UDP path
custom_components/omni_pca/const.py:
  + CONF_TRANSPORT, TRANSPORT_TCP, TRANSPORT_UDP, DEFAULT_TRANSPORT='tcp'

custom_components/omni_pca/config_flow.py:
  + 'transport' field in _USER_SCHEMA with vol.In([tcp, udp]),
    default tcp (so existing flows are unchanged)
  + transport stored in entry.data on create
  + reauth carries the existing transport over from entry.data
  + _probe() takes transport=, propagates to OmniClient

custom_components/omni_pca/coordinator.py:
  + transport= constructor arg, defaults to 'tcp'
  + _ensure_connected passes transport= through to OmniClient

custom_components/omni_pca/__init__.py:
  + reads transport from entry.data (default tcp), passes to coordinator

Backward-compat: existing config entries without a transport key fall
through to 'tcp', identical to current behavior. New entries get the
choice at the config-flow form. The reauth step preserves the existing
transport so users don't have to re-pick it.

357 tests pass; ruff clean across src/ tests/ custom_components/.
HA integration tests don't need updating because they don't pass
transport= explicitly (default tcp matches the mock's default).
2026-05-10 21:15:56 -06:00
2e439364bd HA custom_component scaffold (binary_sensor for zones)
custom_components/omni_pca/ — drop-in HA integration:
- manifest.json (HA 2026.x, iot_class=local_push, requires omni-pca lib)
- config_flow.py — host/port/controller_key with auth + reauth steps,
  parse_controller_key() extracted as pure testable function
- coordinator.py — OmniDataUpdateCoordinator with long-lived OmniClient,
  unsolicited push wiring, ConfigEntryAuthFailed on bad key, reconnect on err
- binary_sensor.py — one entity per named zone, zone_type -> device_class map
  (OPENING/MOTION/SMOKE/etc), is_on derived from ZoneProperties.status
- const.py, strings.json, translations/en.json, README.md
- hacs.json at root for HACS distribution

tests: 97 pass + 2 skip (HA harness not installed; importorskip in
test_ha_imports.py). 12 cases for parse_controller_key validation.
Ruff clean across src/ tests/ custom_components/. Status of HA component
itself NOT validated against a running HA — needs that next.
2026-05-10 13:09:27 -06:00