FICUSONLINE F9E
Building a Standalone Open Thread Border Router for IKEA Matter Devices Using a Single ESP32-C6
If you prepare an Open Thread Border Router (OTBR), you can register, manage, and control Thread-compatible Matter devices regardless of manufacturer, using Matter-compatible home automation systems such as Home Assistant. This project builds such an OTBR using an Single ESP32-C6.
Takanobu FuseAdministrator

2 months ago

Hardware

Building a Open Thread Border Router

Thread Network & OTBR

To use IKEA’s Matter-compatible devices in a home automation system and control them from a smartphone, you would normally use IKEA’s official hub, DIRIGERA, together with the official app IKEA Home smart (a device management/control app).

On the other hand, since Matter is a common standard for home automation devices, IKEA’s Matter-compatible devices can also be registered directly on third-party Matter-compatible hubs such as Apple HomePod / Apple TV, Google Nest Hub, and Amazon Echo. In that case, you can still manage and control the devices from a smartphone app (however, the IKEA Home smart app cannot be used).

Matter uses Thread as its communication protocol. Thread is a low-power mesh network based on IPv6 that operates independently of a regular Wi-Fi network, so Wi-Fi devices cannot directly access Thread devices.

For this reason, an OpenThread Border Router (OTBR) is needed to bridge the Thread network with the home’s IP network (Wi-Fi or Ethernet).

Matter-compatible hubs such as IKEA’s own DIRIGERA, as well as Apple HomePod / Apple TV, Google Nest Hub, and Amazon Echo, already come with built-in OTBR functionality. Because of this, these hubs can also detect, manage, and control Thread-compatible Matter devices.

In other words, if you prepare an OTBR, you can register, manage, and control Thread-compatible Matter devices regardless of manufacturer, using Matter-compatible home automation systems such as Home Assistant.

This project builds such an OTBR using an ESP32. The base code used is the OTBR sample code provided by Espressif Systems for the ESP32; however, it assumes a two-ESP32 configuration in which Wi-Fi and the Thread network operate independently on separate chips. Here, I modify it to run on a single standalone ESP32-C6, and verify whether this works without practical issues.

Note: Since the ESP32-C6’s Wi-Fi (2.4GHz) and Thread (802.15.4, 2.4GHz) share the same 2.4GHz RF band, coexistence control is applied when both are used simultaneously to avoid interference. This may reduce throughput.

Note: To run Wi-Fi and Thread independently, the manufacturer recommends a configuration in which an ESP32-series chip acts as the host (Wi-Fi) and an ESP32-H2 acts as the RCP (Radio Co-Processor, Thread), connected via UART.

Development Environment

VS Code + PlatformIO

ESP32-C6: esp32-c6-devkitc-1 (connected to the PC via USB Type-C <-> Serial converter)

ESP32-C6-DevKitC-1

Runtime Environment

Home Assistant container, Matter Server container, Nginx container

Downloading the Sample Code

Download the base sample code.

$ git clone https://github.com/espressif/esp-thread-br.git

Code Editing

To make it run on a standalone ESP32-C6, add/edit the following files.

platformio.ini: newly created

Create the PlatformIO configuration file in the project folder esp-thread-br/examples/basic_thread_border_router.

esp-thread-br/examples/basic_thread_border_router/platformio.ini

[platformio]
src_dir = main

[env:esp32-c6-devkitc-1]
platform = https://github.com/platformio/platform-espressif32.git
board = esp32-c6-devkitc-1
framework = espidf

board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216

; Port and reset settings
upload_port = /dev/ttyACM0
monitor_port = /dev/ttyACM0
monitor_speed = 115200

upload_flags =
    --before=default_reset
    --after=hard_reset

extra_scripts = pre:build_spiffs.py

board_build.partitions = partitions.csv

board_build.embed_files =
    ../../components/esp_ot_br_server/favicon.ico
board_build.embed_txtfiles =
    server_certs/ca_cert.pem
    ../../components/esp_ot_br_server/frontend/wifi_configuration.html

esp-thread-br/examples/basic_thread_border_router/main/esp_ot_br.c

@@ -46,7 +46,7 @@
 #endif
 #if CONFIG_OPENTHREAD_BR_START_WEB
     esp_vfs_spiffs_conf_t web_server_conf = {
-        .base_path = "/spiffs", .partition_label = "web_storage", .max_files = 10, .format_if_mount_failed = false};
+        .base_path = "/spiffs", .partition_label = "web_storage", .max_files = 10, .format_if_mount_failed = true};
     ESP_RETURN_ON_ERROR(esp_vfs_spiffs_register(&web_server_conf), TAG, "Failed to mount web storage");
 #endif
     return ESP_OK;

