Skip to content

feat(lighting): WHO 1001 dimension 11 diagnostic event - #145

Draft
gdluck wants to merge 2 commits into
OpenWebNet-HA:masterfrom
gdluck:feat/who1001-diagnostic-event
Draft

gdluck wants to merge 2 commits into
OpenWebNet-HA:masterfrom
gdluck:feat/who1001-diagnostic-event

Conversation

@gdluck

@gdluck gdluck commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

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 generic OWNEvent with who == 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 (an OWNEvent subclass, registered for WHO 1001, exported from OWNd.message) exposes diagnostic_mask as sent and cleared_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 with diagnostic_mask is None (the first version of this PR would have reported "101"). Other WHO 1001 shapes (*#1001*74##, dimension 12) have diagnostic_mask is None. MyHOME's device_health.py reads who / dimension / dimension_value on 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*<bits>## , one value, all 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
    }
Loading

Where are the traces

Subsystem Capture Plant Frames verified by this PR
WHO 1001 dimension 11 after WHAT 19 own-firmware-oracle findings/MH200N/what19.md, physical capture EVID-MH200-WHAT19-FAULT (agree with the emulated output; bits 6 and 21 left Open) physical MH200 *1*19*WHERE## + *#1001*WHERE*11*<mask>##
WHO 1001 on a plant without a faulty actuator trace PR: OpenWebNet-HA/MyHOME#799 (merged), f461_live_2026-10-23 run 1 BTicino F461 2.0.11 *#1001*74## NACK (no WHO 1001 device on this plant)
WHO 1001 dimension 11 read on request (2026-10-24, plain OWNd master as sender) capture on file, to be ingested with the next trace PR BTicino F461 2.0.11, healthy BT-F411/4 lights 06 / 07 *#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, 09 and *#1013*257*11*111111111111111111111111## arrive on the event session
same capture on file MyHomeServer1 3.87.13, healthy lights *#1001*0014*11## and *#1001*0010*11## -> same mask 111111111111111101101111 (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 session

What does the Oracle say

Suite what19, results/<P>/<V>/oracle/full/what19.tsv, input *#1*74##:

Gateway Reply / verdict Output
F453 010100, F453AV 030014, F459 020105, F460 020012, F461 020011, MH200N 010108, MyHomeServer1 028206 ack / out *1*19*74## then *#1001*74*11*111110111111111111110111##
MH202 010024 ack / out *#1001*74*11*111110111111111111110111## only
F454 020051, H4684 020054, MH200 020102 ack / out *1*19*74## only
F450 020010 nack / silent none

findings/MH200N/what19.md states 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.)

Testing

  • tests/test_who1001_diagnostic_event.py (new, 13 cases): the oracle mask parses as OWNLightingDiagnosticEvent / OWNEvent with where == "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 plain OWNCommand); 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.)
  • Fail before / pass after: on master the module fails at collection (OWNLightingDiagnosticEvent does not exist; with that import removed the WHO 1001 frame is a plain OWNEvent); on this branch all 8 pass.
  • Full suite (head c42dc2f on the branch base f04d8aa): 1920 passed, 1 skipped; 100 % statement coverage, mypy clean, ruff clean, scripts/sync_documentation.py --check (README regenerated in this PR) and scripts/verify_library_standards.py pass.

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 as OWNLightingDiagnosticEvent with cleared_bits == (17, 20), while *#1013*25x*11*…## stays OWNGatewayDiagnosticEvent. Inference, unverified: that mask is the healthy pattern of these firmware generations and the oracle's 111110111111111111110111 (bits 6 and 21) the fault pattern; the bit meanings stay open and the decoder interprets nothing. No faulty actuator was available, so the push path (*1*19*WHERE## followed by the mask) rests on the oracle rows only. The MyHomeServer1 group reply with where == "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

`*#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-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!

… 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>
@gdluck

gdluck commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Self-correction in c42dc2f (no review asked for it). @GreenGrassBlueOcean could you confirm the reading, or say if you see it differently?

Context. OWNLightingDiagnosticEvent exposes the WHO 1001 DIMENSION 11 autodiagnostic mask as sent; the docstring says the gateway copies the actuator's 24-bit mask verbatim.

How it was found. Reading the decoder against the base-class contract instead of its own tests: the mask check was value and set(value) <= {"0", "1"}, so *#1001*74*11*1## reported "mask is 1 (cleared bits: none)" although no capture has anything but 24 bits; self.cleared_bits or () could never be None at that point (the mask is assigned one line earlier); and the negative-shape test returned early for frames OWNMessage.parse turns into None and skipped the mask assertions when the parse gave a plain OWNCommand, so three of its five frames asserted nothing, and the "status request is not a diagnostic event" test only checked who == 1001.

Live check (F461 2.0.11, 2026-10-26 16:45 UTC). A dimension 11 read on a healthy light, *#1001*06*11##, is ACKed in 0.13 s with *#1001*06*11*111111111111111101101111##: exactly 24 bits (bits 17 and 20 cleared), the same shape as the fault captures.

What changed. The mask requires a single 24-character 0/1 value (other DIMENSION 11 value frames keep diagnostic_mask None), the log computes the cleared bits directly, and the tests assert the exact parsed class per frame (13 cases, two malformed frames pinned to None, the status request pinned to OWNCommand). Suite 1920 passed, ruff / mypy / docs check clean.

This branch has not been deployed

No deployments
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