Repository navigation
feat(cover,lock): impulse garage/gate control, multi-factor safety approval, and intercom events - #667
Conversation
883a2b2 to
7c5b697
Compare
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Hello, |
|
hello @wave68runner you are way too fast here, this is in pure development as we speak. As this is really about the safety of really dangerous stuff like gates and garagedoors we need to be extremely carefull how this is implemented! i really like to continue working on this a little further and then have a chat with other maintainers how and if this will be implemented |
🛡️ Architecture & Security Audit Review: Recommendations & Required FixesThank you for this substantial PR! The access-policy state machine implemented in However, after a complete audit against the released package dependencies, physical bus behavior, and the OpenWebNet protocol specifications, there are several critical blockers that should be addressed before merging: 1.
|
593b96b to
9601d1c
Compare
|
Hi @anotherjulien, @fedem95, @xtimmy86x, @Interstellar0verdrive, Following up on the earlier self-audit and subsequent refactoring commits, this PR is now in a solid state for an initial round of human review and architectural discussion. 📋 Current Status & Addressed FeedbackAll blocking issues identified during the initial protocol and dependency audit have been resolved:
🔍 Key Areas for Review & DiscussionBefore taking this out of Draft, I would appreciate your thoughts on:
Companion protocol work is available in OpenWebNet-HA/OWNd#100. Looking forward to your thoughts and guidance! |
|
Thanks for the updates and for inviting us to review this. I support adding native doorbell/intercom events, door strikes, and impulse-driven covers. Before discussing the individual implementation details, though, I think we should agree on which responsibilities belong in the integration. In my view, the integration should provide a reliable interface to the hardware: send commands, handle pulse timing, read available feedback, expose accurate states and supported actions, and handle communication failures. Phone approval, additional PIN checks, presence conditions, camera confirmation, warning lights, and periodic inspection reminders are policies that users should be able to configure through automations or optional blueprints, rather than mandatory conditions built into every garage operation. For example, a user might legitimately want to operate their garage from a wall-mounted dashboard or a local button without also needing to approve the action on a smartphone. The proposed controller makes the integration responsible for deciding that entire workflow. This does not mean exposing misleading commands or removing technical safeguards. A step-by-step relay cannot guarantee “open”, “close”, or “stop” without sufficient feedback and knowledge of the motor controller’s behavior. Where that cannot be established, an explicit impulse action would be more honest than presenting it as a directional command. Reliable pulse handling and duplicate-command prevention also belong in the integration. I would therefore suggest:
I would also prefer splitting this into the smaller PRs you proposed: intercom events, WHO=6 locks, impulse covers, and then blueprints/documentation. There are still technical points to resolve in the current implementation—particularly STOP being rejected during monitoring, the latched fault being lost on reload/restart, and the software ON/OFF fallback potentially leaving the relay energized if OFF cannot be delivered. However, I think agreeing on this separation of responsibilities first could simplify the implementation considerably. Would you be open to moving the mandatory access-policy workflow into an optional blueprint and keeping the integration focused on the hardware interface? |
|
@xtimmy86x thanks for the review, it is really meant as a conversation starter. Nothing is set in stone. I just want to explore with everybody what we want, what we can do and what works |
…STOP (OpenWebNet-HA#667) An impulse cover is now a plain hardware interface unless it has an `access:` block: open/close/stop pulse the relay at once (deadband, delivery check, sensor-aware skipping of pulses that would only undo the request) with no phone approval and no user requirement. The approval/PIN/presence/pre-warning workflow stays available behind `access:`. Review feedback (xtimmy86x): - STOP is never withheld: with `access:` it cancels a pending request, or pulses a moving door to a stop without approval, for anyone. - A latched fault is restored from the last state after a restart or reload. - A software OFF that is not delivered is retried, the relay is released before the next pulse, and removal delivers an owed OFF. - `pin_code` without `access:` is rejected by validation. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…n OWNd without switch_on_timed (OpenWebNet-HA#667) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
@xtimmy86x thanks, this is the right call, and I agree with the separation of responsibilities. The integration is the hardware interface; the approval workflow is optional. I pushed this to the PR branch (GreenGrassBlueOcean/MyHOME
Your three technical points:
Also: Honest direction commands: with a sensor they are real; without one I agree an explicit "pulse" action is more honest than Split: agreed, in your order: intercom events, WHO 6 locks, impulse covers (hardware interface), then the optional workflow, blueprints and docs. I will cut those once you and the other maintainers are happy with the responsibilities above, so we do not split along a line that is still moving. The doorbell events and the locks do not depend on this discussion, so if you would rather see those two first I can open them right away. Tests: the full suite passes against the development OWNd checkout, and the tests of this PR also pass against the released |
…read-only properties in current OWNd (OpenWebNet-HA#667) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…STOP (OpenWebNet-HA#667) An impulse cover is now a plain hardware interface unless it has an `access:` block: open/close/stop pulse the relay at once (deadband, delivery check, sensor-aware skipping of pulses that would only undo the request) with no phone approval and no user requirement. The approval/PIN/presence/pre-warning workflow stays available behind `access:`. Review feedback (xtimmy86x): - STOP is never withheld: with `access:` it cancels a pending request, or pulses a moving door to a stop without approval, for anyone. - A latched fault is restored from the last state after a restart or reload. - A software OFF that is not delivered is retried, the relay is released before the next pulse, and removal delivers an owed OFF. - `pin_code` without `access:` is rejected by validation. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…n OWNd without switch_on_timed (OpenWebNet-HA#667) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…read-only properties in current OWNd (OpenWebNet-HA#667) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
57c3975 to
4125a53
Compare
…oorbell and lock release trace (OpenWebNet-HA#667)
c5f7794 to
e8e9ce0
Compare
…STOP (OpenWebNet-HA#667) An impulse cover is now a plain hardware interface unless it has an `access:` block: open/close/stop pulse the relay at once (deadband, delivery check, sensor-aware skipping of pulses that would only undo the request) with no phone approval and no user requirement. The approval/PIN/presence/pre-warning workflow stays available behind `access:`. Review feedback (xtimmy86x): - STOP is never withheld: with `access:` it cancels a pending request, or pulses a moving door to a stop without approval, for anyone. - A latched fault is restored from the last state after a restart or reload. - A software OFF that is not delivered is retried, the relay is released before the next pulse, and removal delivers an owed OFF. - `pin_code` without `access:` is rejected by validation. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…n OWNd without switch_on_timed (OpenWebNet-HA#667) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…h English primary defaults
…H200 trace - Add WHO 8 video entrance panel call handling (*8*1#Kind#MMType*WHERE##) to GatewayEventDispatcher emitting myhome_doorbell_event - Ingest 100% disjoint anonymized physical bus trace from MH200 gateway covering bell ring, video call, audio matrix routing, speech, hang-up, and gate/garage impulse actuation - Add replay test test_issue_667_intercom_trace.py verifying full trace replay and event emission
…leased OWNd 2.0.0b10
…eat model, and blueprint alignment - Remove redundant secure_access_approval.yaml blueprint: AccessController handles notifications, nonces, and approvals directly in Python - Add safety notes to doorbell_intercom_notification.yaml explaining that covers route through access approval while locks trigger electric door strikes - Document machinery safety standards (EN 12453 / UL 325), threat model, fail-closed policy, and strict distinction between impulse covers and pedestrian locks in docs/configuration/covers.md and supported_functions.md - Synchronize Supported Entity Domains table in README.md
…impulse access control
…ocks - Use hardware-timed WHAT 18 (*1*18*WHERE##) for 0.5s impulse pulses on WHO 1 - Await gateway confirmed bus delivery for all impulse pulses - Prevent latched relays on entity removal during pending software pulses - Ensure AccessController phase settles on unexpected exceptions during pulse - Enforce PointToPoint addressing and pulse bounds for WHO 1 and WHO 6 locks - Filter discovery bus dispatch by WHO to prevent cross-subsystem leakage - Secure doorbell blueprint with authenticationRequired, entropy, and native camera proxy - Add comprehensive unit tests achieving 100% coverage across all 58 modules
…chronously in event loop
- cover: pulse falls back to ON + software OFF when OWNd lacks OWNLightingCommand.switch_on_timed (released 2.0.0b10); the deadband starts only after the gateway confirmed the write; wall button log and docs say the button has already started the gate - lock: WHO 6 only, no WHO 1 locks (they bypassed the cover access policy), never created from bus traffic, riser addresses (4001#2) validate - doorbell: WHO 8 rings only for entrance panel kinds 1-4 (not handset calls or pagers) and only on the gateway that owns the subsystem - access: a PIN reply without text is no failed attempt and cannot lock out - validate: type: impulse_relay needs who: 1 (and shutter/standard needs who: 2), drop duplicate schema keys - docs: correct config keys, access: example, lock example, chime is unverified Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ernal or pager calls OWNd#100 review: WHO 8 WHAT 1 is any call session. myhome_doorbell_event fired on is_call, so a handset-to-handset call (*8*1#6#...) or a pager broadcast (*8*1#14#...) rang the doorbell. Ring on is_incoming_call (entrance panel call kinds 1..4) or is_chime instead; the fallback for OWNd releases without the WHO 8 class checks the call kind the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lock: a registry entry of another WHO, a general WHERE in myhome.yaml and a translated frame. Discovery: an event of the platform class carrying another WHO. Intercom: an unreadable call kind. DoorEntryWhere repr. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…IN resolution Remove dead close action button from door_gate_motion_notification blueprint to prevent unhandled notification actions and align with human-in-the-loop cover safety policy. Broaden PIN resolution in access_policy.py to accept reply fallback key. Cross-link cancel_cover_request and acknowledge_cover_fault in covers.md.
…STOP (OpenWebNet-HA#667) An impulse cover is now a plain hardware interface unless it has an `access:` block: open/close/stop pulse the relay at once (deadband, delivery check, sensor-aware skipping of pulses that would only undo the request) with no phone approval and no user requirement. The approval/PIN/presence/pre-warning workflow stays available behind `access:`. Review feedback (xtimmy86x): - STOP is never withheld: with `access:` it cancels a pending request, or pulses a moving door to a stop without approval, for anyone. - A latched fault is restored from the last state after a restart or reload. - A software OFF that is not delivered is retried, the relay is released before the next pulse, and removal delivers an owed OFF. - `pin_code` without `access:` is rejected by validation. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…n OWNd without switch_on_timed (OpenWebNet-HA#667) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…read-only properties in current OWNd (OpenWebNet-HA#667) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… firmware oracle conformance
…oorbell and lock release trace (OpenWebNet-HA#667)
11bd651 to
ea95d41
Compare
🔄 Rebase onto
|
…-live-trace test(traces): add MH200 intercom/gate and live F461 + BT-F411/4 WHAT=18 impulse trace corpus (#667)
11e8de5
into
OpenWebNet-HA:v2-phase1-architecture
- Count and summarize every pull request merged since 2.0.0b15, grouped by subsystem, with the expose engine (#750), slat tilt (#508) and impulse covers (#667) as the headline features. - Covers: the start-up floor is the per-cover min_motor_pulse (#708), timed runs end at the stop status (#707) and full-travel keypad runs report the end (#760); drop the "lift only" and open follow-up lines, both shipped. - New behaviour-change section: sync_time refuses half-hour time zones (#764) and advanced: true on an F411 now warns (#762). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What is the problem
In BTicino / Legrand MyHOME SCS installations, three physical device classes share
WHO=1,WHO=6, andWHO=8bus traffic with distinct operational and safety requirements:WHO=1Dry-Contact Relays Driving Gate & Garage Motor Controllers):WHO=1relay channel (e.g.,BT-F411/4orF411U2, configured with a0.5 spulse) rather than a dedicatedWHO=2shutter actuator.OPENSTOPCLOSESTOP) impulse relay as a standardlightentity exposes a latching ON/OFF toggle that can leave the relay coil permanently energized or trigger unintended motor movement without travel-time tracking, deadband protection, or optional supervision.WHO=6):*6*10*WHERE##at4000..4095or4000#2..4095#2, and*6*22*WHERE##in-conversation release at0..99) are momentary pulse devices that auto-relock after a few seconds and do not have a dedicated Home Assistantlockplatform entity.WHO=6/WHO=8):WHO=8call frames (*8*1#1#4*WHERE##or 4-component*8*1#1#4#PANEL*UNIT##, call kinds1..4) andWHO=6chime frames (*6*20*WHERE##), whereas internal room-to-room handset calls (kind 6), external intercom calls (kind 7), and general pager broadcasts (kind 14) also useWHO=8WHAT=1and must not false-trigger visitor doorbell automations.How is it solved
MyHOMEImpulseCover(custom_components/myhome/cover.py,type: impulse_relay):access:block,open_cover,close_cover, andstop_coverpulse theWHO=1relay immediately (works from wall tablets, local dashboards, scripts, and voice assistants without requiring a mobile phone or PIN).WHAT=18Hardware-Timed vs. SoftwareON+ DelayedOFFFallback):OWNLightingCommand.switch_on_timed(where, 18)(*1*18*WHERE##,0.5 shardware-timed momentary pulse on the actuator) whenpulse_duration == 0.5andOWNdprovidesswitch_on_timed.*1*1*WHERE##followed by a scheduled*1*0*WHERE##afterpulse_durationwith 3-attempt retry,_pulse_off_pendingguard (refusing to stack a newONbefore an owedOFFis delivered), and guaranteedOFFdelivery on entity unload.min_cycle_time(HomeAssistantErrorif pulsed again within the deadband), starting_last_pulse_timestrictly after a confirmed bus write so a failed/NACKed write never locks out the user.*1*1*WHERE##echoes within_SELF_ECHO_WINDOW(2.0 s) and ignores*1*0*WHERE##relay-release frames so neither hardware auto-off (WHAT=18) nor software pulse-off toggles the step-by-step state machine.slat_tilt/slat_time(SUPPORT_COVER_TILTonMyHOMEAdvancedCover) incover.py,validate.py,services.yaml, anddocs/configuration/services.md.AccessControllerSupervision Workflow (custom_components/myhome/access_policy.py):access:block is configured on an impulse cover.OPEN_ONLYvs.MAY_CLOSE), cross-platform mobile push approval (authenticationRequired: truefor iOS biometric prompts andaction: "REPLY"for Android PIN input), optionalpresence_entities,camera_entity,pre_warn_light, travel-time watchdog, and persistentFAULTstate recovery viamyhome.acknowledge_cover_fault.WHO=6Door StrikelockPlatform (custom_components/myhome/lock.py):lock:strictly toWHO=6door-entry strikes (4000..4095,4000#2..4095#2, and0..99), disallowingWHO=1relays underlock:and disabling unauthenticated bus auto-discovery (listen=False).*6*10*WHERE##(and tracks incoming*6*10*WHERE##/*6*22*WHERE##releases) with configurableunlock_durationauto-relock timer and optionalcode_formatPIN check.myhome_doorbell_eventDispatcher (custom_components/myhome/gateway_events.py):myhome_doorbell_eventstrictly forWHO=8entrance-panel calls (call_kind in (1, 2, 3, 4), supporting both 3-token1#1#4and 4-token1#1#4#20caller-ID forms) andWHO=6WHAT=20, gated byself._is_active_for_who(who)to prevent duplicate events on multi-gateway shared buses.How is it tested
100%passing on WSL Python 3.14 / HA 2026.10.1):tests/test_component_impulse_cover.py: hardware-timedWHAT=18and softwareON/OFFfallback,_pulse_off_pendingrecovery,min_cycle_timedeadband rollback on write failure, external wall-button synchronization,state_sensordirection reconciliation, and optionalAccessControllerintegration.tests/test_access_policy.py: fail-closed direction classification, token isolation, iOS/Android notification payloads, PIN lockout, pre-warning abort, travel watchdog, and persistentFAULTrestoration across HA restarts.tests/test_component_lock.py:WHO=6lock strike commands (*6*10*4000##,*6*10*4001#2##), bus event tracking (*6*10*and*6*22*), auto-relock timer, and schema rejection ofWHO=1.tests/test_door_entry_events.py&tests/test_issue_667_intercom_trace.py(standalone trace corpus in PR test(traces): add MH200 intercom/gate and live F461 + BT-F411/4 WHAT=18 impulse trace corpus (#667) #755):2.1.0) video door entry & impulse gate/garage capture (myhome_trace_MH200_intercom_bell_gate_2026-10-09.json).1.0.24) visitor doorbell call (*8*1#1#4#20*11##), caller-ID notification (*8*9#1#4*20##), and dual lock release (*6*22*1##,*6*10*4000##) trace (myhome_trace_MH202_all_2026-10-18T10-27-48.json, PR test(traces): ingest Issue #669 MH202 visitor doorbell call and wall control traces #751).WHO=8frames from theintercom-ts10firmware oracle suite (kinds 6, 7, 14), confirming zero false-positivemyhome_doorbell_eventtriggers.2.0.11) +BT-F411/4(WHERE="06","07") +BT-F411/2(WHERE="05") multi-session trace (myhome_trace_F461_impulse_intercom_2026-10-19.json, PR test(traces): add MH200 intercom/gate and live F461 + BT-F411/4 WHAT=18 impulse trace corpus (#667) #755).ruff check custom_components tests scriptspython scripts/typing_ratchet.pymypy --strict: 0 error(s) in 0 module(s); baseline 0python scripts/verify_ha_standards.pypython scripts/sync_documentation.py --checkState Diagram
stateDiagram-v2 [*] --> Closed : Initial / state_sensor == off state "Closed" as Closed state "Opening (travel_time timer)" as Opening state "Open" as Open state "Closing (travel_time timer)" as Closing state "Stopped (mid-flight)" as Stopped Closed --> Opening : open_cover() or external *1*1*W## pulse\n[Send *1*18*W## or *1*1*W## -> *1*0*W##]\n(Ignore *1*1*W## within 2.0s echo window & all *1*0*W##) Opening --> Open : travel_time elapsed or state_sensor -> on Opening --> Stopped : stop_cover() or external *1*1*W## pulse Stopped --> Closing : close_cover() or external *1*1*W## pulse Open --> Closing : close_cover() or external *1*1*W## pulse Closing --> Closed : travel_time elapsed or state_sensor -> off Closing --> Stopped : stop_cover() or external *1*1*W## pulseWhere are the traces
MyHOMEF461+BT-F411/4WHAT=18(0.5 sTimed Pulse), Software Impulse Cycle, Unpopulated71NACK&WHO=6/WHO=8FastNACK(2026-10-19, PR #755)tests/fixtures/traces/issue_667/myhome_trace_F461_impulse_intercom_2026-10-19.json2.0.11/WHO=13code200/WHO=1013134.15.5.0) + BT-F411/4 (06,07) + BT-F411/2 (05)MH200Outdoor Bell (*8*1#1#4*74##), Handset Hang-Up (*8*9#1#4*73##), Camera Off (*6*9##) & Impulse Relays (*1*1*71##,*1*1*72##) (PR #755)tests/fixtures/traces/issue_667/myhome_trace_MH200_intercom_bell_gate_2026-10-09.json2.1.0,WHO=13Dim 15 code4)MH2024-Component Entrance Call (*8*1#1#4#20*11##), Caller ID (*8*9#1#4*20##) & Dual Lock Release (*6*22*1##,*6*10*4000##) (PR #751)tests/fixtures/traces/mh202_sound_intercom_ducking/myhome_trace_MH202_all_2026-10-18T10-27-48.json1.0.24,WHO=13Dim 15 code13)WHO=8Internal/External/Pager Oracle Frames (intercom-ts10) & EmulatedMH200(020102) Verdictstests/golden/firmware_oracle.jsonown-firmware-oracle)What does the Oracle / Encyclopedia say
F461(2.0.11) +BT-F411/4(WHERE="06","07") Hardware Verification (2026-10-19, PR test(traces): add MH200 intercom/gate and live F461 + BT-F411/4 WHAT=18 impulse trace corpus (#667) #755):WHO=1WHAT=18(0.5 sHardware-Timed Momentary Pulse):*1*18*06##(what18_timed_05s_pulse_on_06) is ACKed byF461in0.090 s(0.093 son07).evtmonitor session, the physicalBT-F411/4relay closes and emits*1*1*06##(t = 4.315 s) + Group#1aggregate*#1*#1*15*1##(t = 4.388 s), and 0.539 seconds later (t = 4.854 s;0.511 son07) autonomously opens in hardware and broadcasts*1*0*06##+*#1*#1*15*0##(t = 4.927 s) with zero softwareOFFcommand required.*#1*06##and*#1*07##) confirm['*1*0*06##']and['*1*0*07##'].0.5 sImpulse Cover Step-by-Step Cycle (OPENSTOPCLOSEonWHERE="06"):MyHOMEImpulseCoversoftware fallback pulse sequence (*1*1*06##*1*0*06##) acrossOPEN, mid-flightSTOP, andCLOSEACKs in0.086–0.105 sper frame and transitionsMyHOMEImpulseCoverthroughopeningstoppedclosingwhile ignoring*1*0*06##release frames.WHERE="71") Bus Timeout:*1*18*71##or*1*1*71##to an unpopulated relay address causesbt_flsonF461to wait2.065 s/2.071 son the 2-wire SCS bus before returning*#*0##(NACK) withevents_after = [], validating whyMyHOMEImpulseCoveronly armsmin_cycle_timeafter a confirmed write.WHO=6&WHO=8on Automation-OnlyF461:WHO=6lock/camera commands (*6*10*4000##,*6*10*4001#2##,*6*10*1##,*6*22*1##,*6*0*4000##,*6*9##,*6*9**##,*6*6*1##,*6*6*4100##) and all 6WHO=8call/hang-up frames (*8*1#1#4*74##,*8*1#1#4#20*11##,*8*9#1#4*73##,*8*1#6#2#11*16##,*8*1#7#2#11*16##,*8*1#14#2#11*4##) return a fast~55 msNACK(*#*0##) with zeroevttraffic onF461(automation-only DIN gateway without a video door-entry riser).own-firmware-oracle):WHO=1WHAT=18: Specified as0.5 smomentary ON timer (*1*18*WHERE##); empirically verified on both emulated gateways and physicalF461+BT-F411/4.WHO=6WHAT=10/WHAT=22: Door lock release (4000..4095,#2riser suffix) and in-conversation lock release (0..99), corroborated by the physicalMH202trace (*6*22*1##*6*24*1##and*6*10*4000##*6*23*4000##).WHO=8Call Kinds (1..4vs6, 7, 14):intercom-ts10(TS10_1_0_23videodoorentry_device.cpp) confirms thatWHAT=1withcall_kind in (6, 7, 14)represents internal handset calls, external intercom transfers, and pager broadcasts, whereascall_kind in (1, 2, 3, 4)represents outdoor entrance panel calls.