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

From a board in your hand to a blind moving in Home Assistant. Allow about an hour the first time, most of which is flashing and waiting.

Before you start

You need three things:

  • A supported board. An ESP32 with an SX1276, SX1262 or LR1121 radio at 868 MHz. If you have not bought one yet see Hardware for some recommendations.
  • A working ESPHome setup, with Home Assistant already adopting ESPHome devices. If ESPHome is new to you, follow their getting-started guide first and come back once you can flash a blank node.
  • An IO-Homecontrol device within radio range, and either a way to put it into pairing mode or an existing hub that already controls it. Which one you have decides your route in step 3.

A short antenna and a device two rooms away is usually fine. The protocol is not fussy about signal, and Link Health will tell you later if it is.

1. Make up a node ID and a system key

Your hub needs two identifiers before it will boot. You invent both. There is nothing to look up and nobody issues them to you.

  • node_id — this hub's own address on the radio. Exactly 6 hex characters.
  • system_key — the shared secret that every device on your installation is paired against. Exactly 32 hex characters.

Hex is short for hexadecimal, and it means the only characters allowed are the digits 0–9 and the letters A–F. So C0FFEE is a valid node ID; G0FFEE is not, because G is not one of them. Upper and lower case both work.

Two things worth knowing before you pick:

  • The system key is a credential, not a serial number. Anyone who has it can drive your devices. Keep it out of public issue reports and put it in ESPHome's secrets.yaml rather than in the config you might paste somewhere.
  • Keep both stable. Changing either one after you have paired devices means pairing all of them again.

2. Flash the component

This config gives you a working radio and the entities you need to find your devices. It has no cover: or light: entries yet: your devices' addresses come in step 3.

The pin block is for a Heltec WiFi LoRa 32 (V3). For any other board, swap in its pins from Hardware.

esphome:
  name: io-homecontrol

esp32:
  variant: esp32s3
  framework:
    type: esp-idf

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

logger:
  level: DEBUG

api:

external_components:
  - source: github://laberning/home_io_control

spi:
  clk_pin: 9
  mosi_pin: 10
  miso_pin: 11

home_io_control:
  cs_pin: 8
  rst_pin: 12
  dio1_pin: 14
  busy_pin: 13
  radio_type: sx1262
  tcxo_voltage: 1_8V

  node_id: "A1B2C3"                 # yours, from step 1
  system_key: !secret io_system_key # yours, from step 1

  # Onboarding helpers for 2W devices (Routes A and B in step 3). A 1W-only device
  # (Route C) needs none of them. Remove them once set up -- see "Tidying up".
  discover_and_pair_button: true    # "Discover & Pair" button, for Route A, plus its
                                    # "Last Pairing Result" sensor
  accept_foreign_pairing: true      # "Recover System Key" switch, for Route B
  scan_paired_devices_button: true  # "Scan Paired Devices" button, lists devices that
                                    # already trust your key

logger: at DEBUG matters here: every route below reads its result out of the log.

Flash it and check two things in the log:

  1. The radio starts. A boot block names your chip and its version. If it is missing or reports errors, the SPI, reset or BUSY pins are wrong.
  2. The radio receives. Press any button on an IO-Homecontrol remote near the board. Each press logs a few lines starting with rx 1W remote, even with no devices configured. If the boot block is clean but presses log nothing, check the interrupt pin (dio0_pin on SX1276, dio1_pin on the others) and the antenna: the hub only notices a received packet through that pin.

Don't go on to step 3 until both work.

3. Get your devices onto the hub

First, 1W and 2W

IO-Homecontrol devices speak two dialects:

  • 1W (one-way) is what an ordinary wall remote uses. It transmits and nothing answers back, so the remote never learns whether the device actually moved.
  • 2W (two-way) is what a hub uses. Commands are authenticated with the system key, and the device replies with its real position and state.

This component speaks both. 2W gives you position feedback in Home Assistant, and it is what pairing sets up. Two questions pick your route:

  1. Does the device speak 2W at all? Most do. Some only take 1W commands, for example the VELUX SML roller shutter. Supported devices marks them 1W. Those take Route C.
  2. Is a 2W device already paired to a hub? A paired device holds that hub's system key and no longer answers discovery, so it takes Route B. Wall remotes don't claim a device this way; a shutter driven by three wall remotes but never by a hub takes Route A.

Route A — no hub has claimed it

For a device that is new, factory reset, or has only ever been driven by wall remotes.

  1. Put the device into pairing mode, usually a 2 second press of the device's own PROG button. If that button is out of reach, hold PROG on a remote already registered to the device for about 2 seconds, and let go at the first jog. Keep only this one device in pairing mode: power down anything else the same remote drives. A Double Power Cut is a reset step, not a pairing gesture. After one, register the device's local remote again, if it has one, as its manual describes, then use the remote gesture.
  2. Press Discover & Pair in Home Assistant within a few seconds of that.
  3. Read the log. On success it prints a ready-to-paste block with the device's io_device_id, io_device_type and io_subtype.

Expect this to take a few goes. The pairing window is only a few seconds wide. The hub makes three attempts per press, and two or three presses are still common. Repeat the whole sequence before you change any settings, and give each attempt one PROG press: pressing PROG again while the device is still in pairing mode can close the window the first press opened. Pair one device at a time.

Pairing has the full walkthrough and a decision tree.

Route B — a hub already controls it

For a device controlled today by a Somfy TaHoma, Connexoon or Connectivity Kit, a VELUX KLF200, KLR200 or KIG300, or similar.

Discover & Pair cannot reach these devices at all, however carefully you time it. You have two ways forward.

B1 — take the key from your existing hub

