How to determine if ZBT-1 is functioning

I just bought a ZBT-1 and am attempting to add it as a thread border router. I am running HA Docker and as much as I try to get the OTBR docker container running it just cannot seem to talk to my ZBT-1.

I have updated to the latest firmware using the chrome firmware updater which was 7.4.4.0 if I remember correctly. I also tried multiple USB ports on my computer including ones I know function as I use them for USB storage. I also tried with and without the USB extender as I thought that might be faulty.

I wanted to ensure the actual ZBT-1 is working and though using sudo screen /dev/ttyUSB0 115200 would give me something, but even trying baud rates of 115200, 9600, 38400, 460800 I just get a blank screen. I have tried doing this not only with the specified ttyUSB0, but also using /dev/serial/by-id/usb-Nabu_Casa_Home_Assistant_Connect_ZBT-1_a6c7b1cd7e39ef11ba5b59f454516304-if00-port0. Both of these have just blank output which leads me to believe I have an issue with the dongle but I really don’t know.

If anyone has any thoughts on how I can make sure my dongle is working, I’m all ears. I’ve spent well over 20 hours on the problem and feel like I’m hitting a brick wall. Thanks in advance :slight_smile:

As far as I know, 7.4.4.0 is Zigbee firmware, not thread firmware.

See:
https://connectzbt1.home-assistant.io/about-firmware-options/
and

Edit: and if you are running in a VM, be sure to ā€˜pass-through’ the dongle to the VM.

Yes, I have done the passthrough and I’m pretty sure it works as I originally passed through to HA Docker and it popped up and asked if I would like to add the device before it explained I needed to setup OTBR.

As far as I can tell, I don’t see a way to manually flash the thread firmware like zigbee but correct me if I’m wrong. On the website it says OTBR will flash the thread firmware when connected. I wasn’t cognizant of the difference in firmware so thanks for the explanation, but I still can’t seem to get it working:

Here is my docker compose:

version: '3.8'
services:
  otbr:
    image: openthread/otbr:latest
    container_name: otbr
    hostname: otbr
    restart: unless-stopped
    devices:
      - "/dev/serial/by-id/usb-Nabu_Casa_Home_Assistant_Connect_ZBT-1_a6c7b1cd7e39ef11ba5b59f454516304-if00-port0:/dev/ttyUSB0"
    privileged: true
    group_add:
      - dialout
    cap_add:
      - NET_ADMIN
      - SYS_ADMIN
      - SYS_MODULE
    environment:
      - OTBR_WEB_SERVICE_PORT=8282
      - OTBR_AGENT_PORT=49191
      - OTBR_INTERFACE_NAME=eth0
      - OTBR_RADIO_URL=spinel+hdlc+uart:///dev/ttyUSB0?uart-baudrate=115200  # Explicit baudrate
    volumes:
      - otbr_data:/etc/otbr
      - /lib/modules:/lib/modules:ro
    network_mode: "host"
    extra_hosts:
      - "otbr:127.0.0.1"

volumes:
  otbr_data:

And here are the logs:

