Skip to content

test(trace): add authentic MH201 FW 3.6.43 sweep & WHO 1013 identity trace (#466) - #824

Merged
GreenGrassBlueOcean merged 2 commits into
v2-phase1-architecturefrom
test/trace-issue-466-mh201-3-6-43
Oct 27, 2026
Merged

GreenGrassBlueOcean merged 2 commits into
v2-phase1-architecturefrom
test/trace-issue-466-mh201-3-6-43

Conversation

@GreenGrassBlueOcean

@GreenGrassBlueOcean GreenGrassBlueOcean commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

What is the problem

Prior to this PR, none of the committed MH201 fixtures under tests/fixtures/ carried a physical WHO=1013 Dimension 1 (OBJECT_MODEL = 10) diagnostic export or MH201 firmware 3.6.43 (existing captures were firmware 3.3.6 on 2.0.0b13 and 3.6.27 on 2.0.0b12, predating WHO=1013 identity export in <myhome-bus-card>).

In #466 (comment 6436427163), @emil-d contributed a fresh <myhome-bus-card> sweep export (myhome_sweep_MH201_all_2026-10-27T07-28-09.json) from a physical BTicino MH201 (3.6.43) connected to an IN lighting/scenario bus (E46ADCN + F425 + 5 lighting/dimmer endpoints including F414) coupled via an F422 in galvanic separation mode (MOD empty, I4 = 2) to an OUT sound bus (F441M audio matrix + F500 COAX powered by 346000). The export provides three pieces of field evidence not previously pinned in the repository:

  1. First committed physical WHO=1013 OBJECT_MODEL = 10 (15*0*5) & FW 3.6.43 fixture:
    • Settles WHO=13 shared device code 200 (*#13**15*200##) via startup *#1013**1*10*15*0*5##, recording who1013_code = "10", who1013_model = "MH201", who1013_n_conf = "15", who1013_brand = "0", and who1013_line = "5" — confirming on a second firmware (3.6.43, alongside 3.6.27 in OpenWebNet-HA/OWNd#116) that the MH201 MCU emits BRAND = 0 / LINE = 5 rather than the BRAND = 5 / LINE = 0 tuple emitted by Linux-based gateways (F454, MH202, MyHomeServer1, F461).
  2. Exact on-wire timing proof of fast WHO=13 NACKs vs silent command-session hangs and single-slot TCP lockout on MH201:
    • On unsupported Linux WHO=13 dimensions 23/24, the MH201 MCU emits zero rx event frames and answers *99*0## with a fast *#*0## NACK in ~9.8–9.9 ms (+109.9 ms / +109.8 ms including MH201Profile.command_queue_delay = 100 ms).
    • In contrast, after *#1*0## completes cleanly in 283.8 ms (5 lighting replies) + BUS_QUIET_PERIOD = 0.750 s, *#2*0## on a cover-less plant receives neither an event-session echo nor a command-session ACK/NACK, hitting COMMAND_TIMEOUT = 10.0 s + 0.750 s (+10.753 s).
    • Because the MH201 accepts only 1 concurrent TCP command session (max_command_sessions = 1) and holds the unacknowledged server-side socket open after client close, *#4*0## (still sent on 2.0.0b16 before fix(gateway): skip the startup requests the firmware refuses, poll each zone's temperature on SCS-only gateways #794) backs off across MAX_CONNECT_ATTEMPTS = 5 (1 + 2 + 4 + 6 = 13.0 s + 5 TCP *99*0## handshakes = 13.744 s), then hangs for another 10.0 s + 0.750 s (+24.494 s), and subsequent sweep frames (*#16*0*5##, *#18*51..52*51/1200##) exhaust all MAX_CONNECT_ATTEMPTS = 5 (13.0 s + 0.682–0.727 s for 5 refused TCP *99*0## handshakes) on the locked command slot after CommandWorkerPool.sending_loop logged their tx intent in bus_monitor.
  3. Field evidence on MH201 + WHO=16 Sound System:
    • Contributor independently tested *#16*0*5## on an idle *99*0## command session: the MH201 returned neither ACK nor NACK, echoed *#16*0*5## once on *99*1##, and forwarded zero WHO=16 frames even when physical audio controls were operated on the F441M sound bus.

Refs #466


What is the solution

  1. Verbatim Trace Fixture (tests/fixtures/traces/issue_466/myhome_sweep_MH201_all_2026-10-27T07-28-09.json):
    • Added @emil-d's untouched <myhome-bus-card> sweep export (22 frames: 13 tx / 9 rx, SHA-256: 54717d7ef7b207d04a475d9c379d1b043f8e50a533cbfec400e248c315f5e2bb).
  2. Trace Corpus Documentation (tests/fixtures/traces/issue_466/README.md):
    • Documented the MH201 3.6.43 hardware profile, E46ADCN + F422 (I4 = 2) + F441M (S1 = F500 COAX) plant topology, WHO=13 / WHO=1013 identification fields (10*15*0*5), fast ~10 ms command-session NACKs on WHO=13 dims 23/24, WHO=1 lighting scan, exact COMMAND_TIMEOUT + single-slot MAX_CONNECT_ATTEMPTS lockout intervals, and idle-session WHO=16 probe observations.
  3. Trace Replay & Timing Regression Suite (tests/test_issue_466_mh201_3_6_43_sweep_trace.py):
    • Added 5 deterministic tests pinning verbatim SHA-256 provenance and privacy/schema hygiene, WHO=1013 OBJECT_MODEL = 10 (15*0*5) replay on both SSDP MH201 and manual Generic entries, sub-300 ms WHO=13 replies + 100 ms command_queue_delay + fast ~10 ms NACKs on Linux-only dimensions 23/24, exact derived command-timeout and 5-attempt TCP handshake lockout intervals (+0.751 s, +10.753 s, +24.494 s, +14.477 s, +13.682 / 13.701 / 13.685 s with uniform 0.68–0.75 s 5-handshake RTT overhead) alongside #794's *#4*0## suppression in both _discovery_frames() and _handler_refuses_frame(), and full config-entry replay discovering the 5 OFF light entities (31, 32, 71, 72, 73) with zero phantom cover/climate/audio/alarm/sensor entities.

State Diagram

stateDiagram-v2
    [*] --> WHO13Queries : myhome.sweep_bus (frames 0..7)
    WHO13Queries --> LightingScan : *#13**0/15/16## answered (<254 ms); 23/24 fast ~10 ms NACK (0 rx)
    LightingScan --> QuietAfterLights : tx *#1*0## -> rx echo (67 ms) + 5 lights (284 ms)
    QuietAfterLights --> CoverQueryHang : +0.751 s (BUS_QUIET_PERIOD) -> tx *#2*0##
    CoverQueryHang --> ClimateSlotWait : +10.00 s COMMAND_TIMEOUT + 0.75 s quiet (slot still held on MH201)
    ClimateSlotWait --> ClimateQueryHang : +13.744 s (1+2+4+6 s backoff + 5 TCP handshakes) -> tx *#4*0## (skipped since #794)
    ClimateQueryHang --> ExhaustedSlotLockout : +10.00 s COMMAND_TIMEOUT + 0.75 s quiet (+24.494 s total)
    ExhaustedSlotLockout --> [*] : frames 17..21 (*#16*0*5##, *#18*51..52##) abort after MAX_CONNECT_ATTEMPTS=5 (13.0 s + 0.68-0.73 s RTTs)
Loading

Hardware Verification

  • Captured on physical BTicino MH201 (firmware 3.6.43, WHO=13 code 200, WHO=1013 10*15*0*5, Home Assistant 2026.10.4, MyHOME 2.0.0b16, OWNd 2.0.0b11) by @emil-d on 2026-10-27 (#466 comment 6436427163).
  • Verified against both editable OWNd (master) and manifest-pinned OWNd==2.0.0b11.

How is it tested

  • tests/test_issue_466_mh201_3_6_43_sweep_trace.py (5 tests):
    • test_mh201_3_6_43_sweep_schema_privacy_and_frame_counts: verifies verbatim SHA-256 (54717d7ef7b2…), zero PII findings (findings() == []), capture/telemetry/queue_pacing metadata, and OWNMessage.parse() coverage across all 22 frames.
    • test_mh201_3_6_43_first_physical_who1013_object_model_10_and_replay: pins gateway.identification (who1013_code == "10", 15*0*5, 3.6.43) and replays *#13**15*200##, *#13**16*3*6*43##, and *#1013**1*10*15*0*5## through MyHOMEGatewayHandler on both SSDP MH201 and manual Generic entries.
    • test_mh201_3_6_43_who13_queries_and_non_linux_mcu_silence: pins *#13**0## (253.9 ms), *#13**15## (18.6 ms), and *#13**16## (18.8 ms) latencies, 100 ms MH201Profile.command_queue_delay, and fast ~9.8–9.9 ms command-session NACKs (zero rx event frames) on Linux-only dimensions 23 and 24.
    • test_mh201_3_6_43_command_timeout_and_single_slot_lockout_timings: verifies every inter-frame delta in frames[8..21] directly from BUS_QUIET_PERIOD (0.75 s), COMMAND_TIMEOUT (10.0 s), MAX_CONNECT_ATTEMPTS = 5 (1 + 2 + 4 + 6 = 13.0 s backoff), and uniform 0.68–0.75 s 5-attempt TCP handshake RTT overhead, plus #794's *#4*0## suppression in both _discovery_frames() and _handler_refuses_frame().
    • test_mh201_3_6_43_sweep_replay_discovers_five_lights_and_no_phantom_entities: replays all 9 rx frames through a live MH201 config entry and asserts the exact 5 OFF light entities (31, 32, 71, 72, 73) and zero phantom cover, climate, media_player, alarm_control_panel, or sensor entities.
  • Full verification suite in WSL (Python 3.14.7, Home Assistant 2026.10.1, plus pinned OWNd==2.0.0b11):
    • pytest tests -q -o addopts="" --cov=custom_components/myhome --cov-report=term-missing: 5,028 passed, 5 snapshots passed, 100.0% statement coverage (17,455 / 17,455 statements, 0 missed) (including test_trace_oracle_validation.py and test_fixture_privacy.py).
    • PYTHONPATH=/tmp/ownd_rel_b11 pytest tests/test_issue_466_mh201_3_6_43_sweep_trace.py: 5 passed.
    • ruff check custom_components tests scripts: All checks passed (0 violations).
    • scripts/typing_ratchet.py & mypy --strict: 0 error(s) in 0 module(s); baseline 0.
    • scripts/verify_ha_standards.py: SUCCESS (54/54 Platinum rules satisfied).
    • scripts/sync_documentation.py --check & scripts/manage_translations.py check: SUCCESS (0 broken links, Trace Matrix in sync, 100.0% en 428/428 keys).

Where are the traces


What does the Firmware Oracle / Encyclopedia say

  1. OpenWebNet Encyclopedia (OWN-DEV-0190, OWN-DEV-0078, WHO=1013 Dimension 1):
    • MH201 (devices/definitions/own-dev-0190-mh201-hotel-room-scenario-manager.md): Catalogued in MHCatalogue.db (v3.5.38) as id_item = 1771 (EN_DEVICE 1814) under Access control (id_system = 8) with modobj = 10, brand_modobj = 5 (Legrand BTicino), and line_modobj = 0 (Undefined), hosting Objects 98 (Hotel room manager), 150 (Gateway Open SCS), and 250 (Gateway XOpen SCS). Note that physical MH201 firmware emits *#1013**1*10*15*0*5## (BRAND = 0, LINE = 5), confirming why identity.py resolves OBJECT_MODEL = 10 directly without filtering on id_system = 26 or trailing BRAND/LINE. Its manual (RA00122AB_S_EN) documents scenario families for lighting, automation, temperature, alarm, contacts, hotel, scenarios, and time — never sound (WHO=16 / WHO=22), matching MH201Profile(audio_measured=False).
    • F422 (devices/definitions/own-dev-0078-scs-scs-interface.md, MQ00280-f-EN p. 5): Unconfigured MOD with I1..I3 empty and I4 = 1..9 selects Object 76 (Interface SCS / SCS galvanic), isolating power supplies (IN automation vs OUT sound powered by 346000) while bridging digital SCS bus frames when no multiple automation systems share the sound bus.
  2. Firmware Oracle:
    • MH201 uses an embedded non-Linux MCU firmware (confirming immediate NACK / zero rx data frames on *#13**23## and *#13**24##) and is not part of the 12 Linux-based QEMU images in own-firmware-oracle; all 13 deterministic tx frames and 9 rx frames in this fixture pass tests/test_trace_oracle_validation.py Tiers 1–4.

Checklist

  • PR Description: Follows the structured sections above (What is the problem, What is the solution, State Diagram, How is it tested, Where are the traces, What does the Firmware Oracle / Encyclopedia say).
  • Trace-Backed (if protocol/device change): Behavior is verified against real OpenWebNet bus traces or gateway firmware fixtures rather than guessed.
  • V2 Architecture: New or modified entities implement handle_event() and do not manually subscribe via async_dispatcher_connect.
  • Tests & Linters: pytest, ruff, and scripts/verify_ha_standards.py pass.

…trace (#466)

Ingests the verbatim <myhome-bus-card> sweep export contributed by @emil-d
on #466 (comment 6436427163) from a physical BTicino MH201 gateway (FW 3.6.43,
WHO 13 device code 200, WHO 1013 OBJECT_MODEL 10 with trailing 15*0*5
metadata) and pins its identification, lighting replay, and single-slot
command-session timeout/lockout timing in pytest.

Co-authored-by: emil-d <50502252+emil-d@users.noreply.github.com>
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…3 NACKs (#466)

- Pin verbatim SHA-256 provenance (54717d7ef7b2...) and trailing-newline absence on the <myhome-bus-card> JSON.stringify export.
- Distinguish fast ~10 ms command-session NACKs on unsupported WHO 13 dims 23/24 (+ 100 ms MH201Profile.command_queue_delay) from 10 s silent command-session hangs on *#2*0## / *#4*0##.
- Replace empirical 0.74/0.73/0.64+0.05 offsets with exact protocol-constant derivation (removing the bogus 50 ms _send_query sleep since all 36 WHO 18 queries are already queued in FrameScheduler and failed connect() bypasses command_queue_delay), showing uniform 0.68-0.75 s 5-handshake TCP RTT across frames[16..21].
- Assert _handler_refuses_frame('*#4*0##') sweep suppression (#794), exact light WHERE set, and zero phantom sensor entities.
@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit a1f0bed into v2-phase1-architecture Oct 27, 2026
16 checks passed
@GreenGrassBlueOcean
GreenGrassBlueOcean deleted the test/trace-issue-466-mh201-3-6-43 branch October 27, 2026 17:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants