Skip to content

fix(light): keep a configured light when a WHO 1 motion sensor shares its address (#777) - #781

Merged
GreenGrassBlueOcean merged 10 commits into
v2-phase1-architecturefrom
fix/issue-777-light-motion-same-address
Oct 24, 2026
Merged

GreenGrassBlueOcean merged 10 commits into
v2-phase1-architecturefrom
fix/issue-777-light-motion-same-address

Conversation

@GreenGrassBlueOcean

@GreenGrassBlueOcean GreenGrassBlueOcean commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #777.

Problem

A Legrand 067226 detector has no relay. It sits at the A/PL of the F411 channel it switches (52) and reports WHAT 34 / 39 there. Once binary_sensor discovered <mac>-52-motion, _ForeignAddresses treated 52 as a sensor address. As a result:

  • ghost() removed the configured light.myhome_spots_wc on reload, logging only at INFO.
  • accept() skipped the myhome.yaml entry.
  • WHAT 39 was unknown, so the light logged unknown lighting WHAT 39.
  • Startup dimension queries (*#1*52*5##, *#1*52*7##) are answered by the 067226 with WHERE=0 (*#1*0*5*2##, *#1*0*7*0*0*45##), which were dropped as general frames so the motion sensor kept its defaults (Timeout: 315, Sensitivity: medium).

Commits

  1. gdluck's patch (authorship kept), from the [2.0.0b16] Configured light removed on reload after motion sensor discovery at the same WHO 1 address (F454 / Legrand 067226) #777 discussion:
  2. Trace replay of the reporter's F454 capture:
    • Detector cycle 34 → 1000#1 → 1, then 39 → 1000#0 → 0.
    • The *#1*0*5|6|7*…## dimension replies come back with WHERE 0. They must not create or change a light.
  3. Audit follow-ups:
    • One WHAT 39 test. is_motion_end() in discovery.py is shared by light and binary_sensor. It checks motion_end (OWNd with WHAT 39) or unknown_state == 39 (b11). The patch's broader fallback is gone: it treated any motion_detected with motion False as the end of movement. So is its read of the private _state.
    • Bus interface respected. A configured light only protects its own bus interface: a light at 52#4#01 no longer keeps a ghost at plain 52.
    • Visible removal. Removing a light because its address reports motion or illuminance now logs a WARNING. It names the entity and says to declare the light in myhome.yaml. On/off frames at such an address log a one-time INFO hint.
    • force_update reset after the motion sensor writes a WHAT 39 state.
  4. Leftover warning from the reporter's retest:
  5. Evidence citations:
  6. In-flight correlation for anonymous WHERE=0 dimension 5 and 7 replies:
    • Confirmed by the reporter's reload log and tcpdump capture ([2.0.0b16] Configured light removed on reload after motion sensor discovery at the same WHO 1 address (F454 / Legrand 067226) #777 (comment)): for both *#1*52*5## and *#1*52*7##, the WHERE=0 reply (*#1*0*5*2## / *#1*0*7*0*0*45##) arrives on the event session ~140 ms after the worker unqueues the request, while the command session is still waiting ~2.0 s before returning *#*0## (NACK).
    • CommandWorkerPool tracks messages currently inside await _command_session.send(...) and exposes claim_in_flight_dimension_where(who, dimension) (forwarded by MyHOMEGatewayHandler across secondary delegation and warm-standby failover).
    • When a WHERE=0 dimension 5 or 7 frame arrives on binary_sensor's WHO 1 on_general hook and exactly one point-to-point target WHERE is in flight for (1, dimension), it consumes the in-flight entry immediately and routes the reply to that motion sensor (Sensitivity: high, Timeout: 60.0 s). If zero or multiple workers have concurrent requests for the same (who, dimension), or a second stray frame arrives before the NACK, it is dropped.

Evidence

  • WHAT 39 = end of movement:
    • Specified in the ZigBee OpenWebNet spec (KB ownkb:claim:c001481).
    • Observed on the reporter's F454 about 45 s after the last WHAT 34, matching the DIM 7 timeout *#1*0*7*0*0*45##.
    • Firmware-observed in OpenWebNet-HA/own-firmware-oracle#56 (merged): suite who1-motion-detector (findings), 12 emulated gateways, each run twice and byte-identical in CI (run 39967360131).
    • B8 52 1D 00 / 01 give *1*34*52## / *1*39*52## on F454 2.0.51, F459, F460, F461, MH200N, MH202 and MyHomeServer1. F453 / F453AV give only WHAT 34.
  • The WHERE 0 replies are on the bus and precede the command-session NACK:
    • Firmware oracle: *#1*0*5*2##, *#1*0*6*0## and *#1*0*7*0*0*45## come out byte-exact from D5 00 21 44 {10,15,16} … frames whose address byte is 00.
    • Physical F454 reload + tcpdump capture (#777 comment): *#1*0*5*2## (+138 ms) and *#1*0*7*0*0*45## (+139 ms) arrive on the event connection ~1.86 s before the command connection receives *#*0## (+2.11 s / +2.02 s).
  • Status polling of a shared address is safe: *#1*52## returned exactly one reply, from the F411.
  • Reporter's retest on b16 + OWNd b11:
    • The light came back with its entity ID and settings.
    • A config-entry reload keeps both entities.
    • WHAT 39 clears the motion sensor immediately instead of after 315 s.

Deliberately not done

Testing

gdluck and others added 4 commits October 23, 2026 14:31
… its address

What is the problem
A Legrand 067226 motion detector has no relay: it is configured at the A/PL of
the F411 channel it switches and reports WHAT 34 / 39 and dimensions 5-7 on
that address. Once the binary_sensor platform discovered <mac>-52-motion,
_ForeignAddresses owned address 52, ghost() removed the configured
light.myhome_spots_wc from the registry on the next reload and accept() skipped
the myhome.yaml entry, so the light never came back (#777). WHAT 39 (end of
movement) was also unknown, so the light logged 'unknown lighting WHAT 39'.

How it is fixed
- _ForeignAddresses knows the lights declared in myhome.yaml; blocks_light()
  still blocks switch and cover addresses and unconfigured sensor addresses
  (#244 / #245), but a configured light coexists with a sensor at its address.
- Sensor frames at a configured light's address no longer forget the light,
  and WHAT 39 counts as a sensor frame (also when OWNd reports it as
  unknown_state 39).
- The motion binary sensor turns off on WHAT 39 instead of waiting for its
  timeout.

Firmware evidence
The F461 bt_luci translator decodes the SCS presence status frame (class 0x1D)
value 00 as *1*34*WHERE## and value 01 as *1*39*WHERE## (hex2own corpus rows
B8311D00 / B8311D01).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… address

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- is_motion_end() in discovery.py is the one WHAT 39 test for light and
  binary_sensor: motion_end (OWNd with WHAT 39) or unknown_state 39 (b11).
  The "motion_detected with motion False" fallback and the private _state
  read are gone.
- A configured light vouches for a sensor address only on its own bus
  interface: a light at 52#4#01 no longer protects a ghost at plain 52.
- Removing a light because its address reports motion/illuminance now logs
  a WARNING naming the entity and the myhome.yaml fix; on/off frames at such
  an address log a one-time INFO hint. A 048834 in scenario mode (#244) and
  a 067226 driving its own F411 (#777) cannot be told apart on the bus, so
  an unconfigured light stays blocked.
- The motion sensor resets force_update after a WHAT 39 write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The #777 reporter's retest on b16 (#777, comment
6365352559) still logged "light 52 reports unknown lighting WHAT 39;
keeping its last state" once per occupancy cycle. route_foreign only sees
frames of the light platform's discovery; the binary_sensor motion
discovery publishes WHAT 34 / 39 to WHO 1 key 52, which the light at 52
also listens on. MyHOMELight.handle_event now ignores sensor frames
(motion, end of movement, dimensions 5-7), so the warning and the
unknown_state attribute are gone and a restored state is not cleared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GreenGrassBlueOcean and others added 2 commits October 23, 2026 16:28
…uced corpus

The #777 test module and is_motion_end() cited gdluck's
oracle2/hex2own_corpus.tsv, which is not in OpenWebNet-HA/own-firmware-oracle.
They now cite own-firmware-oracle#56 (suite who1-motion-detector, 12
emulated gateways, each run twice byte-identical in CI): B8 52 1D 00 / 01
give *1*34*52## / *1*39*52## on F454 2.0.51, F459, F460, F461, MH200N,
MH202 and MyHomeServer1, and the reporter's *#1*0*{5,6,7}*..## replies come
out byte-exact from frames whose address byte is 00, while the same replies
carrying 52 come out with 52.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…coverage)

- _ForeignAddresses.__init__ guarded the light loop with
  `if not isinstance(cfg, dict): continue`; runtime.platforms is typed
  dict[str, dict[str, dict[str, Any]]], so mypy --strict reports the
  continue as unreachable and it was never covered. The switch / cover /
  sensor loops have no such guard either.
- _ForeignAddresses.owns() has no callers since blocks_light() replaced it
  in ghost(), accept() and route_foreign().

Restores mypy --strict to 0 errors (baseline 0) and coverage to 100 %.

Co-Authored-By: Claude Opus 5.5 <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!

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

merging this as it is confirmed working by topic starter

#777 (comment)

@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit d215eec into v2-phase1-architecture Oct 24, 2026
17 checks passed
@GreenGrassBlueOcean
GreenGrassBlueOcean deleted the fix/issue-777-light-motion-same-address branch October 24, 2026 17:49
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