2 Commits

Author SHA1 Message Date
b412dc0f37 HA: discover programs over the wire + diagnostic sensor
Coordinator's _discover_programs is no longer a placeholder. It now
drives client.iter_programs() (v2 path) or the v1 adapter's forward
to OmniClientV1.iter_programs (v1 path), populating OmniData.programs
with decoded Program records keyed by slot. Errors are logged and
swallowed so partial enumeration doesn't break entry setup —
programs are non-critical telemetry.

OmniData.programs is now dict[int, Program] rather than the
ProgramProperties dict that was an empty placeholder. The
ProgramProperties dataclass remains in models.py for the Properties
opcode reply path; only the coordinator's value type changed.

New OmniProgramsSensor on the sensor platform: a single diagnostic
entity per panel whose state is the count of defined programs and
whose 'programs' attribute lists each program's slot, type name,
and schedule fields. Easy to consume from automations and the
developer-states UI.

Mock fixture seeds three programs (TIMED+TIMED+EVENT at slots 12 /
42 / 99). New integration test verifies the sensor enumerates them
in slot-ascending order with the expected per-record fields.

Full suite: 494 passed, 1 skipped (fixture-gated).
2026-05-12 19:10:32 -06:00
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