@@ -60,7 +60,7 @@
     // * border router
     size_t max_eventfd = 3;

-#if CONFIG_OPENTHREAD_RADIO_SPINEL_SPI
+#if CONFIG_OPENTHREAD_RADIO_NATIVE || CONFIG_OPENTHREAD_RADIO_SPINEL_SPI
     // * SpiSpinelInterface (The Spi Spinel Interface needs an eventfd.)
     max_eventfd++;
 #endif

esp-thread-br/examples/basic_thread_border_router/main/esp_ot_config.h

@@ -18,7 +18,12 @@

 #define RCP_FIRMWARE_DIR "/spiffs/ot_rcp"

-#if CONFIG_OPENTHREAD_RADIO_SPINEL_UART
+#if CONFIG_OPENTHREAD_RADIO_NATIVE
+#define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG()              \
+    {                                                      \
+        .radio_mode = RADIO_MODE_NATIVE,                   \
+    }
+#elif CONFIG_OPENTHREAD_RADIO_SPINEL_UART
 #define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG()              \
     {                                                      \
         .radio_mode = RADIO_MODE_UART_RCP,                 \
@@ -47,9 +52,9 @@
             .dma_channel = 2,                              \
             .spi_interface =                               \
                 {                                          \
-                    .mosi_io_num = CONFIG_PIN_TO_RCP_MOSI, \
-                    .miso_io_num = CONFIG_PIN_TO_RCP_MISO, \
-                    .sclk_io_num = CONFIG_PIN_TO_RCP_SCLK, \
+                    .mosi_io_num = 11, \
+                    .miso_io_num = 12, \
+                    .sclk_io_num = 13, \
                     .quadwp_io_num = -1,                   \
                     .quadhd_io_num = -1,                   \
                 },                                         \
@@ -59,7 +64,7 @@
                     .input_delay_ns = 100,                 \
                     .mode = 0,                             \
                     .clock_speed_hz = 2500 * 1000,         \
-                    .spics_io_num = CONFIG_PIN_TO_RCP_CS,  \
+                    .spics_io_num = 10,  \
                     .queue_size = 5,                       \
                 },                                         \
             .intr_pin = CONFIG_PIN_TO_RCP_BOOT,            \

esp-thread-br/examples/basic_thread_border_router/partitions.csv

@@ -1,9 +1,8 @@
-# Name,   Type, SubType, Offset,  Size, Flags
-# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
-nvs,        data, nvs,      , 0x6000,
-otadata,    data, ota,      , 0x2000,
-phy_init,   data, phy,      , 0x1000,
-ota_0,      app,  ota_0,    , 2M,
-ota_1,      app,  ota_1,    , 2M,
-web_storage,data, spiffs,   , 200K,
-rcp_fw,     data, spiffs,   , 640K,
+# Name,       Type, SubType, Offset,   Size,    Flags
+nvs,          data, nvs,     0x9000,   0x10000,
+otadata,      data, ota,     0x19000,  0x2000,
+phy_init,     data, phy,     0x1b000,  0x1000,
+ota_0,        app,  ota_0,   0x20000,  4M,
+ota_1,        app,  ota_1,   0x420000, 4M,
+web_storage,  data, spiffs,  0x820000, 2M,
+rcp_fw,       data, spiffs,  0xa20000, 2M,

esp-thread-br/examples/basic_thread_border_router/sdkconfig.defaults

@@ -1,10 +1,10 @@
 # ESP32-S3 is the host SoC on ESP-Thread-Border-Router board
-CONFIG_IDF_TARGET="esp32s3"
+CONFIG_IDF_TARGET="esp32-c6-devkitc-1"

 #
 # Serial flasher config
 #
-CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
+CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
 # end of Serial flasher config

 #
@@ -46,14 +46,14 @@
 CONFIG_OPENTHREAD_BORDER_ROUTER=y
 CONFIG_OPENTHREAD_CLI_OTA=y
 CONFIG_OPENTHREAD_RCP_COMMAND=y
-CONFIG_OPENTHREAD_RADIO_SPINEL_UART=y
+###CONFIG_OPENTHREAD_RADIO_SPINEL_UART=y
 CONFIG_OPENTHREAD_TASK_SIZE=8192
 # end of OpenThread

 #
 # OpenThread Border Router Example
 #
-CONFIG_AUTO_UPDATE_RCP=y
+###CONFIG_AUTO_UPDATE_RCP=y
 # end of OpenThread Border Router Example

 #
