# 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](#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; for any vendor with a catalog the product ID is also resolved to a name from `products.tsv` (and, via `vendor-equivalents.tsv`, an equivalent vendor's catalog) when available | | **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 | | **Pairing Code** | The Matter *manual pairing code* — the PIN a commissioning app (Apple Home, Google Home, the Matter app) asks you to type. Not a raw field: derived from the passcode, discriminator and (for non-standard flows) the vendor/product IDs, then guarded by a Verhoeff check digit. 11 digits for the standard flow, 21 for user-intent/custom | ## Components | File | What it is | |---|---| | [`matter-qr-decoder.html`](matter-qr-decoder.html) | The browser app. Single self-contained HTML file (Vue 3 + Bootstrap 5.3, loaded from CDNs). | | [`matter-decode.sh`](matter-decode.sh) | A pure-bash CLI port of the same decode logic. No camera scanning. | | [`manufacturers.tsv`](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`](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). | | [`products.tsv`](products.tsv) | Tab-separated `vendor ID → product ID → name` table (3 columns) used by `matter-decode.sh` to turn a product ID into a name, for any vendor that has a catalog (e.g. **LIFX**, vendor `0x1306` / `4870`). Optional. | | [`vendor-equivalents.tsv`](vendor-equivalents.tsv) | Tab-separated `alias vendor ID → canonical vendor ID` table recording companies that are the same after a merger/acquisition (e.g. Feit `5155` = LIFX `4870`), so a product resolves under either ID. Hand-edited; optional. | | [`gen-lifx-products.py`](gen-lifx-products.py) | Python 3 script that (re)generates LIFX's rows in `products.tsv`, merging them in from LIFX's upstream `products.json` over the network by default (or a URL/local file); preserves other vendors' rows. | `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://`: ```sh 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: ```sh ./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 Pairing Code: 549080049513012506168 ``` (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. For any vendor that has a catalog in `products.tsv` (e.g. **LIFX**, vendor ID `4870` / `0x1306`) the script additionally resolves the **product ID** to a name, printed on the Product ID line just like the vendor name on the Vendor ID line. When a vendor has an **equivalent** in `vendor-equivalents.tsv` (a company merged with or acquired — e.g. Feit `5155` = LIFX `4870`), the lookup also checks the equivalent's catalog and the Vendor ID line is annotated, so either company's device resolves to the shared product name: ``` $ echo "MT:R5QU6X.D01M3QG5.000" | ./matter-decode.sh Version: 1 Vendor ID: 4870 (0x1306) Lifi Labs INC. dba LIFX (a.k.a. Feit Electric Company, Inc.) Product ID: 123 (0x007B) LIFX Mini 3.1 Color US ``` Both files are optional, like `manufacturers.tsv`: without `products.tsv` the product name is omitted, and without `vendor-equivalents.tsv` no equivalent is considered — but every field still decodes. --- ## 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`: ```sh 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: ```sh python3 gen-manufacturers.py https://.../manufacturers.xml python3 gen-manufacturers.py ./manufacturers.xml ``` No network? Use the embedded block as the source instead: ```sh python3 gen-manufacturers.py matter-qr-decoder.html ``` The same table is also embedded as a `` 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 `