Skip to content

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

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.sh still 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), not iostream_usart which 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=true in the spinel radio URL (S70otbr since v3.3.0). Without the host-side flag, otbr-agent opens /dev/ttyS1 without CRTSCTS, the kernel UART driver does not engage MCR_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.sh will 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

  1. Verify TCP connection: nc -zv <gateway-ip> 8888 (modes A/B) or check otbr-agent is running (mode C)
  2. Check baud rate matches on firmware and host (460800 default for OT-RCP; cat /sys/module/rtl8196e_uart_bridge/parameters/baud on the gateway for modes A/B)
  3. Verify hardware flow control is enabled

HDLC Parsing Errors

  1. Check baud rate mismatch between firmware and host
  2. Check for device flooding (remove battery from problematic devices)
  3. 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.


  • 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

License

Educational and personal use. Silicon Labs SDK components under their respective licenses.