Skip to content

Commit 896a913

Browse files
committed
openthread-br: locate the RCP dongle through otbr-rcp
The protocol handler required a hand-written radio_url, so pointing the border router at its dongle meant knowing which ttyACM the kernel had picked, and a replug that renumbered the device broke the interface until someone edited the config. Bring over the otbr-rcp wrapper from the matter-openwrt packaging: when no radio_url is set, the handler resolves the dongle by its USB properties (a cdc_acm interface on a device whose product string names OpenThread), with an rcp option to pin a bus position or a fixed serial device, and optional firmware install and update through handler plugins. Discovery runs in the setup phase rather than under the launched command, since a flash can take minutes and must not race the bounded wait for the agent's ubus object. A missing dongle fails setup with the restart block every failure needs, netifd retrying immediately and without backoff otherwise. The USB hotplug handler brings such interfaces up again when a device binds, keyed on the recorded RCP_NOT_FOUND error so interfaces an administrator took down on purpose stay down. An explicit radio_url keeps today's behaviour. Assisted-By: Claude Fable 5 Signed-off-by: Christian Glombek <c.glombek@cosa.systems>
1 parent 233cfaa commit 896a913

5 files changed

Lines changed: 455 additions & 9 deletions

File tree

net/openthread-br/Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
66
PKG_NAME:=openthread-br
77
PKG_VERSION:=2026.08.0
88
PKG_SOURCE_VERSION:=v$(PKG_VERSION)
9-
PKG_RELEASE:=1
9+
PKG_RELEASE:=2
1010

1111
PKG_SOURCE_PROTO:=git
1212
PKG_SOURCE_URL=https://github.com/openthread/ot-br-posix.git
@@ -91,10 +91,13 @@ endef
9191

