Repository navigation
feat(lighting): WHO 1001 dimension 11 diagnostic event - #145
GreenGrassBlueOcean merged 4 commits into
Conversation
`*#1001*WHERE*11*<mask>##` is the actuator autodiagnostic frame a gateway pushes with `*1*19*WHERE##` when a status request reaches a faulty actuator: own-firmware-oracle what19 (input `*michnovka#1*74##`) shows it on F453, F453AV, F459, F460, F461, MH200N, MyHomeServer1 (after `*1*19*74##`) and MH202 (alone), and a physical MH200 capture agrees (findings/MH200N/what19.md, EVID-MH200-WHAT19-FAULT). OWNd returned a generic OWNEvent with who == 1001 and exposed nothing. OWNLightingDiagnosticEvent (registered for WHO 1001, exported) exposes diagnostic_mask as sent and cleared_bits (1-based from the left). The bit meanings are Open in the oracle, so nothing is interpreted. Only the single-value bit-string shape is decoded; `*#1001*74*11*101*5##` fails closed. README WHO catalog lists WHO 1001 (sync_documentation --update). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
… class `OWNLightingDiagnosticEvent` took any non-empty 0/1 string as the autodiagnostic mask although every capture (oracle `what19` on eight gateways, physical MH200) carries exactly 24 bits and the docstring said so: `*#1001*74*11*1##` reported "mask is 1 (cleared bits: none)". The mask now requires a single 24-character 0/1 value; other DIMENSION 11 value frames keep `diagnostic_mask` None. The log line computes the cleared bits directly (the `or ()` fallback could never trigger). Tests: the negative-shape test returned before asserting on three of its five frames and skipped the mask assertions for the request shape; it now asserts the exact parsed class per frame (eight frames incl. 1, 23 and 26 bit strings and a 24-character non-bit string), the two malformed frames are pinned as not parsing at all, and the status-request test asserts it is an `OWNCommand`, not a diagnostic event. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Self-correction in c42dc2f (no review asked for it). @GreenGrassBlueOcean could you confirm the reading, or say if you see it differently? Context. How it was found. Reading the decoder against the base-class contract instead of its own tests: the mask check was Live check (F461 2.0.11, 2026-10-26 16:45 UTC). A dimension 11 read on a healthy light, What changed. The mask requires a single 24-character 0/1 value (other DIMENSION 11 value frames keep |
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Thanks for c42dc2f — confirming your reading on the self-correction: requiring a single 24-character 0/1 value is the right call (s2o_selfDiagnosticsA in libdiag.so.0.0 unpacks 3 SCS hex bytes = 24 bits, OPEN.db types the diagnostic bitmask as 24 bits, and every oracle row and plant capture we have is 24 bits), and fixing test_other_who_1001_shapes_have_no_mask so the OWNCommand and malformed cases actually run assertions was needed.
Tested c42dc2f merged locally with current master (050dc1d): after resolving the CHANGELOG.md conflict, 2118 passed (100% statement coverage on OWNd/message/lighting.py), ruff, scripts/sync_documentation.py --check, and scripts/verify_library_standards.py are clean, and MyHOME's tests/test_device_health.py + tests/test_trace_oracle_validation.py pass (104 passed).
Before approving:
-
Rebase onto
master(050dc1d):CHANGELOG.mdconflicts with#136/#140/#148, which also prevented GitHub from buildingrefs/pull/145/merge(0 CI check-runs ran onc42dc2f; the 8 green checks are from4ae2264). -
DIMENSION 7(BITMASK_DIA_A) vsDIMENSION 11:OWNLightingDiagnosticEventcurrently gatesdiagnostic_maskonself._dimension == 11. InOPEN.db(OpenWebNet-Encyclopediadiagnostics/dimension-reference.md),DIMENSION 7(*#[WHO]*[WHERE]*7*[BITMASK_DIA_A]##) is the 24-bit diagnostic bitmask A alongsideDIMENSION 11,MyHOME'sdevice_health.pyalready pairsAUTODIAG_DIMENSIONS = (7, 11), and our committedMyHomeServer1traces (MyHOMEtests/fixtures/traces/issue_466/myhome_trace_MyHomeServer1_monostable_cover_diagnostic_2026-09-27T13-51-19.jsonand..._scenarios_cover_diagnostic_...) carry*#1001*0*7*111111111111111101101111##— with the exact same111111111111111101101111((17, 20)) mask as your liveDIMENSION 11reads. Right now that frame parses asOWNLightingDiagnosticEventwithdiagnostic_mask is None. Either accept(7, 11)here (updating the docstring /README.md/CHANGELOG.mdaccordingly) or document explicitly whyOWNLightingDiagnosticEventonly decodes11and leaves7asNone. -
Routed (
#4#NN), empty-WHERE, andwhere == "0"shapes (OWNd/message/base.py,OWNd/message/lighting.py):OWNMessage.interface(base.py:297-305) checksself._who in [0, 1, 2, 14, 15]. For an actuator behind an F422 interface (*#1001*74#4#01*11*111110111111111111110111##,OPEN.db[I3][I4];MyHOME'smessage_where()works around this via_where_param),message.interfaceisNone,message.unique_iddrops#4#01("1001-74"instead of"1001-74#4#01"), and_human_readable_logomits{self._interface_log_text}(unlikeOWNLightingEvent).- On the empty-
WHEREservice form*#1001**11*<24-bit>##(diagnostics/dimension-reference.md, matched by_DIMENSION_REQUEST_REPLYwithself._where == ""),f"Light {self._where} autodiagnostic mask..."renders a double space ("Light autodiagnostic mask is ..."). - On your question about
where == "0": yes, please add*#1001*0*11*111111111111111101101111##, plus 4-digit0014, routed74#4#01, empty"", and a status-shapedWHO 1001frame such as*1001*4*0##(end-of-interview marker inissue_466, whichOWNEvent.parsealso routes toOWNLightingDiagnosticEventwithdiagnostic_mask is None). (is_generalisFalseon*#1001*0*...##becausebase.pyscopesis_general/is_group/is_areatowho in (1, 2)— worth pinning if intentional.)
-
Update
tests/test_firmware_oracle_conformance.py(lines 155-162):test_emitted_own_frames_parseable_by_owndalready has a# 2. Semantic verification of WHO 1001 diagnostic state responseblock that still assertsisinstance(diag_msg, OWNMessage)andgetattr(diag_msg, "event_content", ...)from beforeWHO 1001had a parser. Please tighten it to assertisinstance(diag_msg, OWNLightingDiagnosticEvent),diag_msg.diagnostic_mask == "111110111111111111110111", anddiag_msg.cleared_bits == (6, 21)like the surrounding WHO blocks. -
Docstrings,
CHANGELOG.md, and trace references (no new trace PR needed):- No need to ingest a new trace PR for this — the 2026-10-24
F461/MyHomeServer1direct reads are fine as Hardware Verification notes, and we already have committedMyHOMEplant traces covering both the physicalWHAT 19push path and non-WHAT 19pushes:issue_619/myhome_sweep_F453AV_all_2026-10-04T19-45-11.json(F453AV3.0.14): five unloaded F414 dimmers (13,14,24,31,34) pushing*1*19*WHERE##+*#1001*WHERE*11*111110111111111111110111##((6, 21)).issue_466/myhome_trace_MH200_all_2026-09-26T21-00-00.jsonandissue_733/myhome_trace_MH200_dim1_probe_2026-10-20T19-22-47.json(MH200):WHERE 74pushing*#1001*74*11*111110111111111111110111##.issue_434/myhome_trace_MH200N_all_2026-09-29T18-31-59.jsonandissue_746/myhome_trace_MH200N_all_2026-10-17T14-28-20.json(MH200N1.1.8): during area sweeps (*#1*4##,*#1*7##),42and72reply with normal*1*0*42##(OFF) and*1*1*72##/*1*0*72##(ON/OFF) followed 168–210 ms later by*#1001*42*11*111011111111111101101101##and*#1001*72*11*111011111111111101101101##(cleared_bits == (4, 17, 20, 23), noWHAT 19).
- Please cite those committed fixtures in the PR table instead of
"capture on file, to be ingested with the next trace PR"(and adjust the"rests on the oracle rows only"note), and broaden theOWNLightingDiagnosticEventdocstring +CHANGELOG.mdentry soDIMENSION 11isn't described as exclusively following*1*19*WHERE##(it is also answered on direct*#1001*WHERE*11##reads and pushed alongside normalWHAT 0/WHAT 1sweep replies onMH200N).
- No need to ingest a new trace PR for this — the 2026-10-24
Non-blocking:
WHO 1001spans Lighting and Automation (WHO 1+WHO 2):OPEN.db(diagnostics/architecture.md) definesWHO 1001for "Light and Automation system" (there is noWHO 1002, and theMyHomeServer1cover diagnostic traces inissue_466useWHO 1001on covers). Keeping the class inlighting.py(EN_SYSTEM.who = 1) is fine, but a short note in the docstring andREADME.mdcatalog row will clarify that aWHO 1001frame can also come from aWHO 2shutter actuator.- Avoid recomputing
cleared_bits:__init__buildscleared = tuple(...)for_human_readable_logand discards it, thencleared_bitsrecomputes the tuple on every access; storingself._cleared_bits: tuple[int, ...] | None = Nonein__init__avoids the duplicate pass. - PR description nits: under
## Testing, bullet 2 still says"on this branch all 8 pass"(13now), and the MermaidDiagnosticstate label still saysone value, all 0/1without the 24-bit constraint fromc42dc2f.
# Conflicts: # CHANGELOG.md
… 0 WHERE, fixtures cited (review follow-up) Review of 2026-10-26 20:32 on OpenWebNet-HA#145: - DIMENSION 7 (`BITMASK_DIA_A`, OPEN.db) carries the same 24-bit mask as DIMENSION 11: MyHOME's device_health pairs (7, 11) and the MyHomeServer1 cover diagnostic in MyHOME issue_466 sends `*#1001*0*7*1111…0111##` with the same bits as the live DIMENSION 11 reads. The decoder accepts both. - WHO 1001 joins the WHOs whose WHERE may carry `michnovka#4#II` in `OWNMessage.interface`, so `interface`, `unique_id` (`1001-74#4#01`) and the log line cover an actuator behind an F422, as for WHO 1 / 2. - The log subject handles an empty WHERE (the gateway's own diagnostic) without a double space; `cleared_bits` is stored once in `__init__`. - Docstring, README catalog row and CHANGELOG: WHO 1001 is the diagnostic WHO of lighting and automation actuators alike; the mask is pushed after `*1*19*WHERE##` (oracle what19; MyHOME fixtures issue_619 F453AV, issue_466 / issue_733 MH200), pushed alongside normal `*1*0/1*WHERE##` sweep replies on an MH200N (issue_434, issue_746), answered on a direct read (live F461 / MyHomeServer1, 2026-10-24) and sent as DIMENSION 7 on WHERE 0 (issue_466). - Tests: WHERE `0`, `0014`, routed `74#4#01`, empty, the status-shaped `*1001*4*0##`, DIMENSION 7 shapes, `is_general False` pinned; the firmware-oracle conformance block asserts the class, mask and cleared bits. - Merged origin/master (050dc1d) into the branch (CHANGELOG conflict with OpenWebNet-HA#136 / OpenWebNet-HA#140 / OpenWebNet-HA#148 resolved, the WHO 1001 entry under "Added"). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Thanks for the detailed pass. All five blocking points and the three notes are in 6014d03 (and the merge commit before it):
Notes: WHO 1001 is described as the diagnostic WHO of lighting and automation actuators alike (docstring, README row); Checks: 2124 passed, 1 skipped; |
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Thanks for 6014d03 — all five blocking points and the three follow-up notes are cleanly addressed:
DIMENSION 7(BITMASK_DIA_A) andDIMENSION 11both decode 24-bit0/1masks while non-mask shapes fail closed (diagnostic_mask is None,cleared_bits is None).WHO 1001joinsOWNMessage.interface(74#4#01->interface == "01",unique_id == "1001-74#4#01"), empty-WHERElogging has no double space, andtests/test_firmware_oracle_conformance.pyassertsOWNLightingDiagnosticEvent,diagnostic_mask, andcleared_bits == (6, 21).- Verified against all
125uniqueWHO 1001frames in theMyHOMEtrace corpus (9masks acrossMH200,MH200N,F453AV, andMyHomeServer1decode cleanly;21non-mask events fail closed;95requests/writes remainOWNCommand), with 100% statement/branch coverage onOWNLightingDiagnosticEventand all 8 CI checks green.
LGTM!
OWNd/__init__.py: __version__ = "2.0.0b12". CHANGELOG.md: [Unreleased] promoted to [2.0.0b12] - 2026-11-01 (every PR merged since v2.0.0-b11: #134, #135, #136, #137, #138, #139, #140, #142, #145, #147, #148, #150, #151) with a fresh empty [Unreleased] header. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Requested by A
Summary
*#1001*74*11*111110111111111111110111##is the actuator autodiagnostic frame the oracle gateways push together with*1*19*74##when a status request (*#1*74##) reaches the emulated faulty actuator (and the physical MH200 capture behind that finding); the same dimension is also answered on request (*#1001*WHERE*11##) by the lights read on the live F461 and MyHomeServer1 (see Hardware Verification). OWNd returned a genericOWNEventwithwho == 1001, so nothing exposed the mask. The bit meanings are marked Open in the oracle, so this PR exposes the mask as sent and interprets nothing. Split out of #139.Changes
OWNd/message/lighting.py:OWNLightingDiagnosticEvent(anOWNEventsubclass, registered for WHO 1001, exported fromOWNd.message) exposesdiagnostic_maskas sent andcleared_bits(1-based from the left;(6, 21)for the oracle mask,()when every bit is set). Only the single-value bit-string shape is decoded:*#1001*74*11*101*5##(a trailing value) fails closed withdiagnostic_mask is None(the first version of this PR would have reported "101"). Other WHO 1001 shapes (*#1001*74##, dimension 12) havediagnostic_mask is None. MyHOME'sdevice_health.pyreadswho/dimension/dimension_valueon the event and is unaffected.scripts/sync_documentation.py+README.md: the WHO catalog row for Lighting now reads**1** / **1001**with the diagnostic class listed (python scripts/sync_documentation.py --update).CHANGELOG.md: entry under[Unreleased]/Added.State Diagram
stateDiagram-v2 direction TB state "1. Gateway on *#1*74## to a faulty actuator (oracle what19; physical MH200 capture)" as Bus { [*] --> Request Request --> Fault : *1*19*74## then *#1001*74*11*<24-bit mask>##\nF453, F453AV, F459, F460, F461, MH200N, MyHomeServer1 Request --> MaskOnly : *#1001*74*11*<mask>## only , MH202 Request --> What19Only : *1*19*74## only , F454, H4684, MH200 Request --> Refused : nack / silent , F450 } state "2. OWNMessage.parse of the WHO 1001 frame" as Parse { [*] --> Who1001 Who1001 --> Diagnostic : *#1001*74*11*<24 bits>## or *7*<24 bits>## , one value, exactly 24 bits 0/1\nOWNLightingDiagnosticEvent, diagnostic_mask as sent, cleared_bits , 6, 21\n, before - generic OWNEvent, nothing exposed Who1001 --> NoMask : *#1001*74*11*101*5##, *#1001*74*12*5##\nOWNLightingDiagnosticEvent, diagnostic_mask None , fail closed }Where are the traces
findings/MH200N/what19.md, physical captureEVID-MH200-WHAT19-FAULT(agreewith the emulated output; bits 6 and 21 left Open)*1*19*WHERE##+*#1001*WHERE*11*<mask>##OpenWebNet-HA/MyHOME#799(merged),f461_live_2026-10-23run 1*#1001*74##NACK (no WHO 1001 device on this plant)*#1001*06*11##->*#1001*06*11*111111111111111101101111##(ACK 120 ms), same for 07; group*#1001*0*11##NACK after 3.0 s but*#1001*05*11*…##,06,07,09and*#1013*257*11*111111111111111111111111##arrive on the event session*#1001*0014*11##and*#1001*0010*11##-> same mask111111111111111101101111(ACK 139 / 157 ms); group*#1001*0*11##ACK after 3.7 s with*#1001*0*11*111111111111111101101111##on the command session, about 80 per-device masks and*#1013*256*11*…##on the event sessionWhat does the Oracle say
Suite
what19,results/<P>/<V>/oracle/full/what19.tsv, input*#1*74##:010100, F453AV030014, F459020105, F460020012, F461020011, MH200N010108, MyHomeServer1028206ack/out*1*19*74##then*#1001*74*11*111110111111111111110111##010024ack/out*#1001*74*11*111110111111111111110111##only020051, H4684020054, MH200020102ack/out*1*19*74##only020010nack/silentfindings/MH200N/what19.mdstates that the gateway "only translates the bitmask verbatim" and leaves the meaning of bits 6 and 21 Open; this PR exposes the mask and interprets nothing. (The findings page's fleet list omits F453 and MH202; the TSV rows above include them.)Revision 6014d03 (review of 2026-10-26 20:32). The mask is accepted on DIMENSION 7 (
BITMASK_DIA_A) as well as 11; WHO 1001 joins the WHOs whose WHERE carries#4#IIinOWNMessage.interface(sointerface,unique_id1001-74#4#01and the log line work behind an F422); the log subject handles an empty WHERE;cleared_bitsis stored once; the docstring, README row and CHANGELOG say WHO 1001 covers lighting and automation actuators alike and list the four ways the mask is seen (after WHAT 19, alongside sweep replies on MH200N, on a direct read, as DIMENSION 7 on WHERE 0);tests/test_firmware_oracle_conformance.pyasserts the class, mask and cleared bits;origin/master050dc1d is merged into the branch (CHANGELOG conflict resolved, the WHO 1001 entry under "Added").Testing
tests/test_who1001_diagnostic_event.py(new, 13 cases): the oracle mask parses asOWNLightingDiagnosticEvent/OWNEventwithwhere == "74",dimension == 11, the mask kept,cleared_bits == (6, 21)and the log line; an all-ones mask gives(); eight other shapes assert the exact parsed class and no mask (trailing value, dimension 12, 1-, 23- and 26-bit strings, 24 characters with a non-bit, the bare dimension 11 request and*#1001*74##as plainOWNCommand); two malformed frames (#inside the value, non-digits) do not parse at all;*#1001*74##is asserted not to be a diagnostic event. (Revision c42dc2f: the mask must be exactly 24 bits, as every capture is and the docstring said; the first revision took any non-empty 0/1 string, and the negative-shape test returned before asserting on three of its frames.)masterthe module fails at collection (OWNLightingDiagnosticEventdoes not exist; with that import removed the WHO 1001 frame is a plainOWNEvent); on this branch all 20 pass (6014d03: WHERE0,0014, routed74#4#01, empty and the status-shaped*1001*4*0##, DIMENSION 7 shapes,is_generalpinned).1920 passed, 1 skipped; 100 % statement coverage,mypyclean,ruffclean,scripts/sync_documentation.py --check(README regenerated in this PR) andscripts/verify_library_standards.pypass.Live retest (2026-10-24)
Exercised live on 2026-10-24 (table above): dimension 11 is also a readable per-device dimension: the two lights read on each plant answered on request, and the group request made every light that answered (05 / 06 / 07 / 09 on the F461, about 80 WHEREs on the MyHomeServer1) report the same mask
111111111111111101101111(bits 17 and 20 cleared, counted from the left); this head parses every one of those captured frames asOWNLightingDiagnosticEventwithcleared_bits == (17, 20), while*#1013*25x*11*…##staysOWNGatewayDiagnosticEvent. Inference, unverified: that mask is the healthy pattern of these firmware generations and the oracle's111110111111111111110111(bits 6 and 21) the fault pattern; the bit meanings stay open and the decoder interprets nothing.The push paths are covered by committed MyHOME fixtures:issue_619(F453AV 3.0.14, five unloaded F414 dimmers pushing*1*19*WHERE##+ the(6, 21)mask),issue_466/issue_733(MH200, WHERE 74),issue_434/issue_746(MH200N 1.1.8, the mask 170-210 ms after normal*1*0/1*WHERE##sweep replies,(4, 17, 20, 23), no WHAT 19) andissue_466again for DIMENSION 7 on WHERE 0 from a MyHomeServer1 cover diagnostic. The MyHomeServer1 group reply withwhere == "0"is worth a test case; I will add it if you want it in this PR.Companion
None. Split from #139 (WHO 13 dimension 27 model name).
🤖 Generated with Claude Code