Skip to content

feat(door_entry): support WHO 6 door entry and WHO 8 lock/intercom commands and events - #100

Merged
GreenGrassBlueOcean merged 8 commits into
OpenWebNet-HA:masterfrom
GreenGrassBlueOcean:feat/door-entry-who6
Oct 11, 2026
Merged

GreenGrassBlueOcean merged 8 commits into
OpenWebNet-HA:masterfrom
GreenGrassBlueOcean:feat/door-entry-who6

Conversation

@GreenGrassBlueOcean

@GreenGrassBlueOcean GreenGrassBlueOcean commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

WHO 6 (Door Entry) & WHO 8 (Video Door Entry & Intercom) command builders and event decoders

Adds parser registrations, typed command builders and event decoders for WHO 6 (Basic Video Door Entry) and WHO 8 (Video Door Entry & Intercom, including lock actuators).

Companion Home Assistant integration PR: OpenWebNet-HA/MyHOME#667.

Evidence level, read first. Only three frames in this PR come from a real capture (*8*1#1#4*74##, *8*9#1#4*73##, *6*9##, MH200, MyHOME#667). The other WHO 6 forms follow the L4686SDK command table; the other WHO 8 forms follow BTicino's own client, libqtdevices videodoorentry_device. They parse and build correctly, but no hardware has been seen sending them. See section 4.


1. What is the problem?

  1. No parser registrations for WHO 6 and WHO 8. OWNMessage.parse / OWNEvent.parse returned a generic OWNEvent for door entry and intercom frames, and OWNCommand.parse returned None for WHO 8. Consumers had no typed classes, readable logs or state properties.
  2. The camera OFF frame *6*9## was rejected. The L4686SDK form has no WHERE field, and the base regexes required one.
  3. Stale fallback. OWNCommand.parse had a legacy if _who in (6, 7, 9): branch; 6 now has a registered parser.

2. How is it solved?

  1. Short-form frame in OWNd/message/base.py

    • _DOOR_ENTRY_SHORT accepts only WHAT 9 without WHERE (*6*9##, and the field variant *6*9*##). *6*9**## keeps where="*" via the normal status regex. Any other short WHO 6 frame (*6*3##, *6*42##) is not valid.
    • The stale (6, 7, 9) fallback is now (7, 9).
  2. OWNDoorEntryEvent / OWNDoorEntryCommand (WHO 6)

    • Properties: is_incoming_call (WHAT 6), is_broadcast_call (WHAT 6, WHERE 4100), is_chime (WHAT 20, unverified: not in the L4686SDK table, no capture), is_call (incoming or chime), is_lock_open (WHAT 10, or 22 in conversation), is_staircase_on / is_staircase_off (12 / 11), is_camera_on / is_camera_off (0 / 9).
    • Builders: open_lock(where) (riser 4001#2 supported), staircase_light_on/off(where), camera_on(where), camera_off().
  3. OWNIntercomEvent / OWNIntercomCommand (WHO 8), with OWNLockEvent / OWNLockCommand kept as aliases

    • Locks (WHAT 19/20) and staircase light (21/22).
    • Calls (WHAT 1): is_call is true for any call session. is_incoming_call is true only for entrance panel call kinds 1–4. is_internal_call for kind 6, is_pager for kind 14 (or callee 4). Plus call_kind, multimedia_type, caller, callee.
    • Sessions: answer (2), end / stop video (3), caller address (9, libqtdevices CALLER_ADDRESS), VCT init (37), rearm (40).
    • Cameras: switch (4), cycle (6), PTZ (59–62).
    • Amplifier mute (63/64), teleloop (76–79).
    • Query: status(where) → *#8*WHERE##.
  4. Profiles & registry

    • register_event_parser / register_command_parser for 6 and 8; constants WHO_DOOR_ENTRY = 6, WHO_LOCK = WHO_INTERCOM = 8; exports in OWNd/message/__init__.py.
    • WHO 6/8 are not in DEFAULT_SUPPORTED_WHO. Whether a gateway reports them depends on the plant having a door entry system and on the gateway firmware relaying it. Only the MH200 has a capture, so MH200Profile lists them (captured) and MH200NProfile by family; the docstrings say so. Other profiles are unchanged until someone captures them.

Frame mapping

Source: C = captured (MyHOME#667, MH200), S = L4686SDK table, L = libqtdevices, U = unverified.

Frame Src Decoded attribute / builder Meaning
*6*6*1## S is_incoming_call, is_call Call to handset 1
*6*6*4100## S is_broadcast_call, is_call Broadcast call
*6*20*1## U is_chime, is_call Chime at handset 1
*6*10*4001## S is_lock_open / open_lock(4001) Lock released at entrance panel 4001
*6*22*4001## S is_lock_open Lock released during conversation
*6*12*1## / *6*11*1## S is_staircase_on/off / staircase_light_on/off(1) Staircase light
*6*0*4001## S is_camera_on / camera_on(4001) Camera ON
*6*9## C, S is_camera_off / camera_off() Camera OFF / session end
*8*1#1#4*74## C is_call, is_incoming_call, call_kind=1, multimedia_type=4 Entrance panel 1 audio/video call to handset 74
*8*1#6#2#11*16## L is_call, is_internal_call, caller="11", callee="16" Internal call 11 → 16 (not a doorbell ring)
*8*1#14#2#11*4## L is_call, is_pager Pager broadcast (not a doorbell ring)
*8*2#6#2*11## L session_action="answer" / answer_call(...) Call answered
*8*3#6#2*411## / *8*3#1#3*11## L session_action="end" / "stop_video" Call ended / video stopped
*8*9#1#4*73## C, L session_action="caller_notification", caller="73" Caller address (meaning beyond libqtdevices' name not established)
*8*4#11*20## / *8*6#11*20## L switch_camera / cycle_camera Camera switch / cycle
*8*19*20## / *8*20*20## L is_unlocked / is_locked / unlock, lock Lock actuator
*8*21*11## / *8*22*0## L is_staircase_on/off Staircase light
*8*59#1*20## L ptz_direction="up", ptz_action="press" PTZ
*8*63*11## / *8*64*11## L is_muted / mute_amplifier, unmute_amplifier Amplifier mute
*#8*11## L status("11") Status request

3. How is it tested?

  • tests/test_door_entry.py: every WHO 6 and WHO 8 decoder and builder, the three camera-off spellings, rejection of other short WHO 6 frames, call classification (entrance panel vs internal vs pager), the OWNLock* aliases, and registry dispatch through OWNMessage.parse / OWNCommand.parse.
  • tests/test_profiles.py: MH200/MH200N list WHO 6/8; DEFAULT_SUPPORTED_WHO, MH201, F452, F454 and MyHomeServer1 do not.
  • tests/test_firmware_oracle_conformance.py: all oracle input frames parse into specialised classes. This shows the parser accepts them, not that hardware sends them.
  • Full suite: 1311 passed, 1 skipped (master merged in). ruff, mypy and scripts/verify_library_standards.py clean.
  • Companion: MyHOME#667 full suite passes against this branch (3573 passed).

4. Where are the real traces?

  1. MyHOME#667, tests/fixtures/traces/issue_667/myhome_trace_MH200_intercom_bell_gate_2026-10-09.json: MH200, firmware 2.1.0, simple BTicino door entry setup. Addresses are remapped for privacy, so the frames are not verbatim. Door entry frames, in order:

    1. *8*1#1#4*74##: entrance panel 1 calls handset 74.
    2. *8*9#1#4*73##: caller address 73 (sent after the call; that it marks the answer is not shown).
    3. *6*9##: camera off, short form, when the call ends.

    There is no WHO 6 call/lock/staircase/camera-on frame and no WHO 8 lock/staircase/PTZ/mute frame in it.

  2. Firmware oracle (tests/golden/firmware_oracle.json): the WHO 8 rows are inputs of the intercom-ts10 suite sent down to emulated gateways; no firmware emitted a WHO 8 frame. They are parse coverage, not captures. The emulated firmwares NACK WHO 8 commands, but the MH200N target does not run the image's bt_vct translator, so those NACKs are not evidence about the real gateway.

  3. Reference sources

    • WHO 6: the L4686SDK command table (WHAT 0, 6, 9, 10, 11, 12, 18, 22).
    • WHO 8: libqtdevices videodoorentry_device (WHAT 1, 2, 3, 4, 6, 9, 19–22, 37, 40, 59–64, 76–79). There is no complete public WHO 8 specification.

Captures wanted: anyone with a door entry system on another gateway (MH201, F452, F454, MyHomeServer1, …), or a richer setup than mine (locks, staircase light, internal calls, pager), please attach a bus trace so the profiles and the unverified rows can be confirmed.

🤖 Generated with Claude Code

@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!

@GreenGrassBlueOcean
GreenGrassBlueOcean marked this pull request as ready for review October 9, 2026 15:00
…ercom dialects

- Register WHO 8 event and command parsers (OWNLockEvent / OWNLockCommand)
- Expose public is_camera_on and is_camera_off properties on OWNDoorEntryEvent
- Expand WHO 8 support with full TS10/oracle and trace dialects from draft PR OpenWebNet-HA#85 (calls, session actions, cameras, PTZ, amplifier mute, teleloops, and command builders)
- Add OWNIntercomEvent and OWNIntercomCommand aliases and export constants in OWNd.message
- Clean up stale WHO 6 from OWNCommand.parse fallback dispatch
- Add WHO_DOOR_ENTRY and WHO_LOCK to MH200 and MH200N supported profiles
- Add comprehensive test coverage asserting WHO 8 registry parsing and 100% statement/branch coverage
@GreenGrassBlueOcean GreenGrassBlueOcean changed the title feat(door_entry): support WHO 6 door entry and WHO 8 lock commands and events feat(door_entry): support WHO 6 door entry and WHO 8 lock/intercom commands and events Oct 9, 2026
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

🔍 Protocol, Grammar & Architecture Review: Recommendations & Required Fixes

Thank you for adding typed message parsing and builders for WHO 6 and WHO 8! The unit test coverage and parsing abstractions are thorough.

Before merging and tagging the release for downstream integrations (like MyHOME), there are several protocol conformance and scoping items that should be corrected:


1. ⚠️ Regex Over-Matching on _DOOR_ENTRY_SHORT (base.py:71–73)

_DOOR_ENTRY_SHORT = re.compile(
    r"^\*6\*(?P<what>\d+)##$"
)
  • Issue: This regex is overly permissive. In OpenWebNet, standard command frames require *WHO*WHAT*WHERE##.
  • Impact: The only exception documented in the Legrand L4686SDK specification for WHO 6 without a WHERE parameter is Camera OFF: *6*9##. Under this broad pattern, non-existent or arbitrary frames like *6*3## or *6*42## parse as valid door entry events.
  • Remediation: Restrict the short pattern strictly to WHAT=9:
    _DOOR_ENTRY_SHORT = re.compile(r"^\*6\*9##$")

2. 🌐 Global Profile Leakage in DEFAULT_SUPPORTED_WHO (profiles.py:28–29)

DEFAULT_SUPPORTED_WHO = (
    ...
    WHO_DOOR_ENTRY,
    WHO_LOCK,
    ...
)
  • Issue: The PR description states:

    "Added WHO_DOOR_ENTRY and WHO_LOCK to MH200Profile and MH200NProfile."
    However, adding them to DEFAULT_SUPPORTED_WHO enables WHO 6 and WHO 8 across almost all gateway profiles in the library. Many standard gateways (such as lighting/automation-only gateways like F452 or basic interfaces) do not relay or support intercom subsystems.

  • Remediation: Keep DEFAULT_SUPPORTED_WHO restricted to core automation/lighting families, and add WHO_DOOR_ENTRY and WHO_LOCK specifically to MH200Profile and MH200NProfile.

3. 📞 Granular Call Classification in WHO 8 (door_entry.py:226–227)

self._is_call: bool = what == 1
self._is_incoming_call: bool = what == 1
  • Issue: In WHO 8, WHAT=1 represents any call session. The first parameter (params[0]) defines the call kind:
    • 1..4: Entrance panels (PE1–PE4)
    • 6: Internal call between handsets
    • 14: Pager broadcast
  • Impact: Setting is_incoming_call = True unconditionally for all what == 1 causes internal handset-to-handset calls and pager messages to be misclassified by downstream consumers (like Home Assistant) as incoming doorbell rings.
  • Remediation: Expose clear granular properties:
    self._is_incoming_call: bool = what == 1 and self._call_kind in (1, 2, 3, 4)
    self._is_internal_call: bool = what == 1 and self._call_kind == 6
    self._is_pager: bool = what == 1 and self._call_kind == 14

4. 🚫 Invented / Non-Standard Frame *#8*WHERE*19## (door_entry.py:707–712)

@classmethod
def lock_status(cls, where: str | int) -> OWNLockCommand:
    """Request door lock status."""
    target = str(where).strip()
    message = cls(f"*#8*{target}*19##")
    ...
    return message
  • Issue: In OpenWebNet syntax, *#WHO*WHERE*DIMENSION## represents a DIMENSION_REQUEST. There is no documented Dimension 19 for WHO 8 in official Legrand/BTicino specifications, nor is it supported in the firmware oracle. Standard status inquiries in OpenWebNet use *#8*WHERE##.
  • Remediation: Remove lock_status or document the specific firmware/hardware source where *#8*WHERE*19## was observed.

5. 📖 Specification Note on WHAT 20 (Chime)

self._is_chime: bool = what == 20
  • The L4686SDK specification for WHO 6 defines WHAT codes 0, 9, 10, 11, 12, 18, 6, 22. WHAT=20 is not documented in the official WHO 6 specification. If this was captured on a specific hardware device, please document the firmware version and hardware module in the docstrings.

6. 🏷️ Class Naming & Canonical Identity

  • WHO 8 is officially designated as Video Door Entry & Intercom/Telephony. Locks represent only one minor operation (WHAT=19/20), while the vast majority of the module handles call routing, answering, video termination, PTZ camera controls, and amplifier muting.
  • Naming the main class OWNLockEvent and aliasing OWNIntercomEvent = OWNLockEvent is inverted.
  • Suggestion: Make OWNIntercomEvent and OWNIntercomCommand the primary implementations, with OWNLockEvent and OWNLockCommand preserved as aliases for backwards compatibility.

@fedem95 fedem95 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked on 5013ae4. Three of the points in your own review are still open in the code:

  • _DOOR_ENTRY_SHORT (^\*6\*(?P<what>\d+)\*?##$) accepts any WHAT: *6*3## and *6*42## parse as valid OWNDoorEntryEvents.
  • WHO 6 and 8 are in DEFAULT_SUPPORTED_WHO, so every profile now advertises them (MH201, F452, F454, MyHomeServer1, Generic), while the description mentions adding them to MH200/MH200N. In MyHOME this doesn't change the discovery sweep (1, 2, 4, 16), but it does feed standby routing, shared-bus delegation and diagnostics.
  • OWNLockEvent.is_incoming_call is True for any WHAT 1, including the internal call *8*1#6#2#11*16## and the pager *8*1#14#2#11*4##. MyHOME#667 fires myhome_doorbell_event whenever is_call is True.

On the evidence:

  • The #667 trace README says all WHERE values were "deterministically mapped to fictitious mock addresses", so the frames aren't verbatim. Its only door-entry frames are *8*1#1#4*74##, *8*9#1#4*73## and *6*9##; there is no WHO 6 call/lock/staircase/camera-on frame and no WHO 8 lock/staircase/PTZ/mute frame.
  • The 59 WHO 8 inputs in firmware_oracle.json are all down rows of the intercom-ts10 suite; no WHO 8 frame was emitted by any firmware. "59 authentic on-wire frames" doesn't describe them.
  • WHO_6.pdf / WHO_8.pdf return 404 on developer.legrand.com and aren't in sxpert/myhomepy lgg-docs. Which documents are cited?
  • libqtdevices videodoorentry_device does back the WHO 8 codes (1, 2, 3, 4, 6, 9, 19–22, 37, 40, 59–64, 76–79). It names 9 CALLER_ADDRESS; the description's sequence calls *8*9#1#4*73## "Handset answers". libqtdevices has no WHO 6 device.

Tests: the branch is 4 commits behind master. As is, MyHOME v2-phase1-architecture (0433228) gets 12 sound-source failures (no OWNSoundCommand.select_track from #95). Merged locally with master: OWNd 1302 passed (test_metadata excluded for my env), MyHOME 3497 passed, 1 skipped.

GreenGrassBlueOcean and others added 2 commits October 9, 2026 22:24
…, call kinds, naming)

- Restrict the WHO 6 short form to WHAT 9 (*6*9## and *6*9*##); *6*3## and
  *6*42## no longer parse as door entry events.
- Remove WHO 6/8 from DEFAULT_SUPPORTED_WHO. Only the MH200 has a capture
  (MyHOME#667), so MH200 and MH200N list them and the docstrings say why.
- WHO 8 WHAT 1: is_incoming_call is true only for entrance panel kinds 1..4;
  add is_internal_call (kind 6). Pager and internal calls are no longer
  reported as doorbell rings. is_call still means any call session.
- Drop lock_status (*OpenWebNet-HA#8*WHERE*19##): no documented dimension 19 for WHO 8 and
  no firmware verdict.
- Make OWNIntercomEvent/OWNIntercomCommand the primary classes, keep
  OWNLockEvent/OWNLockCommand as aliases.
- Note WHAT 20 (chime) is not in the published WHO 6 table and has no capture.

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

Copy link
Copy Markdown
Contributor Author

Thanks @fedem95, this was worth doing, and my own follow-up was sloppy: I flagged the right problems but didn't re-check them against the code or the evidence before you did. Fixes are in 5532cf5 (master merged in, so select_track is there).

Code

  • _DOOR_ENTRY_SHORT only accepts WHAT 9 (*6*9##, *6*9*##). *6*3## / *6*42## no longer parse.
  • WHO 8 WHAT 1: is_incoming_call is true only for entrance-panel call kinds 1–4. New is_internal_call (kind 6). Pager (kind 14) is is_pager only. is_call still means any call session.
  • MyHOME#667 fired myhome_doorbell_event on is_call, so internal and pager calls rang the doorbell. It now rings on is_incoming_call or is_chime (GreenGrassBlueOcean/MyHOME@593b96b), with a regression test for both.
  • Removed lock_status (*#8*WHERE*19##): no documented dimension 19 for WHO 8.
  • OWNIntercomEvent/OWNIntercomCommand are the primary classes, OWNLock* stay as aliases.
  • WHAT 20 (chime) is documented as unverified: it's not in the L4686SDK WHO 6 table and has no capture here.

Profiles
WHO 6/8 are out of DEFAULT_SUPPORTED_WHO. My earlier review suggested "MH200/MH200N only" as if it were a family fact, which is more than the evidence says. Whether a gateway reports door entry depends on two things: the plant having a door entry system, and the gateway firmware relaying it (firmware does filter, e.g. Legrand's WHO 25 spec lists the MH200 as not supporting WHO 25). The evidence:

  • A live MH200 (fw 2.1.0) relayed WHO 6/8 events (#667).
  • The oracle's intercom-ts10 WHO 8 commands were NACKed by all 10 emulated firmwares. That covers commands sent down, not events relayed up. For the MH200N it isn't evidence either way: the 1.1.8 image ships a bt_vct translator that the oracle target doesn't run (same harness gap as the MH200N WHO 4/18 NACKs).
  • Nothing for other models.

So the MH200 lists them (captured), the MH200N by family, both with the evidence in the docstring. If someone with another gateway and a door entry system can capture it, we update that profile. As a general rule for OWNd, I'd suggest supported_who only lists what a capture or a published spec supports, and the docstring says which.

Description corrected

  • I only have an MH200 and a simple BTicino door entry setup, so the trace evidence is just #667: three frames (*8*1#1#4*74##, *8*9#1#4*73##, *6*9##), with addresses remapped, so not verbatim. No WHO 6 call/lock/staircase/camera-on and no WHO 8 lock/staircase/PTZ/mute frames.
  • The WHO 8 rows in the oracle are inputs of the intercom-ts10 suite, not frames a firmware emitted. "59 authentic on-wire frames" was wrong.
  • Sources: WHO 6 follows the L4686SDK command table, WHO 8 follows libqtdevices videodoorentry_device. The WHO_6.pdf/WHO_8.pdf links 404 and are gone. The frame table now marks each row as captured, spec, libqtdevices or unverified.
  • Agreed on WHAT 9: libqtdevices calls it CALLER_ADDRESS. In the trace it comes after the call and before *6*9##, but I can't show it means "handset answers", so the sequence is reworded.

Happy to split the PR if you'd rather merge only the trace-backed part now.

@fedem95 fedem95 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the earlier findings. The parser changes and call classification look good, and CI is green. Approved.
Note: MH200NProfile advertises WHO 6/8 based on the MH200 family, while the docstring correctly notes that this has not been captured on an MH200N. This is transparent and non-blocking here, but should be treated as an inference until verified on MH200N hardware.

@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit f726258 into OpenWebNet-HA:master Oct 11, 2026
8 checks passed
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.

3 participants