Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
Pairing

Getting a device onto your hub. There are two routes, and picking the right one first saves more time than anything else on this page.

Which route should I use?

flowchart TD
    A[Do you have a working<br/>two-way IO-Homecontrol hub today?<br/>TaHoma, KLF200, KLR200,<br/>Connexoon, KIG300… — not just<br/>a 1W wall switch or remote] -->|Yes| B[Use key extraction]
    A -->|No| C[Has a two-way hub ever<br/>controlled the device?]
    C -->|No — it is new, or only ever<br/>driven by 1W remotes<br/>or wall switches| D[Use Discover & Pair]
    C -->|Yes, a hub I<br/>no longer have| E[Factory reset it as its<br/>manual describes, then<br/>register its local remote<br/>again, if it has one]
    E --> D
    D --> F{Does it answer?}
    F -->|Yes| I[Copy the YAML snippet<br/>from the log]
    F -->|No| G[See The device is<br/>never found in<br/>Troubleshooting]
    B --> H[Then press Scan Paired Devices<br/>to list everything on the key]

Key extraction is the route whenever you already own a two-way hub — one that runs its own "add a device" wizard, such as a TaHoma, Connexoon, Connectivity Kit, KLF200, KLR200 or KIG300. It recovers the same node_id/system_key your devices already trust, so nothing leaves its paired state and every device on that installation appears at once. Most field reports of a device that "never responds to discovery" have been resolved this way. A 1W-only wall switch or remote (a VELUX KLI, a Somfy Smoove) does not run that wizard and has nothing key extraction can recover from it — see Supported devices for which controls qualify. See Key extraction.

Discover & Pair is the route for a device that is genuinely unclaimed: new, or factory reset, with no hub holding its key.

Discover & Pair

A flag in the home_io_control: block adds a Home Assistant button that starts discovery and pairing:

home_io_control:
  # ...
  discover_and_pair_button: true

Notes

  • Adds two entities: the "Discover & Pair" button (config entity category) and its companion "Last Pairing Result" diagnostic sensor, the machine-readable outcome of the most recent attempt.
  • Both live on the hub's own ESPHome device and, like every other hub-level entity (the arming switches, Scan Paired Devices, tuning numbers/selects), take no device_id: of their own.

The pairing workflow

  1. Choose a node_id (6 hex characters) and a system_key (32 hex characters) for the hub, and keep them stable across firmware updates.
  2. Flash a config with at least the home_io_control: hub and discover_and_pair_button: true.
  3. Put exactly one device into pairing mode. The gesture depends on the device; its page under Supported devices or its own manual has the details.
    • A device with a reachable PROG button of its own: usually a 2 second press of that button.
    • A device whose button is out of reach, such as a motor inside a tube or a pergola, or a receiver with no button: hold PROG on a remote already registered to that device for about 2 seconds, and let go at the first jog (or blink). Holding on longer can remove the remote. Straight after a power cut, it can factory reset the device.
    • If one remote drives several devices, power the others down first. A single PROG press puts every device registered to that remote into pairing mode at once.
    • Give each attempt one PROG press. On a remote that is already registered, a PROG press is also the add/remove step, so pressing it again while the device is still in pairing mode can close the window the first press opened.
  4. Press Discover & Pair in Home Assistant straight away. Some devices close their pairing window after a few seconds.
  5. Watch the ESPHome log. On success it prints a ready-to-paste YAML snippet with io_device_id, io_device_type and io_subtype; otherwise a follow-up message says why no snippet could be generated.
  6. Add those three values to the matching cover:, light:, lock: or switch: entry in your YAML. If the log printed a raw numeric type such as 0x11, keep that exact value. Don't add device entries with made-up IDs such as 000000 ahead of pairing. The hub polls every device in the YAML, so a placeholder only produces failed exchanges in the log.
  7. Reflash. The entity appears in Home Assistant and the hub starts polling the device for status.
  8. If the log says the type is unsupported or the discovery metadata was incomplete, follow its guidance and open a GitHub issue with the raw type/subtype, the device model and the pairing log.

