Pairing Matter over Thread devices (Ikea et al) using HW Conbee II or III

Again an update as my Thread network grows (I live 5min away from Ikea :slight_smile: )
Stability is such that I will start using the devices now.

Newest addition was 2 Grillplats (BBQ-place :slight_smile: ) power plugs with remotes:


These, as mains powered devices, are adding more routers to the network:


Up to now, all other devices have had the 11 digit pairing code printed on the device itself together with a QR code which I have not used.
The Grillplats, however, have no 11 digit pairing code printed on the device itself, only a QR code. It was provided on a leaflet in the box, but that is risky if lost.

So, this is the way I was able to pair, using just the QR code. AI was helping along the way.
My goal was to not rely on any other smart house apps, so using an Android phone, scanning the QR code using the Binary Eye App the MT code was retrieved.

This can be parsed as follow:

omr@shs3:~$ chip-tool payload parse-setup-payload MT:EX7A042C00VL8P4PR00
[1774814657.503] [446921:446921] [DL] ChipLinuxStorage::Init: Using KVS config file: /home/omr/snap/chip-tool/common/chip_tool_kvs
[1774814657.503] [446921:446921] [SPL] Parsing base38Representation: MT:EX7A042C00VL8P4PR00
[1774814657.503] [446921:446921] [SPL] Version:             0
[1774814657.503] [446921:446921] [SPL] VendorID:            4476
[1774814657.503] [446921:446921] [SPL] ProductID:           32769
[1774814657.503] [446921:446921] [SPL] Custom flow:         0    (STANDARD)
[1774814657.503] [446921:446921] [SPL] Discovery Bitmask:   0x02 (BLE)
[1774814657.503] [446921:446921] [SPL] Long discriminator:  2472   (0x9a8)
[1774814657.503] [446921:446921] [SPL] Passcode:            34458161
omr@shs3:~$

Then, using this AI generated script:

omr@shs3:~$ ./pairnode-8-11.sh 34458161 2472
[+] Reading active Thread dataset from OTBR...
[+] Step 1: Commissioning to OTBR via BLE-Thread...
+ chip-tool pairing ble-thread 109 hex:0e08000000000001000035060004001fffe00708fd04f2301503538f0c0402a0f7f805101212abba1212abba0987654321060362030e4f70656e5468726561644754563704106cc96549133017bd3ffe4913ea8d93d40003000017020847113991121206030102abba 34458161 2472 --bypass-attestation-verifier true
.
.
.
[1774742404.363] [321420:321420] [PAF] WiFiPAF: Closing all WiFiPAF sessions to shutdown
[1774742404.363] [321420:321420] [DL] System Layer shutdown
[+] SUCCESS: Pairing code 23223234242 saved to /home/omr/.matter-pairing-history.log
[+] COMPLETE! Check /home/omr/.matter-pairing-history.log for your openHAB code.
omr@shs3:~$

Note the pairing code to use in openHAB, good for 15min.

The script will work with either the 11 digit pairing code or the passcode+discriminator:

omr@shs3:~$ ./pairnode-8-11.sh
Usage (8-digit):  ./pairnode-8-11.sh <8-digit PIN> <Discriminator> [Node ID]
Usage (11-digit): ./pairnode-8-11.sh <11-digit PIN> [Node ID]

The script:

omr@shs3:~$ cat ./pairnode-8-11.sh
#!/usr/bin/env bash
set -euo pipefail

###############################################################################
# Matter-over-Thread pairing helper (Final Version - v2.0)
# Auto-detects 8-digit Passcode (ble-thread) vs 11-digit Manual Code (code-thread).
###############################################################################

# --- Configuration & Variables ---
PIN=""
DISCRIMINATOR=""
NODE_ID=""
ENDPOINT="1"
CWIN_SECS="900"
ITERATOR="1000"

CHIP_TOOL="chip-tool"
OT_CTL="sudo openthread-border-router.ot-ctl"

BYPASS_ATTESTATION="true"
REQUIRE_PAIRMODE="yes"
DO_JOINER_STEP="yes"
JOINER_PSKD="J01NME"

REGISTRY_FILE="${HOME}/.matter-node-registry.tsv"
HISTORY_LOG="${HOME}/.matter-pairing-history.log"

# --- Helper Functions ---
log()  { printf "\033[1;37m[+]\033[0m %s\n" "$*" >&2; }
warn() { printf "\033[1;33m[!]\033[0m %s\n" "$*"; }
err()  { printf "\033[1;31m[x]\033[0m %s\n" "$*"; }
die()  { err "$*"; exit 1; }

need_cmd() {
  command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1"
}

