Running OTBR in Docker

I’m trying to rung OTBR in Docker so it can work with a SLZB-07Mg24 USB stick.

Running the following command to launch OTBR:

$ docker run -it --rm \
    --sysctl "net.ipv6.conf.all.disable_ipv6=0 net.ipv4.conf.all.forwarding=1 net.ipv6.conf.all.forwarding=1" \
    -p 8080:8080 \
    -p 80:8888 \
    --dns=127.0.0.1 \
    --volume /dev/serial/by-id/usb-SMLIGHT_SMLIGHT_SLZB-07Mg24_6add7a39f772ed118f6073773dbf42d5-if00-port0:/dev/ttyMyStick \
    --privileged \
    openthread/otbr --radio-url spinel+hdlc+uart:///dev/ttyMyStick

Used the command to determine the USB device:

$ ls -la /dev/serial/by-id/usb-*
lrwxrwxrwx 1 root root 13 Jan 24 04:53 /dev/serial/by-id/usb-SMLIGHT_SMLIGHT_SLZB-07Mg24_6add7a39f772ed118f6073773dbf42d5-if00-port0 -> ../../ttyUSB0

The output from OTBR is:

WARNING: Localhost DNS setting (--dns=127.0.0.1) may fail in containers.
RADIO_URL: spinel+hdlc+uart:///dev/ttyMyStick
TREL_URL: 
TUN_INTERFACE_NAME: wpan0
BACKBONE_INTERFACE: eth0
NAT64_PREFIX: 64:ff9b::/96
DEBUG_LEVEL: 7
+++ dirname /app/script/server
++ cd /app/script/..
++ HAVE_SYSTEMCTL=0
++ have systemctl
++ command -v systemctl
++ HAVE_SERVICE=0
++ have service
++ command -v service
++ HAVE_SERVICE=1
++ [[ ! -n x ]]
++ echo 'Current platform is ubuntu'
Current platform is ubuntu
++ with BORDER_ROUTING
++ local value
+++ printenv BORDER_ROUTING
++ value=1
++ [[ -z 1 ]]
++ [[ 1 == 1 ]]
++ with DHCPV6_PD
++ local value
+++ printenv DHCPV6_PD
++ value=
++ [[ -z '' ]]
++ [[ -f examples/platforms/ubuntu/default ]]
++ [[ '' == 1 ]]
++ with BORDER_ROUTING
++ local value
+++ printenv BORDER_ROUTING
++ value=1
++ [[ -z 1 ]]
++ [[ 1 == 1 ]]
++ with NETWORK_MANAGER
++ local value
+++ printenv NETWORK_MANAGER
++ value=
++ [[ -z '' ]]
++ [[ -f examples/platforms/ubuntu/default ]]
++ [[ '' == 1 ]]
++ STAGE_DIR=/app/stage
++ BUILD_DIR=/app/build
++ [[ -d /app/stage ]]
++ mkdir -v -p /app/stage
mkdir: created directory '/app/stage'
++ [[ -d /app/build ]]
++ mkdir -v -p /app/build
mkdir: created directory '/app/build'
++ export PATH=/app/stage/usr/bin:/app/stage/usr/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
++ PATH=/app/stage/usr/bin:/app/stage/usr/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+++ basename /app/script/server
++ TASKNAME=server
++ BEFORE_HOOK=examples/platforms/ubuntu/before_server
++ AFTER_HOOK=examples/platforms/ubuntu/after_server
++ [[ ! -f examples/platforms/ubuntu/before_server ]]
++ BEFORE_HOOK=/dev/null
++ [[ ! -f examples/platforms/ubuntu/after_server ]]
++ AFTER_HOOK=/dev/null
+ . script/_nat64
++ NAT64_SERVICE=openthread
++ TAYGA_DEFAULT=/etc/default/tayga
++ TAYGA_CONF=/etc/tayga.conf
++ TAYGA_IPV4_ADDR=192.168.255.1
++ TAYGA_IPV6_ADDR=fdaa:bb:1::1
++ TAYGA_TUN_V6_ADDR=fdaa:bb:1::2
++ NAT64_PREFIX=64:ff9b::/96
++ DYNAMIC_POOL=192.168.255.0/24
++ NAT44_SERVICE=/etc/init.d/otbr-nat44
++ WLAN_IFNAMES=eth0
++ THREAD_IF=wpan0
+ . script/_dns64
++ BIND_CONF_OPTIONS=/etc/bind/named.conf.options
++ NAT64_PREFIX=64:ff9b::/96
++ DNS64_NAMESERVER_ADDR=127.0.0.1
+++ echo 64:ff9b::/96
+++ tr '"/"' '"/"'
++ DNS64_CONF='dns64 64:ff9b::/96 { clients { thread; }; recursive-only yes; };'
++ without NAT64
++ with NAT64
++ local value
+++ printenv NAT64
++ value=1
++ [[ -z 1 ]]
++ [[ 1 == 1 ]]
++ without DNS64
++ with DNS64
++ local value
+++ printenv DNS64
++ value=0
++ [[ -z 0 ]]
++ [[ 0 == 1 ]]
++ '[' ubuntu = raspbian ']'
++ '[' ubuntu = beagleboneblack ']'
++ '[' ubuntu = ubuntu ']'
++ RESOLV_CONF_HEAD=/etc/resolvconf/resolv.conf.d/head
+ . script/_firewall
++ FIREWALL_SERVICE=/etc/init.d/otbr-firewall
++ sudo modprobe ip6table_filter
sudo: modprobe: command not found
++ true
++ FIREWALL=1
+ OTBR_MDNS=mDNSResponder
+ OT_BACKBONE_CI=0
+ REFERENCE_DEVICE=0
+ main
+ [[ '' == \s\h\u\t\d\o\w\n ]]
+ startup
+ . /dev/null
+ sudo sysctl --system
* Applying /etc/sysctl.d/10-console-messages.conf ...
kernel.printk = 4 4 1 7
* Applying /etc/sysctl.d/10-ipv6-privacy.conf ...
net.ipv6.conf.all.use_tempaddr = 2
net.ipv6.conf.default.use_tempaddr = 2
* Applying /etc/sysctl.d/10-kernel-hardening.conf ...
kernel.kptr_restrict = 1
* Applying /etc/sysctl.d/10-link-restrictions.conf ...
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
* Applying /etc/sysctl.d/10-magic-sysrq.conf ...
kernel.sysrq = 176
* Applying /etc/sysctl.d/10-network-security.conf ...
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.tcp_syncookies = 1
* Applying /etc/sysctl.d/10-ptrace.conf ...
kernel.yama.ptrace_scope = 1
* Applying /etc/sysctl.d/10-zeropage.conf ...
vm.mmap_min_addr = 65536
* Applying /etc/sysctl.d/60-otbr-accept-ra.conf ...
net.ipv6.conf.eth0.accept_ra = 2
net.ipv6.conf.eth0.accept_ra_rt_info_max_plen = 64
* Applying /etc/sysctl.d/60-otbr-ip-forward.conf ...
net.ipv6.conf.all.forwarding = 1
net.ipv4.ip_forward = 1
* Applying /etc/sysctl.conf ...
+ nat64_start
+ with NAT64
+ local value
++ printenv NAT64
+ value=1
+ [[ -z 1 ]]
+ [[ 1 == 1 ]]
+ '[' openthread = tayga ']'
+ nat44_start
+ with DOCKER
+ local value
++ printenv DOCKER
+ value=1
+ [[ -z 1 ]]
+ [[ 1 == 1 ]]
+ service otbr-nat44 start
+ dns64_start
+ with NAT64
+ local value
++ printenv NAT64
+ value=1
+ [[ -z 1 ]]
+ [[ 1 == 1 ]]
+ with DNS64
+ local value
++ printenv DNS64
+ value=0
+ [[ -z 0 ]]
+ [[ 0 == 1 ]]
+ return 0
+ firewall_start
+ with FIREWALL
+ local value
++ printenv FIREWALL
+ value=1
+ [[ -z 1 ]]
+ [[ 1 == 1 ]]
+ with DOCKER
+ local value
++ printenv DOCKER
+ value=1
+ [[ -z 1 ]]
+ [[ 1 == 1 ]]
+ service otbr-firewall start
+ case "$1" in
+ firewall_start
+ firewall_stop
+ ip6tables -C FORWARD -o wpan0 -j OTBR_FORWARD_INGRESS
ip6tables v1.6.1: Couldn't load target `OTBR_FORWARD_INGRESS':No such file or directory

