Repository navigation
fix(light): keep a configured light when a WHO 1 motion sensor shares its address (#777) - #781
Merged
GreenGrassBlueOcean merged 10 commits intoOct 24, 2026
Conversation
… 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
changed the base branch from
master
to
v2-phase1-architecture
October 23, 2026 13:26
This was referenced Oct 23, 2026
Merged
…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 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>
Contributor
Author
|
merging this as it is confirmed working by topic starter |
GreenGrassBlueOcean
deleted the
fix/issue-777-light-motion-same-address
branch
October 24, 2026 17:49
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.
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_sensordiscovered<mac>-52-motion,_ForeignAddressestreated 52 as a sensor address. As a result:ghost()removed the configuredlight.myhome_spots_wcon reload, logging only at INFO.accept()skipped themyhome.yamlentry.unknown lighting WHAT 39.*#1*52*5##,*#1*52*7##) are answered by the 067226 withWHERE=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
_ForeignAddressesknows the configured lights.blocks_light()still blocks switch and cover addresses, and sensor addresses with no configured light, so the [Feature / Bug]: Dynamic discovery for WHO 1 light/motion sensors (Legrand 048834) & ghost light prevention #244 / fix(sensor): dynamic discovery for WHO 1 light/motion sensors and ghost light prevention (#244) #245 ghost pruning is unchanged.34 → 1000#1 → 1, then39 → 1000#0 → 0.*#1*0*5|6|7*…##dimension replies come back with WHERE 0. They must not create or change a light.is_motion_end()indiscovery.pyis shared by light and binary_sensor. It checksmotion_end(OWNd with WHAT 39) orunknown_state == 39(b11). The patch's broader fallback is gone: it treated anymotion_detectedwithmotion Falseas the end of movement. So is its read of the private_state.52#4#01no longer keeps a ghost at plain52.myhome.yaml. On/off frames at such an address log a one-time INFO hint.force_updatereset after the motion sensor writes a WHAT 39 state.light 52 reports unknown lighting WHAT 39; keeping its last statewas still logged once per cycle (comment [2.0.0b16] Configured light removed on reload after motion sensor discovery at the same WHO 1 address (F454 / Legrand 067226) #777 (comment)).52, which the light at 52 also listens on, soroute_foreignnever sees those frames.MyHOMELight.handle_eventnow ignores sensor frames: motion, end of movement, and dimensions 5–7. The new test reproduces the reporter's exact warning without this guard.is_motion_end()used to cite a firmware corpus file that is not in our oracle.WHERE=0dimension 5 and 7 replies:tcpdumpcapture ([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##, theWHERE=0reply (*#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).CommandWorkerPooltracks messages currently insideawait _command_session.send(...)and exposesclaim_in_flight_dimension_where(who, dimension)(forwarded byMyHOMEGatewayHandleracross secondary delegation and warm-standby failover).WHERE=0dimension 5 or 7 frame arrives onbinary_sensor's WHO 1on_generalhook and exactly one point-to-point targetWHEREis in flight for(1, dimension), it consumes the in-flight entry immediately and routes the reply to that motion sensor (Sensitivity: high,Timeout: 60.0s). 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
ownkb:claim:c001481).*#1*0*7*0*0*45##.who1-motion-detector(findings), 12 emulated gateways, each run twice and byte-identical in CI (run 39967360131).B8 52 1D 00/01give*1*34*52##/*1*39*52##on F4542.0.51, F459, F460, F461, MH200N, MH202 and MyHomeServer1. F453 / F453AV give only WHAT 34.*#1*0*5*2##,*#1*0*6*0##and*#1*0*7*0*0*45##come out byte-exact fromD5 00 21 44 {10,15,16} …frames whose address byte is00.tcpdumpcapture (#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).*#1*52##returned exactly one reply, from the F411.Deliberately not done
myhome.yaml. The new WARNING / INFO lines say so.WHERE=0illuminance (DIMENSION 6). Every reading in the reporter's capture was*#1*0*6*0##even with the spots on, soDIMENSION 6is not attributed until its behaviour on the 067226 is understood.myhome.send_messageerrors. The ERRORs in the reporter's first trace came from sending these requests by hand with that service. That is fixed separately in fix(services): send status and dimension requests from send_message as requests #782 (merged).Testing
mypy --strict(scripts/typing_ratchet.py),ruff,verify_ha_standards.py, andsync_documentation.py --checkare clean.