Try this first: nothing gets reset, and every device stays paired exactly as it is.

  1. Turn on the Recover System Key switch in Home Assistant. The hub arms for 10 minutes.
  2. Put your existing hub into its own "add a device" mode — the same wizard you would use to add a new shutter to it. Your hub pairs to this component as though it were a new device.
  3. The log prints your installation's real node_id and system_key.
  4. Paste both into your YAML, replacing the values you made up in step 1, and reflash.
  5. Press Scan Paired Devices. Every device that trusts that key answers with its own ready-to-paste block.

If a device you expected is missing from the scan, press the button again. Paired devices duty-cycle across the three radio channels on their own schedule, so the hub sweeps all three, once per power class, on every scan — and one can still be missed. A repeat press has no side effects.

If a device never turns up, you can still write its entity by hand. All you need is its io_device_id. Turn on frame logging:

esphome:
  build_flags:
    - -DIOHOME_FRAME_LOG

logger:
  level: DEBUG

Reflash, then operate the device from its existing hub or remote while watching the log. Each logged frame ends in its raw bytes: pairs three to five are the destination address and pairs six to eight are the sender, so in 4B 20 30 E1 F2 C0 FF EE 03 the device is 30E1F2 and the controller talking to it is C0FFEE. Your device is whichever address is not your own hub's. Then pick io_device_type from Named device types to match what the device actually is.

Key extraction covers this route in detail.

B2 — factory reset the device, then use Route A

This works, but it drops the device from your existing hub, and you repeat it for every device. The reset is usually a Double Power Cut plus a device-specific sequence of button presses; check the device's manual for the exact gesture. A Somfy device, for one, comes out of a reset waiting for its local remote, so register that again before starting Route A.

Route C — a 1W-only device

The hub joins the device as one more remote. You get open, close and stop buttons, but no position feedback, because a 1W device never answers.

  1. Add a oneway_controllers: entry with enrollment: true.
  2. Put the device into its learn mode with a remote it already obeys. The gesture depends on the vendor.
  3. Press Enroll 1W Controller in Home Assistant.

Sending 1W commands has the YAML, the gesture per vendor, and how to tell that it worked. Skip step 4 below: a 1W device has no cover: entry.

4. Add your devices to the YAML

Routes A and B leave you with the same three values per device, printed in the log. Add one entity per device, on the platform that matches what it is: cover: for shutters, blinds, awnings and window openers, or light:, lock:, switch: and climate: for the rest.

This example is a cover — see Configuration for the other platforms:

cover:
  - platform: home_io_control
    name: "Patio Awning"
    io_device_id: "FEEB1E"      # from the log
    io_device_type: "vertical_exterior_awning"    # from the log
    io_subtype: 0               # from the log

Take io_device_type from the log rather than guessing — it is what the device reports itself to be, and it may not be the class you expected. Named device types lists every value the option accepts. If the log printed a raw numeric type such as 0x11, keep that exact value.

Declaring io_device_type is worth doing even though it is optional. It is what enables type-specific behaviour: a vertical_exterior_awning gets a Patio Awning Favorite Position button, a window_opener also gets a Ventilation Position button, and families such as horizontal awnings get their direction inverted automatically. A garage opener gets no Favorite Position button but a Partial Position one, a gate opener gets an experimental Pedestrian Position button (both disabled by default), and an opener of subtype 58 is an open/close/stop cover without a slider (see Covers that only open and close). These entities are generated at compile time, so a type learned later over the radio cannot add them.

A few additional options worth knowing about:

  • low_power: true — set this on a solar or battery actuator. Leaving it off on such a device is a common reason a hub pairs successfully and then hears nothing back.
  • invert_position: true — if open and closed come out backwards.
  • travel_speed: — silent moves at the manufacturer app's quieter, slower speed; default moves at the motor's own speed, as its wall control pad does, rather than the fast profile the hub asks for otherwise. Only kinds that take a travel-speed parameter accept it; config validation says so for the others, among them garage openers, interior venetian blinds and dual roller shutters. See Travel speed.
  • rain_sensor_poll_interval: 15min — on a VELUX window with a wired rain sensor, adds a Rain sensor binary sensor; see Polling for rain.
  • status_refresh_interval: 5min — reads a cover at rest once it has reported nothing for that long, so a change the hub did not hear (another controller, a rain closure) shows up; see Refreshing the status at rest.

The rest are in Covers, and on each platform's own page.

Reflash with the updated YAML.

5. Check it works

Your devices appear in Home Assistant as native entities. Operate one — open a cover, toggle a light, unlock a lock.

What success looks like: the entity responds immediately (that is the optimistic state), the device itself moves or switches, and a second or two later the entity settles to what the device actually reports back. That settle is the 2W half doing its job.

If nothing happens, the log is the place to look. Troubleshooting is indexed by what you are seeing rather than by subsystem, so start there and work from the symptom.

Tidying up

The three onboarding flags from step 2 have done their job once your devices are in the YAML. Drop discover_and_pair_button:, accept_foreign_pairing: and scan_paired_devices_button:, then reflash.

None of them is dangerous to leave in place; this only spares you the controls you will never press in normal operation.

Next steps

  • Group the entities so a cover and its companion buttons appear as one device in Home Assistant, using ESPHome's device_id: — see Tips and tricks.
  • Wire up your existing remotes so a physical press keeps the entity's state fresh, or fires a Home Assistant automation — see Linked remotes.
  • Read the full reference at Configuration for every option.
  • Pick up a fix as soon as it lands. ESPHome downloads the component from GitHub at most once a day, so a rebuild right after a change can still use the old copy. Add refresh: always to the external_components: entry to download it on every build:
external_components:
  - source: github://laberning/home_io_control
    refresh: always

Remove the line again afterwards, since every build then waits for the download.

  • If the radio is unreliable, Radio tuning covers timing and discovery parameters.