Updated to be pure bash and remove references to the web pages.

This commit is contained in:
Scott Garrett committed 2026-09-01 17:40:24 -04:00
1 parent 589ebaff46
commit f56678833e
7 files changed
+133 -1719

No files matched your search

+1
View File
@@ -1 +1,2 @@
.DS_Store
__pycache__
+109 -146
View File
@@ -7,136 +7,121 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
A collection of Matter commissioning QR decoders (the `MT:` URI format used by
Matter/CHIP devices), plus a standalone bash decoder for Apple **HomeKit**
setup codes (the `X-HM://` payload — a different on-wire format, in
`homekit-decode.sh`). For Matter, users either paste an `MT:` URI or scan a QR
code; the result shows the decoded payload (version, vendor/product IDs,
commissioning flow, discovery capabilities, discriminator, passcode). Key files:
`homekit-decode.sh`). For Matter, a user runs `matter-decode.sh` with an `MT:`
URI (as a positional argument or on stdin); it decodes the 84-bit payload and
prints the fields — version, vendor/product IDs, commissioning flow, discovery
capabilities, discriminator, passcode, and the derived manual pairing code. Key
files:
- `matter-qr-decoder.html` — the single-file browser app (Vue 3 + Bootstrap).
- `matter-decode.sh` — a pure-bash CLI port of the same decode logic (no camera
scanning; takes a URI as an argument or on stdin).
- `matter-decode.sh` — the pure-bash CLI Matter decoder. Takes an `MT:` URI as an
argument or on stdin and prints the decoded fields. It has no camera/QR-scanning
(that was the only feature the former browser app added, and it is gone).
- `homekit-decode.sh` — a standalone pure-bash decoder for Apple **HomeKit**
setup codes (the `X-HM://` payload an accessory shows on first setup — a
*different* format from Matter's `MT:`). No browser counterpart and no
companion TSV (the accessory-category table is inlined). See the
HomeKit setup-code decode section below.
- `manufacturers.tsv` — `vendor ID → name` table (~900 rows), shared by both
tools and optional (a missing file only drops the vendor-name lookup).
*different* format from Matter's `MT:`). No companion TSV (the accessory-category
table is inlined). See the HomeKit setup-code decode section below.
- `manufacturers.tsv` — `vendor ID → name` table (~900 rows), consumed by
`matter-decode.sh`; optional (a missing file only drops the vendor-name lookup).
- `gen-manufacturers.py` — Python 3 stdlib script that regenerates
`manufacturers.tsv`. Default source is the upstream `manufacturers.xml`
**fetched over the network** (via `curl`; a GitHub `blob/` URL is auto-rewritten
to its `raw.githubusercontent.com` form). A positional arg overrides with
another URL or a local XML file; passing `matter-qr-decoder.html` uses the
embedded `<map>` block as an offline fallback. Reproduces the committed TSV
byte-for-byte.
another URL or a local XML file. Reproduces the committed TSV byte-for-byte.
- `products.tsv` — 3-column `vendor ID → product ID → name` table, consumed by
`matter-decode.sh`'s `get_product_name()`: when the decoded vendor ID (or an
`matter-decode.sh`'s `get_product_name()`: when the decoded vendor ID (or an
equivalent, see `vendor-equivalents.tsv`) is present, the product ID is
resolved to a name and printed on the Product ID line. LIFX's rows are keyed
under `4870`. Optional — a missing file only drops the product-name lookup.
under `4870`. Optional — a missing file only drops the product-name lookup.
- `vendor-equivalents.tsv` — 2-column `alias vendor ID → canonical vendor ID`
table, hand-edited, recording companies that are the same after a
table, hand-edited, recording companies that are the same after a
merger/acquisition (e.g. Feit `5155` → LIFX `4870`). Used by
`matter-decode.sh` so a product resolves under either ID and the vendor name
`matter-decode.sh` so a product resolves under either ID and the vendor name
is annotated `(a.k.a. ...)`. `#`-comment lines are ignored. Optional.
- `gen-lifx-products.py` — Python 3 stdlib script that regenerates LIFX's rows
(vendor `4870`) in `products.tsv`. Default source is LIFX's upstream
`products.json` **fetched over the network** (via `curl`, falling back to
`urllib`); a positional arg overrides with another URL or a local `.json`
file. It **merges** LIFX's rows into `products.tsv` (dropping LIFX's old
rows, preserving every other vendor's), so re-running it is safe alongside
other vendors' generators. Caveat: `products.json` carries only a `pid` per
product (no separate Matter/CHIP product-id field), so the lookup assumes
LIFX's Matter product ID equals their `pid`.
(vendor `4870`) in `products.tsv`. Default source is LIFX's upstream
`products.json` **fetched over the network** (via `curl`, falling back to
`urllib`); a positional arg overrides with another URL or a local `.json`
file. It **merges** LIFX's rows into `products.tsv` (dropping LIFX's old
rows, preserving every other vendor's), so re-running it is safe alongside
other vendors' generators. Caveat: `products.json` carries only a `pid` per
product (no separate Matter/CHIP product-id field), so the lookup assumes
LIFX's Matter product ID equals their `pid`.
- `README.md` — user-facing docs; this file documents the internals.
## Commands
There is **no build, test, lint, or package step** — this is not a git repo and has no
`package.json`. The entire app is one HTML file that runs in the browser:
There is **no build, test, lint, or package step** — this is not a build project.
The two decoders are standalone bash scripts and the two `gen-*.py` are Python 3
stdlib regenerators; nothing is compiled or bundled.
- Run/develop: open `matter-qr-decoder.html` in a modern browser and edit it directly.
- Camera scanning (`getUserMedia`) requires a **secure context** — serve over HTTPS or
`localhost` (e.g. `python3 -m http.server` then open `http://localhost:8000`).
- The "Scan QR" feature uses the `BarcodeDetector` API, which is **Chromium-only**
(Chrome/Edge). Firefox/Safari fall back to a "not supported" message; paste-input works everywhere.
- The page loads Vue 3, Bootstrap 5.3, and Bootstrap Icons from CDNs, so it needs network access.
- Run the Matter decoder: `./matter-decode.sh "MT:..."`, or pipe it:
`echo "MT:..." | ./matter-decode.sh`.
- Run the HomeKit decoder: `./homekit-decode.sh [code]` (with no argument it
decodes the demo payload `005KEOUCE`).
- Regenerate `manufacturers.tsv`: `python3 gen-manufacturers.py` pulls the
pinned upstream `manufacturers.xml` over the network; pass a URL or a local
`.xml` file to use a different source.
- Regenerate LIFX's `products.tsv` rows: `python3 gen-lifx-products.py` (network
by default; a positional URL or local `.json` overrides).
- The decoders read their companion TSVs (`manufacturers.tsv`, `products.tsv`,
`vendor-equivalents.tsv`) from their own directory; a missing TSV drops that
extra lookup — it is never an error.
- Pure bash (3.x+); the `gen-*.py` scripts need only the Python 3 standard
library.
## Architecture
Everything lives in `matter-qr-decoder.html` (~1500 lines). Two regions matter:
The decode logic lives directly in the bash scripts — there is no shared library,
no embedded data map, and no build output. There are two decoders, and they
decode **two different on-wire formats**, so they are independent and should not
be unified.
1. **App logic** (the `<script>` before `#app` is mounted, ~lines 255-559): a single
`Vue.createApp({...})` mounted to `#app`, with all state in `setup()` via `ref`/`computed`.
The UI template (lines ~26-253) is plain HTML using `v-model`/`v-if`/`v-on` bindings,
no SFC or build step.
2. **Vendor map** (`<script type="text/xml">`, ~lines 560-1495): a `<map>` of
`<mapping code="0xXXXX" translation="Name"/>` entries. This block is the bulk of the
file. It is **derived from upstream**
`project-chip/connectedhomeip .../zcl/data-model/manufacturers.xml` (Apache-2.0,
generated 2022-08-02) and carries an "do not edit manually" note — refresh it from
upstream rather than hand-editing individual rows.
### matter-decode.sh — the Matter `MT:` pipeline
### Decode pipeline (the core)
`decodeUri()` (triggered live on input) runs, in order:
`main()` decodes a URI given as an argument or read from stdin, in order:
1. Validate the `MT:` prefix, then take the base38 payload after it.
2. `base38Decode(str)` → `Uint8Array`. Charset is `0-9A-Z-.` (38 chars). Chunking is
**greedy**: 5 chars→3 bytes, 4→2, 2→1; a length that doesn't fit those groupings
throws `Invalid base38 string length`. Bytes are extracted **little-endian**
(`value & 0xFF`, then `>>= 8`).
3. `extractBits(bytes, startBit, bitCount)` reads the 84-bit field layout **LSB-first**.
Fixed field order/widths (see the in-page "Data Encoding Format" table for the source of truth):
2. Base38-decode it to bytes. The charset is `0-9A-Z-.` (38 chars). Chunking is
**greedy**: 5 chars→3 bytes, 4→2, 2→1; a length that doesn't fit those
groupings errors. Bytes are extracted **little-endian**
(`value & 0xFF`, then `>>= 8`).
3. Read the 84-bit field layout **LSB-first**. Fixed field order/widths (see the
"Data Encoding Format" table in `README.md` for the source of truth):
version(3), vendor ID(16), product ID(16), custom flow(2), discovery caps(8),
discriminator(12), passcode(27).
4. Display helpers translate raw numbers: `getCustomFlowName`, `getDiscoveryCapabilities`
(bit flags: SoftAP / BLE / On IP Network), `formatVendorId`/`formatProductId`
(decimal + `0x`-hex padded to 4).
4. Display helpers translate the raw numbers: a custom-flow name, discovery
capabilities (bit flags: SoftAP / BLE / On IP Network), and decimal + `0x`-hex
(padded to 4) for the vendor/product IDs.
### Vendor name lookup
### Vendor / product / equivalence lookups (matter-decode.sh only)
`getVendorName(id)` → `parseVendorMappings()` **lazily parses the embedded XML once**
(cached on `vendorMap`, a `let` inside `setup()`) via `DOMParser`, keyed by
`parseInt(code, 16)`. It is exposed reactively through the `vendorName` `computed`, so
the lookup re-runs when the decoded vendor ID changes.
Three optional companion TSVs add names on top of the raw decode; each degrades
silently (a missing file drops its extra, never an error):
### Product name lookup + vendor equivalences (bash only)
- **`manufacturers.tsv`** (`vendor ID \t name`) — resolves the decoded vendor ID
to a name, printed on the Vendor ID line.
- **`products.tsv`** (`vendor ID \t product ID \t name`) — resolved via
`get_product_name VENDOR PRODUCT`, which checks the exact vendor first, then
every equivalent; first match wins.
- **`vendor-equivalents.tsv`** (`alias \t canonical`, `#` lines ignored,
canonical-form: the canonical id never appears in col 1) — resolved via
`equivalent_vendor_ids VENDOR`, which returns the whole equivalence class
(the vendor plus every alias sharing its canonical id, sorted). The *other*
class members' names are joined and appended to the Vendor ID line as
`(a.k.a. X, ...)`, so a Feit (`5155`) device reads `Feit Electric Company,
Inc. (a.k.a. Lifi Labs INC. dba LIFX)` and a LIFX (`4870`) device reads the
mirror image.
Unlike the vendor-name lookup, the product-name lookup and the vendor-equivalence
table live **only in `matter-decode.sh`** — the browser app has neither. This is
a deliberate, documented divergence from the usual "keep the two in lockstep"
rule, chosen because the product catalog and the equivalence list change more
often than the vendor map and are not yet worth embedding in the static HTML.
`matter-decode.sh` defines two helpers:
- `equivalent_vendor_ids VENDOR` — resolves VENDOR to its whole equivalence class
via `vendor-equivalents.tsv` (2 cols `alias \t canonical`, `#` lines ignored,
canonical-form: the canonical id never appears in col 1). It prints the vendor
itself plus every alias sharing its canonical id, sorted. A missing
`vendor-equivalents.tsv` yields just the vendor.
- `get_product_name VENDOR PRODUCT` — checks `products.tsv` (3 cols `vendor \t
product \t name`) for the exact vendor first, then every equivalent; first
match wins. A missing `products.tsv` yields no name. It must `return 0` on a
miss: under `set -e`, a non-zero status in the `pname=$(...)` substitution
would abort the script before anything prints.
In `main()`, the product name (`pname`) is printed on the Product ID line via the
same `[[ -n $pname ]] && printf` pattern the vendor name uses. The vendor
name is annotated with its equivalents: the *other* class members' names (from
`manufacturers.tsv`) are joined and appended as `(a.k.a. X, ...)`, so a Feit
(`5155`) device reads `Feit Electric Company, Inc. (a.k.a. Lifi Labs INC. dba
LIFX)` and a LIFX (`4870`) device reads the mirror image. Both `products.tsv` and
`vendor-equivalents.tsv` are optional; a missing file only drops its extra.
`get_product_name` must `return 0` on a miss: under `set -e`, a non-zero status
in the `pname=$(...)` substitution would abort the script before anything prints.
`gen-lifx-products.py` regenerates LIFX's rows into `products.tsv` by merging
(dropping only vendor `4870`'s rows, preserving others). To add another vendor,
write a sibling `gen-<vendor>-products.py` that parses that vendor's catalog and
merges its rows — keyed under its own vendor ID — into the same `products.tsv`;
then add that vendor's equivalences (if any) to `vendor-equivalents.tsv`.
Mirroring this into the HTML app is the natural next step if it is wanted.
### Manual pairing code (bash only)
### Manual pairing code (matter-decode.sh only)
`matter-decode.sh` also prints a **Pairing Code:** line: the Matter *manual
pairing code* — the PIN a commissioning app (Apple Home, Google Home, the Matter
@@ -144,27 +129,26 @@ app) asks you to type. It is **not** a raw payload field; it is *derived* from
the passcode and discriminator and, for non-standard commissioning flows, the
vendor/product IDs, then guarded by a Verhoeff check digit. This is a port of
`generateManualPairingCode()` from the standalone "Matter QR Code Tool"
(`chill-uk/matter-qr-app`, `web/app.js`); the browser app in this repo has
neither this nor the product/equivalence lookups, so like them it is bash-only.
(`chill-uk/matter-qr-app`, `web/app.js`).
Two helpers, defined just above `main()`:
- `verhoeff_check_digit DIGITS` — the Verhoeff check digit over a decimal string
(the three standard Verhoeff tables are inlined; digits are processed
right-to-left, as the reference does).
(the three standard Verhoeff tables are inlined; digits are processed
right-to-left, as the reference does).
- `compute_pairing_code VENDOR PRODUCT FLOW DISCRIMINATOR PASSCODE` — assembles
the code. The 12-bit discriminator contributes its top 4 bits; the 27-bit
passcode is split into its low 14 and high 13 bits; non-standard flows
(user-intent `1`, custom `2`) append 5-digit vendor and product groups. Result:
11 digits for the standard flow, 21 otherwise, plus the check digit.
the code. The 12-bit discriminator contributes its top 4 bits; the 27-bit
passcode is split into its low 14 and high 13 bits; non-standard flows
(user-intent `1`, custom `2`) append 5-digit vendor and product groups.
Result: 11 digits for the standard flow, 21 otherwise, plus the check digit.
### HomeKit setup-code decode (bash only)
### HomeKit setup-code decode (homekit-decode.sh)
`homekit-decode.sh` is a *second, independent* decoder — for Apple **HomeKit**
setup codes, the `X-HM://` payload an accessory (or its packaging) shows on first
setup. That is a different on-wire format from Matter's `MT:`, so this is **not**
a port of the HTML app's pipeline; it is standalone, with no browser counterpart
and no companion TSV (the accessory-category table is inlined). It sits alongside
setup. That is a different on-wire format from Matter's `MT:`, so this is
**not** a port of `matter-decode.sh`'s pipeline; it is standalone, with no
companion TSV (the accessory-category table is inlined). It sits alongside
`matter-decode.sh` as the repo's other CLI decoder.
Input is an optional positional argument, defaulting to the demo payload
@@ -182,11 +166,11 @@ stderr, exit 1). `val` is then sliced **LSB-first** into the HomeKit layout:
- `flag` is a 4-bit sub-flag set — `1`=NFC, `2`=IP, `4`=BLE, `8`=WAC/MFi —
printed as True/False lines.
- `categoryId` is resolved to its HAP `homekit_accessory_category_*` name (e.g.
`5` → "Lightbulb", `8` → "Switch") via an inlined `case`; an id not in the
table prints "unknown" (id `25` is reserved by HomeKit, so it is unknown by
design, and "Heater" corrects a "Header" typo in the source table). A `case`
rather than an associative array is used on purpose so the script runs on the
stock macOS bash 3.2 that `#!/usr/bin/env bash` resolves to.
`5` → "Lightbulb", `8` → "Switch") via an inlined `case`; an id not in the
table prints "unknown" (id `25` is reserved by HomeKit, so it is unknown by
design, and "Heater" corrects a "Header" typo in the source table). A `case`
rather than an associative array is used on purpose so the script runs on the
stock macOS bash 3.2 that `#!/usr/bin/env bash` resolves to.
- The decoded fields are packed back into a 9-char block (mirroring
`composeSetupUri`'s stepwise packing) and printed as a reconstructed
`X-HM://…` payload, so the output is a complete record the barcode can be
@@ -199,41 +183,20 @@ The header comments in `homekit-decode.sh` record each fix (the categoryId / fla
## Gotchas
- The `BarcodeDetector` scanner polls the camera via `setInterval` every 200ms and
auto-stops the first time it sees a QR starting with `MT:`; `stopScanning()` clears
the interval **and** stops the media tracks — keep those paired when changing scanning.
- `parseVendorMappings` short-circuits on a non-empty `vendorMap`; if you add a mechanism
to update the map, clear/reset that cache or new codes won't be seen.
- The relative links `/tools/` and `/` (header/footer) imply this page is deployed as part
of a larger static site (Tommie Gannert's blog/tools), but that site isn't in this repo.
- `matter-decode.sh` is a bash port of the HTML app's decode pipeline, kept in lockstep
with it — the base38 charset/chunking, little-endian byte extraction, and the 84-bit
LSB-first field layout are identical. When you change the decode logic in the HTML app,
mirror it in the script (and vice versa). The script's extras are the optional `manufacturers.tsv` (vendor name),
`products.tsv` (product name), and `vendor-equivalents.tsv` (vendor
equivalences); without any of them it still decodes everything else.
- `homekit-decode.sh` is **not** part of that lockstep: it decodes a *different*
on-wire format (Apple HomeKit `X-HM://`, **base36** — not Matter's base38) and
has no HTML-app counterpart. Don't try to unify the two decoders or mirror
HomeKit changes into the HTML app; when editing it, keep the corrected bit layout
(`pin / flag / categoryId / reserved / version`) as the source of truth.
- `manufacturers.tsv` is regenerated with `gen-manufacturers.py` (Python 3
stdlib). Default source is the **upstream `manufacturers.xml` fetched over the
network** (via `curl`; a GitHub `blob/` URL is auto-rewritten to its
`raw.githubusercontent.com` form). A positional arg overrides with another URL
or a local XML file, and passing `matter-qr-decoder.html` uses the embedded
`<map>` block as an **offline** fallback. The default fetch is pinned to the
same commit the HTML block came from, so it reproduces the committed TSV
byte-for-byte. Caveat: the script writes **only the TSV** — the embedded
`<script type="text/xml">` block in the HTML app has the same upstream
provenance and a "do not edit manually" note, so refreshing the TSV from new
upstream XML leaves the HTML copy stale. Refresh both from the same source.
- The product-name lookup and vendor equivalences are **bash-only** —
`matter-decode.sh` has them, the browser app does not, so this is one place the
two are *not* in lockstep (deliberate, for now). `products.tsv` and
`vendor-equivalents.tsv` are optional and degrade silently (a missing file means
no name / no equivalent, never an error). `vendor-equivalents.tsv` is
canonical-form, one hop: the canonical id must not itself appear in col 1.
`get_product_name` must `return 0` on a miss or `set -e` aborts the decode.
Mirror these into the HTML app (a second embedded map plus a `productName` /
`equivalentVendorName` computed) only if that is wanted.
- `matter-decode.sh` (Matter `MT:`, **base38**) and `homekit-decode.sh`
(HomeKit `X-HM://`, **base36**) decode *different* on-wire formats. Don't try
to unify the two decoders; when editing `homekit-decode.sh`, keep the
corrected bit layout (`pin / flag / categoryId / reserved / version`) as the
source of truth.
- `manufacturers.tsv` is the *only* copy of the vendor map now. Regenerate it with
`gen-manufacturers.py`: the default source is the **upstream `manufacturers.xml`
fetched over the network** (via `curl`; a GitHub `blob/` URL is auto-rewritten
to its `raw.githubusercontent.com` form), pinned to the same commit the
committed TSV was generated from, so it reproduces the TSV byte-for-byte. A
positional arg overrides with another URL or a local XML file.
- The product-name lookup and vendor equivalences live **only in
`matter-decode.sh`** — `products.tsv` and `vendor-equivalents.tsv` are
optional and degrade silently (a missing file means no name / no equivalent,
never an error). `vendor-equivalents.tsv` is canonical-form, one hop: the
canonical id must not itself appear in col 1. `get_product_name` must
`return 0` on a miss or `set -e` aborts the decode.
+16 -59
View File
@@ -6,13 +6,13 @@ 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.
No build, no install, no package step. A command-line script decodes the Matter
`MT:` payload for you.
There is also a separate, standalone command-line decoder for Apple **HomeKit**
setup codes — the `X-HM://` payload an accessory shows on first setup. That is a
different format from Matter's `MT:` (see [HomeKit setup-code decoder](#homekit-setup-code-decoder));
it has no browser counterpart and no companion tables.
it is a separate, standalone decoder with no companion tables.
## What it decodes
@@ -34,11 +34,10 @@ split into these fields:
| 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. |
| [`homekit-decode.sh`](homekit-decode.sh) | A standalone pure-bash decoder for Apple **HomeKit** setup codes (the `X-HM://` payload) — a different format from Matter's `MT:`, with no browser counterpart and no companion tables. |
| [`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). |
| [`matter-decode.sh`](matter-decode.sh) | A pure-bash CLI decoder for the Matter `MT:` payload. No camera scanning. |
| [`homekit-decode.sh`](homekit-decode.sh) | A standalone pure-bash decoder for Apple **HomeKit** setup codes (the `X-HM://` payload) — a different format from Matter's `MT:`, with no companion tables. |
| [`manufacturers.tsv`](manufacturers.tsv) | Tab-separated `vendor ID → name` table (~900 rows) used to turn a numeric vendor ID into a name. Used by `matter-decode.sh`; 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 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. |
@@ -48,40 +47,10 @@ 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:
`matter-decode.sh` decodes a URI from an argument or from stdin and prints these
fields:
```sh
./matter-decode.sh "MT:..."
@@ -142,12 +111,11 @@ version(3) | vendor ID(16) | product ID(16) | custom flow(2)
```
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.
none of those groupings is an error. The shell script uses this scheme.
## Vendor name lookup
Both tools map a numeric **Vendor ID** to a name using
`matter-decode.sh` maps 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`
@@ -163,8 +131,8 @@ only). By default it **pulls the upstream `manufacturers.xml` over the network**
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
The source is pinned to the same commit the committed `manufacturers.tsv` was
generated from, so a fresh pull reproduces it 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:
@@ -173,17 +141,6 @@ 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 `<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.
## Product name lookup
`matter-decode.sh` also maps a **Product ID** to a name, for any vendor that has
@@ -237,7 +194,7 @@ is considered. The canonical (col-2) id should not itself appear in col-1.
`homekit-decode.sh` decodes a **HomeKit** setup code — the `X-HM://` payload an
Apple accessory (or its packaging) shows on first setup. It is a *different*
on-wire format from Matter's `MT:`, a standalone tool with no browser app and no
on-wire format from Matter's `MT:`, a standalone tool with no
companion tables — the accessory-category names are inlined in the script.
```sh
@@ -300,5 +257,5 @@ The vendor mapping is derived from Matter/CHIP's `manufacturers.xml`, which is
Apache-2.0. The LIFX product mapping is derived from LIFX's `products.json`.
Vendor equivalences (`vendor-equivalents.tsv`) are maintained locally. The
HomeKit decoder's accessory-category names follow Apple's HomeKit
`homekit_accessory_category` definitions. The app
itself is a small single-file utility.
`homekit_accessory_category` definitions. The decoders are small standalone bash
utilities.
Binary file not shown.
+4 -13
View File
@@ -2,8 +2,8 @@
# Regenerate manufacturers.tsv from the Matter/CHIP manufacturer ID map.
#
# The TSV has two columns, "<decimal vendor id>\t<name>", sorted by vendor id. It
# is consumed by matter-decode.sh and is mirrored by the embedded <map> in
# matter-qr-decoder.html. It is derived from upstream project-chip/connectedhomeip's
# is consumed by matter-decode.sh and is derived from upstream
# project-chip/connectedhomeip's
# manufacturers.xml -- refresh from there rather than hand-editing.
#
# https://raw.githubusercontent.com/project-chip/connectedhomeip/refs/heads/master/src/app/zap-templates/zcl/data-model/manufacturers.xml
@@ -19,11 +19,10 @@
# Override the source with a positional argument that is a URL or a local file:
#
# python3 gen-manufacturers.py <url-or-file>
# python3 gen-manufacturers.py matter-qr-decoder.html # offline: embedded block
# python3 gen-manufacturers.py -o out.tsv <source>
#
# The default network fetch uses curl (falling back to urllib if curl is
# missing); a .html source and a plain XML file need no network. Requires only
# missing); a local XML file needs no network. Requires only
# the Python 3 standard library.
import argparse
@@ -105,13 +104,6 @@ def read_source(source):
path = Path(source)
if not path.exists():
sys.exit(f"error: no such file: {path}")
if path.suffix.lower() == ".html":
html = path.read_text(encoding="utf-8")
m = re.search(r'<script\s+type="text/xml"[^>]*>(.*?)</script>',
html, re.DOTALL | re.IGNORECASE)
if not m:
sys.exit('error: no <script type="text/xml"> block in ' + str(path))
return m.group(1)
return path.read_text(encoding="utf-8")
@@ -129,8 +121,7 @@ def main():
ap.add_argument(
"source", nargs="?", default=UPSTREAM_RAW,
help="a URL or local file (default: pinned upstream manufacturers.xml). "
"A GitHub blob/ URL is auto-converted to its raw form; a .html path "
'uses its embedded <script type="text/xml"> block.')
"A GitHub blob/ URL is auto-converted to its raw form.")
ap.add_argument(
"-o", "--output", default=str(SCRIPT_DIR / "manufacturers.tsv"),
help="output path (default: manufacturers.tsv next to this script)")
+3 -3
View File
@@ -2,9 +2,9 @@
#
# matter-decode.sh — decode a Matter commissioning QR code (MT: URI) from the CLI.
#
# This is a bash port of the decode logic in matter-qr-decoder.html (the browser
# app's camera/QR-scanning feature has no shell equivalent and is intentionally
# omitted). It decodes the 84-bit payload and prints the same fields the web UI shows.
# This decodes the Matter 84-bit payload (the format a Matter/CHIP commissioning
# QR encodes) and prints the fields a commissioning app (Apple Home, Google Home,
# the Matter app) needs to pair with the device.
#
# Usage:
# ./matter-decode.sh "MT:..."
File diff suppressed because it is too large. Load diff