Repository navigation
fix(expose,yaml): gate UI WHO 4/18 injection by firmware & fix MappingProxyType MAC mismatch (#832) - #883
Merged
GreenGrassBlueOcean merged 1 commit intoNov 10, 2026
Conversation
3 tasks done
…gProxyType MAC mismatch (#832) - Fix _log_mac_mismatch in legacy_yaml.py and _extract_gateway_info in websocket.py checking isinstance(..., dict) instead of Mapping, which ignored real Home Assistant ConfigEntry.data (MappingProxyType) and logged false-positive myhome.yaml MAC warnings on secondary gateways (#832). - Gate WHO 4 and WHO 18 expose options and validation in ExposeSubentryFlowHandler by effective gateway profile (including shared-bus delegation), and guard WHO 18 Dimension 1200 queries/writes when WHO 18 injection is unsupported. - Allow zone master probe addresses 1..99 in WHO 4 expose validation while blocking 3-digit probe addresses >= 100 on bt_termo #0-only gateways (F461/F460/MHS1 translate 1..99 to SCS D1 <ZZ> 03 02 F0 <TT> <TT> 00 and NACK >= 100), and defer climate zone discovery on exposed WHO 4 Dimension 0 frames until a real zone frame arrives. - Document 8-gateway Firmware Oracle findings for WHO 1/2/4/13/18 expose compatibility in docs/configuration/expose.md.
GreenGrassBlueOcean
force-pushed
the
fix/issue-832-expose-gateways-and-mac-mismatch
branch
from
November 9, 2026 08:26
7dce035 to
96de2fb
Compare
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is the problem
In #832 (comment), a multi-gateway setup (
F454primary + Legrand067292/H4890secondary on a shared SCS bus) surfaced four bugs and UX/documentation gaps around Expose to Bus and legacymyhome.yamlloading:myhome.yamlMAC mismatch warning on multi-gateway setups (legacy_yaml.py&websocket.py):_log_mac_mismatchincustom_components/myhome/legacy_yaml.pycheckedisinstance(getattr(other, "data", None), dict). In Home Assistant core, a realConfigEntry.datais an immutabletypes.MappingProxyType(acollections.abc.Mapping, not adict).ConfigEntrywas skipped,other_macswas always[], and any secondary gateway without its ownmyhome.yamlsection logged a false-positive warning claiming the primary gateway's MAC section "match[es] no configured gateway".custom_components/myhome/websocket.py(_extract_gateway_info) had the identicalisinstance(config_data, dict)check, discardingconfig_entry.datafallbacks (name,host,port,firmware,command_worker_count) on real entries.WHO = 4andWHO = 18bindings on gateways that cannot inject them (config_flow_expose.py):ExposeSubentryFlowHandleroffered all five WHO subsystems (1,2,4,13,18) unconditionally, even on gateways whose firmware rejects or dropsWHO = 4/WHO = 18sensor injection (067292/H4890,H4684,F454,F459,MH200N,MH202), only to disable the created subentry at runtime with startup log warnings.ExposeManager._async_handle_bus_message,WHO = 18DIMENSION 1200status queries (*#18*W*1200##) and streaming handshakes (*#18*W*#1200#1*T##) were not gated by_injection_block_reason(18).WHO = 4#0probe addressing contradiction onbt_termogateways (validate.py,expose.py,climate.py):a902370brestricted_WHO4_EXPOSE_PROBE_REto 3-digit addresses (100..900,101..899) and forbade1..99.F461(2.0.11) capture (f461_live_2026-10-18) and 8-gateway QEMU Firmware Oracle verification prove thatbt_termo(F461,F460,MyHomeServer1) only translates*#4*WHERE*#0*TTTT##onto the SCS bus (D1 <ZZ> 03 02 F0 <TT> <TT> 00) for zone master addresses1..99, andNACKs (*#*0##, zero SCS bus bytes) every 3-digit probe address>= 100(100,101,150) and#Z(#50).docs/configuration/expose.md):where: "100"forWHO = 4and implied virtual temperature (WHO = 4) and virtual energy (WHO = 18) sensors could feed067292/H4890wall touchscreens across standard gateways.Related to #832 (and complements #880).
What is the solution
Mapping-safeConfigEntry.datainspection (legacy_yaml.py,websocket.py):isinstance(..., dict)withisinstance(..., collections.abc.Mapping)in_log_mac_mismatchand_extract_gateway_info, skip sibling entries matchingentry.entry_id, and fall back to stringother.unique_idwhenCONF_MACis absent fromother.data.config_flow_expose.py,expose.py,strings.json,translations/{en,fr,it,nl}.json):effective_expose_profile(gateway, who)andinjection_block_reason(profile, who, where=None)inexpose.py(accounting for shared-bus WHO delegation).WHO = 4/WHO = 18choices inExposeSubentryFlowHandler._build_schema(while keepingwho_defaultvisible when reconfiguring an existing subentry) and reject unsupported submissions with translatedwho_injection_unsupported/who4_probe_where_unsupportederrors acrossen,fr,it, andnl.WHO = 18DIMENSION 1200queries and streaming writes inExposeManager._async_handle_bus_messagebehindself._injection_block_reason(18) is None.WHO = 4#0master-probe1..99addressing & phantom-climate protection (validate.py,expose.py,climate.py):1..99in_WHO4_EXPOSE_PROBE_REalongside100..900and101..899(continuing to reject0,001..099,#Z, and901..999).WHO = 4probe addresses (is_probe(where)) on#0-only gateways (supports_dimension_response_injectionnotTrue, e.g.F461) ininjection_block_reason.climate.py(accept), deferWHO = 4DIMENSION 0-only bus discovery on exposed addresses (is_exposed_on_bus) inpending_actionsso unused virtual master probes (e.g.WHERE = "50") never spawn phantomclimateentities, while real heating zones using a virtual master probe still discover on their first zone frame (DIMENSION 14, mode event, etc.) and retain the deferred temperature.docs/configuration/expose.md):where: "100"towhere: "50"and document the exact Firmware Oracle matrix forWHO = 1,2,4,13, and18across gateways and067292/H4890wall touchscreens.State Diagram
flowchart TD Input["Expose Binding / UI Subentry<br>(who, where)"] --> Resolve["effective_expose_profile(gateway, who)<br>(resolves shared-bus delegation)"] Resolve --> GateWHO{"who in {1, 2, 13}?"} GateWHO -->|Yes| AllowAll["Allowed on ALL gateways<br>(F454, MH200N, MHS1, F461, 067292, H4890)"] GateWHO -->|No: WHO 4 or 18| CheckFlag{"Profile supports injection?<br>(supports_who4_command_injection /<br>supports_who18_response_injection)"} CheckFlag -->|False or None| BlockWHO["Blocked: hidden in UI dropdown,<br>who_injection_unsupported error,<br>single startup warning if YAML"] CheckFlag -->|True & WHO 18| AllowW18["Allowed (TCP session report)"] CheckFlag -->|True & WHO 4| CheckDimResp{"supports_dimension_response_injection?"} CheckDimResp -->|True| Allow3Digit["1..99 & 100..900 allowed<br>(*#4*W*0*TTTT##)"] CheckDimResp -->|False: bt_termo #0 mode| CheckMaster{"WHERE in 1..99?"} CheckMaster -->|Yes: e.g. 50| AllowMaster["Allowed (*#4*50*#0*0215## -> SCS D1 32 03 02 F0 00 D7 00)<br>climate.py defers Dim 0 until real zone frame"] CheckMaster -->|No: 3-digit 100..900| Block3Digit["Blocked: who4_probe_where_unsupported<br>(bt_termo NACKs >= 100)"]Hardware Verification
F461(v2.0.11) hardware capture (tests/fixtures/traces/f461_live_2026-10-18/myhome_trace_F461_live_2026-10-18.json):*#4*50*#0*0215##(WHERE = 50, zone master probe1..99) was acknowledged with*#*1##in155 ms(t = 66.612s..66.768s), whereas*#4*000*0*0215##was rejected with*#*0##.F454+ Legrand067292) (#832):ConfigEntry.dataMappingProxyTypecaused the false-positivelegacy_yaml.py:272MAC mismatch warning on the secondary067292entry whenmyhome.yamlcontained only the primaryF454section.How is it tested
tests/test_legacy_yaml.py::test_legacy_yaml_mappingproxy_entries_and_unique_id_fallback_do_not_warn:MockConfigEntryinstances (isinstance(entry.data, MappingProxyType)),unique_id-only fallback entries, and non-Mappingpeer guards log zero false-positive warnings.tests/test_websocket.py::test_extract_gateway_info_edge_cases:_extract_gateway_infopreservesmodel,host,port,firmware,mac_prefix, andworker_countwhenconfig_entry.datais aMappingProxyType.tests/test_expose.py::test_expose_subentry_flow_hides_and_rejects_unsupported_gateway_whos:H4890/067292profiles filterWHO = 4andWHO = 18out ofSelectSelectoroptions (["1", "2", "13"]), rejectWHO = 4/18submissions withwho_injection_unsupported, keepwho_defaultselectable when reconfiguring a legacy subentry, and preserve all 5 options whenruntime_datais unloaded.tests/test_expose.py::test_expose_subentry_flow_and_diagnostics&test_unverified_gateway_disables_who4_who18_with_one_warning:F461Profile()rejects 3-digitWHERE = "100"(who4_probe_where_unsupported) and acceptsWHERE = "50", logs thebt_termo1..99restriction once for legacy 3-digit bindings, and suppressesWHO = 18DIMENSION 1200queries/writes whenWHO = 18is blocked.tests/test_expose.py::test_climate_discovery_defers_exposed_who4_master_probe_until_real_zone_frame:WHO = 4master probes (50,6#4#01) do not create phantomclimateentities onDIMENSION 0frames, while a real zone (5) receivingDIMENSION 0followed byDIMENSION 14(*#4*5*14*0210*1##) discovers cleanly and retains the deferred21.5 °Cprobe reading.pytest(100.0%statement coverage onconfig_flow_expose.py,expose.py,legacy_yaml.py,validate.py,websocket.py, and all touched lines inclimate.py), plusOWNd==2.0.0b13pinned wheel verification (166 passed).ruff check custom_components tests scripts: passed (0violations).scripts/verify_ha_standards.py: passed (all 54/54 Platinum rules).scripts/sync_documentation.py --check: passed (0broken links/anchors).scripts/typing_ratchet.py(mypy --strict):0errors.Where are the traces
tests/fixtures/traces/f461_live_2026-10-18/myhome_trace_F461_live_2026-10-18.json(lines2941–2986,run4_expose_virtual_bus_verification:*#4*50*#0*0215##->*#*1##in155 ms;*#4*000*0*0215##->*#*0##).What does the Firmware Oracle / Encyclopedia say
WHO = 4&WHO = 18):WHO = 4(ownkb:claim:c003147,ownkb:source:s000138,functional/who-4-temperature-control/addressing.mdownkb:section:d000068:s000002): specifies1..99asMaster probe of zone 1..99,0asAll probes,001..099asAll probes in zone,PZZ(P=1..8,ZZ=01..99) asIndividual probe, and#1..#99asZone via central unit.WHO = 18(ownkb:claim:c002222,ownkb:chunk:r000405&ownkb:chunk:r000421,ownkb:source:s000145,functional/who-18-energy-management/dimensions.md):DIMENSION 113active power (*#18*WHERE*113##/*#18*WHERE*113*Val##) is specified as a meter report/query;#113write is not specified, andDIMENSION 1200(*#18*WHERE*#1200#Type*Time##) controls active-power notification streaming.own-firmware-oraclev3):F461/02001103152383…,F460/020012ba867963…,MyHomeServer1/028206aa1cd393…,F459/02010509db3bd1…,F454/020051be0304f6…,MH200N/010108e32d3404…,MH202/010024bb561eb2…,H4684/02005409b0cf4e…):*#4*1*#0*0215##,*#4*50*#0*0215##,*#4*99*#0*0215##:ACK+ SCS TX ($06D1<ZZ>0302F000D700\r,0x00D7 = 215 = 21.5 °C) onF461,F460, andMyHomeServer1(bt_termo);NACK/ silent onF454,F459,MH200N,MH202(bt_H4690), andH4684(bt_termoreg).*#4*100*#0*0215##,*#4*101*#0*0215##,*#4*150*#0*0215##,*#4*#50*#0*0215##:NACK/ silent on all 8 gateways.*#18*51*#113*570##:NACK/ silent on all 8 gateways.*#18*51*113*570##:NACK/ silent onH4684(bt_energia);ACKon TCP onF461,F460,MyHomeServer1,F459,F454,MH200N, andMH202, but emits$06D1A10233001D0000\r(D1 A1 02 33 00 1D 00 00) — byte-for-byte identical to the*#18*51*113##active-power read request, discarding570.Checklist
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).handle_event()and do not manually subscribe viaasync_dispatcher_connect.pytest,ruff, andscripts/verify_ha_standards.pypass.