Repository navigation
feat(message): WHO 1004 thermoregulation diagnostics, WHO 16 tuner steps and station store, WHO 22 seek and step builders (companion of MyHOME#839) - #153
Conversation
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
… 22 seek builders ship in 2.0.0b12 The three companion bullets (OpenWebNet-HA#153) move under [2.0.0b12] - 2026-11-01, as the release carries them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Hi @gdluck — thank you for putting this PR together so cleanly and thoroughly, and huge credit for flagging the three oracle/trace discrepancies in your PR body:
*16*4001*101##,*16*4101*101##,*22*7*2#1##,*22*8*2#1##, and the cited firmware versions being absent fromfirmware_oracle.json, while noting that the functional docs list*22*5#STEP*2#S##/*22*6#STEP*2#S##;*#16*101*#10*5##emitting$0493018FD4($0494018FA0being*22*5*2#1##); and*#22*5#2#1*11*1*10530*0##not appearing inmyhome_trace_HL4684_all_2026-11-01T11-48-15.json.
Every single one of those catches was right on the money. I audited the implementation and my earlier specification comment (#152 (comment)) directly against libdiag.so, libwhere.so, bt_difson, and bt_multi disassemblies, tests/golden/firmware_oracle.json, and the OpenWebNet-Encyclopedia Machine KB (ownkb-build-0.9.0). All of the protocol defects below originated in my #152 spec comment — my apologies for handing you an unverified spec.
Let's reset #152 back to 76b9550c so 2.0.0b12 ships cleanly right away, and rework #153 on top of master for 2.0.0b13 using the verified specification below. (OWNd/message/__init__.py, scripts/sync_documentation.py, WHO 22 seek_up/seek_down + oracle rows, and the is_who4_dimension_22 negative guards are already clean and reusable as-is.)
1. Summary of Protocol Findings (F1–F7)
| ID | Severity | Location | Finding & Primary-Source Proof |
|---|---|---|---|
| F1 | Critical | heating.py (request_zone_actuator_diagnostic) |
Emits *#1004*#Z*22##. Across libdiag.so (F453, F454, F450, MH200N, MH202, MyHomeServer1, F460, F461), OPN_OpenScsDiagTermoTr registers only R20P0, R21P0, and R23P0 on top of base R0P0..R12P0. R22P0 does not exist ("Termo Object command not Found for key: R22P0" -> *#*0## NACK); DIMENSION 22 is spontaneous s2o-only (ownkb:chunk:r000205, "No request defined in this flow"). Single-zone queries use *#1004*#Z*21## (R21P0). |
| F2 | Critical | heating.py (OWNHeatingDiagnosticEvent) |
Wrong wire semantics for DIM 7, 13, 20, 21, 23:• DIM 7 (s2o_diagnosticsA, ownkb:claim:c004423): 24-bit '0'/'1' diagnostic mask on #0/#0#C and addressed devices (mirroring OWNLightingDiagnosticEvent _MASK_DIMENSIONS = (7, 11)), never 0..8 slave_count (DIM 8 & DIM 12 are also 24-bit masks).• DIM 21 (s2o_autoDiagnostics in libdiag.so): zone == 0 is rejected ('Invalid zone value', so #0*21*0 is never emitted); 1 <= zone <= 99 jumps to the exact same 16-bit '0'/'1' mask formatter as DIM 22, emitting *#1004*#ZONE*21*BIT16## on #Z / #Z#C (currently fails closed to None).• DIM 23 (s2o_autoDiagnostics zone == 100, ownkb:claim:c004442): emitted on #0 / #0#C as two decimal integer counts *#1004*#0*23*NO_RESPONSE*FAULTS##, not per-actuator 1..9 on #Z.• DIM 20 (R20P0) is a request-only trigger returning zero or more *#1004*#ZONE*21*BIT16## frames; DIM 13 in OPN_OpenScsDiagIdTr is a generic 32-bit hex Device ID, not WHO 4 local offset. |
| F3 | Medium | heating.py (WHERE & unique_id) |
OPN_ThermoWhereTransl (libwhere.so) accepts #0/#0#C, #Z/#Z#C (zone Z under 4-zone central unit C), Z (1..99 master probes), NZZ (101..899 slave probes, N=1..8, ZZ=01..99), [1-9]00 (100..900 external probes), and Z#N (Z=1..99, N=1..9 zone actuators such as 66#1). Also, because 1004 is (rightly) excluded from OWNMessage.interface, inherited OWNMessage.unique_id drops _where_param, colliding #0/#0#1/#0#2, #66/#66#2, and 66/66#1. |
| F4 | Critical | sound.py (WHO 16 step_frequency_*) |
Emits/decodes WHAT 4001..4015 / 4101..4115, which bt_difson NACKs on every WHO 16 gateway. Per ownkb:chunk:r000371 (ownkb:claim:c002096, c002098) and bt_difson o2s, manual frequency steps are 5001..5015 / 5101..5115 (5000/5100 = auto-seek) — and *16*5001*101## / *16*5101*101## are already in tests/golden/firmware_oracle.json (ack / out $0494018FA1 / $0494018FB1 across F453, F453AV, F454, H4684, MH200, MH200N). |
| F5 | High | sound.py (WHO 16 store_station & DIM 10) |
OWNSoundCommand.store_station allows 6..15, which bt_difson (cmp r0, #5; bgt NACK) NACKs on every WHO 16 gateway (ownkb:claim:c002128: "For DIMENSION 10, Established detail is station range 1 through 5"; 6..15 requires WHO 22 OWNSoundDiffusionCommand.store_preset *22*33#P*2#S##). And bt_difson s2o only formats dimensions 1, 6, 7, 8, 11 — never DIMENSION 10 (ownkb:claim:c002135), and *#16*101*10## is NACKed. |
| F6 | High | sound.py (WHO 22 step_frequency_*) |
Emits/decodes WHAT 7 / WHAT 8, which bt_multi logs as "Increment/Decrement subSource - not implemented yet" and NACKs (*#*0##), while leaving real manual steps *22*5#STEP*2#S## / *22*6#STEP*2#S## (1..15, ownkb:claim:c002729) with is_step_frequency_up == False. *22*5#1*2#1## and *22*6#1*2#1## are already in tests/golden/firmware_oracle.json (ack / out $0494018FA1 / $0494018FB1 across all 8 WHO 22 gateways). |
| F7 | Medium-High | sound.py, CHANGELOG.md, tests |
(a) sound.py docstrings/comments and CHANGELOG.md attribute *#22*5#2#1*11*1*10530*0## to (H/L4684 6.0.11 F500 trace, MyHOME#839) (bt_multi emits DIM 5 and skips DIM 11 when SCS station nibble is 'F', and *0## is absent from that trace).(b) kb_who_04_thermoregulation_diagnostics, kb_who_16_what_frequency_step, and kb_who_16_dim_station (13 occurrences across 5 files) do not exist in the Machine KB (ownkb:* IDs listed below). |
flowchart LR
subgraph WHO16["WHO 16 (bt_difson)"]
W16Step["*16*5001..5015*101## / *16*5101..5115*101##"] -->|"ACK + bus"| BusStep["$0494018FA1..FAF / FB1..FBF"]
W16Store["*#16*101*#10*1..5## (write-only)"] -->|"ACK + bus"| BusStore["$0493018FD0..FD4"]
end
subgraph WHO22["WHO 22 (bt_multi)"]
W22Seek["*22*5*2#1## / *22*6*2#1##"] -->|"ACK + bus"| BusSeek["$0494018FA0 / FB0"]
W22Step["*22*5#1..15*2#1## / *22*6#1..15*2#1##"] -->|"ACK + bus"| BusStep
W22Store["*22*33#1..15*2#1##"] -->|"ACK + bus"| BusStore22["$0493018FD0..FDE"]
end
subgraph WHO1004["WHO 1004 (libdiag.so)"]
D7["*#1004*WHERE*7## (R7P0)"] -->|"24-bit mask"| E7["*#1004*WHERE*7*BIT24##"]
D20["*#1004*#0*20## (R20P0)"] -->|"0..N faulty zones"| E21["*#1004*#Z*21*BIT16##"]
D21["*#1004*#Z*21## or #0*21## (R21P0)"] -->|"16-bit mask"| E21
D22["Spontaneous s2o only (no R22P0)"] -->|"16-bit mask"| E22["*#1004*#Z*22*BIT16##"]
D23["*#1004*#0*23## (R23P0)"] -->|"2 decimal counts"| E23["*#1004*#0*23*NO_RESPONSE*FAULTS##"]
end
2. Corrected Specification for #153
(Note across all new integer validators in heating.py and sound.py: guard if isinstance(val, bool) or not isinstance(val, (int, str)): raise ValueError(...) so True / False / 1.9 never silently coerce via int().)
A. WHO 1004 Thermoregulation Autodiagnostics (OWNd/message/heating.py + OWNd/message/__init__.py)
- Verified Machine KB citations:
ownkb:chunk:r000204–r000209,ownkb:claim:c004423,ownkb:claim:c004429–c004448(WHO_4v2.0.0 pp. 69–74), pluslibdiag.so(OPN_OpenScsDiagGenTr,OPN_OpenScsDiagTermoTr) andlibwhere.so(OPN_ThermoWhereTransl). WHEREgrammar & scopes (OPN_ThermoWhereTransl):- Central unit (
#0or#0#<1..99>,_WHO4_CENTRAL_WHERE_REGEX) ->scope == "central",zone_number is None. - Central-unit zone (
#<1..99>or#<1..99>#<1..99>— zoneZon central unit / 4-zone central unitC, matchingheating.py:116-120) ->scope == "zone"(or"zone_actuators"),zone_number == Z. - Addressed bus devices:
- Master probe
1..99(Z) ->scope == "probe",zone_number == Z. - Slave probe
NZZ(N = 1..8,ZZ = 01..99, i.e.101..199,201..299, …,801..899) ->scope == "slave_probe"(or"probe"),zone_number == ZZ,probe_number == N. - External probe
[1-9]00(100, 200, …, 900) ->scope == "external_probe",zone_number is None. - Zone actuator
Z#N(Z = 1..99,N = 1..9, e.g.66#1) ->scope == "actuator",zone_number == Z,actuator_number == N. - Anything else (
0,901,66#0,#0#0,#66#1#2, etc.) ->scope is None,zone_number is None.
- Master probe
- Central unit (
unique_idcollision fix:- Keep
1004excluded fromOWNMessage.interface. - Override
unique_idon bothOWNHeatingDiagnosticEventandOWNHeatingDiagnosticCommandusing the full reconstructedWHERE(#0#2->"1004-#0#2",#66#2->"1004-#66#2",66#1->"1004-66#1") so_where_paramis never dropped.
- Keep
OWNHeatingDiagnosticEventproperties (fail-closed):diagnostic_mask: str | None:- 24-bit
'0'/'1'mask whendimension in (7, 8, 11, 12)andscope in ("central", "probe", "slave_probe", "external_probe", "actuator"),len(dimension_value) == 1,len(v) == 24,set(v) <= {"0", "1"}. - 16-bit
'0'/'1'mask whendimension in (21, 22)andscope == "zone"(or"zone_actuators"),len(dimension_value) == 1,len(v) == 16,set(v) <= {"0", "1"}. - Otherwise
None.
- 24-bit
cleared_bits: tuple[int, ...] | None(1-based positions from left where bit =='0') andhas_fault: bool | None(bool(cleared_bits)whendiagnostic_mask is not None, elseNone). Note in docstring (ownkb:chunk:r000206–r000207) thatWHO_4v2.0.0 pp. 69–74 documents active-low bit conditions (13..16, 21..24for 24-bit central masks;11..16for 16-bit zone masks) while keepingdiagnostic_maskraw until hardware fault injection confirms bit-order indexing on the bus.DIMENSION 23fault-count summary onscope == "central"(*#1004*#0*23*NO_RESPONSE*FAULTS##,ownkb:claim:c004442): whendimension == 23,scope == "central",len(dimension_value) == 2, and both values are ASCII digits, exposenon_responding_units: int | Noneandfaulty_units: int | None(elseNone).- Remove
slave_count, scalarfaulty_zone, andDIM 13local-offset claims.
OWNHeatingDiagnosticCommandbuilders (only keys registered inlibdiag.so:R7P0,R8P0,R11P0,R12P0,R20P0,R21P0,R23P0— removerequest_zone_actuator_diagnostic/*22##):request_central_diagnostic(cls, where: str = "#0", *, dimension: int = 7) -> OWNHeatingDiagnosticCommand->*#1004*{target}*{dimension}##(dimension in (7, 8, 11, 12), defaulting to7perownkb:chunk:r000205, validated with_validate_central_where).request_device_diagnostic(cls, where: int | str, *, dimension: int = 7) -> OWNHeatingDiagnosticCommand(orrequest_probe_diagnostic,dimension in (7, 8, 11, 12)on valid addressed bus devicesZ,NZZ,[1-9]00,Z#N).request_zones_with_failures(cls, where: str = "#0") -> OWNHeatingDiagnosticCommand->*#1004*{target}*20##(R20P0,_validate_central_where).request_zone_diagnostic(cls, where: int | str = "#0") -> OWNHeatingDiagnosticCommand->*#1004*{target}*21##(R21P0: accepts#0,#0#C,1..99normalized to#Z,#Z, or#Z#C).request_fault_counts(cls, where: str = "#0") -> OWNHeatingDiagnosticCommand->*#1004*{target}*23##(R23P0,_validate_central_where).
B. WHO 16 FM Tuner Steps & Preset Store (OWNd/message/sound.py)
- Verified citations:
ownkb:chunk:r000371,ownkb:chunk:r000376,ownkb:claim:c002096,ownkb:claim:c002098,ownkb:claim:c002128,ownkb:claim:c002135, plustests/golden/firmware_oracle.json(sound-who16-source). - Frequency steps (
5001..5015/5101..5115):OWNSoundCommand.step_frequency_up(cls, where: str | int, step: int = 1)->*16*{5000 + s}*{target}##(1 <= s <= 15).OWNSoundCommand.step_frequency_down(cls, where: str | int, step: int = 1)->*16*{5100 + s}*{target}##(1 <= s <= 15).OWNSoundEvent: decode5001 <= self._state <= 5015->frequency_step_forward = self._state - 5000and5101 <= self._state <= 5115->frequency_step_backward = self._state - 5100.- Pin
*16*5001*101##and*16*5101*101##positively againsttests/golden/firmware_oracle.json(ack/out$0494018FA1and$0494018FB1onF453,F453AV,F454,H4684,MH200,MH200N).
- Station store (
1..5write-only):- Restrict
OWNSoundCommand.store_station(cls, where: str | int, station: int)to1 <= station <= 5(ownkb:claim:c002128,bt_difsonceiling), noting in the docstring that presets6..15require WHO 22OWNSoundDiffusionCommand.store_preset(*22*33#<P>*2#<S>##). - Revert
OWNSoundEventback toelif self._dimension == 7:(DIMENSION 10is write-only and never emitted bybt_difson). (We also cleaned up the matching downstreamMyHOMEcheck inOpenWebNet-HA/MyHOME#844.)
- Restrict
C. WHO 22 Seek, Frequency Steps & is_preset_cleared (OWNd/message/sound.py)
- Verified citations:
ownkb:chunk:r000478–r000480,ownkb:claim:c002729, plustests/golden/firmware_oracle.json(sound-who22). - Keep
OWNSoundDiffusionCommand.seek_up(*22*5*{target}##) andseek_down(*22*6*{target}##) +is_seek_up/is_seek_down(bareWHAT 5/6with no parameter) as-is. - Manual frequency steps (
*22*5#<1..15>*W##/*22*6#<1..15>*W##):- Change
OWNSoundDiffusionCommand.step_frequency_up(cls, where: str | int = "6", step: int = 1)->*22*5#{s}*{target}##andstep_frequency_down(cls, where: str | int = "6", step: int = 1)->*22*6#{s}*{target}##, validating1 <= s <= 15(bt_multi5#0aliases seekFA0,>=16overflows%.1X). - Add
*22*5#1*2#1##and*22*6#1*2#1##to the WHO 22 oracle conformance parametrisation (ack/out$0494018FA1/$0494018FB1across all 8 WHO 22 gateways intests/golden/firmware_oracle.json). - Remove
WHAT 7/WHAT 8fromOWNSoundDiffusionEventand wireis_step_frequency_up/is_step_frequency_down+frequency_step: int | None(1..15, failing closed outside1..15) toWHAT 5#<step>/6#<step>.
- Change
is_preset_cleared& KB slugs:- Keep
is_preset_clearedas defensive parsing for explicit*#22*...*11*1*<freq>*0##frames, removing the(H/L4684 6.0.11 F500 trace, MyHOME#839)attribution fromsound.pyandCHANGELOG.md. - Replace all
kb_who_*slugs acrossCHANGELOG.md,heating.py,sound.py, and tests with the verifiedownkb:*IDs above.
- Keep
…mmand
Add OWNHeatingDiagnosticEvent and OWNHeatingDiagnosticCommand for WHO 1004,
the diagnostic WHO of the temperature control system (Encyclopedia Machine
KB ownkb:chunk:r000204-r000209, ownkb:claim:c004423, c004429-c004448;
libdiag.so OPN_OpenScsDiagGenTr / OPN_OpenScsDiagTermoTr, libwhere.so
OPN_ThermoWhereTransl), registered for WHO 1004 and re-exported from
OWNd.message.
The event follows the OPN_ThermoWhereTransl WHERE grammar: #0 / #0#C is
the central unit (scope "central"), #Z / #Z#C a zone through the central
unit ("zone", zone_number), Z a master probe ("probe"), NZZ a slave probe
("slave_probe", zone_number ZZ, probe_number N), [1-9]00 an external probe
("external_probe"), Z#N a zone actuator ("actuator", actuator_number);
anything else has no scope. diagnostic_mask is the 24-bit mask of
DIMENSIONS 7, 8, 11 and 12 on the central unit and the addressed devices
or the 16-bit mask of DIMENSIONS 21 and 22 on a zone, only when the frame
carries exactly one all-0/1 value of that length (cleared_bits, has_fault
follow it; the mask stays raw, WHO_4 v2.0.0 pp. 69-74 bit conditions are
noted but not indexed). DIMENSION 23 on the central unit is decoded into
non_responding_units and faulty_units when it carries two decimal counts.
Every other shape fails closed with the raw values in dimension_value.
unique_id is overridden on both classes with the full WHERE (1004-#0#2,
1004-OpenWebNet-HA#66#2, 1004-66#1): WHO 1004 stays out of OWNMessage.interface, so
the inherited id would have dropped the WHERE parameters.
The builders emit only the keys libdiag.so registers (R7P0, R8P0, R11P0,
R12P0, R20P0, R21P0, R23P0): request_central_diagnostic (*#1004*#0*7##,
dimension 7 / 8 / 11 / 12), request_device_diagnostic (Z, NZZ, [1-9]00,
Z#N), request_zones_with_failures (*20##), request_zone_diagnostic
(*21##; #0, #0#C, 1..99 as #Z, #Z, #Z#C) and request_fault_counts
(*23##). There is no *22## request. Integer and address validators refuse
bool and non int / str values before int().
The physical frames are the first WHO 1004 frames captured on a bus,
pushed by an H/L4684 6.0.11 with a 3550 central unit once the F430/4
channel 66#1 came back online (MyHOME#839, fixture issue_466
myhome_trace_HL4684_all_2026-11-01T11-30-19.json):
*#1004*66*11*111111111111111111111111##, *#1004*OpenWebNet-HA#66*22*1111111111111111##
and *#1004*#0*11*111111111111111111111111##. No WHO 1004 frame is in the
firmware oracle index. The WHO 4 DIMENSION 22 guard anchors on WHO 4, so
*#1004*OpenWebNet-HA#66*22*...## is not a crash form; the tests pin that. The README
WHO catalog lists WHO 1004 under WHO 4.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add OWNSoundCommand.step_frequency_up / step_frequency_down (WHAT 5001..5015 / 5101..5115, step 1..15; Encyclopedia Machine KB ownkb:chunk:r000371, ownkb:claim:c002096 / c002098; 5000 / 5100 stay the automatic seeks of seek_up / seek_down) and OWNSoundCommand.store_station (*OpenWebNet-HA#16*WHERE*OpenWebNet-HA#10*STATION##, station 1..5: ownkb:chunk:r000376, ownkb:claim:c002128, the bt_difson ceiling; stations 6..15 of an F500N go through WHO 22 store_preset). An out-of-range step or station, a bool or non int / str value and an empty WHERE raise ValueError. OWNSoundEvent decodes WHAT 5001..5015 into frequency_step_forward and 5101..5115 into frequency_step_backward (1..15, as track_step_forward / track_step_backward do for 6001..6115). DIMENSION 10 is write-only (bt_difson never formats it, ownkb:claim:c002135), so a DIMENSION 10 value frame keeps nothing decoded; only DIMENSION 7 reads into track. Evidence: *16*5001*101## and *16*5101*101## are ACKed and put on the bus ($0494018FA1 / $0494018FB1) by F453, F453AV, F454, H4684, MH200 and MH200N in the firmware oracle (sound-who16-source; F450, F459, F460, F461, MH202 and MyHomeServer1 NACK them), as is *OpenWebNet-HA#16*101*OpenWebNet-HA#10*5## ($0493018FD4) on the same gateways; the tests pin all three rows. The physical F500 frames *OpenWebNet-HA#16*101*6*0*105300## and *OpenWebNet-HA#16*101*7*0*5## (105.30 MHz, station 5) come from an H/L4684 6.0.11 (MyHOME#839, fixture issue_466 myhome_trace_HL4684_all_2026-11-01T11-48-15.json). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add OWNSoundDiffusionCommand.seek_up (*22*5*WHERE##), seek_down (*22*6*WHERE##), step_frequency_up (*22*5#STEP*WHERE##) and step_frequency_down (*22*6#STEP*WHERE##), STEP 1..15 (Encyclopedia Machine KB ownkb:chunk:r000478-r000480, ownkb:claim:c002729), WHERE defaulting to "6" like next_station / previous_station; a step outside 1..15 (5#0 aliases the seek on bt_multi, 16 and above overflow its one-nibble step), a bool or non int / str step and an empty WHERE raise ValueError. WHAT 7 / 8 are not emitted: bt_multi NACKs them as "Increment/Decrement subSource - not implemented yet". OWNSoundDiffusionEvent gains is_seek_up / is_seek_down for a bare WHAT 5 / 6, frequency_step (1..15, None outside that range) with is_step_frequency_up / is_step_frequency_down for WHAT 5#STEP / 6#STEP, human-readable log lines for all four, and is_preset_cleared: True for a DIMENSION 11 report whose frequency decoded and whose preset field is an explicit 0, defensive parsing of the spec-shaped frame. preset stays None there, as it is for a frame without a preset field (DIMENSION 5), so the flag is what tells the two apart. Evidence: *22*5*2#1##, *22*6*2#1##, *22*5#1*2#1## and *22*6#1*2#1## are ACKed and put on the bus ($0494018FA0 / FB0 / FA1 / FB1) by F453, F453AV, F454, F459, F460, F461, MH200N and MyHomeServer1 in the firmware oracle (sound-who22; F450, H4684 and MH200 NACK them, MH202 NACKs but forwards) and join the WHO 22 oracle conformance rows; *22*7*2#1## and *22*8*2#1## are not in the oracle index, and the tests pin that. The physical F500 frames *OpenWebNet-HA#22*5#2#1*5*1*10530## and *OpenWebNet-HA#22*5#2#1*11*1*10530*5## (105.30 MHz, station 5) come from an H/L4684 6.0.11 (MyHOME#839, fixture issue_466 myhome_trace_HL4684_all_2026-11-01T11-48-15.json). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… seek and step builders Add the Unreleased / Added bullets for OWNHeatingDiagnosticEvent and OWNHeatingDiagnosticCommand (WHO 1004: OPN_ThermoWhereTransl scopes, 24-bit and 16-bit masks, Dimension 23 fault counts, the libdiag.so request keys, full-WHERE unique_id), the WHO 16 step_frequency_up / step_frequency_down (WHAT 5001..5015 / 5101..5115) and store_station (1..5) builders and event decoding, and the WHO 22 seek_up / seek_down / step_frequency_up / step_frequency_down (5#STEP / 6#STEP) builders, the seek and step flags, frequency_step and is_preset_cleared, with the Encyclopedia ownkb:* ids, the firmware oracle rows and the H/L4684 6.0.11 traces (MyHOME#839) they rest on. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
cc2f37e to
3b76ab1
Compare
|
Reworked to the corrected specification (review 5738866331), revision
2786 passed, |
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Thank you @gdluck — exemplary rework on 3b76ab1f. Every finding (F1–F7) and all four judgement calls match libdiag.so / libwhere.so / bt_difson / bt_multi, tests/golden/firmware_oracle.json, and the Machine KB (ownkb:*), with comprehensive fail-closed and non-integer/bool guards, synced WHO catalog docs, and all 8 CI gates (including the MyHOME suite) green. Approved and merging!
|
@gdluck Since
|
Requested by A
Summary
Before: OWNd has no WHO 1004 (thermoregulation autodiagnostics) classes, no WHO 16 FM tuner step or station-store builders, and no WHO 22 manual-step builders; a WHO 22 dimension 11 report with preset
0is indistinguishable from one without a preset field.After:
OWNHeatingDiagnosticEvent/OWNHeatingDiagnosticCommanddecode and request WHO 1004 diagnostics with thelibwhere.so/libdiag.sogrammar;OWNSoundCommand.step_frequency_up/step_frequency_down(WHAT 5001..5015 / 5101..5115) andstore_station(1..5) build the WHO 16 frames the firmware ACKs;OWNSoundDiffusionCommand.seek_up/seek_down(bare WHAT 5 / 6) andstep_frequency_up/step_frequency_down(5#STEP/6#STEP, 1..15) build the WHO 22 frames;OWNSoundDiffusionEventexposesfrequency_step, the seek / step flags andis_preset_cleared.Context. Companion of OpenWebNet-HA/MyHOME#839 (andrea-mag's H/L4684 6.0.11 captures on OpenWebNet-HA/MyHOME#466), for 2.0.0b13; the CHANGELOG bullets sit under a new
[Unreleased]/Addedabove the shipped 2.0.0b12 section. Revision3b76ab1(on master360e4f4, after #152 merged) implements @GreenGrassBlueOcean's corrected, firmware-verified specification (review 5738866331, findings F1..F7), replacing the first spec from #152; #152 ships 2.0.0b12 without it.Changes
OWNd/message/heating.py(F1, F2, F3):OWNHeatingDiagnosticEventscopes perOPN_ThermoWhereTransl:#0/#0#C->central;#Z/#Z#C->zone(zone_number);Z1..99 ->probe;NZZ->slave_probe(zone_number,probe_number);[1-9]00->external_probe;Z#N->actuator(zone_number,actuator_number); anything else (0,901,66#0,#0#0,#66#1#2) ->None.unique_idis1004-<full WHERE>on both classes (1004-#0#2,1004-#66#2,1004-66#1), so central-unit selectors and actuator numbers no longer collide;1004stays out ofOWNMessage.interface.diagnostic_mask: 24-bit0/1string for dimensions 7, 8, 11, 12 oncentraland the addressed devices, 16-bit for dimensions 21 and 22 onzone, exactly one value, elseNone;cleared_bits1-based from the left,has_fault; the docstring notes the WHO_4 v2.0.0 pp. 69-74 active-low bit tables while keeping the mask raw. Dimension 23 oncentralwith two ASCII-digit values ->non_responding_units,faulty_units.slave_count, scalarfaulty_zoneand the dimension 13 "local offset" claim are gone. Builders, only the keyslibdiag.soregisters:request_central_diagnostic(where="#0", *, dimension=7)(7 / 8 / 11 / 12,_validate_central_where),request_device_diagnostic(where, *, dimension=7)(Z,NZZ,[1-9]00,Z#N),request_zones_with_failures(*20##),request_zone_diagnostic(*21##;#0,#0#C,1..99->#Z,#Z,#Z#C),request_fault_counts(*23##). No*22##request (R22P0does not exist). Every integer validator rejectsbooland non-int/strbeforeint(). Theis_who4_dimension_22/is_who4_dimension_22_crash_formnegative guards for*#1004*#66*22##stay.OWNd/message/sound.py, WHO 16 (F4, F5):step_frequency_up->*16*{5000+s}*W##,step_frequency_down->*16*{5100+s}*W##(1..15);OWNSoundEvent.frequency_step_forward/frequency_step_backwardfrom 5001..5015 / 5101..5115 (4001 / 4101 no longer decoded);store_stationrestricted to 1..5 with the note that 6..15 need WHO 22store_preset; the dimension 10 event decode is gone (bt_difsonnever emits it).OWNd/message/sound.py, WHO 22 (F6, F7a):step_frequency_up(where="6", step=1)->*22*5#{s}*W##,step_frequency_down->*22*6#{s}*W##(1..15); WHAT 7 / 8 removed;frequency_step(1..15, elseNone) withis_step_frequency_up/is_step_frequency_downon5#STEP/6#STEP(a step of 0, 16 or01sets no flag);is_seek_up/is_seek_downfor bare WHAT 5 / 6;is_preset_clearedkept as defensive parsing of*#22*…*11*1*<freq>*0##, without the F500-trace attribution.kb_who_*slugs are replaced everywhere by theownkb:*ids of the review.scripts/sync_documentation.py/README.md: the4 / 1004catalogue row describes the corrected scopes and dimensions.CHANGELOG.md: three bullets under[Unreleased]/Added.State Diagram
stateDiagram-v2 direction TB state "WHO 1004 WHERE (OPN_ThermoWhereTransl) -> scope" as Scope { [*] --> Central : #0, #0#C (dims 7 8 11 12 -> 24 bits, 20 request, 23 -> two counts) [*] --> Zone : #Z, #Z#C (dims 21, 22 -> 16 bits) [*] --> Probe : Z 1..99 (24 bits) [*] --> SlaveProbe : NZZ, N 1..8 (24 bits, probe_number) [*] --> ExternalProbe : 100..900 (24 bits) [*] --> Actuator : Z#N, N 1..9 (24 bits, actuator_number) [*] --> Unknown : 0, 901, 66#0, #0#0, #66#1#2 -> scope None, mask None } state "WHO 16 / WHO 22 tuner (golden oracle rows)" as Tuner { [*] --> W16Step : *16*5001..5015*W## / *16*5101..5115*W## ack on 6 gateways [*] --> W16Store : *#16*W*#10*1..5## ack on 6 gateways [*] --> W22Seek : *22*5*W## / *22*6*W## ack on 8 gateways [*] --> W22Step : *22*5#1..15*W## / *22*6#1..15*W## ack on 8 gateways }Where are the traces
tests/fixtures/traces/issue_466/myhome_trace_HL4684_all_2026-11-01T11-30-19.json*#1004*66*11*111111111111111111111111##(probe),*#1004*#66*22*1111111111111111##(zone),*#1004*#0*11*111111111111111111111111##(central), verbatim in the tests...T11-48-15.json*#16*101*6*0*105300##,*#16*101*7*0*5##,*#22*5#2#1*5*1*10530##,*#22*5#2#1*11*1*10530*5##. The off-preset*0##shape is not in the trace and is tested as a spec-shaped frame only.What does the Oracle say
tests/golden/firmware_oracle.json, pinned by the tests:*16*5001*101##,*16*5101*101##ack/out$0494018FA1/$0494018FB1on F453, F453AV, F454, H4684, MH200, MH200N;nack/silenton the other six. The old4001/4101spellings are asserted absent.*#16*101*#10*5##ack/out$0493018FD4on the same six*22*5*2#1##,*22*6*2#1##,*22*5#1*2#1##,*22*6#1*2#1##ack/out$0494018FA0/FB0/FA1/FB1on F453, F453AV, F454, F459, F460, F461, MH200N, MyHomeServer1; MH202nack/out; F450, H4684, MH200nack/silentlibdiag.soas the review documentsTesting
tests/test_who1004_heating_diagnostic.py: every scope on the physical frames and on edge WHEREs (0,100,901,66#0,#0#0,#66#1#2,10#1,166,800), the per-scope mask dimensions (a 16-bit mask on a probe or a 24-bit mask on a zone givesNone),cleared_bits/has_fault, dimension 23 counts,unique_idwith every WHERE form, all five builders with rejected inputs (bool, float, out of range, wrong scope), the dimension 22 guard regressions,OWNEvent.parse==OWNMessage.parse.tests/test_sound.py: the 5001 / 5101 steps and the 1..5 store with rejected inputs, the event decode, and the golden-row pins above.tests/test_sound_diffusion.py: the5#STEP/6#STEPbuilders and decode,frequency_stepedges (0, 16,01), bare seeks,is_preset_cleared, the four oracle rows in the WHO 22 conformance parametrisation plus a positive pin on all eight ack gateways.scripts/verify_coverage.py6,986 / 6,986 statements; branches 100 % onheating.py,sound.pyadds no partial branch (its 11 pre-exist on master);ruff check,mypy OWNd,scripts/verify_library_standards.pyandscripts/sync_documentation.py --checkpass.Judgement calls
is_step_frequency_up/is_step_frequency_downareTrueonly with a valid step 1..15 (5#0aliases the seek onbt_multi,5#16overflows; neither sets a flag).request_central_diagnosticdefaults to dimension 7 (the review'sownkb:chunk:r000205); the first revision defaulted to 11.4 / 1004catalogue row text was rewritten because its old wording contradicted the corrected scopes; row structure and dispatch keys are unchanged.zone,slave_probe,request_device_diagnostic).🤖 Generated with Claude Code