Between the device answering discovery and the key exchange starting, the hub sends a discover-confirm frame directly to that device and waits up to a few seconds for its acknowledgement — the same handshake every real controller in this project's corpus performs before proceeding. It never blocks pairing on its own: a refusal, a timeout, or nothing at all still lets the attempt continue. If a specific device seems to dislike this step, pairing_discover_confirm: skip in the tuning: block reverts to sending nothing there at all — see Radio tuning.

Expect this to take a few goes. The hub retries discovery three times per press, and it is still common to need two or three presses because the timing between PROG and the button matters. Press PROG again and repeat before you change any settings.

If the device never answers, read The device is never found before touching Radio tuning. A device that already belongs to a hub has nothing left to answer a discovery with, and no tuning changes that; key extraction does.

Diagnosing a failed pairing attempt

discover_and_pair_button: true also creates a companion "Last Pairing Result" diagnostic text sensor alongside the button. It updates after every attempt with a frozen, machine-readable summary:

v1; outcome=paired; phase=complete; node=30E1F2; type=awning; attempts=1; lbt=0; dur_ms=842; heard=3; advice=none
Field Meaning
outcome paired, no_response, invalid_response, or key_exchange_failed.
phase The furthest stage the pairing state machine reached.
node / type The paired device's node ID and type, or - if nothing was paired.
attempts Number of discovery command retries sent.
lbt Listen-before-talk retries consumed across the whole attempt; a channel-busy indicator.
dur_ms Attempt duration in milliseconds.
heard Total RX events seen, including ones the pairing classifiers rejected and a 1W gesture overheard just before the window opened (see the advisor below).
advice Comma-separated advisor codes (see below), or none.

The format is stable and versioned (the v1; prefix), so an automation can alert when outcome is not paired.

The log also gets a full human-readable summary of every TX, RX, rejected RX, listen-before-talk deferral and phase event, plus a total channel-hop count. When the overheard traffic tells a story, one or more pairing advisor WARN lines turn it into a diagnosis. The advisor also considers the 15 seconds before the button press: a PROG press completed just ahead of it is 1W traffic, not RF silence, and is reported as 1w_traffic.