Try `ip6tables -h' or 'ip6tables --help' for more information.
+ ip6tables -L OTBR_FORWARD_INGRESS
ip6tables: No chain/target/match by that name.
+ ipset_destroy_if_exist otbr-ingress-deny-src
+ ipset list otbr-ingress-deny-src
ipset v6.34: Kernel support protocol versions 6-7 while userspace supports protocol versions 6-6
The set with the given name does not exist
+ ipset_destroy_if_exist otbr-ingress-deny-src-swap
+ ipset list otbr-ingress-deny-src-swap
ipset v6.34: Kernel support protocol versions 6-7 while userspace supports protocol versions 6-6
The set with the given name does not exist
+ ipset_destroy_if_exist otbr-ingress-allow-dst
+ ipset list otbr-ingress-allow-dst
ipset v6.34: Kernel support protocol versions 6-7 while userspace supports protocol versions 6-6
The set with the given name does not exist
+ ipset_destroy_if_exist otbr-ingress-allow-dst-swap
+ ipset list otbr-ingress-allow-dst-swap
ipset v6.34: Kernel support protocol versions 6-7 while userspace supports protocol versions 6-6
The set with the given name does not exist
+ ipset create -exist otbr-ingress-deny-src hash:net family inet6
+ ipset create -exist otbr-ingress-deny-src-swap hash:net family inet6
+ ipset create -exist otbr-ingress-allow-dst hash:net family inet6
+ ipset create -exist otbr-ingress-allow-dst-swap hash:net family inet6
+ ip6tables -N OTBR_FORWARD_INGRESS
+ ip6tables -I FORWARD 1 -o wpan0 -j OTBR_FORWARD_INGRESS
+ ip6tables -A OTBR_FORWARD_INGRESS -m pkttype --pkt-type unicast -i wpan0 -j DROP
+ ip6tables -A OTBR_FORWARD_INGRESS -m set --match-set otbr-ingress-deny-src src -j DROP
+ ip6tables -A OTBR_FORWARD_INGRESS -m set --match-set otbr-ingress-allow-dst dst -j ACCEPT
+ ip6tables -A OTBR_FORWARD_INGRESS -m pkttype --pkt-type unicast -j DROP
+ ip6tables -A OTBR_FORWARD_INGRESS -j ACCEPT
+ start_service rsyslog
+ local service_name=rsyslog
+ [[ 0 == 1 ]]
+ [[ 1 == 1 ]]
+ sudo service rsyslog status
 * rsyslogd is not running
+ sudo service rsyslog start
 * Starting enhanced syslogd rsyslogd                                                                                                                      [fail] 
+ start_service dbus
+ local service_name=dbus
+ [[ 0 == 1 ]]
+ [[ 1 == 1 ]]
+ sudo service dbus status
 * dbus is not running
+ sudo service dbus start
 * Starting system message bus dbus                                                                                                                        [ OK ] 
+ [[ mDNSResponder == \a\v\a\h\i ]]
+ [[ 0 == 1 ]]
+ [[ 0 == 1 ]]
+ have service
+ command -v service
+ sudo service mdns status
Usage: /etc/init.d/mDNS {start|stop|reload|restart}
+ sudo service mdns start
Starting Apple Darwin Multicast DNS / DNS Service Discovery daemon: mdnsd.
mDNSResponder: Default: mDNSResponder (Engineering Build) (Jan 22 2025 15:22:37) starting
+ without WEB_GUI
+ with WEB_GUI
+ local value
++ printenv WEB_GUI
+ value=1
+ [[ -z 1 ]]
+ [[ 1 == 1 ]]
+ start_service otbr-web
+ local service_name=otbr-web
+ [[ 0 == 1 ]]
+ [[ 1 == 1 ]]
+ sudo service otbr-web status
 * otbr-web is not running
+ sudo service otbr-web start
 * Starting thread web interface otbr-web                                                                                                                  [ OK ] 
otbr-web[168]: [INFO]-WEB-----: Running 0.3.0-b510577
+ start_service otbr-agent
+ local service_name=otbr-agent
otbr-web[168]: [INFO]-WEB-----: Border router web started on wpan0
+ [[ 0 == 1 ]]
+ [[ 1 == 1 ]]
+ sudo service otbr-agent status
otbr-web[168]: [ERR ]-WEB-----: OpenThread daemon is not running.
 * otbr-agent is not running
+ sudo service otbr-agent start
 * Starting thread border agent otbr-agent                                                                                                                 [ OK ] 
+ . /dev/null
otbr-agent[195]: [NOTE]-AGENT---: Running 0.3.0-b510577
otbr-agent[195]: [NOTE]-AGENT---: Thread version: 1.4.0
otbr-agent[195]: [NOTE]-AGENT---: Thread interface: wpan0
otbr-agent[195]: [NOTE]-AGENT---: Radio URL: spinel+hdlc+uart:///dev/ttyMyStick
otbr-agent[195]: [NOTE]-ILS-----: Infra link selected: eth0
otbr-agent[195]: [INFO]-RCP_HOS-: OpenThread log level changed to 5
otbr-agent[195]: 49d.18:13:26.467 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:0, cmd:RESET
otbr-agent[195]: 49d.18:13:26.467 [D] P-SpinelDrive-: Waiting response: key=0
otbr-agent[195]: 49d.18:13:28.469 [W] P-SpinelDrive-: Wait for response timeout
otbr-agent[195]: 49d.18:13:28.469 [I] P-SpinelDrive-: co-processor self reset successfully
otbr-agent[195]: 49d.18:13:28.469 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:1, cmd:PROP_VALUE_GET, key:PROTOCOL_VERSION
otbr-agent[195]: 49d.18:13:28.469 [D] P-SpinelDrive-: Waiting response: key=1
otbr-agent[195]: 49d.18:13:30.471 [W] P-SpinelDrive-: Wait for response timeout
otbr-agent[195]: 49d.18:13:30.471 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:1, cmd:PROP_VALUE_GET, key:PROTOCOL_VERSION
otbr-agent[195]: 49d.18:13:30.472 [D] P-SpinelDrive-: Waiting response: key=1
otbr-agent[195]: 49d.18:13:32.474 [W] P-SpinelDrive-: Wait for response timeout
otbr-agent[195]: 49d.18:13:32.474 [C] Platform------: Init() at spinel_driver.cpp:83: Failure
otbr-agent[195]: 49d.18:13:32.474 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:1, cmd:PROP_VALUE_GET, key:PROTOCOL_VERSION
otbr-agent[195]: 49d.18:13:32.474 [D] P-SpinelDrive-: Waiting response: key=1
otbr-agent[195]: 49d.18:13:34.476 [W] P-SpinelDrive-: Wait for response timeout

The web server doesn’t seem to be running.
I have flushed the OpenThread (RCP) SL-OPENTHREAD/2.4.4.0_GitHub-7074a43e4 firmware on the SLZB-07mg24.

Would appreciate some input here on how to proceed. Once this works I will be trying to connect it to the Home Assistant.

I already replied to your other thread on this topic, but note that if you run OTBR with “network_mode=host” you don’t need the sysctl and port settings, not to mention avoiding other IPv6 routing/mDNS issues if Matter server is running in a different container on the same host. This specific error appears to be related to accessing the USB stick. Again see my other post for specifying a baud rate as part of the radio-url and see if that helps.

Your port mapping is backward. You are mapping HOST:80 to CONTAINER:8888

You should give this image a try instead. It includes the HA tweaks found in the add on container without requiring HAOS.

There is also a post I made detailing my struggles to run OTBR as a container and getting it to work with HA docker.

1 Like

The problem was with the image. The one I was using was only exposing web service. Doesn’t seem like it has the REST APIs running. Or at least I couldn’t make that happen. The image you provided really did the trick! Thanks a lot!

I could add the Thread and Matter integrations. Here is what my config now looks like.

The question is, now what do I do now? :smiley: Should I connect Matter add=on to Thread?

services:

  homeassistant:
    container_name: homeassistant
    image: "ghcr.io/home-assistant/home-assistant:stable"
    volumes:
      - /home/ubuntu/home-assistant/config:/config
      - /etc/localtime:/etc/localtime:ro
      - /run/dbus:/run/dbus:ro
      - /dev/serial/by-id:/dev/serial/by-id:ro
    restart: unless-stopped
    privileged: true
    network_mode: host

  otbr:
    hostname: otbr
    container_name: otbr
    image: "ghcr.io/ownbee/hass-otbr-docker:latest"
    privileged: true
    network_mode: host
    cap_add:
      - SYS_ADMIN
      - NET_ADMIN    
    environment:
      DEVICE: "/dev/ttyUsbTread"
      BACKBONE_IF: wlp0s20f3 #enp3s0
      FLOW_CONTROL: 1
      FIREWALL: 1
      NAT64: 1
      BAUDRATE: 460800
      OTBR_REST_PORT: 8081
      OTBR_WEB_PORT: 7586
      AUTOFLASH_FIRMWARE: 0      
    devices:
      - /dev/serial/by-id/usb-SMLIGHT_SMLIGHT_SLZB-07Mg24_6add7a39f772ed118f6073773dbf42d5-if00-port0:/dev/ttyUsbTread
      - /dev/net/tun:/dev/net/tun
    volumes:
      - ./openthread/data:/var/lib/thread

  matter-server:
    container_name: matter-server
    image: ghcr.io/home-assistant-libs/python-matter-server:stable
    restart: unless-stopped
    security_opt:
      - apparmor=unconfined
    volumes:
      - /home/ubuntu/home-assistant/addons/matter-server:/data
      - /run/dbus:/run/dbus:ro
    network_mode: host

This was my abbreviated setup checklist

:white_check_mark: Run and have access to OTBR WEB

:white_check_mark: Correctly recognize RCP dongle

:white_check_mark: Autodiscover Thread after enabling the OTBR integration http://localhost:8081

:white_check_mark: Have HA create a new preferred thread network & enable “used for Android + IOS credentials

:white_check_mark: Run python-matter-server and enable matter integration in HA ws://localhost:5580/ws

:white_check_mark: Sync Thread Credentials on mobile device
Settings > Companion App > Troubleshooting > Sync Thread credentials
Run sync twice, the message should read “Home Assistant and this device use the same network

:white_check_mark: Device Pairing
Add device via HA Companion App Settings > Devices & services > Devices tab > Add Device button > Add Matter device

1 Like

This is awesome! I would have never figured this out…

@D34DC3N73R, the OTBR crashed overnight. Is this something familiar?

13:03:23.187 [C] Platform------: ------------------ BEGINNING OF CRASH -------------
13:03:23.187 [C] Platform------: *** FATAL ERROR: Caught signal: 6 (Aborted)
otbr-agent: /usr/src/ot-br-posix/src/mdns/mdns.cpp:232: void otbr::Mdns::Publisher::OnServiceResolved(std::string, DiscoveredInstanceInfo): Assertion `aInstanceInfo.mNetifIndex > 0' failed.
13:03:23.193 [C] Platform------: # 0: /usr/sbin/otbr-agent(+0x35c3c9) [0x59e00e9f03c9]
13:03:23.193 [C] Platform------: # 1: /usr/sbin/otbr-agent(+0x35c4f4) [0x59e00e9f04f4]
13:03:23.193 [C] Platform------: # 2: /lib/x86_64-linux-gnu/libc.so.6(+0x3c050) [0x783fabcf4050]
13:03:23.193 [C] Platform------: # 3: /lib/x86_64-linux-gnu/libc.so.6(+0x8aebc) [0x783fabd42ebc]
13:03:23.193 [C] Platform------: # 4: /lib/x86_64-linux-gnu/libc.so.6 gsignal+0x12 [0xabcf3fb2]
13:03:23.193 [C] Platform------: # 5: /lib/x86_64-linux-gnu/libc.so.6 abort+0xd3 [0xabcde472]
13:03:23.193 [C] Platform------: # 6: /lib/x86_64-linux-gnu/libc.so.6(+0x26395) [0x783fabcde395]
13:03:23.193 [C] Platform------: # 7: /lib/x86_64-linux-gnu/libc.so.6(+0x34eb2) [0x783fabceceb2]
13:03:23.194 [C] Platform------: # 8: /usr/sbin/otbr-agent otbr::Mdns::Publisher::OnServiceResolved(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, otbr::Mdns::Publisher::DiscoveredInstanceInfo)+0x284 [0xe8c899c]
13:03:23.194 [C] Platform------: # 9: /usr/sbin/otbr-agent otbr::Mdns::Publisher::OnServiceRemoved(unsigned int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)+0x105 [0xe8c8cb9]
13:03:23.194 [C] Platform------: #10: /usr/sbin/otbr-agent otbr::Mdns::PublisherMDnsSd::ServiceSubscription::HandleBrowseResult(_DNSServiceRef_t*, unsigned int, unsigned int, int, char const*, char const*, char const*)+0x243 [0xe8e0e0b]
13:03:23.194 [C] Platform------: #11: /usr/sbin/otbr-agent otbr::Mdns::PublisherMDnsSd::ServiceSubscription::HandleBrowseResult(_DNSServiceRef_t*, unsigned int, unsigned int, int, char const*, char const*, char const*, void*)+0x46 [0xe8e0bc0]
13:03:23.194 [C] Platform------: #12: /lib/libdns_sd.so(+0x5d02) [0x783fac1a6d02]
13:03:23.194 [C] Platform------: #13: /lib/libdns_sd.so DNSServiceProcessResult+0x36a [0xac1a595d]
13:03:23.194 [C] Platform------: #14: /usr/sbin/otbr-agent otbr::Mdns::PublisherMDnsSd::Process(otbr::MainloopContext const&)+0x2ee [0xe8de04a]
13:03:23.194 [C] Platform------: #15: /usr/sbin/otbr-agent otbr::MainloopManager::Process(otbr::MainloopContext const&)+0x6e [0xea22f26]
13:03:23.194 [C] Platform------: #16: /usr/sbin/otbr-agent otbr::Application::Run()+0x1cb [0xe8b2817]
13:03:23.194 [C] Platform------: #17: /usr/sbin/otbr-agent(+0x222ca4) [0x59e00e8b6ca4]
13:03:23.194 [C] Platform------: #18: /usr/sbin/otbr-agent main+0x92 [0xe8b6f62]
13:03:23.194 [C] Platform------: #19: /lib/x86_64-linux-gnu/libc.so.6(+0x2724a) [0x783fabcdf24a]
13:03:23.194 [C] Platform------: #20: /lib/x86_64-linux-gnu/libc.so.6 __libc_start_main+0x85 [0xabcdf305]
13:03:23.194 [C] Platform------: #21: /usr/sbin/otbr-agent _start+0x21 [0xe8b2171]
13:03:23.194 [C] Platform------: ------------------ END OF CRASH ------------------

