Ryan Malloy 0e3835d4ff MockPanel: v1 wire dispatch for hermetic OmniClientV1 tests
Adds OmniLinkMessage (0x10) outer-packet handling to the mock so the
v1 path no longer requires a real panel for testing. Exercised over
UDP because OmniClientV1 is UDP-only by design, but the dispatcher
itself is transport-agnostic and the TCP _handle_client routes
OmniLinkMessage packets through the same _dispatch_v1 method too.

Coverage today:
  * RequestSystemInformation (17) -> SystemInformation (18)
  * RequestSystemStatus      (19) -> SystemStatus (20), 8 area mode bytes
  * RequestZoneStatus        (21) -> ZoneStatus (22), short + long form
  * RequestUnitStatus        (23) -> UnitStatus (24), short + long form
                                     (long form auto-selected for indices > 255)
  * RequestThermostatStatus  (30) -> ThermostatStatus (31)
  * RequestAuxiliaryStatus   (25) -> AuxiliaryStatus  (26) (zero records)
  * UploadNames              (12) -> NameData (11) streaming, lock-step
                                     Ack-driven across Zone/Unit/Button/
                                     Area/Thermostat, terminated by EOD (3)
  * Command                  (15) -> Ack (5) / Nak (6), reuses v2 state
                                     mutator so light-on/off, set-level,
                                     bypass-zone, restore-zone all work
  * ExecuteSecurityCommand   (102) -> Ack (5) / ExecuteSecurityCommandResponse
                                      (103) on bad code, with structured
                                      status byte preserved
  * MessageCrcError          -> v1 Nak (opcode 6)

The dispatcher writes replies wrapped in OmniLinkMessage (16) outer
packets (vs OmniLink2Message (32) used by v2) so OmniClientV1 routes
them correctly. The 4-step handshake is shared with v2 -- it's
protocol-version-agnostic at the outer-packet layer.

UploadNames state is panel-instance scoped via _upload_names_cursor
(int | None) -- there is only one active session at a time on the
mock so a single cursor suffices.

tests/test_e2e_v1_mock.py: 13 cases driving OmniClientV1 through the
mock's UDP socket, covering the full read API + UploadNames streaming
+ write methods + structured-failure path on a wrong security code.

Full suite: 400 passed, 1 skipped (was 387 / 1).
2026-05-11 16:32:51 -06:00
..