Advice code When it fires What it means
1w_traffic A 1W remote was seen performing 1W pairing (a broadcast to 00003F with a 1W pairing command byte). The broadcast does not identify a target, so this fires on any 1W pairing gesture in range. A remote's PROG gesture was heard, and no device answered. That shows a gesture happened, not what state the device is in: the same frames have come just before a successful pairing, too. Check in order. 1. If a two-way hub already controls the device (a TaHoma, KLF200, KLR200, KIG300, and similar; not a 1W wall switch or remote), it is almost certainly paired to that hub: use key extraction. 2. Was the PROG press on a remote already registered to this device, released at the first jog? A longer hold can remove the remote, or reset the device straight after a power cut. On such a remote a PROG press is also the add/remove step, so give each attempt one press: a second press while the device is still in pairing mode can close the window the first press opened. 3. Was this the only device in pairing mode? One remote often drives several devices. 4. A Double Power Cut or factory reset is not a pairing gesture on its own. Afterwards a device with a local remote expects that remote to be registered again, as its manual describes, so do that first, then repeat 2. Some families document a separate gesture: a VELUX SSL has a physical P button that opens a 10-minute 2W registration window (see VELUX INTEGRA).
channel_busy Listen-before-talk retries were exhausted and the same source was heard repeatedly during the wait. A repeating beacon (usually a nearby remote or sensor) is flooding the channel and delaying discovery transmissions. Try again, or tune lbt_max_retries/lbt_rssi_threshold_dbm — see Radio tuning.
foreign_controller A discovery response (0x29) was seen addressed to a node ID that is not this hub's. Another controller (a TaHoma, say) is pairing the same device right now. Wait for it to finish, or make sure yours is the only controller with the device in pairing mode.
rf_silent Nothing at all was heard on any channel during the whole discovery window. Either the radio is not receiving (antenna, wiring, wrong tuning), or the channel was simply quiet for those few seconds. Look at the rest of the log first. If it shows other IO-Homecontrol frames being received (your remotes, a neighbour's devices), the radio works, and this reads as "the device did not answer". Only if nothing is ever received, check the antenna and radio tuning before pressing PROG again.

Scan Paired Devices

Once you hold a system key — recovered from another hub, or established by pairing — a roll-call lists every device that trusts it, whether or not you have a YAML entity for it. Each device you have no entity for answers with a ready-to-paste snippet, and no pairing handshake is involved. This is the fastest way to bring up an installation after key extraction.

Enable it from the hub block, not as a button: entry:

home_io_control:
  # ... radio pins, node_id, system_key ...
  scan_paired_devices_button: true

The button and the scan_paired_devices action run the same roll-call. The action is always available; the button saves a trip to Developer Tools.

The scan_paired_devices action

It takes no fields. Like the other actions it is named esphome.<node_name>_scan_paired_devices; for a config with name: hioc-heltec-v2:

action: esphome.hioc_heltec_v2_scan_paired_devices

The hub broadcasts a roll-call (CMD_DISCOVER_SPE_REQ, 0x2A) and listens for every device that holds its system key. Responders are grouped into a Known: section (already in your YAML) and an Unknown: section, and each unknown one gets a paste-ready block. Either section is omitted when it would be empty.

Roll-call: 2 devices detected (1 known, 1 unknown)
Known:
30E1F2: horizontal_awning subtype=0 rssi=-52dBm manufacturer=Somfy turnaround=40s power_save=always_alive [known]
Unknown:
415CE4: light subtype=0 rssi=-61dBm manufacturer=Somfy turnaround=40s power_save=always_alive [unknown]
Paste this into your YAML to register it:
light:
- platform: home_io_control
name: "My Device"
io_device_id: "415CE4"
io_device_type: "light"
io_subtype: 0

What to expect

  • A scan takes about 13 seconds and blocks the ESPHome loop for that long. Sleeping solar/battery devices need a long wake-up call that a mains-powered device ignores, so the hub calls each power class in turn on all three radio channels: a low-power pass first (a long wake-up preamble), then the always-alive pass (a short preamble) — six broadcasts total, each with a full pairing_discovery_wait_ms listen window. The "operation took a long time" warning ESPHome logs on every run is expected.
  • An installation with no solar or battery devices can skip the low-power pass. Set `scan_power_classes: always_alive` to restore the original ~6 second, single-pass scan. The same tunable also isolates one class when bisecting a mixed installation in the field.
  • A device can still be missed. Press again if one you expect is absent. A scan has no side effects, so repeating it costs nothing.
  • Hearing nothing is a valid result. The result event's success is true whenever the broadcast went out. Its device_id is always empty, because there is no single target, and the full report above is the event's message.
  • At most 24 devices are listed per scan. If more answer, the report says so with a NOTE: more than 24 devices answered; the list below is truncated. line rather than silently showing a subset.
  • An unknown responder is almost never an intruder. It usually means a device you paired earlier whose YAML entry was never saved. The reply proves only that the device once received your system key.
  • A known device can get a hint: line when its self-reported power-save class disagrees with its registered low_power: YAML property — for example, a device that answers the low-power pass but has no low_power: true in its YAML. Follow the hint's suggestion; it only ever proposes a YAML edit, never changes the registry itself.
  • It cannot pair a new device. A device in learning mode holds no key yet and stays silent to a roll-call. Use Discover & Pair for that, and the scan to check on devices you already have. `pairing_discovery_commands` explains why 0x2A is deliberately not a discovery option.
  • A scan puts roughly 0.7 seconds of traffic on air, dominated by the low-power pass's long wake-up preambles. It is a manually triggered action — do not call it from a tight automation loop.

Notes on the Scan Paired Devices button

  • The button defaults to the config entity category.
  • There is no companion result sensor. Output goes to the log and to the esphome.home_io_control_action_result event, exactly like the action.
  • The button shows as pending in Home Assistant for the duration of the scan.
  • A press is ignored if a radio exchange is already in flight. That can only happen from an ESPHome automation pressing the button inside another entity's callback, never from a tap in the Home Assistant UI. The result event still fires with success: false, so an automation sees the rejection.

See also