Skip to content

fix(heating,gateway): address review follow-ups on #116 (MH201 in device type 200, dead _is_active assignment, fixture comments) - #119

Merged
GreenGrassBlueOcean merged 3 commits into
masterfrom
fix/f461-review-followups
Oct 16, 2026
Merged

GreenGrassBlueOcean merged 3 commits into
masterfrom
fix/f461-review-followups

Conversation

@GreenGrassBlueOcean

@GreenGrassBlueOcean GreenGrassBlueOcean commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Related: #116 (review 5538969896), #119, #97, #117, OpenWebNet-HA/MyHOME#370, OpenWebNet-HA/MyHOME#466, and OpenWebNet-HA/MyHOME#714.

What is the problem?

Following the merge of #116 (fix(heating,gateway): dimension 19 is_active, plain protection form and device type 200 from a live F461 capture) and #97 (feat/who4-consolidated-heating), post-merge review by @fedem95 (review 5538969896) and follow-up review on this PR (review 5540201753) plus the master merge of #97 identified five items:

  1. WHO 13 Dimension 15 code 200 omitted MH201:
    OWNd/message/gateway.py mapped *#13**15*200## to "F454 / MyHomeServer1 / MH202 / F461 / H4890" and described it as "shared by every Linux-based gateway generation". However, physical MH201 gateways (which are catalogued in the firmware oracle on 030644 as a bare-metal image rather than one of the Linux gateways emulated by QEMU) also reply *#13**15*200## on firmware 3.3.6 and 3.6.27. Furthermore, on MH201 (fw 3.6.27), WHO 1013 dimension 1 only distinguishes the model when queried with an empty WHERE (*#1013**1## → *#1013**1*10*15*0*5##, model 10), whereas *#1013*0*1## is NACKed and no unsolicited WHO 1013 frame is pushed after the 200 reply.
  2. Dead / redundant self._is_active assignments in WHO 4 Dimension 19 (OWNd/message/heating.py):
    After #116 consolidated self._is_active = bool(self._is_cooling or self._is_heating or self._cooling_fan_on or self._fan_on) at the end of the dimension 19 block, the initial self._is_active = self._is_cooling | self._is_heating assignment and the two self._is_active = True assignments inside the cooling/heating fan-speed branches were unconditionally overwritten and redundant.
  3. Stale fixture & oracle path comments in OWNd/message/heating.py and tests/test_f461_live_plant.py:
    Two comments in OWNd/message/heating.py still referenced tests/fixtures/f461_live_2026-10-15/ (removed in ca0519e when the F461 frames were inlined into tests/test_f461_live_plant.py) and the legacy oracle3/out/termo_f461.tsv path (now superseded by the canonical thermo-zone-mode.tsv, ownd-pr82-heating.tsv, and thermo-ts10.tsv suites in own-firmware-oracle), and the module docstring in tests/test_f461_live_plant.py did not yet list MH201 under device type 200.
  4. Missing OWNd/message/door_entry.py row in README.md and scripts/sync_documentation.py:
    The coverage table in README.md and COMPONENT_NOTES in scripts/sync_documentation.py were missing an entry for OWNd/message/door_entry.py (WHO 6 & WHO 8).
  5. Semantic conflict between PR #116 and PR #97 on WHO 4 bare WHAT 0 / 1 season frames (tests/test_f461_live_plant.py):
    PR #97 (6af9377) aligned bare *4*0*Z## / *4*1*Z## frames with the Legrand WHO 4 specification (season == "conditioning" / "heating", message_type == "hvac_season", mode is None), causing test_season_frames_and_area_zero_lights_parse_as_before (added in #116 asserting .mode == "heat" / "cool") to fail on pull_request merge commits against master.