@@ -98,16 +98,16 @@
 #
 # Ethernet
 # 
-CONFIG_EXAMPLE_USE_W5500=y
-CONFIG_EXAMPLE_ETH_SPI_HOST=2
-CONFIG_EXAMPLE_ETH_SPI_SCLK_GPIO=21
-CONFIG_EXAMPLE_ETH_SPI_MOSI_GPIO=45
-CONFIG_EXAMPLE_ETH_SPI_MISO_GPIO=38
-CONFIG_EXAMPLE_ETH_SPI_CS_GPIO=41
-CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ=36
-CONFIG_EXAMPLE_ETH_SPI_INT_GPIO=39
-CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=40
-CONFIG_EXAMPLE_ETH_PHY_ADDR=1
+# CONFIG_EXAMPLE_USE_W5500=y
+# CONFIG_EXAMPLE_ETH_SPI_HOST=2
+# CONFIG_EXAMPLE_ETH_SPI_SCLK_GPIO=21
+# CONFIG_EXAMPLE_ETH_SPI_MOSI_GPIO=45
+# CONFIG_EXAMPLE_ETH_SPI_MISO_GPIO=38
+# CONFIG_EXAMPLE_ETH_SPI_CS_GPIO=41
+# CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ=36
+# CONFIG_EXAMPLE_ETH_SPI_INT_GPIO=39
+# CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=40
+# CONFIG_EXAMPLE_ETH_PHY_ADDR=1
 # end of Ethernet

 #

esp_projects/esp-thread-br/examples/basic_thread_border_router/sdkconfig.defaults

@@ -124,6 +124,9 @@

         // Stop SoftAP mode
         esp_br_wifi_config_stop();
+        
+        esp_wifi_stop();
+        esp_wifi_deinit();
 #else
         // Standard Wi-Fi connection mode - get from Kconfig
         strncpy(wifi_ssid, CONFIG_EXAMPLE_WIFI_SSID, sizeof(wifi_ssid) - 1);

Build Configuration

The list of PlatformIO command buttons can be displayed by clicking the PlatformIO icon on the left side of the screen. If you want to run commands in a terminal, be sure to click the terminal icon at the bottom of the screen. The Terminal->New Terminal option at the top of the screen will not recognize the pio command.

VS-Code: PlatformIO

Terminal: Help

$ pio --help

The device-specific build file sdkconfig.esp32-c6-devkitc-1, as specified in platformio.ini, is generated automatically. You may edit this file directly, but you can also change/add build settings via

Platform -> Run Menuconfig

PlatformIO Menu Config

Terminal: Show configuration menu

$ pio run -t menuconfig

Check that the following items are checked as indicated below.

(Top) → ESP Thread Border Router Example

    Border router board type (Standalone dev kits)  --->
    Board Configuration  --->
    Border router RCP target (ESP32-C6)  --->
[*] Enable the automatic start mode in Thread Border Router. (default value)
[*] Enable the web server in Thread Border Router. (default value)
[*] Enable SoftAP Wi-Fi configuration mode (default value)
(Top) → Component config → ESP-STDIO

    Channel for console output (Default: UART0)  --->
    Channel for console secondary output (USB_SERIAL_JTAG PORT)  --->
(Top) → Serial flasher config

[ ] Disable download stub (default value)
    Flash SPI mode (DIO)  --->
    Flash Sampling Mode (STR Mode)  --->
    Flash SPI speed (80 MHz)  --->
    Flash size (16 MB)  --->
[*] Detect flash size when flashing bootloader (default value)
    Before flashing (Reset to bootloader)  --->
    After flashing (Reset after flashing)  --->
(Top) → Config for OpenThread Examples

[*] Enable the automatic start mode of Thread network.
    External Console Commands  --->
(Top) → Component config → Wi-Fi

[ ] WiFi AMPDU TX
[ ] WiFi AMPDU RX

(20) Minimum active time (default value)
(Top) → Component config → OpenThread → OpenThread → Thread Core Features

[*] Enable timing optimization
(Top) → Component config → OpenThread → OpenThread → Thread Core Features → OpenThread Stack Parameters

(20) The size of max MLE children entries

(8) Maximum backoffs times before declaring a channel access failure.

Build & Upload

General->Build starts the build.

Terminal: Build

$ pio run 

General -> Upload starts flashing.

Terminal: Upload

$ pio run --target upload

A web UI is provided for configuring Wi-Fi and Thread network settings, but you must separately generate a binary image and write it to flash using the commands below.

Generating the SPIFFS Image