otbr  | RADIO_URL: spinel+hdlc+uart:///dev/ttyUSB0
otbr  | TREL_URL: 
otbr  | TUN_INTERFACE_NAME: wpan0
otbr  | BACKBONE_INTERFACE: eth0
otbr  | NAT64_PREFIX: 64:ff9b::/96
otbr  | DEBUG_LEVEL: 7
otbr  | +++ dirname /app/script/server
otbr  | ++ cd /app/script/..
otbr  | ++ HAVE_SYSTEMCTL=0
otbr  | ++ have systemctl
otbr  | ++ command -v systemctl
otbr  | ++ HAVE_SERVICE=0
otbr  | ++ have service
otbr  | ++ command -v service
otbr  | ++ HAVE_SERVICE=1
otbr  | ++ [[ ! -n x ]]
otbr  | ++ echo 'Current platform is ubuntu'
otbr  | Current platform is ubuntu
otbr  | ++ with BORDER_ROUTING
otbr  | ++ local value
otbr  | +++ printenv BORDER_ROUTING
otbr  | ++ value=1
otbr  | ++ [[ -z 1 ]]
otbr  | ++ [[ 1 == 1 ]]
otbr  | ++ with DHCPV6_PD
otbr  | ++ local value
otbr  | +++ printenv DHCPV6_PD
otbr  | ++ value=
otbr  | ++ [[ -z '' ]]
otbr  | ++ [[ -f examples/platforms/ubuntu/default ]]
otbr  | ++ [[ '' == 1 ]]
otbr  | ++ with BORDER_ROUTING
otbr  | ++ local value
otbr  | +++ printenv BORDER_ROUTING
otbr  | ++ value=1
otbr  | ++ [[ -z 1 ]]
otbr  | ++ [[ 1 == 1 ]]
otbr  | ++ with NETWORK_MANAGER
otbr  | ++ local value
otbr  | +++ printenv NETWORK_MANAGER
otbr  | ++ value=
otbr  | ++ [[ -z '' ]]
otbr  | ++ [[ -f examples/platforms/ubuntu/default ]]
otbr  | ++ [[ '' == 1 ]]
otbr  | ++ STAGE_DIR=/app/stage
otbr  | ++ BUILD_DIR=/app/build
otbr  | ++ [[ -d /app/stage ]]
otbr  | ++ mkdir -v -p /app/stage
otbr  | mkdir: created directory '/app/stage'
otbr  | ++ [[ -d /app/build ]]
otbr  | ++ mkdir -v -p /app/build
otbr  | mkdir: created directory '/app/build'
otbr  | ++ export PATH=/app/stage/usr/bin:/app/stage/usr/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
otbr  | ++ PATH=/app/stage/usr/bin:/app/stage/usr/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
otbr  | +++ basename /app/script/server
otbr  | ++ TASKNAME=server
otbr  | ++ BEFORE_HOOK=examples/platforms/ubuntu/before_server
otbr  | ++ AFTER_HOOK=examples/platforms/ubuntu/after_server
otbr  | ++ [[ ! -f examples/platforms/ubuntu/before_server ]]
otbr  | ++ BEFORE_HOOK=/dev/null
otbr  | ++ [[ ! -f examples/platforms/ubuntu/after_server ]]
otbr  | ++ AFTER_HOOK=/dev/null
otbr  | + . script/_nat64
otbr  | ++ NAT64_SERVICE=openthread
otbr  | ++ TAYGA_DEFAULT=/etc/default/tayga
otbr  | ++ TAYGA_CONF=/etc/tayga.conf
otbr  | ++ TAYGA_IPV4_ADDR=192.168.255.1
otbr  | ++ TAYGA_IPV6_ADDR=fdaa:bb:1::1
otbr  | ++ TAYGA_TUN_V6_ADDR=fdaa:bb:1::2
otbr  | ++ NAT64_PREFIX=64:ff9b::/96
otbr  | ++ DYNAMIC_POOL=192.168.255.0/24
otbr  | ++ NAT44_SERVICE=/etc/init.d/otbr-nat44
otbr  | ++ WLAN_IFNAMES=eth0
otbr  | ++ THREAD_IF=wpan0
otbr  | + . script/_dns64
otbr  | ++ BIND_CONF_OPTIONS=/etc/bind/named.conf.options
otbr  | ++ NAT64_PREFIX=64:ff9b::/96
otbr  | ++ DNS64_NAMESERVER_ADDR=127.0.0.1
otbr  | +++ echo 64:ff9b::/96
otbr  | +++ tr '"/"' '"/"'
otbr  | ++ DNS64_CONF='dns64 64:ff9b::/96 { clients { thread; }; recursive-only yes; };'
otbr  | ++ without NAT64
otbr  | ++ with NAT64
otbr  | ++ local value
otbr  | +++ printenv NAT64
otbr  | ++ value=1
otbr  | ++ [[ -z 1 ]]
otbr  | ++ [[ 1 == 1 ]]
otbr  | ++ without DNS64
otbr  | ++ with DNS64
otbr  | ++ local value
otbr  | +++ printenv DNS64
otbr  | ++ value=0
otbr  | ++ [[ -z 0 ]]
otbr  | ++ [[ 0 == 1 ]]
otbr  | ++ '[' ubuntu = raspbian ']'
otbr  | ++ '[' ubuntu = beagleboneblack ']'
otbr  | ++ '[' ubuntu = ubuntu ']'
otbr  | ++ RESOLV_CONF_HEAD=/etc/resolvconf/resolv.conf.d/head
otbr  | + . script/_firewall
otbr  | ++ FIREWALL_SERVICE=/etc/init.d/otbr-firewall
otbr  | ++ sudo modprobe ip6table_filter
otbr  | sudo: unable to resolve host otbr: Resource temporarily unavailable
otbr  | sudo: modprobe: command not found
otbr  | ++ true
otbr  | ++ FIREWALL=1
otbr  | + OTBR_MDNS=mDNSResponder
otbr  | + OT_BACKBONE_CI=0
otbr  | + REFERENCE_DEVICE=0
otbr  | + main
otbr  | + [[ '' == \s\h\u\t\d\o\w\n ]]
otbr  | + startup
otbr  | + . /dev/null
otbr  | + sudo sysctl --system
otbr  | sudo: unable to resolve host otbr: Resource temporarily unavailable
otbr  | * Applying /etc/sysctl.d/10-console-messages.conf ...
otbr  | kernel.printk = 4 4 1 7
otbr  | * Applying /etc/sysctl.d/10-ipv6-privacy.conf ...
otbr  | net.ipv6.conf.all.use_tempaddr = 2
otbr  | net.ipv6.conf.default.use_tempaddr = 2
otbr  | * Applying /etc/sysctl.d/10-kernel-hardening.conf ...
otbr  | kernel.kptr_restrict = 1
otbr  | * Applying /etc/sysctl.d/10-link-restrictions.conf ...
otbr  | fs.protected_hardlinks = 1
otbr  | fs.protected_symlinks = 1
otbr  | * Applying /etc/sysctl.d/10-magic-sysrq.conf ...
otbr  | kernel.sysrq = 176
otbr  | * Applying /etc/sysctl.d/10-network-security.conf ...
otbr  | net.ipv4.conf.default.rp_filter = 1
otbr  | net.ipv4.conf.all.rp_filter = 1
otbr  | net.ipv4.tcp_syncookies = 1
otbr  | * Applying /etc/sysctl.d/10-ptrace.conf ...
otbr  | kernel.yama.ptrace_scope = 1
otbr  | * Applying /etc/sysctl.d/10-zeropage.conf ...
otbr  | vm.mmap_min_addr = 65536
otbr  | * Applying /etc/sysctl.d/60-otbr-accept-ra.conf ...
otbr  | * Applying /etc/sysctl.d/60-otbr-ip-forward.conf ...
otbr  | net.ipv6.conf.all.forwarding = 1
otbr  | net.ipv4.ip_forward = 1
otbr  | * Applying /etc/sysctl.conf ...
otbr  | + nat64_start
otbr  | + with NAT64
otbr  | + local value
otbr  | ++ printenv NAT64
otbr  | + value=1
otbr  | + [[ -z 1 ]]
otbr  | + [[ 1 == 1 ]]
otbr  | + '[' openthread = tayga ']'
otbr  | + nat44_start
otbr  | + with DOCKER
otbr  | + local value
otbr  | ++ printenv DOCKER
otbr  | + value=1
otbr  | + [[ -z 1 ]]
otbr  | + [[ 1 == 1 ]]
otbr  | + service otbr-nat44 start
otbr  | + dns64_start
otbr  | + with NAT64
otbr  | + local value
otbr  | ++ printenv NAT64
otbr  | + value=1
otbr  | + [[ -z 1 ]]
otbr  | + [[ 1 == 1 ]]
otbr  | + with DNS64
otbr  | + local value
otbr  | ++ printenv DNS64
otbr  | + value=0
otbr  | + [[ -z 0 ]]
otbr  | + [[ 0 == 1 ]]
otbr  | + return 0
otbr  | + firewall_start
otbr  | + with FIREWALL
otbr  | + local value
otbr  | ++ printenv FIREWALL
otbr  | + value=1
otbr  | + [[ -z 1 ]]
otbr  | + [[ 1 == 1 ]]
otbr  | + with DOCKER
otbr  | + local value
otbr  | ++ printenv DOCKER
otbr  | + value=1
otbr  | + [[ -z 1 ]]
otbr  | + [[ 1 == 1 ]]
otbr  | + service otbr-firewall start
otbr  | + case "$1" in
otbr  | + firewall_start
otbr  | + firewall_stop
otbr  | + ip6tables -C FORWARD -o wpan0 -j OTBR_FORWARD_INGRESS
otbr  | ip6tables v1.6.1: Couldn't load target `OTBR_FORWARD_INGRESS':No such file or directory
otbr  | 
otbr  | Try `ip6tables -h' or 'ip6tables --help' for more information.
otbr  | + ip6tables -L OTBR_FORWARD_INGRESS
otbr  | ip6tables: No chain/target/match by that name.
otbr  | + ipset_destroy_if_exist otbr-ingress-deny-src
otbr  | + ipset list otbr-ingress-deny-src
otbr  | ipset v6.34: Kernel support protocol versions 6-7 while userspace supports protocol versions 6-6
otbr  | The set with the given name does not exist
otbr  | + ipset_destroy_if_exist otbr-ingress-deny-src-swap
otbr  | + ipset list otbr-ingress-deny-src-swap
otbr  | ipset v6.34: Kernel support protocol versions 6-7 while userspace supports protocol versions 6-6
otbr  | The set with the given name does not exist
otbr  | + ipset_destroy_if_exist otbr-ingress-allow-dst
otbr  | + ipset list otbr-ingress-allow-dst
otbr  | ipset v6.34: Kernel support protocol versions 6-7 while userspace supports protocol versions 6-6
otbr  | The set with the given name does not exist
otbr  | + ipset_destroy_if_exist otbr-ingress-allow-dst-swap
otbr  | + ipset list otbr-ingress-allow-dst-swap
otbr  | ipset v6.34: Kernel support protocol versions 6-7 while userspace supports protocol versions 6-6
otbr  | The set with the given name does not exist
otbr  | + ipset create -exist otbr-ingress-deny-src hash:net family inet6
otbr  | + ipset create -exist otbr-ingress-deny-src-swap hash:net family inet6
otbr  | + ipset create -exist otbr-ingress-allow-dst hash:net family inet6
otbr  | + ipset create -exist otbr-ingress-allow-dst-swap hash:net family inet6
otbr  | + ip6tables -N OTBR_FORWARD_INGRESS
otbr  | + ip6tables -I FORWARD 1 -o wpan0 -j OTBR_FORWARD_INGRESS
otbr  | + ip6tables -A OTBR_FORWARD_INGRESS -m pkttype --pkt-type unicast -i wpan0 -j DROP
otbr  | + ip6tables -A OTBR_FORWARD_INGRESS -m set --match-set otbr-ingress-deny-src src -j DROP
otbr  | + ip6tables -A OTBR_FORWARD_INGRESS -m set --match-set otbr-ingress-allow-dst dst -j ACCEPT
otbr  | + ip6tables -A OTBR_FORWARD_INGRESS -m pkttype --pkt-type unicast -j DROP
otbr  | + ip6tables -A OTBR_FORWARD_INGRESS -j ACCEPT
otbr  | + start_service rsyslog
otbr  | + local service_name=rsyslog
otbr  | + [[ 0 == 1 ]]
otbr  | + [[ 1 == 1 ]]
otbr  | + sudo service rsyslog status
otbr  | sudo: unable to resolve host otbr: Resource temporarily unavailable
otbr  |  * rsyslogd is not running
otbr  | + sudo service rsyslog start
otbr  | sudo: unable to resolve host otbr: Resource temporarily unavailable
otbr  |  * Starting enhanced syslogd rsyslogd
otbr  |    ...done.
otbr  | + start_service dbus
otbr  | + local service_name=dbus
otbr  | + [[ 0 == 1 ]]
otbr  | + [[ 1 == 1 ]]
otbr  | + sudo service dbus status
otbr  | sudo: unable to resolve host otbr: Resource temporarily unavailable
otbr  |  * dbus is not running
otbr  | + sudo service dbus start
otbr  | sudo: unable to resolve host otbr: Resource temporarily unavailable
otbr  |  * Starting system message bus dbus
otbr  |    ...done.
otbr  | + [[ mDNSResponder == \a\v\a\h\i ]]
otbr  | + [[ 0 == 1 ]]
otbr  | + [[ 0 == 1 ]]
otbr  | + have service
otbr  | + command -v service
otbr  | + sudo service mdns status
otbr  | sudo: unable to resolve host otbr: Resource temporarily unavailable
otbr  | Usage: /etc/init.d/mDNS {start|stop|reload|restart}
otbr  | + sudo service mdns start
otbr  | sudo: unable to resolve host otbr: Resource temporarily unavailable
otbr  | Starting Apple Darwin Multicast DNS / DNS Service Discovery daemon: mdnsd.
otbr  | + without WEB_GUI
otbr  | + with WEB_GUI
otbr  | + local value
otbr  | ++ printenv WEB_GUI
otbr  | + value=1
otbr  | + [[ -z 1 ]]
otbr  | + [[ 1 == 1 ]]
otbr  | + start_service otbr-web
otbr  | + local service_name=otbr-web
otbr  | + [[ 0 == 1 ]]
otbr  | + [[ 1 == 1 ]]
otbr  | + sudo service otbr-web status
otbr  | sudo: unable to resolve host otbr: Resource temporarily unavailable
otbr  |  * otbr-web is not running
otbr  | + sudo service otbr-web start
otbr  | sudo: unable to resolve host otbr: Resource temporarily unavailable
otbr  |  * Starting thread web interface otbr-web
otbr  |    ...done.
otbr  | + start_service otbr-agent
otbr  | + local service_name=otbr-agent
otbr  | + [[ 0 == 1 ]]
otbr  | + [[ 1 == 1 ]]
otbr  | + sudo service otbr-agent status
otbr  | sudo: unable to resolve host otbr: Resource temporarily unavailable
otbr  |  * otbr-agent is not running
otbr  | + sudo service otbr-agent start
otbr  | sudo: unable to resolve host otbr: Resource temporarily unavailable
otbr  |  * Starting thread border agent otbr-agent
otbr  |    ...done.
otbr  | + . /dev/null
otbr  | Mar 23 05:20:32 otbr mDNSResponder: Default: mDNSResponder (Engineering Build) (Mar 21 2025 16:56:40) starting
otbr  | Mar 23 05:20:32 otbr otbr-web[137]: [INFO]-WEB-----: Running 0.3.0-d45b444
otbr  | Mar 23 05:20:32 otbr otbr-web[137]: [INFO]-WEB-----: Border router web started on wpan0
otbr  | Mar 23 05:20:32 otbr otbr-web[137]: [ERR ]-WEB-----: OpenThread daemon is not running.
otbr  | Mar 23 05:20:32 otbr otbr-agent: [NOTE]-AGENT---: Backbone interface: eth0
otbr  | Mar 23 05:20:32 otbr otbr-agent[152]: [NOTE]-AGENT---: Running 0.3.0-d45b444
otbr  | Mar 23 05:20:32 otbr otbr-agent[152]: [NOTE]-AGENT---: Thread version: 1.4.0
otbr  | Mar 23 05:20:32 otbr otbr-agent[152]: [NOTE]-AGENT---: Thread interface: wpan0
otbr  | Mar 23 05:20:32 otbr otbr-agent[152]: [NOTE]-AGENT---: Radio URL: spinel+hdlc+uart:///dev/ttyUSB0
otbr  | Mar 23 05:20:32 otbr otbr-agent[152]: [NOTE]-ILS-----: Infra link selected: eth0
otbr  | Mar 23 05:20:32 otbr otbr-agent[152]: [INFO]-RCP_HOS-: OpenThread log level changed to 5
otbr  | Mar 23 05:20:32 otbr otbr-agent[152]: 49d.17:08:48.088 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:0, cmd:RESET
otbr  | Mar 23 05:20:32 otbr otbr-agent[152]: 49d.17:08:48.088 [D] P-SpinelDrive-: Waiting response: key=0
otbr  | Mar 23 05:20:33 otbr rsyslogd: rsyslogd's groupid changed to 101
otbr  | Mar 23 05:20:33 otbr rsyslogd: rsyslogd's userid changed to 101
otbr  | Mar 23 05:20:33 otbr rsyslogd:  [origin software="rsyslogd" swVersion="8.32.0" x-pid="87" x-info="http://www.rsyslog.com"] start
otbr  | Mar 23 05:20:34 otbr otbr-agent[152]: 49d.17:08:50.090 [W] P-SpinelDrive-: Wait for response timeout
otbr  | Mar 23 05:20:34 otbr otbr-agent[152]: 49d.17:08:50.090 [I] P-SpinelDrive-: co-processor self reset successfully
otbr  | Mar 23 05:20:34 otbr otbr-agent[152]: 49d.17:08:50.090 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:1, cmd:PROP_VALUE_GET, key:PROTOCOL_VERSION
otbr  | Mar 23 05:20:34 otbr otbr-agent[152]: 49d.17:08:50.091 [D] P-SpinelDrive-: Waiting response: key=1
otbr  | Mar 23 05:20:36 otbr otbr-agent[152]: 49d.17:08:52.092 [W] P-SpinelDrive-: Wait for response timeout
otbr  | Mar 23 05:20:36 otbr otbr-agent[152]: 49d.17:08:52.092 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:1, cmd:PROP_VALUE_GET, key:PROTOCOL_VERSION
otbr  | Mar 23 05:20:36 otbr otbr-agent[152]: 49d.17:08:52.092 [D] P-SpinelDrive-: Waiting response: key=1
otbr  | Mar 23 05:20:38 otbr otbr-agent[152]: 49d.17:08:54.094 [W] P-SpinelDrive-: Wait for response timeout
otbr  | Mar 23 05:20:38 otbr otbr-agent[152]: 49d.17:08:54.094 [C] Platform------: Init() at spinel_driver.cpp:83: Failure
otbr  | Mar 23 05:20:38 otbr otbr-agent[152]: 49d.17:08:54.094 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:1, cmd:PROP_VALUE_GET, key:PROTOCOL_VERSION
otbr  | Mar 23 05:20:38 otbr otbr-agent[152]: 49d.17:08:54.094 [D] P-SpinelDrive-: Waiting response: key=1
otbr  | Mar 23 05:20:40 otbr otbr-agent[152]: 49d.17:08:56.096 [W] P-SpinelDrive-: Wait for response timeout
otbr  | Mar 23 05:21:43 otbr kernel: [  431.584848] audit: type=1400 audit(1742707303.495:126): apparmor="DENIED" operation="capable" class="cap" profile="/snap/snapd/23771/usr/lib/snapd/snap-confine" pid=6389 comm="snap-confine" capability=12  capname="net_admin"

Ah, snap. Can’t help with that.

Hang on. I’ve never used docker, but I’m pretty sure you can’t use both /dev/serial/by-id and /dev/ttyUSB0 in the same line as a single string.

It’s either one or the other, so try removing one of them from:

devices:
      - "/dev/serial/by-id/usb-Nabu_Casa_Home_Assistant_Connect_ZBT-1_a6c7b1cd7e39ef11ba5b59f454516304-if00-port0:/dev/ttyUSB0"

The mapping is correct, for docker you separate the ā€œrealā€ device to the left of the colon and the docker device to the right of the colon so /dev/ttyUSB0 is a virtual mapping that only exist in the container.

However you’re still getting the same spinel_driver failure you posted in your other thread. Looking at your log, note how the Radio URL is different than what’s in your compose (the baud rate is missing) so that could be why the spinel driver is failing.

Edited to add: judging by this post, it appears the baud rate should be set to 460800, and you also need to specify uart-flow-control for the zbt-1.

1 Like