Skip to content

feat(cover,lock): impulse garage/gate control, multi-factor safety approval, and intercom events - #667

Merged
GreenGrassBlueOcean merged 26 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
GreenGrassBlueOcean:feat/impulse-cover-gate-garage
Oct 19, 2026
Merged

GreenGrassBlueOcean merged 26 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
GreenGrassBlueOcean:feat/impulse-cover-gate-garage

Conversation

@GreenGrassBlueOcean

@GreenGrassBlueOcean GreenGrassBlueOcean commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

What is the problem

In BTicino / Legrand MyHOME SCS installations, three physical device classes share WHO=1, WHO=6, and WHO=8 bus traffic with distinct operational and safety requirements:

  1. Step-by-step Impulse Covers (WHO=1 Dry-Contact Relays Driving Gate & Garage Motor Controllers):
    • Motorized gates and garage doors are frequently wired to a momentary WHO=1 relay channel (e.g., BT-F411/4 or F411U2, configured with a 0.5 s pulse) rather than a dedicated WHO=2 shutter actuator.
    • Treating a step-by-step (OPEN $\rightarrow$ STOP $\rightarrow$ CLOSE $\rightarrow$ STOP) impulse relay as a standard light entity 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.
  2. Door Entry Strike Locks (WHO=6):
    • Physical door-entry lock strikes (*6*10*WHERE## at 4000..4095 or 4000#2..4095#2, and *6*22*WHERE## in-conversation release at 0..99) are momentary pulse devices that auto-relock after a few seconds and do not have a dedicated Home Assistant lock platform entity.
  3. Video Door Entry & Intercom Doorbell Ring Events (WHO=6 / WHO=8):
    • Outdoor entrance panel doorbell presses emit WHO=8 call frames (*8*1#1#4*WHERE## or 4-component *8*1#1#4#PANEL*UNIT##, call kinds 1..4) and WHO=6 chime 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 use WHO=8 WHAT=1 and must not false-trigger visitor doorbell automations.

How is it solved

  1. MyHOMEImpulseCover (custom_components/myhome/cover.py, type: impulse_relay):
    • Direct Hardware Interface by Default: When configured without an access: block, open_cover, close_cover, and stop_cover pulse the WHO=1 relay immediately (works from wall tablets, local dashboards, scripts, and voice assistants without requiring a mobile phone or PIN).
    • Dual-Mode Pulse Delivery (WHAT=18 Hardware-Timed vs. Software ON + Delayed OFF Fallback):
      • Uses OWNLightingCommand.switch_on_timed(where, 18) (*1*18*WHERE##, 0.5 s hardware-timed momentary pulse on the actuator) when pulse_duration == 0.5 and OWNd provides switch_on_timed.
      • Falls back to *1*1*WHERE## followed by a scheduled *1*0*WHERE## after pulse_duration with 3-attempt retry, _pulse_off_pending guard (refusing to stack a new ON before an owed OFF is delivered), and guaranteed OFF delivery on entity unload.
    • Confirmed-Write Deadband & Echo Guard:
      • Enforces min_cycle_time (HomeAssistantError if pulsed again within the deadband), starting _last_pulse_time strictly after a confirmed bus write so a failed/NACKed write never locks out the user.
      • Ignores self-generated *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.
    • Rebased Coexistence with Venetian Blind Slat Tilt (feat(cover): support venetian blind slat / louvre tilt positioning (WHO 2 Dimension 11) #492 / PR feat(cover): implement venetian blind slat tilt inching and clarify dimension 11 shutter level (#492) #508):
      • Cleanly integrates alongside slat_tilt / slat_time (SUPPORT_COVER_TILT on MyHOMEAdvancedCover) in cover.py, validate.py, services.yaml, and docs/configuration/services.md.
  2. Optional AccessController Supervision Workflow (custom_components/myhome/access_policy.py):
    • Activated only when an explicit access: block is configured on an impulse cover.
    • Provides direction-aware policy enforcement (OPEN_ONLY vs. MAY_CLOSE), cross-platform mobile push approval (authenticationRequired: true for iOS biometric prompts and action: "REPLY" for Android PIN input), optional presence_entities, camera_entity, pre_warn_light, travel-time watchdog, and persistent FAULT state recovery via myhome.acknowledge_cover_fault.
  3. Dedicated WHO=6 Door Strike lock Platform (custom_components/myhome/lock.py):
    • Restricts lock: strictly to WHO=6 door-entry strikes (4000..4095, 4000#2..4095#2, and 0..99), disallowing WHO=1 relays under lock: and disabling unauthenticated bus auto-discovery (listen=False).
    • Unlocks via *6*10*WHERE## (and tracks incoming *6*10*WHERE## / *6*22*WHERE## releases) with configurable unlock_duration auto-relock timer and optional code_format PIN check.
  4. Scoped myhome_doorbell_event Dispatcher (custom_components/myhome/gateway_events.py):
    • Emits myhome_doorbell_event strictly for WHO=8 entrance-panel calls (call_kind in (1, 2, 3, 4), supporting both 3-token 1#1#4 and 4-token 1#1#4#20 caller-ID forms) and WHO=6 WHAT=20, gated by self._is_active_for_who(who) to prevent duplicate events on multi-gateway shared buses.

How is it tested

  • 314 Automated Tests (100% passing on WSL Python 3.14 / HA 2026.10.1):
    • tests/test_component_impulse_cover.py: hardware-timed WHAT=18 and software ON/OFF fallback, _pulse_off_pending recovery, min_cycle_time deadband rollback on write failure, external wall-button synchronization, state_sensor direction reconciliation, and optional AccessController integration.
    • tests/test_access_policy.py: fail-closed direction classification, token isolation, iOS/Android notification payloads, PIN lockout, pre-warning abort, travel watchdog, and persistent FAULT restoration across HA restarts.
    • tests/test_component_lock.py: WHO=6 lock strike commands (*6*10*4000##, *6*10*4001#2##), bus event tracking (*6*10* and *6*22*), auto-relock timer, and schema rejection of WHO=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):
  • Static Analysis & Architectural Standards:
    • ruff check custom_components tests scripts $\rightarrow$ All checks passed!
    • python scripts/typing_ratchet.py $\rightarrow$ mypy --strict: 0 error(s) in 0 module(s); baseline 0
    • python scripts/verify_ha_standards.py $\rightarrow$ SUCCESS: All Home Assistant architectural standards passed!
    • python scripts/sync_documentation.py --check $\rightarrow$ SUCCESS: All documentation is calibrated and in sync with the codebase!

State 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## pulse
Loading

Where are the traces

Note: The trace fixtures and standalone replay suite for this PR are also submitted in a dedicated, standalone trace PR (PR #755) against v2-phase1-architecture.

Subsystem / Mechanism Hardware Trace / Fixture in MyHOME Gateway & Firmware
Live F461 + BT-F411/4 WHAT=18 (0.5 s Timed Pulse), Software Impulse Cycle, Unpopulated 71 NACK & WHO=6/WHO=8 Fast NACK (2026-10-19, PR #755) tests/fixtures/traces/issue_667/myhome_trace_F461_impulse_intercom_2026-10-19.json BTicino F461 (2.0.11 / WHO=13 code 200 / WHO=1013 134.15.5.0) + BT-F411/4 (06, 07) + BT-F411/2 (05)
MH200 Outdoor 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.json BTicino MH200 (2.1.0, WHO=13 Dim 15 code 4)
MH202 4-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.json BTicino MH202 (1.0.24, WHO=13 Dim 15 code 13)
59 WHO=8 Internal/External/Pager Oracle Frames (intercom-ts10) & Emulated MH200 (020102) Verdicts tests/golden/firmware_oracle.json 12 Emulated Gateways (own-firmware-oracle)

What does the Oracle / Encyclopedia say

  1. Live Physical BTicino 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=1 WHAT=18 (0.5 s Hardware-Timed Momentary Pulse):
      • Sending *1*18*06## (what18_timed_05s_pulse_on_06) is ACKed by F461 in 0.090 s (0.093 s on 07).
      • On the evt monitor session, the physical BT-F411/4 relay closes and emits *1*1*06## (t = 4.315 s) + Group #1 aggregate *#1*#1*15*1## (t = 4.388 s), and 0.539 seconds later (t = 4.854 s; 0.511 s on 07) autonomously opens in hardware and broadcasts *1*0*06## + *#1*#1*15*0## (t = 4.927 s) with zero software OFF command required.
      • Subsequent point status polls (*#1*06## and *#1*07##) confirm ['*1*0*06##'] and ['*1*0*07##'].
    • Software 0.5 s Impulse Cover Step-by-Step Cycle (OPEN $\rightarrow$ Mid-Flight STOP $\rightarrow$ CLOSE on WHERE="06"):
      • Exercising the 3-step MyHOMEImpulseCover software fallback pulse sequence (*1*1*06## $\rightarrow$ 0.5 s $\rightarrow$ *1*0*06##) across OPEN, mid-flight STOP, and CLOSE ACKs in 0.086–0.105 s per frame and transitions MyHOMEImpulseCover through opening $\rightarrow$ stopped $\rightarrow$ closing while ignoring *1*0*06## release frames.
    • Unpopulated Impulse Relay Address (WHERE="71") Bus Timeout:
      • Sending *1*18*71## or *1*1*71## to an unpopulated relay address causes bt_fls on F461 to wait 2.065 s / 2.071 s on the 2-wire SCS bus before returning *#*0## (NACK) with events_after = [], validating why MyHOMEImpulseCover only arms min_cycle_time after a confirmed write.
    • WHO=6 & WHO=8 on Automation-Only F461:
      • All 9 WHO=6 lock/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 6 WHO=8 call/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 ms NACK (*#*0##) with zero evt traffic on F461 (automation-only DIN gateway without a video door-entry riser).
  2. OpenWebNet Encyclopedia Machine KB & Firmware Oracle (own-firmware-oracle):
    • WHO=1 WHAT=18: Specified as 0.5 s momentary ON timer (*1*18*WHERE##); empirically verified on both emulated gateways and physical F461 + BT-F411/4.
    • WHO=6 WHAT=10 / WHAT=22: Door lock release (4000..4095, #2 riser suffix) and in-conversation lock release (0..99), corroborated by the physical MH202 trace (*6*22*1## $\rightarrow$ *6*24*1## and *6*10*4000## $\rightarrow$ *6*23*4000##).
    • WHO=8 Call Kinds (1..4 vs 6, 7, 14): intercom-ts10 (TS10_1_0_23 videodoorentry_device.cpp) confirms that WHAT=1 with call_kind in (6, 7, 14) represents internal handset calls, external intercom transfers, and pager broadcasts, whereas call_kind in (1, 2, 3, 4) represents outdoor entrance panel calls.

@GreenGrassBlueOcean
GreenGrassBlueOcean force-pushed the feat/impulse-cover-gate-garage branch from 883a2b2 to 7c5b697 Compare October 9, 2026 07:55
@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!

@wave68runner

Copy link
Copy Markdown

Hello,
If I understand correctly, this looks interesting. However, would it be possible to provide another short tutorial, specifically about doorbell snapshots etc.
which parameters need to be sent and received for the different options ?

@GreenGrassBlueOcean

GreenGrassBlueOcean commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor Author

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

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

🛡️ Architecture & Security Audit Review: Recommendations & Required Fixes

Thank you for this substantial PR! The access-policy state machine implemented in access_policy.py is exceptionally well-structured, featuring clean separation of concerns, fail-closed direction checks, and robust cryptographic token isolation.

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. ⚠️ Dependency Incompatibility with Pinned OWNd (manifest.json)

The impulse cover and lock pulse code rely on OWNLightingCommand.switch_on_timed (cover.py:1424, lock.py:284):

cmd = OWNLightingCommand.switch_on_timed(self._full_where, 18)
  • Issue: switch_on_timed does not exist in released OWNd==2.0.0b10 (the version pinned in manifest.json; it was introduced in unreleased commit 6cd5c39 on OWNd master).
  • Impact: Running the MyHOME test suite against the pinned requirement OWNd==2.0.0b10 fails 7 tests (test_impulse_cover_* and test_impulse_lock_*) with:
    AttributeError: type object 'OWNLightingCommand' has no attribute 'switch_on_timed'
    
    Every impulse cover pulse fails, and a WHO 1 lock unlock raises an AttributeError. CI didn't catch this because tests/conftest.py dynamically reinstalls OWNd from git+master when attributes are missing, and ownd-smoke.yml only runs protocol corpus tests.
  • Remediation: Either provide a fallback in cover.py and lock.py (switch_on with delayed switch_off), or hold this PR until OWNd releases 2.0.0b11 to PyPI and bump manifest.json.

2. 🚨 Critical Safety & Behavioral Findings

  1. Doorbell Events Over-Triggering on Internal Calls & Pagers:

    • In OWNd (door_entry.py:226-227), what == 1 sets is_call = True and is_incoming_call = True for all WHO 8 calls.
    • In gateway_events.py:516, any who == 8 and what == 1 triggers myhome_doorbell_event.
    • Problem: WHO 8 what == 1 includes internal intercom calls between handsets (kind 6) and pager broadcasts (kind 14). When a resident calls another room internally, the blueprint pushes: "🔔 Doorbell: Someone is ringing! Incoming call from entrance panel" with a one-tap release button.
    • Fix: Check call_kind when who == 8, and restrict myhome_doorbell_event to entrance panels (kind in (1, 2, 3, 4)).
  2. Duplicate Doorbell Events on Shared Bus:

    • gateway_events.py:508 lacks the self._is_active_for_who(who) check present in the alarm (line 484) and CEN (line 466) handlers.
    • On dual-gateway setups on a shared bus (e.g., active + standby gateways), both gateways observe the bus frame and fire myhome_doorbell_event, causing every ring to fire twice.
    • Fix: Guard with if self._is_active_for_who(getattr(message, "who", 6)):.
  3. Safety System Bypass via Lock Platform:

    • lock_schema accepts who: 1 for any relay address (validate.py:667).
    • Motorized gates (where: 72) can be configured as a lock, bypassing the entire AccessController (no sensor verification, no pre-warning, no presence/camera checks) and enabling one-tap blind actuation.
    • Fix: Disallow who: 1 under lock_schema, restricting locks to dedicated door entry strike mechanisms (who: 6).
  4. Unconfigured Lock Auto-Creation from Bus Traffic:

    • In lock.py:157-163, accept_who6 matches any bus event with WHAT in (10, 22).
    • When lock: is enabled in YAML, observing any lock release on the physical bus dynamically creates a lock entity with code = None. Anyone with dashboard access can then unlock the front door without authentication.
    • Fix: Disable bus listener for locks (PlatformDiscovery.start(listen=False)).
  5. Android PIN Approval Lockout:

    • In access_policy.py:432, PIN prompts use {"behavior": "textInput"}, which is iOS-specific.
    • The Android Companion App requires action: "REPLY". On Android, behavior is ignored, rendering a plain button with no text input. The action event returns reply = None, which fails PIN comparison, increments failed attempts, and locks the user out after 3 tries.
    • Fix: Provide cross-platform compatibility by sending action: "REPLY" for Android.
  6. Deadband Lockout on Send Failure:

    • In cover.py:1413, self._last_pulse_time = now is set before send(). If sending fails or times out, the timestamp is not rolled back, locking the user out for min_cycle_time (5s) despite no command reaching the bus.
    • Fix: Update _last_pulse_time only upon confirmed write.
  7. Wall Button Physical Desync:

    • cover.py:1538 cancels Home Assistant's pre-warning when a wall button pulse is observed. On physical hardware, that button pulse has already closed the relay and energized the gate motor. Pressing the wall button does not abort movement; it starts it. Documentation and logging should clarify this distinction.

3. 📖 Specification & Schema Checks

  1. *6*20* (Chime):
    • WHAT 20 is not in the official L4686SDK WHO 6 specification (which defines 0, 9, 6, 10, 11, 12, 18, 22). If observed on specific firmware, please document the hardware provenance.
  2. Lock Addressing vs. Spec:
    • The WHO 6 specification defines lock endpoints as 4000..4095 with #2 appended for riser installations. validate.py:668 rejects 4001#2 because PointToPoint and SpecialWhere require digits only.
    • lock_schema also rejects who: 8 locks (In(["1", "6"])), though listed in the PR body.
  3. Phantom Config Keys in Documentation:
    • closed_sensor_entity_id, open_sensor_entity_id, impulse_duration_ms, and stuck_timeout_sec in covers.md do not exist in the code (which expects state_sensor, pulse_duration, and watchdog_margin).
  4. Ignored type: impulse_relay:
    • cover.py:148-151 branches strictly on CONF_WHO == "1". A configuration with who: 2 and type: impulse_relay silently becomes a standard shutter (MyHOMECover).
  5. Missing access: YAML Documentation:
    • covers.md lacks a working YAML example with an access: block. Because AccessConfig.from_config defaults allowed_users to an empty tuple when access: is omitted, any cover without an access: block refuses all commands with AccessDenied.
  6. Duplicate Schema Keys:
    • validate.py defines both CONF_PULSE_DURATION and "pulse_duration", CONF_TYPE and "type", CONF_PIN_CODE and "pin_code" in dictionary literals, where the string key silently overwrites the constant key.

4. 🗺️ Recommended Roadmap

To make review and testing reliable, we recommend splitting this PR into four focused, incremental PRs:

  1. PR 1: Doorbell & Intercom Event Dispatcher: Scoped strictly to entrance panel calls with gateway de-duplication.
  2. PR 2: Lock Platform (WHO 6 strikes only): Fix riser address validation (4000..4095#2), disable unauthenticated bus auto-discovery, and remove who: 1 bypass.
  3. PR 3: Impulse Covers & Access Policy: Resolve Android notification differences, fix deadband timer rollback on write failure, and add comprehensive access: block YAML examples.
  4. PR 4: Automation Blueprints: Updated once the underlying event and platform APIs are stabilized.

@GreenGrassBlueOcean
GreenGrassBlueOcean force-pushed the feat/impulse-cover-gate-garage branch from 593b96b to 9601d1c Compare October 9, 2026 21:05
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

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 Feedback

All blocking issues identified during the initial protocol and dependency audit have been resolved:

  • Dual-OWNd Compatibility: Added a clean fallback pulse path (momentary ON followed by delayed OFF) so the suite passes against both the currently released OWNd==2.0.0b10 and development OWNd>=2.0.0b11 (which supports hardware-timed WHAT 18 pulses via switch_on_timed).
  • Doorbell & Intercom Scope: Restricted myhome_doorbell_event strictly to entrance-panel call kinds (1–4) with gateway ownership checks (_is_active_for_who), eliminating phantom rings on internal handset-to-handset calls, pagers, or secondary standby gateways on shared buses.
  • Strict Machinery vs. Pedestrian Lock Separation: Restricted the lock platform strictly to WHO=6 door strikes (*6*10*WHERE##), rejecting who: 1 relays so motorized gates cannot bypass the AccessController. Disabled bus-driven lock entity creation (listen=False).
  • AccessController & Approval Hardening: Updated push notifications to support cross-platform action responses (iOS biometric prompt authenticationRequired: true and Android REPLY handling), ensured deadband timers start strictly upon confirmed write, and added state-drift protection.
  • Trace Replay & Coverage: Replays authentic customer MH200 on-wire traces (tests/fixtures/traces/issue_667/); passes 100% statement coverage on new modules with all CI checks green.

🔍 Key Areas for Review & Discussion

Before taking this out of Draft, I would appreciate your thoughts on:

  1. AccessController State Machine & Fail-Closed Safety Model:

    • For motorized impulse gates/garages, commands (open_cover, close_cover) only create approval requests. Direction classification treats any position other than a verified closed sensor as MAY_CLOSE (requiring full safety audits, pre-warning flashers, presence/camera validation, and travel watchdog).
    • In the event of a travel timeout or obstacle, the system latches into a FAULT state with zero automatic retries, requiring explicit on-site acknowledgment (person.state == home).
    • Does this balance between safety standards (EN 12453 / EN 12445 principles) and user experience feel right for Home Assistant?
  2. PR Scope vs. Incremental Splitting:

    • The PR currently spans ~5,000 lines across 35 files (including comprehensive tests and blueprints). While the modules are tightly integrated, we could split this into smaller, sequential PRs if preferred:
      1. Doorbell / Intercom Event Dispatcher (myhome_doorbell_event)
      2. WHO=6 Lock Platform (Door strikes)
      3. Impulse Cover Platform (type: impulse_relay) & AccessController
      4. Automation Blueprints & Documentation
    • Would you prefer reviewing this as a cohesive whole or splitting it up?
  3. Field Validation:

    • The implementation has been validated against real MH200 traces and simulated buses. Community testing on other gateway models (e.g. F454, MyHomeServer1) and varied motor/photocell setups would be welcome (@wave68runner if you have an active test bench, feedback is appreciated).

Companion protocol work is available in OpenWebNet-HA/OWNd#100. Looking forward to your thoughts and guidance!

@GreenGrassBlueOcean
GreenGrassBlueOcean marked this pull request as ready for review October 10, 2026 17:17
@xtimmy86x

Copy link
Copy Markdown
Contributor

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:

  • Keeping hardware communication, feedback, and device-specific command handling in the integration.
  • Providing the proposed approval and supervision workflow as an optional blueprint.
  • Letting users adapt that workflow to their installation while keeping the physical motor controller responsible for its hardware safety functions.

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?

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

@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

GreenGrassBlueOcean added a commit to GreenGrassBlueOcean/MyHOME that referenced this pull request Oct 13, 2026
…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>
GreenGrassBlueOcean added a commit to GreenGrassBlueOcean/MyHOME that referenced this pull request Oct 13, 2026
…n OWNd without switch_on_timed (OpenWebNet-HA#667)

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

Copy link
Copy Markdown
Contributor Author

@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 feat/impulse-cover-gate-garage, commits 9731fcc4 and 32f05f90) and rewrote the description:

  • An impulse cover without an access: block pulses the relay straight away. No user context, no phone, no PIN: a wall tablet, a button, a script or a voice assistant all work. A physical keypad on the bus never needed approval anyway, since its frame goes straight to the actuator.
  • The integration keeps what belongs to the hardware: pulse timing (WHAT 18 where OWNd has it, ON + OFF otherwise), the min_cycle_time deadband (now an error instead of a silent drop), and duplicate prevention. With a state_sensor a pulse that would only undo the request is skipped (open on an open door, close on a closed one). Without a sensor the cover stays assumed_state and pulses as asked.
  • The phone approval, PIN, presence, camera, pre-warning and safety-audit workflow is only active when a cover has an access: block. I would be happy to move it into a blueprint if you prefer that over a config block; I kept it as opt-in config for now because the supervision (watchdog, latched fault) needs to see the cover's own state.

Your three technical points:

  1. STOP rejected while monitoring: fixed. STOP is never withheld. With access: it cancels a pending request, or pulses a moving door to a stop at once, for anyone, without approval. If that pulse is dropped the watchdog keeps running.
  2. Latched fault lost on reload/restart: fixed. The fault is restored from the entity's last state and is cleared only by myhome.acknowledge_cover_fault from an allowed user who is at home.
  3. Software OFF could leave the relay energised: fixed. The OFF is retried (3 attempts); if it still fails, the next command releases the relay first and refuses to stack another ON on it, and removal delivers an owed OFF.

Also: pin_code without access: is now rejected by validation, since only the policy checks it.

Honest direction commands: with a sensor they are real; without one I agree an explicit "pulse" action is more honest than open/close. I have not added it yet; I would add it in the covers PR rather than grow this one.

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 OWNd==2.0.0b10 wheel (the software ON/OFF fallback path).

GreenGrassBlueOcean added a commit to GreenGrassBlueOcean/MyHOME that referenced this pull request Oct 13, 2026
…read-only properties in current OWNd (OpenWebNet-HA#667)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
GreenGrassBlueOcean added a commit to GreenGrassBlueOcean/MyHOME that referenced this pull request Oct 18, 2026
…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>
GreenGrassBlueOcean added a commit to GreenGrassBlueOcean/MyHOME that referenced this pull request Oct 18, 2026
…n OWNd without switch_on_timed (OpenWebNet-HA#667)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
GreenGrassBlueOcean added a commit to GreenGrassBlueOcean/MyHOME that referenced this pull request Oct 18, 2026
…read-only properties in current OWNd (OpenWebNet-HA#667)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
GreenGrassBlueOcean added a commit to GreenGrassBlueOcean/MyHOME that referenced this pull request Oct 18, 2026
@GreenGrassBlueOcean
GreenGrassBlueOcean force-pushed the feat/impulse-cover-gate-garage branch from 57c3975 to 4125a53 Compare October 18, 2026 11:32
GreenGrassBlueOcean added a commit to GreenGrassBlueOcean/MyHOME that referenced this pull request Oct 18, 2026
@GreenGrassBlueOcean
GreenGrassBlueOcean force-pushed the feat/impulse-cover-gate-garage branch from c5f7794 to e8e9ce0 Compare October 19, 2026 08:35
GreenGrassBlueOcean added a commit to GreenGrassBlueOcean/MyHOME that referenced this pull request Oct 19, 2026
…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>
GreenGrassBlueOcean added a commit to GreenGrassBlueOcean/MyHOME that referenced this pull request Oct 19, 2026
…n OWNd without switch_on_timed (OpenWebNet-HA#667)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
GreenGrassBlueOcean and others added 23 commits October 19, 2026 10:51
…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
…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
…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
- 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>
@GreenGrassBlueOcean
GreenGrassBlueOcean force-pushed the feat/impulse-cover-gate-garage branch from 11bd651 to ea95d41 Compare October 19, 2026 08:51
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

🔄 Rebase onto v2-phase1-architecture & Live F461 + BT-F411/4 Hardware Trace Verification

I have rebased this PR onto the latest v2-phase1-architecture (d743b548) and captured a new multi-session (cmd + evt) hardware trace on our physical BTicino F461 (firmware 2.0.11 / WHO=1013 134.15.5.0) + BT-F411/4 (WHERE="06", "07") + BT-F411/2 (WHERE="05") test plant.

The trace fixtures and standalone replay suite have also been opened in a separate PR (#755) against v2-phase1-architecture so the trace corpus can be merged independently.


1. 🛠️ What Changed in the Rebase & Update

  1. Rebased onto v2-phase1-architecture (d743b548):
  2. Standalone Trace Compatibility (tests/test_issue_667_intercom_trace.py & PR test(traces): add MH200 intercom/gate and live F461 + BT-F411/4 WHAT=18 impulse trace corpus (#667) #755):

2. 📡 What the New Live F461 + BT-F411/4 Trace Added & Proved (myhome_trace_F461_impulse_intercom_2026-10-19.json, PR #755)

We executed a 4-run live capture against the physical BTicino F461 (2.0.11) + BT-F411/4 (06, 07, assigned to Group #1) + BT-F411/2 (05, 5 s shutter) installation (34 evt frames, 161 chronological cmd/evt/result rows):

  1. WHO=1 WHAT=18 (0.5 s Hardware-Timed Momentary Pulse) on Physical BT-F411/4 (*1*18*06## & *1*18*07##):
    • F461 ACKs *1*18*06## in 0.090 s (0.093 s on 07).
    • On the evt monitor session, the physical BT-F411/4 relay closes and emits *1*1*06## (t = 4.315 s) + Group #1 aggregate *#1*#1*15*1## (t = 4.388 s), and 0.539 s later (t = 4.854 s; 0.511 s on 07) autonomously opens in hardware and broadcasts *1*0*06## + *#1*#1*15*0## (t = 4.927 s) with zero software OFF command required.
    • Subsequent point status polls (*#1*06## and *#1*07##) return ['*1*0*06##'] and ['*1*0*07##'], proving that OWNLightingCommand.switch_on_timed(where, 18) is natively supported by physical BT-F411/4 actuators and eliminates the risk of a stuck-energized relay if TCP drops mid-pulse.
  2. Software 0.5 s Impulse Cover Step-by-Step Cycle (OPEN $\rightarrow$ Mid-Flight STOP $\rightarrow$ CLOSE on WHERE="06"):
    • Exercised the 3-step MyHOMEImpulseCover software fallback pulse sequence (*1*1*06## $\rightarrow$ 0.5 s $\rightarrow$ *1*0*06##) across OPEN, mid-flight STOP, and CLOSE.
    • Every command ACKed in 0.086–0.105 s, and replaying the captured evt stream (['*1*1*06##', '*#1*#1*15*1##'] followed by ['*1*0*06##', '*#1*#1*15*0##']) through MyHOMEImpulseCover verified that the cover transitions cleanly through opening $\rightarrow$ stopped $\rightarrow$ closing while ignoring *1*0*06## relay-release frames.
  3. Unpopulated Relay Address (WHERE="71") Bus Timeout (2.065 s NACK):
    • Sending *1*18*71## or *1*1*71## to an unpopulated relay address causes bt_fls on F461 to wait 2.065 s / 2.071 s on the 2-wire SCS bus before returning *#*0## (NACK) with events_after = [], empirically confirming why MyHOMEImpulseCover only starts min_cycle_time after a confirmed write.
  4. WHO=6 Door Entry & WHO=8 Video Door Entry Fast NACK (~55 ms) on Automation-Only F461:
    • All 9 WHO=6 lock/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 6 WHO=8 call/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 ms NACK (*#*0##) with zero evt frames on F461 (automation-only DIN gateway without a VDE riser), matching our own-firmware-oracle verdicts for F461 (020011).

GreenGrassBlueOcean added a commit that referenced this pull request Oct 19, 2026
…-live-trace

test(traces): add MH200 intercom/gate and live F461 + BT-F411/4 WHAT=18 impulse trace corpus (#667)
@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit 11e8de5 into OpenWebNet-HA:v2-phase1-architecture Oct 19, 2026
23 checks passed
GreenGrassBlueOcean added a commit that referenced this pull request Oct 21, 2026
- 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>
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.

4 participants