9292
define Package/openthread-br/install
9393
$(INSTALL_DIR) \
94+
$(1)/etc/hotplug.d/usb \
9495
$(1)/etc/init.d \
9596
$(1)/lib/netifd/proto \
9697
$(1)/usr/sbin
9798
$(INSTALL_BIN) ./files/openthread-proto.sh $(1)/lib/netifd/proto/openthread.sh
99+
$(INSTALL_BIN) ./files/otbr-rcp $(1)/usr/sbin
100+
$(INSTALL_DATA) ./files/otbr-rcp.hotplug $(1)/etc/hotplug.d/usb/50-otbr-rcp
98101
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/* $(1)/usr/sbin
99102
endef
100103

net/openthread-br/README.md

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,17 +126,44 @@ config interface 'thread'
126126
option device 'wpan0'
127127
option proto 'openthread'
128128
option backbone_network 'lan'
129-
option radio_url 'spinel+hdlc+uart:///dev/ttyACM0?uart-baudrate=460800'
129+
option rcp '2-1'
130+
option uart_baudrate '460800'
130131
list prefix 'fd6f:5772:5468:7200::/64 paros'
131132
option verbose '0'
132133
```
133134

134-
Only backbone_network, device and radio_url are required; the protocol handler
135-
fails the interface if one of them is missing, or if backbone_network names an
136-
interface that has no device. Everything else — dataset, prefix, verbose,
135+
Only backbone_network and device are required, plus a radio named through
136+
either rcp or radio_url; the protocol handler fails the interface if one of
137+
them is missing, or if backbone_network names an interface that has no
138+
device. Everything else — dataset, prefix, verbose,
137139
rest_listen_address and rest_listen_port — is optional. See
138140
[REST Server](#rest-server) before moving the REST API off the loopback
139-
default. If something isn't working, check ifstatus for the OpenThread
141+
default.
142+
143+
### Finding the RCP
144+
145+
The radio is named with the `rcp` option rather than a full radio URL. It takes
146+
one of three forms:
147+
148+
| value | meaning |
149+
| --- | --- |
150+
| `/dev/ttyACM0` | a fixed serial device; no discovery is done |
151+
| `2-1` | a USB bus position, resolved to whatever serial device it currently exposes |
152+
| `any` (the default) | pick a dongle automatically |
153+
154+
`uart_baudrate` and `uart_flow_control` are appended to the resulting URL, and
155+
the port is always opened exclusively. Setting `radio_url` directly still works
156+
and overrides all of this.
157+
158+
Prefer a bus position to `any` unless the dongle advertises itself. Unattended
159+
selection only accepts a device whose USB product string contains the word
160+
"OpenThread", which many dongles — the Home Assistant Connect ZBT-2 among them
161+
— do not. Naming the bus position is the operator saying "this one is the RCP",
162+
so no product string is needed. `ls /sys/bus/usb/devices/` shows the positions.
163+
164+
`otbr-rcp` also has a plugin point for installing or updating dongle firmware,
165+
used when `rcp_firmware_update` is set. No handlers ship with this package, so
166+
nothing is flashed unless you add one. If something isn't working, check ifstatus for the OpenThread
140167
interface:
141168

142169
```
@@ -234,7 +261,8 @@ config interface 'thread'
234261
option backbone_network 'lan'
235262
option dataset '0e080000000000010000000300000f35060004001fffe0020836b86cd9746ab3080708fd9850cbe719b1d205101f11a11320828c7a6ebc2f2e675c0dca030e686f6d652d617373697374616e740102716f041025804ed78614258ebedf4e2db37b3b6e0c0402a0f7f8'
236263
list prefix 'fd6f:5772:5468:7200::/64 paros'
237-
option radio_url 'spinel+hdlc+uart:///dev/ttyACM0?uart-baudrate=460800'
264+
option rcp '2-1'
265+
option uart_baudrate '460800'
238266
option verbose '0'
239267
```
240268

net/openthread-br/files/openthread-proto.sh

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,19 @@ proto_openthread_add_prefix() {
2020
# shellcheck disable=SC2086
2121
[ -n "$prefix" ] && $OTCTL prefix add $prefix
2222
}
23+
RCP_PROG="/usr/sbin/otbr-rcp"
24+
2325

2426
proto_openthread_init_config() {
2527
proto_config_add_array 'prefix:list(string)'
2628
proto_config_add_boolean verbose
2729
proto_config_add_string backbone_network
2830
proto_config_add_string dataset
2931
proto_config_add_string radio_url
32+
proto_config_add_string rcp
33+
proto_config_add_boolean rcp_firmware_update
34+
proto_config_add_int uart_baudrate
35+
proto_config_add_boolean uart_flow_control
3036
proto_config_add_string rest_listen_address
3137
proto_config_add_int rest_listen_port
3238

@@ -44,21 +50,56 @@ proto_openthread_setup_error() {
4450
exit 1
4551
}
4652

53+
proto_openthread_setup_retry() {
54+
# A missing RCP dongle is not a configuration error, but the interface
55+
# must still be blocked: netifd re-runs a failed setup immediately and
56+
# without backoff, which would busy-loop until a dongle appears. The
57+
# hotplug handler's ifup lifts the block, so recovery is unaffected;
58+
# this helper differs from proto_openthread_setup_error only in intent.
59+
proto_openthread_setup_error "$@"
60+
}
61+
4762
proto_openthread_setup() {
4863
interface="$1"
4964
device="$2"
5065

5166
mkdir -p /var/lib/thread
5267

53-
json_get_vars backbone_network dataset device radio_url rest_listen_address rest_listen_port verbose:0
68+
json_get_vars backbone_network dataset device radio_url rcp \
69+
rcp_firmware_update:1 uart_baudrate:0 uart_flow_control:1 \
70+
rest_listen_address rest_listen_port verbose:0
5471

5572
[ -n "$backbone_network" ] || proto_openthread_setup_error "$interface" MISSING_BACKBONE_NETWORK
5673
proto_add_host_dependency "$interface" "" "$backbone_network"
5774
network_get_device backbone_ifname "$backbone_network"
5875

5976
[ -n "$backbone_ifname" ] || proto_openthread_setup_error "$interface" MISSING_BACKBONE_IFNAME
6077
[ -n "$device" ] || proto_openthread_setup_error "$interface" MISSING_DEVICE
61-
[ -n "$radio_url" ] || proto_openthread_setup_error "$interface" MISSING_RADIO_URL
78+
if [ -z "$radio_url" ]; then
79+
case "$rcp" in
80+
/dev/*)
81+
# A fixed serial device needs no discovery.
82+
radio_url="spinel+hdlc+uart://$rcp"
83+
;;
84+
*)
85+
# Let otbr-rcp locate the dongle by its USB properties and,
86+
# when a handler knows how, install or update its firmware.
87+
# This runs here rather than under the launched command: a
88+
# flash can take minutes, and it must not race the bounded
89+
# wait for the agent's ubus object below.
90+
RCPTTY=
91+
eval "$("$RCP_PROG" \
92+
$([ "$rcp_firmware_update" -eq 0 ] || echo --update) \
93+
"${rcp:-any}")"
94+
[ -n "$RCPTTY" ] || \
95+
proto_openthread_setup_retry "$interface" RCP_NOT_FOUND
96+
radio_url="spinel+hdlc+uart://$RCPTTY"
97+
;;
98+
esac
99+
radio_url="${radio_url}?uart-exclusive"
100+
[ "$uart_baudrate" -eq 0 ] || radio_url="${radio_url}&uart-baudrate=${uart_baudrate}"
101+
[ "$uart_flow_control" -eq 0 ] || radio_url="${radio_url}&uart-flow-control"
102+
fi
62103

63104
opts="--auto-attach=0"
64105
[ "$verbose" -eq 0 ] || append opts -v

0 commit comments

Comments
 (0)