|
Home IO Control
ESPHome add-on for IO-Homecontrol devices
|
Status: Accepted · Recorded: 2026-09
ADR 0031 made the 1W control frames (CMD_EXECUTE) vendor-correct via manufacturer:. The enrollment frames (CMD_ONEWAY_ADD_CONTROLLER 0x30 / CMD_ONEWAY_REMOVE 0x39) were still shaped entirely from Somfy: send_enrollment() sent one 0x39 then one 0x30, both to the identity's own io_device_type.
A real VELUX KLI 310/311/312/313 registration gesture does something structurally different. Reconstructed from two independent sources — the issue #74 capture of a real KLI 310 gesture (decoded with this project's own broadcast_target_type()) and samr037/iohc-flipper's tx_runner.c (send_pair_with_identity, built from real KLI 313 "rings" captures), cross-checked against the KLI manual:
The receiver side is the Gear button ("open for registration"), pressed for about 1 second on an already-registered VELUX control. In the KLI manual's add-a-control procedure, Gear on the existing control is followed by the Pair button ("register") on the new control, which is the role this gesture plays: the frames above are what a KLI's Pair press sends. (Gear then Pair on the same switch deletes all its products instead.) The existing control's Gear press sends a 0x2E burst to a set of device classes, and the product runs a "ready" sequence. On a shutter this takes around half a minute: it travels to about 10% closed, jogs several times, then returns to its starting position. In both confirmed enrollments (below), sweeping exactly those classes after the ready sequence registered the hub. How long the product stays open for registration after the sequence is not measured. The manual's "STOP then DOWN within 3 seconds" wording belongs to the from-scratch registration flow that starts with P on the product itself, not to this add-a-control flow.
The 0x39 prelude itself is not vendor-specific — a real Somfy Smoove capture (somfy_smoove_enrollment_add_and_remove_controller_sx1276.yaml) and the KLI 310 both send it, and iohc-flipper sends it for both vendors, so it is not opt-in.
OneWayWireProfile (oneway_controller.h, from ADR 0031) gains:
resolve_oneway_wire_profile() maps manufacturer velux → VELUX_KLI + that list; somfy / unset / any unprofiled vendor → SOMFY + empty. The exterior-shading set is the profile default because it is the one captured from a real KLI gesture. A per-identity enrollment_classes: YAML key overrides it (via effective_enrollment_classes()). That is how an interior-blind identity gets [blind, venetian_blind]. The classes to set are the ones the existing remote's 0x2E burst targets after a Gear press, visible in the DEBUG rx 1W remote … targets … log lines (which keep one line per destination for intent-less frames).
send_enrollment() dispatches on enroll_gesture:
The schema warns when a manufacturer: velux + enrollment: true identity has an io_device_type of screen / blind / venetian_blind and no enrollment_classes: override. The default sweep ignores that io_device_type and targets the exterior-shading classes, which such a device is unlikely to listen on. The warning names the KLI 312 set and the 0x2E lines as the fix.
The VELUX gesture is 6 bursts (0x39 + a 3-class 0x30 sweep + STOP + DOWN), and it blocks loop() for the whole gesture, feeding the watchdog in the gaps. How long depends on the identity's low_power: power class (ADR 0038):
The exemption for the long shapes is taken knowingly: 1W enrollment is a user-initiated, once-per-device action, the same shape as the pairing button, whose pairing_discovery_wait_ms already goes to 5000. Gesture duration is an airtime concern, not an established cause of a failed enrollment, given the long window after Gear.