OpenThread RCP Firmware for Lidl Silvercrest Gateway¶
OpenThread Radio Co-Processor (RCP) firmware for the EFR32MG1B232F256GM48 chip found in the Lidl Silvercrest Smart Home Gateway.
This firmware transforms the EFR32 into a raw 802.15.4 radio using the Spinel/HDLC protocol. It is the single firmware shared by all 3 use cases described below.
1. Firmware¶
Hardware¶
| Component | Specification |
|---|---|
| Zigbee SoC | EFR32MG1B232F256GM48 |
| Flash | 256 KB (firmware uses ~100 KB) |
| RAM | 32 KB (firmware uses ~16 KB) |
| Radio | 2.4 GHz IEEE 802.15.4 |
| UART | PA0 (TX), PA1 (RX), PA4 (RTS), PA5 (CTS) @ 460800 baud |
Flash Pre-built Firmware (recommended)¶
From the repository root:
./flash_efr32.sh -y otrcp # default IP 192.168.1.88
./flash_efr32.sh -y -g 10.0.0.5 otrcp # custom IP
./flash_efr32.sh --help # full CLI reference
The script handles everything: pulse nRST for a clean chip state, switch
the in-kernel UART bridge to flash mode, run the Xmodem upload, then write
FIRMWARE=otrcp + FIRMWARE_BAUD=460800 + MODE=otbr to
/userdata/etc/radio.conf so S70otbr launches otbr-agent on next
boot — that's use case 3 below (OTBR on gateway).
For use case 1 (ZoH) or use case 2 (OTBR on host), drop the
MODE=otbr line so S50uart_bridge takes over instead — see
docker/README.md for the per-use-case Quick Start
that includes the radio-mode switch.
OT-RCP supports only 460800 baud (otbr-agent ceiling per CHANGELOG v3.0.0); the script and the build matrix both reflect this.
Legacy env-var interface (deprecated):
FW_CHOICE=4 CONFIRM=y ./flash_efr32.shstill works with a deprecation warning. Prefer the flag form above.
Gateway state after flash (per use case)¶
The same OT-RCP firmware drives 3 use cases — what differs is what's in
/userdata/etc/radio.conf and which init script wakes up:
All three use cases share the same FIRMWARE=otrcp +
FIRMWARE_BAUD=460800 chip-side identity (flash_efr32.sh writes them);
what differs is the daemon-routing keys:
| Use case | Daemon-routing keys in radio.conf |
Init script | Runs on gateway |
|---|---|---|---|
3 — OTBR on gateway (default after -y otrcp) |
MODE=otbr |
S70otbr |
otbr-agent (native) |
| 1 — ZoH | (no MODE line) |
S50uart_bridge |
bridge TCP:8888 |
| 2 — OTBR on host | (no MODE line) |
S50uart_bridge |
bridge TCP:8888 |
For cases 1 and 2, switch the gateway state after the EFR32 flash — see
docker/README.md "Switching Radio Mode". The
FIRMWARE=otrcp line stays the same in all three cases — it describes
the chip, not the deployment.
Build from Source¶
For users who want to customize the firmware.
# Install Silicon Labs tools (once)
cd 1-Build-Environment/12-silabs-toolchain && ./install_silabs.sh
# Build
cd 2-Zigbee-Radio-Silabs-EFR32/26-OT-RCP
./build_ot_rcp.sh # default baud 460800
./build_ot_rcp.sh --help # show baud options
Output: firmware/ot-rcp-460800.gbl (UART flash) and
firmware/ot-rcp-460800.s37 (J-Link/SWD).
Technical Notes¶
- UART driver: Uses
uartdrv_usart(low-level, DMA, async), notiostream_usartwhich would corrupt the binary Spinel stream with LF→CRLF conversion. - RTL8196E boot delay: 1-second delay at startup for host UART initialization.
- Hardware flow control: RTS/CTS enabled in the EFR32 firmware, and
enabled on the host side via
&uart-flow-control=truein the spinel radio URL (S70otbrsince v3.3.0). Without the host-side flag,otbr-agentopens/dev/ttyS1withoutCRTSCTS, the kernel UART driver does not engageMCR_AFE, and the RX FIFO can overrun under bursty Spinel traffic at 460800 — this was the root cause of #89. - Hardware radio acceleration: All 802.15.4 MAC operations in hardware.
- Baud rate: 460800 default (aligned with OpenThread's own default).
All bauds up to 892857 work with the in-kernel UART bridge on kernel
6.18. Pre-built firmware uses 460800. If
you recompile at a different baud,
flash_efr32.shwill automatically detect it, send a Spinel reset-to-bootloader command, and reflash via the Gecko Bootloader — no J-Link/SWD needed.
2. Use Cases¶
All 3 use cases run the same OT-RCP firmware on the EFR32. They differ in what runs on the gateway's RTL8196E main CPU and on the Docker host.
| # | Use case | Protocol | Gateway runs | Host runs (Docker) |
|---|---|---|---|---|
| 1 | ZoH | Zigbee | in-kernel UART bridge | Zigbee2MQTT + Mosquitto |
| 2 | OTBR on host | Thread/Matter | in-kernel UART bridge | OTBR + Matter Server + HA |
| 3 | OTBR on gateway | Thread/Matter | otbr-agent (native) | Matter Server + HA |
Use case 1: ZoH (Zigbee on Host)¶
The Zigbee stack (zigbee-on-host
by @Nerivec) runs on the host via Zigbee2MQTT's
zoh adapter. The gateway only bridges UART to TCP.
Zigbee Devices Docker Host
│ 802.15.4 ┌──────────────────────────┐
▼ │ Zigbee2MQTT (zoh) │
┌─────────────┐ UART ┌────────┐ │ + zigbee-on-host stack │
│ EFR32 RCP │◄──────► │ kernel │◄─┤ Web UI :8080 │
│ Spinel/ │ 460800 │ bridge │ └──────────────────────────┘
│ HDLC │ │ :8888 │
└─────────────┘ └────────┘
Gateway (Zigbee mode)
Gateway setup: flash userdata in Zigbee mode.
Quick start: see docker/README.md — Use Case 1.
zigbee-on-host is open-source, integrated in Zigbee2MQTT 2.x, and under active development. See the GitHub repo.
Use case 2: OTBR on Host¶
OTBR runs in a Docker container on the host PC. It connects to the gateway's in-kernel UART bridge over TCP to reach the EFR32 radio.
Matter Devices Docker Host
│ Thread 802.15.4 ┌──────────────────────────┐
▼ │ OTBR (Docker container) │
┌─────────────┐ UART ┌────────┐ │ REST API :8081 │
│ EFR32 RCP │◄──────► │ serial │◄─┤ Matter Server :5580 │
│ Spinel/ │ 460800 │ gateway│ │ Home Assistant :8123 │
│ HDLC │ │ :8888 │ │ ← Companion App (BLE) │
└─────────────┘ └────────┘ └──────────────────────────┘
Gateway (Zigbee mode)
Gateway setup: flash userdata in Zigbee mode (the in-kernel UART
bridge forwards the radio to TCP; OTBR runs on the host).
Quick start: see docker/README.md — Use Case 2.
Operating the network: see OT-CTL-CHEATSHEET.md for channel, TX power, dataset, and commissioning commands.
Use case 3: OTBR on Gateway (v2.0+)¶
OTBR runs natively on the gateway (otbr-agent on the RTL8196E CPU).
No TCP bridge between OTBR and the radio — otbr-agent opens /dev/ttyS1
directly. The host only runs Matter Server + Home Assistant.
Matter Devices Docker Host
│ Thread 802.15.4 ┌──────────────────┐
▼ │ Matter Server │
┌─────────────┐ UART ┌──────────────────┐ REST │ :5580 │
│ EFR32 RCP │◄──────► │ otbr-agent │◄──────┤ Home Assistant │
│ Spinel/ │ 460800 │ (native on CPU) │ :8081 │ :8123 │
│ HDLC │ │ REST API :8081 │ │ ← Companion App │
└─────────────┘ └──────────────────┘ └──────────────────┘
Gateway (Thread mode)
Gateway setup: flash userdata in Thread mode.
Advantages over use case 2: - Lower latency — OTBR talks directly to the EFR32 via UART - Simpler — no OTBR Docker container to manage - Self-contained — Thread mesh stays up even without the host
This is the recommended setup for Thread/Matter since v2.0.
Quick start: see docker/README.md — Use Case 3.
Operating the network: see OT-CTL-CHEATSHEET.md — includes a section on the project-specific tmpfs/flash dataset sync done by S70otbr.
3. Docker Stacks¶
Pre-configured Docker Compose files are in docker/:
| # | Use case | Compose file | Command |
|---|---|---|---|
| 1 | ZoH | docker-compose-zoh.yml |
docker compose -f docker-compose-zoh.yml up -d |
| 2 | OTBR on host | docker-compose-otbr-host.yml |
docker compose -f docker-compose-otbr-host.yml up -d |
| 3 | OTBR on gateway | docker-compose-otbr-gateway.yml |
docker compose -f docker-compose-otbr-gateway.yml up -d |
See docker/README.md for full setup instructions:
IPv6 forwarding, HA integrations, Companion App commissioning, chip-tool
alternative, and troubleshooting.
4. Tested Devices¶
| Device | Protocol | Use case | Status |
|---|---|---|---|
| Xiaomi LYWSD03MMC | Zigbee | 1 (ZoH) | OK |
| IKEA TIMMERFLOTTE temp/hmd sensor | Matter/Thread | 2 (OTBR host) | OK |
| IKEA TIMMERFLOTTE temp/hmd sensor | Matter/Thread | 3 (OTBR gateway) | OK |
| IKEA BILRESA dual button | Matter/Thread | 3 (OTBR gateway) | OK |
| IKEA MYGGSPRAY wrlss mtn sensor | Matter/Thread | 3 (OTBR gateway) | OK |
Troubleshooting¶
No response from RCP¶
- Verify TCP connection:
nc -zv <gateway-ip> 8888(modes A/B) or check otbr-agent is running (mode C) - Check baud rate matches on firmware and host (460800 default for
OT-RCP;
cat /sys/module/rtl8196e_uart_bridge/parameters/baudon the gateway for modes A/B) - Verify hardware flow control is enabled
HDLC Parsing Errors¶
- Check baud rate mismatch between firmware and host
- Check for device flooding (remove battery from problematic devices)
- Verify hardware flow control is enabled
Files¶
26-OT-RCP/
├── build_ot_rcp.sh # Build script
├── README.md # This file
├── THREAD-MATTER-PRIMER.md # Educational guide: Thread vs Zigbee vs Matter
├── patches/
│ ├── ot-rcp.slcp # Project config (based on SDK sample)
│ ├── main.c # Entry point (RTL8196E boot delay)
│ ├── app.c / app.h # OT instance init + hardware watchdog
│ ├── sl_uartdrv_usart_vcom_config.h # UART: 460800, HW flow control
│ └── sl_rail_util_pti_config.h # PTI disabled (suppresses SDK warning)
├── docker/ # Docker Compose stacks
│ ├── README.md # Full setup guide (3 use cases)
│ ├── docker-compose-zoh.yml # Use case 1: ZoH
│ ├── docker-compose-otbr-host.yml # Use case 2: OTBR on host
│ ├── docker-compose-otbr-gateway.yml # Use case 3: OTBR on gateway
│ ├── z2m/configuration.yaml # Zigbee2MQTT config
│ └── mosquitto/mosquitto.conf # MQTT broker config
├── firmware/ # Pre-built binaries
│ ├── ot-rcp.gbl # For UART flashing
│ └── ot-rcp.s37 # For SWD flashing
└── range-testing/ # Thread mesh range-test toolset + field-test report
├── README.md # Layout, install, quick start
├── REPORT.md # 16-sensor home deployment results + recipes
├── gateway/ # Scripts that run on the gateway (BusyBox sh)
│ ├── range_test.sh # Generic CSV sampler (one palier per run)
│ ├── phase1_tx_sweep.sh # TX power sweep, abort on detach
│ ├── phase2_channel_migration.sh # Channel migration via Pending Op Dataset
│ ├── orientation_runner.sh # Operator-paced orientation runner
│ ├── healthmon.sh # Opt-in host-side health sampler (1 sample/min)
│ ├── ha_link_publisher.sh # Opt-in Thread RSSI/LQI → HA push daemon
│ ├── ha_link_publisher.conf.example # Annotated config template
│ └── examples/ # Optional helpers (per-gateway, not in skeleton)
│ └── S75ha_link_publisher # Init script to auto-start the publisher
└── analysis/ # Developer-machine tooling (Python 3.10+)
├── ha_matter_map.py # HA WS API → label/node_id/ext_mac mapping
└── analyze.py # Per-palier and per-sensor stats (stdlib only)
See range-testing/README.md for the test
methodology and range-testing/REPORT.md for
field-test results — TX power, channel, gateway orientation and sensor
orientation, plus a 12 h validation soak. The same scripts let you
characterise your own deployment instead of relying on these defaults.
Related Projects¶
24-NCP-UART-HW/— NCP firmware (EZSP protocol)25-RCP-UART-HW/— RCP firmware (CPC protocol, for cpcd + zigbeed)27-Router/— Autonomous Zigbee router (no host needed)3-Main-SoC.../34-Userdata/— Gateway firmware with native OTBR (v2.0+)
References¶
- zigbee-on-host — Open-source Zigbee stack by Nerivec
- Zigbee2MQTT
- OpenThread RCP
- bnutzer/docker-otbr-tcp — OTBR Docker image for TCP-based RCPs
- Home Assistant Matter integration
- Discussion #47 — Thread/Matter on the Lidl gateway
License¶
Educational and personal use. Silicon Labs SDK components under their respective licenses.