validate_inputs() {
    PIN=$(echo "$PIN" | tr -d ' -')

    if [[ "$PIN" =~ ^[0-9]{11}$ ]]; then
        CODE_TYPE="MANUAL_11"
        log "Detected 11-digit Manual Code. Discriminator is not required."
    elif [[ "$PIN" =~ ^[0-9]{8}$ ]]; then
        CODE_TYPE="PASSCODE_8"
        [[ -n "$DISCRIMINATOR" ]] || die "8-digit Passcode requires a Discriminator (e.g. 2540)."
    else
        die "PIN must be 8 or 11 digits (got: '$PIN')."
    fi

    [[ "$NODE_ID" =~ ^[0-9]+$ ]] || die "NODE_ID must be a decimal integer."
}

get_dataset_hex() {
  log "Reading active Thread dataset from OTBR..."
  local raw_output
  raw_output="$($OT_CTL dataset active -x 2>/dev/null)"
  local ds
  ds=$(echo "$raw_output" | sed -n '1p' | tr -d '\r\n[:space:]')
  [[ "$ds" =~ ^[0-9a-fA-F]+$ ]] || die "Dataset hex invalid. Check OTBR status."
  echo "$ds"
}

get_next_node_id() {
  local start_id=100
  if [[ ! -f "$REGISTRY_FILE" ]]; then echo "$start_id"; return; fi
  local max_id
  max_id=$(awk -F'\t' '{print $2}' "$REGISTRY_FILE" | sort -n | tail -1)
  [[ -z "$max_id" ]] && echo "$start_id" || echo $((max_id + 1))
}

pair_device() {
  local dataset_hex="$1"
  local bypass_flag=""
  [[ "$BYPASS_ATTESTATION" == "true" ]] && bypass_flag="--bypass-attestation-verifier true"

  if [[ "$CODE_TYPE" == "MANUAL_11" ]]; then
    log "Step 1: Commissioning via code-thread (11-digit manual code)..."
    set -x
    "$CHIP_TOOL" pairing code-thread "$NODE_ID" "hex:${dataset_hex}" "$PIN" $bypass_flag
    set +x
  else
    log "Step 1: Commissioning via ble-thread (8-digit passcode)..."
    set -x
    "$CHIP_TOOL" pairing ble-thread "$NODE_ID" "hex:${dataset_hex}" "$PIN" "$DISCRIMINATOR" $bypass_flag
    set +x
  fi
}

open_commissioning_window() {
  log "Step 2: Opening commissioning window for openHAB..."
  # For the window itself, we use a fallback discriminator if none was provided
  local window_disc="${DISCRIMINATOR:-3840}"

  local out
  out=$("$CHIP_TOOL" pairing open-commissioning-window "$NODE_ID" "$ENDPOINT" "$CWIN_SECS" "$ITERATOR" "$window_disc" 2>&1 | tee /dev/stderr)

  local setup_code
  setup_code=$(echo "$out" | grep -oP 'Manual pairing code: \[\K[0-9]+(?=\])' || echo "NOT_FOUND")

  if [[ "$setup_code" != "NOT_FOUND" ]]; then
    local ts; ts=$(date "+%Y-%m-%d %H:%M:%S")
    echo "[$ts] Node $NODE_ID (PIN: $PIN) -> openHAB Setup Code: $setup_code" >> "$HISTORY_LOG"
    log "SUCCESS: Pairing code $setup_code saved to $HISTORY_LOG"
  else
    warn "Could not auto-extract code. Check output manually."
  fi
}

main() {
  if [ $# -lt 1 ]; then
    echo "Usage (8-digit):  $0 <8-digit PIN> <Discriminator> [Node ID]"
    echo "Usage (11-digit): $0 <11-digit PIN> [Node ID]"
    exit 1
  fi

  PIN="$1"

  # Logic to handle shifts in arguments if 11-digit code is used
  if [[ $(echo "$1" | tr -d ' -' | wc -c) -gt 10 ]]; then
      # 11-digit path: Discriminator is not needed, $2 becomes Node ID
      NODE_ID="${2:-$(get_next_node_id)}"
  else
      # 8-digit path: $2 is Discriminator, $3 is Node ID
      DISCRIMINATOR="${2:-}"
      NODE_ID="${3:-$(get_next_node_id)}"
  fi

  validate_inputs
  need_cmd "$CHIP_TOOL"

  local ds; ds="$(get_dataset_hex)"
  pair_device "$ds"

  printf "%s\t%s\t%s\tcommissioned\n" "$(date -Iseconds)" "$NODE_ID" "${PIN: -4}" >> "$REGISTRY_FILE"
  open_commissioning_window

  log "COMPLETE! Check $HISTORY_LOG for your openHAB code."
}

main "$@"
omr@shs3:~$

Hope this can be useful for somebody else as well.

Cheers.