2026-08-23 20:48:38 -04:00
2026-08-23 20:48:38 -04:00
2026-08-23 20:48:38 -04:00
2026-08-23 20:48:38 -04:00
2026-08-23 20:48:38 -04:00
2026-08-23 20:48:38 -04:00
2026-08-23 20:48:38 -04:00
2026-08-23 20:48:38 -04:00

Matter QR Code Decoder

Decode the commissioning QR code that Matter / CHIP devices ship with. A Matter device's setup QR encodes a short MT: URI that carries the information a commissioning app (e.g. Apple Home, Google Home, the Matter app) needs to pair with the device for the first time. This tool takes that URI and shows you what is inside.

No build, no install, no package step. Two ways to use it: a browser app and a command-line script that share the same decode logic.

What it decodes

Given an MT: URI, the payload (84 bits, see Data format) is split into these fields:

Field Meaning
Version Encoding version (3 bits)
Vendor ID 16-bit manufacturer identifier, shown as decimal + 0x-hex, with a name looked up from manufacturers.tsv when available
Product ID 16-bit vendor-assigned product identifier, decimal + 0x-hex
Custom Flow Commissioning flow type: 0 = Standard, 1 = User Intent, 2 = Custom, 3 = Reserved
Discovery How the device can be found: SoftAP / BLE / On IP Network (a bitset — a device may show more than one)
Discriminator 12-bit discriminator
Passcode 27-bit commissioning passcode

Components

File What it is
matter-qr-decoder.html The browser app. Single self-contained HTML file (Vue 3 + Bootstrap 5.3, loaded from CDNs).
matter-decode.sh A pure-bash CLI port of the same decode logic. No camera scanning.
manufacturers.tsv Tab-separated vendor ID → name table (~900 rows) used to turn a numeric vendor ID into a name. Shared by both tools; optional.
gen-manufacturers.py Python 3 script that regenerates manufacturers.tsv; pulls the upstream manufacturers.xml over the network by default (or a URL/local file/HTML block you pass in).

CLAUDE.md documents the internals for anyone (or Claude Code) working on the code.


Browser app

Requirements

  • A modern browser with network access (the page loads Vue 3, Bootstrap 5.3, and Bootstrap Icons from CDNs).

  • Camera scanning needs a secure context — serve it over HTTPS or localhost, not file://:

    python3 -m http.server 8000
    # then open http://localhost:8000/matter-qr-decoder.html
    

Use it

  1. Paste an MT: URI into the Input box — decoding happens live as you type.
  2. Or click Scan QR and point your camera at the code. Scanning stops itself the moment it reads an MT: QR.

Two caveats:

  • Scan QR is Chromium-only (Chrome / Edge). It uses the BarcodeDetector API. Firefox and Safari show a "not supported" message — pasting a URI works everywhere, so use that on those browsers.
  • The Data Encoding Format and Discovery Capabilities Bits sections at the bottom of the page explain the layout shown in the output.

Command line

matter-decode.sh decodes a URI from an argument or from stdin and prints the same fields as the web UI:

./matter-decode.sh "MT:..."
echo "MT:..." | ./matter-decode.sh

Example output (run the URI through the script to reproduce):

$ echo "MT:O083635783973181337368400343813154179" | ./matter-decode.sh
Version:      0
Vendor ID:    13012 (0x32D4)
Product ID:    50616 (0xC5B8)
Custom Flow:  User Intent Commissioning Flow
Discovery:    BLE
Discriminator:1723
Passcode:      8126392

(The vendor ID 0x32D4 isn't in manufacturers.tsv, so no name is printed — that's expected, not an error.)

The script is pure bash (3.x+). It reads manufacturers.tsv from its own directory to resolve the vendor name; without that file every field still decodes and only the vendor name is omitted.


Data format

The URI is MT: followed by a base38 payload (charset 0-9 A-Z - ., 38 symbols). The payload decodes to bytes, read little-endian, then sliced into the 84-bit field layout LSB-first:

version(3) | vendor ID(16) | product ID(16) | custom flow(2)
           | discovery caps(8) | discriminator(12) | passcode(27)

Base38 decoding is greedy: 5 chars → 3 bytes, 4 → 2, 2 → 1; a length that fits none of those groupings is an error. Both the HTML app and the shell script use this same scheme, which is why the two stay in lockstep.

Vendor name lookup

Both tools map a numeric Vendor ID to a name using manufacturers.tsv (vendor ID in the first column, name in the second). The table is derived from upstream project-chip/connectedhomeip .../zcl/data-model/manufacturers.xml (Apache-2.0, generated 2022-08-02). Refresh it from upstream rather than hand-editing individual rows.

To refresh the table, run gen-manufacturers.py (Python 3 standard library only). By default it pulls the upstream manufacturers.xml over the network (via curl) and writes the sorted two-column TSV (decimal id \t name) to manufacturers.tsv:

python3 gen-manufacturers.py

The source is pinned to the same commit the HTML block was generated from, so a fresh pull reproduces the current manufacturers.tsv exactly. To refresh from a different upstream revision, pass a source — a URL (a GitHub blob/ URL is auto-rewritten to its raw.githubusercontent.com form) or a local XML file:

python3 gen-manufacturers.py https://.../manufacturers.xml
python3 gen-manufacturers.py ./manufacturers.xml

No network? Use the embedded block as the source instead:

python3 gen-manufacturers.py matter-qr-decoder.html

The same table is also embedded as a <map> XML block near the end of matter-qr-decoder.html (marked "do not edit manually"). This script regenerates only the TSV, so if you refresh the TSV from new upstream XML, also paste that XML into the HTML's <script type="text/xml"> block to keep the two copies in sync.

License

The vendor mapping is derived from Matter/CHIP's manufacturers.xml, which is Apache-2.0. The app itself is a small single-file utility.

S
Description
A tool to decode matter and homekit QR code payloads.
Readme
136 KiB
0 Stars 1 Watchers 0 Forks
Languages
Shell 60.7%
Python 39.3%