How is it solved?

  1. Include MH201 in WHO 13 code 200 and document firmware oracle provenance (OWNd/message/gateway.py):
    • Updated self._device_type for WHO 13 dimension 15 code 200 to "F454 / MyHomeServer1 / MH202 / F461 / H4890 / MH201".
    • Updated the inline comment to clarify that code 200 is shared across modern gateways (F454, MyHomeServer1, MH202, F461, H4890; plus emulated F450/F459/F460 in trace-corpus.tsv) and MH201 (fw 3.3.6, 3.6.27; catalogued on 030644 outside the Linux QEMU fleet), and that WHO 1013 dimension 1 tells them apart (the MH201 on 3.6.27 answers only *#1013**1##, model 10, and NACKs *#1013*0*1##).
    • Updated corresponding assertions and module docstring in tests/test_profiles.py and tests/test_f461_live_plant.py.
  2. Remove dead self._is_active assignments (OWNd/message/heating.py):
    • Removed the dead self._is_active = self._is_cooling | self._is_heating assignment and the two redundant self._is_active = True assignments in the cooling and heating fan-coil branches, leaving the single authoritative bool(self._is_cooling or self._is_heating or self._cooling_fan_on or self._fan_on) computation after both sides are parsed.
  3. Update F461 capture & firmware oracle suite comments (OWNd/message/heating.py, tests/test_f461_live_plant.py):
    • Pointed both F461 capture comments (dimension 19 active state and standalone protection addressing) to tests/test_f461_live_plant.py.
    • Replaced the legacy oracle3/out/termo_f461.tsv comment in OWNHeatingCommand.set_mode with exact own-firmware-oracle suite references (thermo-zone-mode.tsv, ownd-pr82-heating.tsv, and thermo-ts10.tsv) and SCS bus telegrams (D1 00 03 02 C2 ZZ 00 00 for 311, 93 ZZ 30 04 / 93 ZZ 30 0F for plain zone 302/303, and D1 00 03 02 C2 ZZ 07 00 for #Z central-unit 302).
  4. Synchronize door_entry.py documentation (README.md, scripts/sync_documentation.py):
    • Added "OWNd/message/door_entry.py": "WHO 6 & WHO 8: Door entry, electric lock, and video intercom events/commands" to COMPONENT_NOTES in scripts/sync_documentation.py and added the corresponding 100% coverage row to README.md.
  5. Rebase onto master (6af9377) and align F461 season assertion (tests/test_f461_live_plant.py):
    • Updated test_season_frames_and_area_zero_lights_parse_as_before to assert OWNEvent.parse("*4*1*2##").season == "heating" and OWNEvent.parse("*4*0*2##").season == "conditioning".

How is it tested?

  • Unit & Live-Capture Tests:
    • tests/test_f461_live_plant.py: All 17 F461 live-plant tests pass, including all 9 WHO 4 dimension 19 actuator/fan-coil combinations (5*1, 5*0, 8*0, 1*5, 5*5, 0*6, 0*1, 1*0, 0*0), test_device_type_200_is_not_a_single_model (asserting "MH201" in msg.device_type), and test_season_frames_and_area_zero_lights_parse_as_before.
    • tests/test_profiles.py: test_owngateway_event_properties verifies OWNGatewayEvent("*#13**15*200##").device_type == "F454 / MyHomeServer1 / MH202 / F461 / H4890 / MH201" and device_type_code == "200".
  • Full Test Suite & Coverage:
    • pytest: 1,488 passed, 1 skipped on rebased master.
    • Statement coverage: 100.0% (4,808 / 4,808 statements across all 25 modules verified by scripts/verify_coverage.py).
  • Static Analysis & Documentation Checks:
    • ruff check, mypy, scripts/verify_library_standards.py, and scripts/sync_documentation.py --check: 0 violations.

Where are the traces (real and firmware oracle)?

  1. Real Hardware Traces:

    • BTicino F461 (fw 2.0.11, captured 2026-10-15):
      • Inlined in OWNd: tests/test_f461_live_plant.py (GATEWAY_FRAMES, ACKED_FRAMES, NACKED_FRAMES, EVENTS_AFTER), including *#13**15*200## followed by *#1013**1*134*15*5*0##, WHO 4 dimension 19 frames (*#4*2*19*5*1##, *#4*2*19*5*0##, *#4*2*19*8*0##), season frames (*4*1*2##, *4*0*2##), and standalone protection mode transitions (*4*302*2## → *4*202*2##).
      • Full multi-session capture archive: Stored in the MyHOME trace corpus at tests/fixtures/traces/f461_live_2026-10-15/ (OpenWebNet-HA/MyHOME#714).
    • BTicino MH201 (fw 3.6.27 and 3.3.6):
      • fw 3.6.27 (captured 2026-10-14 by @fedem95): Reports *#13**15*200##, NACKs *#1013*0*1##, and answers *#1013**1## with *#1013**1*10*15*0*5## (model 10); plant fixture archived in OpenWebNet-HA/MyHOME at tests/fixtures/plants/mh201_physical_plant/.
      • fw 3.3.6: Reports *#13**15*200## in OpenWebNet-HA/MyHOME at tests/fixtures/traces/issue_466/myhome_sweep_MH201_all_2026-09-25T19-11-35.json (OpenWebNet-HA/MyHOME#466).
    • F454 / MyHomeServer1 / MH202 / H4890:
      • Captured in OpenWebNet-HA/MyHOME issues #370 and #466 and catalogued in tests/golden/frames/who13_gateway.yaml.
  2. Firmware Oracle Traces & Disassembly (OpenWebNet-HA/own-firmware-oracle, synced in tests/golden/firmware_oracle.json, tests/oracle/who4_facts.json, & #117):

    • WHO 13 Dimension 15 (*#13**15##) & WHO 1013 Dimension 1 (*#1013*0*1##) (trace-corpus suite, results/<PRODUCT>/<VERSION>/oracle/full/trace-corpus.tsv):
      • Across the 11 QEMU-emulated gateways (F450 020010, F453AV 030014, F454 020051, F459 020105, F460 020012, F461 020011, H4684 020054, MH200 020102, MH200N 010108, MH202 010024, MyHomeServer1 028206):
        • F450, F454, F459, F460, F461, MH202, and MyHomeServer1 all reply *#13**15*200## (while H4684 replies *#13**15*13##, MH200 replies *#13**15*4##, and F453AV / MH200N reply *#13**15*0##).
        • MH201 (030644, SHA-256 8972720b01d1...) is catalogued in results/mcp_index.json (status: "catalogued"), not among the Linux QEMU-emulated targets.
        • All 11 emulated gateways accept *#1013*0*1## with ACK (H4684 emits *#1013**1*19##, MH200 emits *#1013**1*4##, and the unprovisioned Linux images emit *#1013**1*0*0*0*0## when AS_ITEM_SYSTEM is unpopulated in the clean QEMU rootfs), whereas on physical hardware MH201 (fw 3.6.27) NACKs *#1013*0*1## and answers only *#1013**1## (*#1013**1*10*15*0*5##).
    • WHO 4 Mode & Protection Addressing (ownd-pr82-heating, thermo-zone-mode, thermo-central-mode, thermo-ts10 suites):
      • Automatic mode (311): Plain *4*311*1## and *4*311*99## are NACKed by all 11 emulated gateways with zero bus frames emitted, whereas #Z (*4*311*#1##, *4*311*#99##, *4*311*#23#1##) is translated and dispatched onto the SCS bus (24 30 36 44 31 30 30 30 33 30 32 43 32 30 31 30 30 30 30 0d → D1 00 03 02 C2 01 00 00).
      • Standalone vs Central-Unit Zone Mode (302 / 102 / 202 / 303): Emulated bt_termo translates plain zone commands into direct 4-byte SCS zone telegrams (e.g., *4*302*23## → 93 17 30 04; *4*303*1## → 24 30 34 39 33 30 31 33 30 30 46 0d / 93 01 30 0F in thermo-zone-mode.tsv), whereas #Z commands (*4*302*#23##, *4*302*#23#1##, *4*102*#0##, *4*202*#0##) are translated into extended D1 central-unit frames (D1 00 03 02 C2 17 07 00 / 24 30 36 44 31 30 31 30 33 30 32 43 32 31 37 30 37 30 30 0d in thermo-ts10.tsv).
    • WHO 4 Dimension 19 & Season (thermo-valve suite & tests/oracle/who4_facts.json):
      • *#4*1*19## is replayed in results/<PRODUCT>/<VERSION>/oracle/full/thermo-valve.tsv (no virtual fan-coil/valve actuator is attached on the emulated bus to emit *#4*Z*19*COOL*HEAT## responses, so the 9 actuator/fan-coil combinations are verified via the live F461 capture in tests/test_f461_live_plant.py), and tests/oracle/who4_facts.json (1,745 decoded firmware facts) verifies the WHO 4 season contract (*4*0*Z## / *4*1*Z##).

@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!

fedem95
fedem95 previously approved these changes Oct 15, 2026

@fedem95 fedem95 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking these up. Tested 6869de1: 1354 passed locally (test_metadata deselected, local env), CI green. The fan-branch _is_active = True removals don't change behaviour, since the final bool(...) already covers cooling_fan_on/fan_on, and the nine dimension 19 cases in test_f461_live_plant.py still pass. Approving.

One nit on the new comment, non-blocking: model 10 comes from my MH201 on 3.6.27 only; I haven't seen a *#1013 reply from a 3.3.6. And the other gateways in the traces were asked *#1013*0*1##, not *#1013**1##, so "*#1013**1## tells them apart" is only shown for the MH201 so far. Maybe: "WHO 1013 dimension 1 tells them apart (the MH201 on 3.6.27 answers only *#1013**1##, model 10)".

(The README also gains the missing door_entry.py coverage row, which isn't in the summary.)

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

Thanks @fedem95! Addressed both points in 34e23e3:

  1. Updated the WHO 13 code 200 comment in OWNd/message/gateway.py to your exact phrasing (WHO 1013 dimension 1 tells them apart (the MH201 on 3.6.27 answers only *#1013**1##, model 10)).
  2. Added OWNd/message/door_entry.py to COMPONENT_NOTES in scripts/sync_documentation.py (so README.md has the proper WHO 6 & WHO 8 description instead of the generic fallback) and documented it in the PR summary.

@fedem95 fedem95 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the latest commit 13dc38f. The follow-ups are addressed, and CI and the MyHOME conformance checks are green. Approving.

@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit 06a967d into master Oct 16, 2026
16 checks passed
@GreenGrassBlueOcean
GreenGrassBlueOcean deleted the fix/f461-review-followups branch October 16, 2026 22:10
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.

3 participants