Ryan Malloy e560d98f87 programs: add multi-record decoder properties (firmware >=3.0 records)
The 6 multi-record ProgType values (WHEN/AT/EVERY/AND/OR/THEN) now have
typed accessors on the Program dataclass:

  is_multi_record()  - classifier for ProgTypes 5-10
  event_id           - WHEN trigger event-id (same property as EVENT, no
                       Mon/Day swap, BE wire form)
  and_family         - AND record byte-1 family + operand bits (mirrors
                       compact-form cond's high byte: ZONE=0x04, CTRL+ON=0x0A,
                       OTHER=0x00, etc.)
  and_instance       - AND record bytes 3-4 BE u16 (zone#, unit#,
                       MiscConditional value, ...)
  every_interval     - EVERY record bytes 3-4 BE u16 (recurrence interval)

AT records reuse the existing month/day/days/hour/minute fields (same
byte layout as compact-form TIMED, just with cmd/par/pr2 zero).
OR records carry no payload — only the ProgType byte distinguishes
them. THEN records reuse cmd/par/pr2 (same layout and LE byte order
as compact-form action fields).

10 new tests cover the empirical captures from pca-re/clausal-re:

  - is_multi_record() classifier
  - WHEN event_id for Zone 5 Secure and Zone 1 Secure
  - EVERY 5 SECONDS interval decoding
  - AND IF UNIT 1 ON, AND IF ZONE 5 SECURE, AND IF NEVER family+instance
  - AT record month/day/days/hour/minute
  - OR record all-zero invariants
  - THEN record cmd/par/pr2 (UNIT 1 ON)

All byte vectors in the tests come from real PC Access captures in
pca-re/clausal-re/06-10.pca with firmware override at 3.0+.

The and_family and and_instance properties derive from the existing
cond and cond2 fields via byte-swap — disk bytes 1-4 of AND records
use BE u16 order, but Program's cond/cond2 fields are LE-decoded
(per compact-form convention). The byte-swap formula
((cond2 & 0xFF) << 8) | ((cond2 >> 8) & 0xFF) yields the BE
interpretation without re-reading raw bytes.

473 tests passing (up from 463).
2026-05-12 04:57:48 -06:00
..