Building a Open Thread Border Router
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)
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.
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
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 tablepartitions.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 generatedweb_storage.binto the flash address0x820000. This address must match the starting offset of the SPIFFS (or storage) partition defined in the partition tablepartitions.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.
After switching to STA mode, check the router’s IP to access the router’s admin page. http://192.168.xx.xx
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
- Click the Add Integration button at the bottom right of the screen
- Select OpenThread Border Router from the list
- Enter the OTBR URI http://192.168.xx.xx
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)
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
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.
For LED lighting, a wide range of controls is possible, including brightness, color tone, on/off transition time, and dimming.
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.