OpenKNX firmware module for the direct integration of Velux, Somfy and other io-homecontrol devices into KNX. The module uses an ESP32 together with an SX1276 or SX1262 868 MHz radio and does not require an external manufacturer gateway.
This implementation combines protocol research and practical implementation work from several community projects. The main protocol sources for this implementation are Velocet/iown-homecontrol for protocol documentation and its 2W reference implementation, and the ESPHome project laberning/home_io_control for actively developed 1W/2W protocol behavior and implementation work. The 1W remote-profile behavior is also cross-checked against rspaargaren/iohomecontrol and RF captures. Additional implementation references are listed in Related protocol sources.
The io-homecontrol protocol should cover a wide range of motorized building products (most of them are still untested!):
| Category | Examples |
|---|---|
| Roller shutters | Velux SSL, Somfy RS100 io |
| Venetian blinds | Somfy Venetian io, external venetian blinds |
| Horizontal blinds | Somfy horizontal blind io |
| Awnings | Somfy Sunea io, horizontal awnings |
| Roof windows | Velux Integra (KMX/KSX), window openers |
| Garage doors | Somfy Dexxo io, gate openers |
| Gates | Somfy Slidymoove io |
| Curtain tracks | Somfy Glydea io |
| Thermostats | Atlantic Cozy io |
| Lights | io-homecontrol dimmers and switches |
| Locks | io-homecontrol door locks |
| Ventilation | io-homecontrol ventilation units |
The protocol implementation covers manufacturers such as Velux, Somfy, Atlantic, Honeywell, Niko, WindowMaster, Renson and ASSA ABLOY. The practical validation status may differ between individual device families.
- Up to 16 independent channels — each paired with one io-homecontrol device
- Bidirectional (2-way) communication with AES-128 encrypted command authentication
- Position control (0–100%) with position feedback from the device
- Up/Down/Stop commands via standard KNX datapoints
- Slat/tilt control for venetian blinds
- Thermostat control for Atlantic Cozy io — temperature setpoint, operating mode, presence, window contact. Setpoints use the protocol's unsigned 16-bit little-endian tenths encoding across the full supported 7.0–28.0 °C range.
- Direction inversion per channel for different mounting orientations
- Protocol mode selectable per channel (2-way bidirectional or 1-way unidirectional)
- Independent persistent 1W controller profiles per channel, with explicit profile sharing for 1W groups
- Strictly separated 2W and 1W identities — 1W remotes use their own node address/key and never silently reuse the 2W gateway identity
- Configurable typed 1W broadcast destinations and controller manufacturer, plus Situo QR identity import
- Device pairing/unpairing via ETS workflows, function properties, or service console
- Automatic status polling with configurable intervals (30s – 30min); after pairing, the module also tries to enable device-driven status updates when the device supports them
- Position estimation during travel using configurable opening/closing times and linear interpolation
- Battery level object for solar-powered devices; it deliberately remains unknown until a capture-backed private battery response layout is available
- Signal strength (RSSI) per channel for radio coverage verification
- Favorite & ventilation positions — dedicated KOs for device-stored presets
- Channel lock/unlock for child safety or maintenance
- Error status reporting per channel (communication error, duty cycle, pairing lost)
- KNX scene support (DPT 17.001 / 18.001) — up to 10 scenes per channel with device-type-specific scene data
- Wind/rain alarm safety input — auto-retract awnings, close windows on alarm
- Step-stop (Langzeitbetrieb) — standard KNX blind behavior for venetian blinds
- Power-on behavior configurable per channel (nothing, request status, restore last feedback position)
- Optional brightness control for dimmable lights, plus dedicated binary command/status handling for lights, switches and locks
- 3-channel frequency hopping across the 868 MHz ISM band
- EU duty cycle compliance with per-sub-band tracking (1-hour window); airtime includes the configured preamble, sync word, transport CRC, and io-homecontrol UART framing
- Command queue with automatic retries (up to 3 attempts, cycling frequencies)
- Remote observation — track io-homecontrol remotes on the bus, link devices to remotes
- Explicit passive key sniff workflow for diagnostics (listen-only session with separate start/stop/status/clear controls)
- Active 2W key extraction workflow for owned hubs (temporary device-role responder via
iohc extract ...) - Network scan with per-node packet statistics and RSSI tracking; observation-only
- Encrypted discovery (SPE) for scanning already-paired devices
- Flash persistence of 2W identity/pairing data and complete per-channel 1W controller profiles
- Per-channel 2W Execute profile with the captured Somfy default ACEI
0x67and selectable KIG300 alternative0x63 - Tolerant 2W discovery confirmation: normal pairing sends directed
0x2Cafter0x29, accepts optional0x2D, retries three times and continues to0x31after ACK, refusal, or silence - Pairing preamble continuity: the preamble of the accepted discovery request caps directed
0x2C,0x31, and optional0x6FSTART frames for that transaction, while0x32/0x3Dremain short and the learned power-class bits stay unchanged - Separated 2W diagnostics for complete reply timeouts versus authenticated exchanges without a closing response, including a frozen radio snapshot in
iohc radioand per-channel counters iniohcNN status - Byte-exact protocol self-tests for serializer boundaries, 1W/2W crypto vectors and key-transfer transcripts
For 2W, the module uses one global controller node ID and system key. For 1W, each channel uses a Cyril-style virtual remote profile containing its own controller address, key, sequence counter, reserved sequence watermark, type and manufacturer. Channels that should control the same 1W group can explicitly share one profile. Normal 1W commands reserve sequence numbers ahead in flash instead of saving after every frame; pairing/add/remove still force an immediate persistence update. New/imported profiles use sequence 1 for their first frame.
The 1W path follows the reference remote model more closely than older gateway-derived implementations:
- Default 1W pairing uses the observed remove/add flow (
0x39 RemoveControllerfollowed by unauthenticated0x30 SendKey1W). - VELUX/KLI-compatible profiles use the KLI enrollment variant: one logical
0x30is broadcast to0x0000BF,0x0000FF, and0x00037Fwith a shared sequence number, then authenticated STOP (0xD200) and DOWN/CLOSE (0xC800) commands are sent to ALL (0x00003F) with fresh sequences. The KLI 312 interior preset instead enrollsBlindandVenetianBlind(0x0002BF,0x00007F). DOWN starts well within the required three-second window after STOP. - The ETS parameter 1W enrollment finalizer is conservative by default: Automatic selects STOP + DOWN only for a VELUX controller manufacturer. None disables it, while STOP + DOWN explicitly enables it for another profile. It is not assumed that Somfy or unknown 1W devices need this finalizer.
0x30 SendKey1Whas 29 declared bytes: 9-byte header plusserial/wrappedControllerKey[16] + manufacturer + 0x01 + sequence[2]. The default profile sends no trailer and no normal in-frame 1W HMAC; an ETS profile option can append a six-byte trailer MAC outside CTRL0's declared length. Controller NID and serial are independent values and the NID must never be inferred from the serial suffix.- A channel can clone an existing original remote instead of enrolling a new identity:
iohcNN pair1w receivearms a listener that captures the remote's0x30 SendKey1W"copy remote" frame, decrypts the contained key with the public transfer key, and stores the remote's address, key, manufacturer, and a future reserved sequence window into the channel's own unshared 1W profile. This is required for actuators that only obey remotes added through the manufacturer's copy procedure. Usepair1w stop/pair1w statusto cancel or inspect the capture. - Normal 1W commands use typed broadcast destinations by default, computed as
dst=((type << 6) | 0x3F). Automatic mode maps the ETS device role to its protocol class; type0remains the explicit all-device target. - Normal 1W control uses the raw io-homecontrol closedness convention internally (
0=open,100=closed). UI/KNX open percentages are converted explicitly at the channel boundary. - 1W radio transmission uses four total sends with 40 ms spacing. The exact preamble shape comes from the controller profile and configured power class. Enrollment is an asynchronous serialized operation, so normal queued traffic cannot appear between REMOVE, ADD, STOP, and DOWN.
- VELUX automatic power shaping follows the first hardware-confirmed mains-powered SML roller-shutter profile (powered by a KUX 110 24 V supply): four normal 32-symbol preambles with no
LOW_POWERbit. Select Low Power explicitly for solar/battery products; it sends1024/32/32/32and setsLOW_POWERonly on the wake-up copy.
- Put the owned actuator or window product into its physical PROG/association window before starting module enrollment. A KUX 110 is the 24 V power supply, not the io-homecontrol actuator; for example, the radio product can be an SML electric roller shutter powered by the KUX 110. Follow the relevant product manual. Trigger only one PROG/registration gesture per pairing attempt. Do not press PROG again while that registration window is still active, because a second gesture can close or toggle it.
- Configure the effective 1W controller manufacturer as VELUX (
0x01). Automatic finalization depends on this profile value. - Leave the 1W command ACEI at the VELUX remote value
0x61unless a capture of the original remote proves another value. - For exterior products use the automatic enrollment classes (
roller_shutter,awning,dual_shutter). For KLI 312 interior blinds, select Interior blinds KLI 312, which sends ADD toblindandvenetian_blind. The classes visible in an original remote's0x2Eframes are the classes the ADD sweep must contain. - Use Automatic or Always alive for a mains-powered actuator such as an SML supplied by a KUX 110 (
32/32/32/32). Use Low Power for solar/battery receivers (1024/32/32/32,LOW_POWERon the first copy). - One KLI Gear press can open registration on every product controlled by that KLI. Use a control that operates only the intended product and prefer one 1W identity per separately controlled cover. Power-cycling a shared KUX supply can affect every connected product.
- Leave the
0x30MAC trailer disabled unless the original remote or actuator is known to require the 35-byte variant. Both MAC and no-MAC forms remain supported. - If enrollment fails, run
iohcNN 1wctrl statusandiohc pairdiag status. Check profile ownership, controller source/key, finalizer resolution, per-phase destination/sequence/TX result, and STOP-to-DOWN timing. Diagnostics redact wrapped keys and key material. - After the target confirms pairing, test OPEN, STOP, and CLOSE, then reboot the module and repeat a command to verify that the persistent controller key and reserved sequence remain accepted.
Because 1W devices do not acknowledge these frames, a successful local TX trace alone is not proof of physical enrollment. A second receiver capture and the target's confirmation movement are the definitive checks.
A VELUX SSL solar roller shutter was paired successfully with an SX1262 by using 0x28, destination 0x00003F, ACK enabled, discovery LOW_POWER disabled, a 32-symbol discovery preamble, and discovery confirmation set to Send. Its 0x29 identifies the target as low power, but the accepted 32-symbol discovery preamble is retained as the upper limit for the directed 0x2C, 0x31, and optional 0x6F START frames. The low-power flag remains set where required; 0x32 and 0x3D still use their short continuation preambles.
Measured behavior on that SSL matters for normal commands too: when resting, its low-power receiver needs the long 1024-symbol wake preamble; while the motor is moving it can ignore that long preamble but accept the normal directed START preamble. The controller therefore snapshots a wake belief at the start of each exchange and orders retries accordingly. This observation is specific to the tested VELUX SSL and is not assumed for every VELUX product.
Receipt of 0x33 is the definitive successful key exchange. The following 0x6F SetConfig1 only requests automatic status feedback: rejection, silence, or a local TX error is reported as an optional configuration result and does not turn the pairing into a failure.
| Component | Description |
|---|---|
| MCU | ESP32 or ESP32-S3 |
| Radio | SX1276 or SX1262 868 MHz module (see below) |
| KNX interface | NCN5120 or NCN5130 via OpenKNX hardware |
The firmware supports two Semtech radio chips, selected at compile time via a build flag:
| Chip | Build Flag | Example Modules | Status |
|---|---|---|---|
| SX1276 | -DRADIO_SX1276 |
RFM95W, HopeRF RFM96W, Heltec LoRa32 v2, TTGO LoRa32 v2 | default, implemented |
| SX1262 | -DRADIO_SX1262 |
Heltec LoRa32 v3, Waveshare SX1262, E22-868T | Implemented |
If no build flag is set, the firmware defaults to SX1276 and emits a compiler warning.
Both chips use the same io-homecontrol FSK protocol parameters (38.4 kbps, 19.2 kHz deviation, 250 kHz BW, 3-channel hopping). The abstraction layer (Radio.h) provides a compile-time typedef that maps Radio to the correct driver class — only one chip is compiled into the binary at a time.
Default GPIO assignments (configurable in IoHomecontrolHardware.h):
Common pins (both chips):
| Signal | GPIO |
|---|---|
| SPI_SCK | 18 |
| SPI_MISO | 19 |
| SPI_MOSI | 23 |
| SPI_CS | 5 |
| RADIO_RST | 14 |
SX1276-specific pins:
| Signal | GPIO | Description |
|---|---|---|
| RADIO_DIO0 | 26 | TX done / RX payload ready interrupt |
| RADIO_DIO4 | 27 | Preamble detection (optional) |
SX1262-specific pins:
| Signal | GPIO | Description |
|---|---|---|
| RADIO_DIO1 | 26 | Combined IRQ (TX/RX/preamble/sync/CRC/timeout) |
| RADIO_BUSY | 27 | SPI busy indicator (required, active high) |
Optional SX1262 board-control overrides:
IOHC_RADIO_RF_SW: shared external RF switch GPIO, driven HIGH for RX and LOW for TXIOHC_RADIO_DIO2_RF_SW=1: enable the chip's DIO2 RF-switch control on boards wired that wayIOHC_RADIO_RX_EN: dedicated external RX enable GPIOIOHC_RADIO_TX_EN: dedicated external TX enable GPIOIOHC_RADIO_TCXO_VOLTAGE: SX1262 DIO3 TCXO voltage codeIOHC_RADIO_TCXO_DELAY_US: initial TCXO startup delay in microseconds; the driver now retries longer delays automatically if XOSC start errors persist
Add the radio selection flag to your platformio.ini:
build_flags =
-DRADIO_SX1276 ; for SX1276-based boards
; -DRADIO_SX1262 ; for SX1262-based boardsTo override default pin assignments:
build_flags =
-DRADIO_SX1262
-DIOHC_RADIO_DIO1=33
-DIOHC_RADIO_BUSY=34
-DIOHC_RADIO_RF_SW=38
; -DIOHC_RADIO_DIO2_RF_SW=1
; -DIOHC_RADIO_RX_EN=21
; -DIOHC_RADIO_TX_EN=22
; -DIOHC_RADIO_TCXO_VOLTAGE=0x00
; -DIOHC_RADIO_TCXO_DELAY_US=5000ULETS parameters, communication objects, DPTs, pairing workflows, scenes and user-facing diagnostics are documented in the application description:
Applikationsbeschreibung io-homecontrol
The firmware provides an iohc serial console for commissioning, service and bench diagnostics. The commands intended for normal commissioning and service are documented in the application description to avoid duplicating ETS/user documentation here.
Useful diagnostic entry points include:
iohc status/iohcNN status— show 2W identity and per-channel 1W remote identity separately.iohc extract start [SEC]/stop/status/clear— arm the temporary 2W device-role responder used to recover the system key from an owned third-party 2W hub during a manual pairing attempt.iohc 1wctrl status/iohcNN 1wctrl status— show effective 1W profile, type, manufacturer, sequence, reserved sequence, and configured/resolved enrollment finalizer.iohcNN pair1w [ADDR] add-only|announce-add— test reference-style 1W add flows without inserting0x39automatically.iohcNN remove1w [ADDR]— send the explicit 1W remove flow.iohcNN send1w-type open|close|stop|vent|force [TYPE|dst=typed|dst=all|dst=exact ADDR]— test typed/all/exact 1W destinations.iohcNN 1wctrl reuse2w [MFG]— diagnostic-only command to intentionally reuse the 2W identity for a 1W profile.iohc pairdiag on|off|status— show compact pairing, TX, crypto, key, repeat and sequence diagnostics.iohc 2wdiag power auto|always|low/preamble auto|N/wake on|off/status/reset— independently override the 2W power bit and directed START preamble in RAM. Wake-belief ordering is on by default for low-power START frames (awake: normal/long/normal,maybe-awake: normal/long/long,asleep: long/long/long);wake offrestores long/long/long for troubleshooting. The normal value comes from the radio (SX1276: 32, SX1262: 48), explicit overrides win, and continuation, pairing, authentication-response, discovery, and 1W frames remain independent.iohc 2wdiag discovery destination auto|3b|3f|1bb|1bf/listen auto|skip|all— exposes the generic and lighting-class discovery destinations and the discovery response-channel policy.skipremains the default.iohc radio rxbw 39.0|46.9|58.6|78.2|117.3|156.2|187.2— SX1262-only runtime RX-bandwidth selection for reception A/B tests. The validated 58.6 kHz setting remains the startup default; Issue #119's 156.2 kHz result is useful but device-specific.iohc proto selftest— run byte-exact protocol self-tests on-device.
ETS function property interface (objectIndex=160, propertyId=10).
| Code | Request payload | Response payload | Description |
|---|---|---|---|
| 0x10 | cmd, channel[, nodeIdHi, nodeIdMid, nodeIdLo] |
status |
Start pairing on the selected channel. The optional 3-byte node ID override is used for 1W commissioning. |
| 0x11 | cmd |
status |
Cancel the active pairing session. |
| 0x12 | cmd, channel |
paired, nodeIdHi, nodeIdMid, nodeIdLo, controllerState, lastPairStartStatus, profileChannel, profileNodeHi, profileNodeMid, profileNodeLo, manufacturer, sequenceHi, sequenceLo, broadcastType |
Read pairing state plus the effective 1W profile diagnostics for one channel. The profile fields are unused and should be ignored for 2W channels. The sequence field reports the active profile sequence; the console status additionally shows the reserved high-water sequence. |
| 0x13 | cmd, channel |
status |
Unpair the selected channel, erase the stored key, and persist the change. |
| 0x15 | cmd, channel |
status |
Generate a new own 1W controller profile. Rejected for shared profiles or while any paired channel uses the profile. |
| 0x16 | cmd, channel |
status |
Start the 1W clone listener on the selected channel with the default timeout. The original remote must then send its manufacturer copy frame. |
| 0x17 | cmd |
status |
Start the active 2W key extraction/import workflow with the default timeout. Afterwards trigger the manual add-device flow on the owned third-party gateway. |
| 0x18 | cmd |
status, phase, hubNodeId[3], controllerNodeId[3], resultCount, controllerState, flags |
Read extraction/import progress. After the gateway verification window, the module adopts the newly enrolled controller identity and automatically starts authenticated SPE discovery. |
| 0x19 | cmd, resultIndex |
status, index, count, nodeId[3], type[2], subtype, manufacturer, powerClass |
Read one deduplicated device found by the automatic discovery. |
| 0x1A | cmd, resultIndex, channel |
status, existingChannel |
Store a discovered device and the extracted network key in an unused channel. Existing device assignments are reported and never overwritten. |
| 0x1B | cmd |
status |
Force-persist the imported controller identity and all channel assignments. |
| 0x20 | cmd, channel, percent |
status |
Test helper for sending a position command to an already paired device. |
Common status byte values used by commands 0x10, 0x11, 0x13, 0x17, and 0x20:
| Value | Meaning |
|---|---|
| 0x00 | Command accepted / executed successfully |
| 0x04 | Command rejected because the controller is currently busy |
| 0xFF | Invalid request, channel out of range, or command not supported in the current state |
Status byte values specific to command 0x15:
| Value | Meaning |
|---|---|
| 0x00 | New own 1W controller profile generated and persisted |
| 0x02 | Channel is not configured for 1W, or profile generation failed |
| 0x03 | Profile is currently used by at least one paired channel |
| 0x04 | Channel uses a shared profile instead of its own profile |
| 0xFF | Invalid request or channel out of range |
For command 0x16, the currently used status byte meanings are:
| Value | Meaning |
|---|---|
| 0x00 | 1W clone listener armed successfully |
| 0x02 | Channel is not configured for 1W |
| 0x04 | 1W clone listener could not start because the controller is busy or the selected profile is shared |
| 0xFF | Invalid request or channel out of range |
For the 0x12 status query, lastPairStartStatus is currently encoded as follows:
| Value | Meaning |
|---|---|
| 0 | Last pairing start request was accepted |
| 1 | Last pairing start request was blocked because the controller was busy |
| 3 | Generic pairing start failure |
Value 2 is retired. It previously reported a missing 1W target node ID; a 1W controller commands typed broadcasts and needs no target node.
The radio layer uses compile-time polymorphism to support multiple Semtech chips without virtual dispatch overhead:
src/radio/
├── Radio.h ← selector: typedef Radio = RadioSX1276 or RadioSX1262
├── RadioTypes.h ← shared enums (RadioState, RadioError)
├── RadioSX1276.h/.cpp ← SX1276 driver (register-based SPI)
├── RadioSX1262.h/.cpp ← SX1262 driver (command-based SPI)
├── sx1276Regs-Fsk.h ← SX1276 FSK register map
└── sx1262Regs-Fsk.h ← SX1262 command opcodes and register addresses
Both drivers expose an identical public API. The controller includes Radio.h and uses Radio mRadio; — the only conditional code in the controller is the init() call (different pin arguments per chip).
This OFM defines a single channel template (IoHomecontrol.templ.xml). The OAM (application module) controls how many instances are created via op:define NumChannels="16". ETS always shows the complete channel-selection table: channel 1 is enabled by default, channels 2–16 are disabled by default, and each enabled channel can additionally be suspended without losing its configuration.
IoHomecontrol (OpenKNX::Module)
├── IoHomecontrolChannel[0..15] (OpenKNX::Channel) — one per paired device
│ ├── Pairing data (node ID, encryption key, challenge)
│ ├── Persistent 1W virtual-remote profile (controller ID, key, sequence reserve, type, manufacturer)
│ ├── Position tracking (current, target, opening/closing time)
│ ├── Status polling (interval, timer)
│ ├── Thermostat state (temperature, mode, presence — Cozy devices)
│ └── KO callbacks (position, up/down, stop, slat, thermostat, status)
│
└── IoHomeController
├── Radio (SX1276 or SX1262 via compile-time selection)
├── Command Queue (8 entries, circular buffer)
├── State Machine (Idle → TxPending → TX → WaitResponse → Process)
├── Pairing State Machine (2W discovery/key-transfer/auth; 1W announce/add/remove)
├── Network Scan (passive packet capture, per-node stats; observation-only)
├── Remote Observation (track remotes, link to devices)
├── Passive Key Sniff (explicit session, retained capture result)
├── Active Key Extraction (temporary 2W device-role responder)
├── Protocol Builders (centralized 1W/2W frame templates)
├── IoHomeFrame (frame serialization/deserialization, 9–32 bytes)
└── IoHomeCrypto (AES-128 ECB, HMAC, CRC-16 Kermit, IV construction)
- AES-128 ECB encryption for all authenticated commands (via mbedtls)
- 6-byte HMAC for frame authentication
- Challenge-response protocol for bidirectional verification
- Per-device encryption keys derived during pairing
- 2W system key (16 bytes) generated per module, persisted in flash
- Independent 1W controller keys and monotonic sequence counters, persisted per profile with reserved high-water sequence windows
- Dedicated 1W key-transfer encryption based on the 1W remote/controller node address
- CRC-16 Kermit checksum on explicit raw/diagnostic frame paths
- Replay protection via 2W challenge freshness and persistent 1W sequence reservations
- Modulation: 2-FSK, 38.4 kbps, no shaping
- Bandwidth: 250 kHz, deviation: 19.2 kHz
- Frequencies: 868.25 / 868.95 / 869.85 MHz (3-channel hopping)
- Preamble: 1024 symbols for long/first transmissions, 8 symbols for short follow-up/repeat transmissions
- Sync word: 0xFF 0x33 (preceded by 0x55 preamble anchor byte)
- Packet format: variable length, hardware CRC (CCITT), io-homecontrol mode enabled
- Frame size: 9–35 bytes; authenticated 1W frames declare the appended HMAC in CTRL0
- 2W
0x3DChallengeResponse carries HMAC bytes as command data, not as an appended frame HMAC - 1W
0x30 SendKey1Wdefaults to 29 bytes total; selected profiles may use the observed 35-byte form with a six-byte trailer MAC outside the declared length - 1W frames use the low-power flag and type-dependent broadcast destinations
The io-homecontrol protocol support in this module consolidates findings from several open-source projects. These projects cover different parts of the protocol and hardware landscape, including protocol documentation, 1W and 2W communication, ESP32 targets, SX1276-based radios and later SX1262 support.
- Velocet/iown-homecontrol — main protocol documentation and reference source for this implementation.
- laberning/home_io_control — main ESPHome implementation and protocol behavior reference for this implementation.
- cridp/iown-homecontrol-esp32sx1276 — ESP32/SX1276 implementation reference.
- psolyca/iown-homecontrol — 1W/2W implementation reference with detailed command and crypto handling.
- CyrilOpenSource/iown-homecontrol-esp32sx1276 — ESP32/SX1276 1W/2W implementation reference.
- rspaargaren/iohomecontrol — ESP32 implementation reference with additional 1W-focused work.
- samr037/iohc-flipper — public KLI-compatible VELUX pairing reference for manufacturer/ACEI values, multicast ADD destinations, and STOP/DOWN finalization.
- nicolas5000/io-rts-esp32- — ESP32 implementation reference for io-homecontrol 2W and legacy RTS.
- io-homecontrol protocol research and implementation references: see Related protocol sources
- OpenKNX framework: openknx.de