$ python ~/.platformio/packages/framework-espidf/components/spiffs/spiffsgen.py 0x200000 \
../../components/esp_ot_br_server/frontend \
.pio/build/esp32-c6-devkitc-1/web_storage.bin

spiffsgen.py is a tool bundled with ESP-IDF that packages the contents of a specified directory into a SPIFFS (SPI Flash File System) image file.

  • 0x200000: The size of the SPIFFS image to generate (hexadecimal, in bytes). In this case, 2,097,152 bytes = 2MB is specified. This must match the size of the SPIFFS partition defined in the partition table partitions.csv.
  • ../../components/esp_ot_br_server/frontend: The source directory to be imaged. It contains the HTML/CSS/JS files for the web UI.
  • .pio/build/esp32-c6-devkitc-1/web_storage.bin: The output binary filename. After the build, the SPIFFS image is generated as this file.

Writing to Flash

$ python ~/.platformio/packages/tool-esptoolpy/esptool.py --chip esp32c6 -p /dev/ttyACM0 \
-b 460800 write_flash 0x820000 .pio/build/esp32-c6-devkitc-1/web_storage.bin

esptool.py is the standard tool for writing firmware and data to Espressif chips over a serial connection.

  • --chip esp32c6: Specifies that the target chip is the ESP32-C6.
  • -p /dev/ttyACM0: The serial port used for writing (a Linux device file).
  • -b 460800: The communication baud rate (460800bps). This setting is for high-speed writing.
  • write_flash 0x820000 ...web_storage.bin: Writes the previously generated web_storage.bin to the flash address 0x820000. This address must match the starting offset of the SPIFFS (or storage) partition defined in the partition table partitions.csv.

Various Command Operations

After building, you can check operation via serial communication. General -> Monitor

Terminal: Device monitor

$ pio device monitor
esp32c6> ot wifi state
esp32c6> ot state
esp32c6> ot help

Starting the OTBR

After flashing, the OTBR starts in access point mode. Connect to this access point from a PC or mobile device, and access the following URL in a browser. Enter the SSID and password of your home LAN and click “Save Wi-Fi Configuration”, and it will automatically switch to station mode and be assigned a local address on the LAN.

http://192.168.4.1

OTBR AP Mode

After switching to STA mode, check the router’s IP to access the router’s admin page. http://192.168.xx.xx

OTBR STA Mode

Registering with Home Assistant

Beforehand, register Open Thread Matter Router Thread and Matter Server under Settings —> Devices & Services—> Integrations.

The Matter Server is run in advance in a separate container from Home Assistant.

https://github.com/matter-js/matterjs-server

Open Thread Matter Router

  • Settings > Devices & services

HA Device & Services

HA Add Integrations

  • Select OpenThread Border Router from the list

Select OTBR

OTBR URL

Thread

  • Settings > Devices & services

  • Click the Add Integration button at the bottom right of the screen

  • Select Thread from the list

  • Follow the on-screen instructions (the Thread network from the OTBR above will be selected)

Thread

Matter Server

  • Settings > Devices & services

  • Click the Add Integration button at the bottom right of the screen

  • Select Matter -> Matter from the list

  • Enter the Matter server URL: ws://localhost:5580/ws

Registering Matter Devices

Matter devices can only be registered via the Home Assistant mobile app.

Note: The phone’s Wi-Fi and the OTBR running on the ESP32-C6 must use the same 2.4GHz SSID.

IKEA Matter-Compatible Devices Registered

The following IKEA Matter-compatible devices were registered this time:

  • KAJPLATSLED bulb E17 810 lumen, smart white spectrum/globe/opal white
  • BILRESA Remote control, white smart/dual button

IKEA Matter Devices

Companion -> Trouble Shootings

Trouble Shottings -> Thread Sync

Add Matter Device 001

Add Matter Device 002

Add Matter Device load QR Code

Add Matter Device Start

Registered Matter Devices

Verifying Matter Device Operation

I have only tried two devices so far, but responsiveness was good even through walls, with no cases of unresponsiveness. I believe that with around 5–6 devices, the OTBR built here should not cause any particular practical issues (though there may be some cases of delayed response due to uneven signal strength, interference, etc.).

In Matter, remote control switch patterns can be recognized as three types: single click, double click, and long press-release.

IKEA BILRESA

For LED lighting, a wide range of controls is possible, including brightness, color tone, on/off transition time, and dimming.

IKEA KAJPLATS

IKEA KAJPLATS Home Assistant Control

By combining the recognition patterns of the above devices with Home Assistant automation rules, you can achieve control tailored to your preferences.

Finally

For troubleshooting or updates to this content, please refer to the forum below.

https://forum.ficusonline.com/t/topic/562