Describe the bug
otbr-agent starts the radio at OpenThread's default transmit power of 0 dBm. In the Docker image there is no supported way to change that: no command-line option, no environment variable, and the one mechanism OpenThread provides for it is not compiled in.
The only way we found is ot-ctl txpower <n> after startup, which lives in the running agent's memory and is lost on every restart. We currently re-apply it from a Compose post_start hook, which is a workaround, not a configuration.
Why 0 dBm is not a safe default in practice
End devices typically transmit at +8 to +20 dBm, so at 0 dBm the border router is by far the weaker half of every link. The failure mode is asymmetric and hard to read: the BR hears the device fine, the device barely hears the BR.
On our deployment (SMLIGHT SLZB-06M over USB, EFR32MG21 RCP):
-
An IKEA GRILLPLATS plug at 8.5 m line-of-sight could not attach at all. Its Child ID Requests arrived, the replies did not:
[W] Mle-----------: Failed to process Child ID Request - Security (17x)
[N] MeshForwarder-: Failed to send IPv6 UDP msg ... error:NoAck ...
-
A second device sat with QMsgCnt 6 — six queued messages the BR could not deliver.
-
In Home Assistant the plug was unavailable for six hours, having flapped every 30–90 minutes before that.
After a single ot-ctl txpower 20, the plug was back within a minute — and not as a child but as a Router, at −76 dBm with 0.00 % frame error, giving the network its first second hop. Two other nodes then attached through it. We have since settled at +14 dBm, which keeps ~20 dB of margin over the sensitivity we measured (a device is decoded reliably at −92 dBm here).
Nothing about that deployment is exotic: one border router, seven Matter-over-Thread nodes, ordinary domestic distances.
The mechanism already exists — it is just not enabled here
max-power-table was added to the POSIX platform in openthread/openthread#4878 (merged 2020-04-30) precisely to "customize the max allowed transmit power of each channel". It is a radio URL parameter, and the radio URL is already an environment variable in this image (OT_RCP_DEVICE), so no new variable would be needed:
OT_RCP_DEVICE: "spinel+hdlc+uart:///dev/ttyOTBR?uart-baudrate=460800&max-power-table=14"
But it is gated behind OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE, which defaults to 0 in src/posix/platform/openthread-posix-config.h, and the image does not turn it on. The container's own help output confirms it — the sibling parameters from the same block are all present, max-power-table is not:
$ docker exec otbr otbr-agent --help
...
Parameters:
uart-parity[=even|odd] ...
uart-baudrate[=baudrate] ...
region[=region-code] Set the radio's region code...
cca-threshold[=dbm] Set the radio's CCA ED threshold in dBm...
enable-coex[=1|0] ...
fem-lnagain[=dbm] Set the Rx LNA gain in dBm of the external FEM.
no-reset ...
product-config-file[=path] ...
factory-config-file[=path] ...
Defaulting the flag to 0 makes sense for constrained embedded targets, where the table costs footprint. A container image is not that.
Request
Build the Docker image with OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE=1, so that operators can set the radio's maximum transmit power through the existing OT_RCP_DEVICE variable.
On the regulatory question
This is a maximum, not an override: max-power-table caps what the radio may use per channel, so it is as much a compliance tool as a tuning one. An operator who knows their antenna gain and region can set a correct ceiling; today they cannot express that at all in this image, and are left with an undocumented runtime command.
The alternative path — product-config-file with target_power / calibrated_power and a region domain mapping — is the right mechanism for vendors shipping calibrated hardware, but it needs RCP support that is not universally present. On the RCP above:
$ docker exec otbr ot-ctl region
Error 12: NotImplemented
so that route is not available to us.
Environment
|
|
| Image |
openthread/border-router:latest, built 2026-08-10 |
| otbr |
OPENTHREAD/3e6568b; POSIX |
| Host |
Debian 12 (bookworm), kernel 6.1, x86-64, Docker 27.4.1 |
| RCP |
SMLIGHT SLZB-06M (EFR32MG21) over USB, 460800 baud, channel 15 |
Describe the bug
otbr-agentstarts the radio at OpenThread's default transmit power of 0 dBm. In the Docker image there is no supported way to change that: no command-line option, no environment variable, and the one mechanism OpenThread provides for it is not compiled in.The only way we found is
ot-ctl txpower <n>after startup, which lives in the running agent's memory and is lost on every restart. We currently re-apply it from a Composepost_starthook, which is a workaround, not a configuration.Why 0 dBm is not a safe default in practice
End devices typically transmit at +8 to +20 dBm, so at 0 dBm the border router is by far the weaker half of every link. The failure mode is asymmetric and hard to read: the BR hears the device fine, the device barely hears the BR.
On our deployment (SMLIGHT SLZB-06M over USB, EFR32MG21 RCP):
An IKEA GRILLPLATS plug at 8.5 m line-of-sight could not attach at all. Its Child ID Requests arrived, the replies did not:
A second device sat with
QMsgCnt 6— six queued messages the BR could not deliver.In Home Assistant the plug was
unavailablefor six hours, having flapped every 30–90 minutes before that.After a single
ot-ctl txpower 20, the plug was back within a minute — and not as a child but as a Router, at −76 dBm with 0.00 % frame error, giving the network its first second hop. Two other nodes then attached through it. We have since settled at +14 dBm, which keeps ~20 dB of margin over the sensitivity we measured (a device is decoded reliably at −92 dBm here).Nothing about that deployment is exotic: one border router, seven Matter-over-Thread nodes, ordinary domestic distances.
The mechanism already exists — it is just not enabled here
max-power-tablewas added to the POSIX platform in openthread/openthread#4878 (merged 2020-04-30) precisely to "customize the max allowed transmit power of each channel". It is a radio URL parameter, and the radio URL is already an environment variable in this image (OT_RCP_DEVICE), so no new variable would be needed:But it is gated behind
OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE, which defaults to0insrc/posix/platform/openthread-posix-config.h, and the image does not turn it on. The container's own help output confirms it — the sibling parameters from the same block are all present,max-power-tableis not:Defaulting the flag to
0makes sense for constrained embedded targets, where the table costs footprint. A container image is not that.Request
Build the Docker image with
OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE=1, so that operators can set the radio's maximum transmit power through the existingOT_RCP_DEVICEvariable.On the regulatory question
This is a maximum, not an override:
max-power-tablecaps what the radio may use per channel, so it is as much a compliance tool as a tuning one. An operator who knows their antenna gain and region can set a correct ceiling; today they cannot express that at all in this image, and are left with an undocumented runtime command.The alternative path —
product-config-filewithtarget_power/calibrated_powerand a region domain mapping — is the right mechanism for vendors shipping calibrated hardware, but it needs RCP support that is not universally present. On the RCP above:so that route is not available to us.
Environment
openthread/border-router:latest, built 2026-08-10OPENTHREAD/3e6568b; POSIX