@D34DC3N73R, one more thing. When setting up Thread network what security considerations should be made? I believe I used all default settings from the OTBR web ui. That probably is not the best choice.

Do I just go there into the web UI to “form” a new network? What else needs to be changed there in HA for the settings to take effect?

I’ve never had the crash you experienced with that image. Might be worth creating a github issue. Or just restart the container and see if you experience it again.

In regards to creating a thread network, I would recommend letting HA take care of that. If the thread integration shows a network from OTBR, choose the “reset network” option and it’ll be configured for you. After the network is loaded (takes a minute or two to go through the reset process), then choose the “Make Preferred Network” and the “Use for Android + IOS Credentials” option. Then you’ll be ready to sync credentials in the companion app.

I’ll submit to the OTBR project.

Resetting through HA worked. Although I do not see any other indication of the completion. What would be the place to confirm the Thread network parameters?

You can click on the (i) icon and see the parameters. You won’t really need any of them though. Just set it as the preferred network in the thread integration and choose “use for android/ios credentials”. That’s pretty much it. Afterwards sync credentials in the companion app and you’ll be ready to pair a device.

Got it. Will do.

With matter is it possible to discover all nearby devices without scanning QR codes? Basically, can I get the guys to install the switches and use them as “dumb switches” before I bring in the HA and have them discovered?

Not that I’m aware of. As far as I know, if you don’t have the QR or 11-digit numerical code, the devices are not possible to pair. If the guys are willing, my advice would be to get a label maker or write the numerical codes on the inside of the faceplate when installing. They should work as dumb switches until paired.

So I should first add the devices before plugging them into the walls? Would you suggest taking the photos and maintaining the QR codes for my records in case I would need to recreate the network?

What type of devices do you have? If they are receptacles/outlets or light switches that require installation, meaning you will not easily be able to see the code printed on the device after install, I would suggest noting where the receptacle or switch is located, and the 11-digit numerical pairing code. You wouldn’t be able to add these devices until they have power. That’s why it makes sense to write the code or affix a label of the code on the inside of the faceplate in these circumstances. It saves you from having to uninstall the switch/receptacle to get the code (should you ever need it again) and then reinstall.

If they are smart plugs you can just pair them as you plug them in or anytime after. I haven’t seen a matter over thread device that doesn’t have the code printed on the device itself, but it is possible such devices exist.

@D34DC3N73R, those would be Inovelli white switches.

I also have Z-Wave Shelly plugs that area also going behind the plate. Is it the same case with Z-Wave, right?