Skip to content

Docker image: no supported way to set the radio's max transmit power, and the 0 dBm default is too low for some deployments #3520

Description

@skirmess

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions