Updated to be pure bash and remove references to the web pages.
This commit is contained in:
1 parent
589ebaff46
commit
f56678833e
7 files changed
+111
-1697
No files matched your search
@@ -1 +1,2 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
__pycache__
|
||||||
@@ -7,27 +7,26 @@ 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
|
A collection of Matter commissioning QR decoders (the `MT:` URI format used by
|
||||||
Matter/CHIP devices), plus a standalone bash decoder for Apple **HomeKit**
|
Matter/CHIP devices), plus a standalone bash decoder for Apple **HomeKit**
|
||||||
setup codes (the `X-HM://` payload — a different on-wire format, in
|
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
|
`homekit-decode.sh`). For Matter, a user runs `matter-decode.sh` with an `MT:`
|
||||||
code; the result shows the decoded payload (version, vendor/product IDs,
|
URI (as a positional argument or on stdin); it decodes the 84-bit payload and
|
||||||
commissioning flow, discovery capabilities, discriminator, passcode). Key files:
|
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` — the pure-bash CLI Matter decoder. Takes an `MT:` URI as an
|
||||||
- `matter-decode.sh` — a pure-bash CLI port of the same decode logic (no camera
|
argument or on stdin and prints the decoded fields. It has no camera/QR-scanning
|
||||||
scanning; takes a URI as an argument or on stdin).
|
(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**
|
- `homekit-decode.sh` — a standalone pure-bash decoder for Apple **HomeKit**
|
||||||
setup codes (the `X-HM://` payload an accessory shows on first setup — a
|
setup codes (the `X-HM://` payload an accessory shows on first setup — a
|
||||||
*different* format from Matter's `MT:`). No browser counterpart and no
|
*different* format from Matter's `MT:`). No companion TSV (the accessory-category
|
||||||
companion TSV (the accessory-category table is inlined). See the
|
table is inlined). See the HomeKit setup-code decode section below.
|
||||||
HomeKit setup-code decode section below.
|
- `manufacturers.tsv` — `vendor ID → name` table (~900 rows), consumed by
|
||||||
- `manufacturers.tsv` — `vendor ID → name` table (~900 rows), shared by both
|
`matter-decode.sh`; optional (a missing file only drops the vendor-name lookup).
|
||||||
tools and optional (a missing file only drops the vendor-name lookup).
|
|
||||||
- `gen-manufacturers.py` — Python 3 stdlib script that regenerates
|
- `gen-manufacturers.py` — Python 3 stdlib script that regenerates
|
||||||
`manufacturers.tsv`. Default source is the upstream `manufacturers.xml`
|
`manufacturers.tsv`. Default source is the upstream `manufacturers.xml`
|
||||||
**fetched over the network** (via `curl`; a GitHub `blob/` URL is auto-rewritten
|
**fetched over the network** (via `curl`; a GitHub `blob/` URL is auto-rewritten
|
||||||
to its `raw.githubusercontent.com` form). A positional arg overrides with
|
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
|
another URL or a local XML file. Reproduces the committed TSV byte-for-byte.
|
||||||
embedded `<map>` block as an offline fallback. Reproduces the committed TSV
|
|
||||||
byte-for-byte.
|
|
||||||
- `products.tsv` — 3-column `vendor ID → product ID → name` table, consumed by
|
- `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
|
equivalent, see `vendor-equivalents.tsv`) is present, the product ID is
|
||||||
@@ -51,92 +50,78 @@ commissioning flow, discovery capabilities, discriminator, passcode). Key files:
|
|||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
There is **no build, test, lint, or package step** — this is not a git repo and has no
|
There is **no build, test, lint, or package step** — this is not a build project.
|
||||||
`package.json`. The entire app is one HTML file that runs in the browser:
|
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.
|
- Run the Matter decoder: `./matter-decode.sh "MT:..."`, or pipe it:
|
||||||
- Camera scanning (`getUserMedia`) requires a **secure context** — serve over HTTPS or
|
`echo "MT:..." | ./matter-decode.sh`.
|
||||||
`localhost` (e.g. `python3 -m http.server` then open `http://localhost:8000`).
|
- Run the HomeKit decoder: `./homekit-decode.sh [code]` (with no argument it
|
||||||
- The "Scan QR" feature uses the `BarcodeDetector` API, which is **Chromium-only**
|
decodes the demo payload `005KEOUCE`).
|
||||||
(Chrome/Edge). Firefox/Safari fall back to a "not supported" message; paste-input works everywhere.
|
- Regenerate `manufacturers.tsv`: `python3 gen-manufacturers.py` pulls the
|
||||||
- The page loads Vue 3, Bootstrap 5.3, and Bootstrap Icons from CDNs, so it needs network access.
|
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
|
## 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
|
### matter-decode.sh — the Matter `MT:` pipeline
|
||||||
`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.
|
|
||||||
|
|
||||||
### Decode pipeline (the core)
|
`main()` decodes a URI given as an argument or read from stdin, in order:
|
||||||
|
|
||||||
`decodeUri()` (triggered live on input) runs, in order:
|
|
||||||
|
|
||||||
1. Validate the `MT:` prefix, then take the base38 payload after it.
|
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
|
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
|
**greedy**: 5 chars→3 bytes, 4→2, 2→1; a length that doesn't fit those
|
||||||
throws `Invalid base38 string length`. Bytes are extracted **little-endian**
|
groupings errors. Bytes are extracted **little-endian**
|
||||||
(`value & 0xFF`, then `>>= 8`).
|
(`value & 0xFF`, then `>>= 8`).
|
||||||
3. `extractBits(bytes, startBit, bitCount)` reads the 84-bit field layout **LSB-first**.
|
3. Read the 84-bit field layout **LSB-first**. Fixed field order/widths (see the
|
||||||
Fixed field order/widths (see the in-page "Data Encoding Format" table for the source of truth):
|
"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),
|
version(3), vendor ID(16), product ID(16), custom flow(2), discovery caps(8),
|
||||||
discriminator(12), passcode(27).
|
discriminator(12), passcode(27).
|
||||||
4. Display helpers translate raw numbers: `getCustomFlowName`, `getDiscoveryCapabilities`
|
4. Display helpers translate the raw numbers: a custom-flow name, discovery
|
||||||
(bit flags: SoftAP / BLE / On IP Network), `formatVendorId`/`formatProductId`
|
capabilities (bit flags: SoftAP / BLE / On IP Network), and decimal + `0x`-hex
|
||||||
(decimal + `0x`-hex padded to 4).
|
(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**
|
Three optional companion TSVs add names on top of the raw decode; each degrades
|
||||||
(cached on `vendorMap`, a `let` inside `setup()`) via `DOMParser`, keyed by
|
silently (a missing file drops its extra, never an error):
|
||||||
`parseInt(code, 16)`. It is exposed reactively through the `vendorName` `computed`, so
|
|
||||||
the lookup re-runs when the decoded vendor ID changes.
|
|
||||||
|
|
||||||
### 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
|
`get_product_name` must `return 0` on a miss: under `set -e`, a non-zero status
|
||||||
table live **only in `matter-decode.sh`** — the browser app has neither. This is
|
in the `pname=$(...)` substitution would abort the script before anything prints.
|
||||||
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.
|
|
||||||
|
|
||||||
`gen-lifx-products.py` regenerates LIFX's rows into `products.tsv` by merging
|
`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,
|
(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
|
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`;
|
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`.
|
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
|
`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
|
pairing code* — the PIN a commissioning app (Apple Home, Google Home, the Matter
|
||||||
@@ -144,8 +129,7 @@ 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
|
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
|
vendor/product IDs, then guarded by a Verhoeff check digit. This is a port of
|
||||||
`generateManualPairingCode()` from the standalone "Matter QR Code Tool"
|
`generateManualPairingCode()` from the standalone "Matter QR Code Tool"
|
||||||
(`chill-uk/matter-qr-app`, `web/app.js`); the browser app in this repo has
|
(`chill-uk/matter-qr-app`, `web/app.js`).
|
||||||
neither this nor the product/equivalence lookups, so like them it is bash-only.
|
|
||||||
|
|
||||||
Two helpers, defined just above `main()`:
|
Two helpers, defined just above `main()`:
|
||||||
|
|
||||||
@@ -155,16 +139,16 @@ Two helpers, defined just above `main()`:
|
|||||||
- `compute_pairing_code VENDOR PRODUCT FLOW DISCRIMINATOR PASSCODE` — assembles
|
- `compute_pairing_code VENDOR PRODUCT FLOW DISCRIMINATOR PASSCODE` — assembles
|
||||||
the code. The 12-bit discriminator contributes its top 4 bits; the 27-bit
|
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
|
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:
|
(user-intent `1`, custom `2`) append 5-digit vendor and product groups.
|
||||||
11 digits for the standard flow, 21 otherwise, plus the check digit.
|
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**
|
`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 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**
|
setup. That is a different on-wire format from Matter's `MT:`, so this is
|
||||||
a port of the HTML app's pipeline; it is standalone, with no browser counterpart
|
**not** a port of `matter-decode.sh`'s pipeline; it is standalone, with no
|
||||||
and no companion TSV (the accessory-category table is inlined). It sits alongside
|
companion TSV (the accessory-category table is inlined). It sits alongside
|
||||||
`matter-decode.sh` as the repo's other CLI decoder.
|
`matter-decode.sh` as the repo's other CLI decoder.
|
||||||
|
|
||||||
Input is an optional positional argument, defaulting to the demo payload
|
Input is an optional positional argument, defaulting to the demo payload
|
||||||
@@ -199,41 +183,20 @@ The header comments in `homekit-decode.sh` record each fix (the categoryId / fla
|
|||||||
|
|
||||||
## Gotchas
|
## Gotchas
|
||||||
|
|
||||||
- The `BarcodeDetector` scanner polls the camera via `setInterval` every 200ms and
|
- `matter-decode.sh` (Matter `MT:`, **base38**) and `homekit-decode.sh`
|
||||||
auto-stops the first time it sees a QR starting with `MT:`; `stopScanning()` clears
|
(HomeKit `X-HM://`, **base36**) decode *different* on-wire formats. Don't try
|
||||||
the interval **and** stops the media tracks — keep those paired when changing scanning.
|
to unify the two decoders; when editing `homekit-decode.sh`, keep the
|
||||||
- `parseVendorMappings` short-circuits on a non-empty `vendorMap`; if you add a mechanism
|
corrected bit layout (`pin / flag / categoryId / reserved / version`) as the
|
||||||
to update the map, clear/reset that cache or new codes won't be seen.
|
source of truth.
|
||||||
- The relative links `/tools/` and `/` (header/footer) imply this page is deployed as part
|
- `manufacturers.tsv` is the *only* copy of the vendor map now. Regenerate it with
|
||||||
of a larger static site (Tommie Gannert's blog/tools), but that site isn't in this repo.
|
`gen-manufacturers.py`: the default source is the **upstream `manufacturers.xml`
|
||||||
- `matter-decode.sh` is a bash port of the HTML app's decode pipeline, kept in lockstep
|
fetched over the network** (via `curl`; a GitHub `blob/` URL is auto-rewritten
|
||||||
with it — the base38 charset/chunking, little-endian byte extraction, and the 84-bit
|
to its `raw.githubusercontent.com` form), pinned to the same commit the
|
||||||
LSB-first field layout are identical. When you change the decode logic in the HTML app,
|
committed TSV was generated from, so it reproduces the TSV byte-for-byte. A
|
||||||
mirror it in the script (and vice versa). The script's extras are the optional `manufacturers.tsv` (vendor name),
|
positional arg overrides with another URL or a local XML file.
|
||||||
`products.tsv` (product name), and `vendor-equivalents.tsv` (vendor
|
- The product-name lookup and vendor equivalences live **only in
|
||||||
equivalences); without any of them it still decodes everything else.
|
`matter-decode.sh`** — `products.tsv` and `vendor-equivalents.tsv` are
|
||||||
- `homekit-decode.sh` is **not** part of that lockstep: it decodes a *different*
|
optional and degrade silently (a missing file means no name / no equivalent,
|
||||||
on-wire format (Apple HomeKit `X-HM://`, **base36** — not Matter's base38) and
|
never an error). `vendor-equivalents.tsv` is canonical-form, one hop: the
|
||||||
has no HTML-app counterpart. Don't try to unify the two decoders or mirror
|
canonical id must not itself appear in col 1. `get_product_name` must
|
||||||
HomeKit changes into the HTML app; when editing it, keep the corrected bit layout
|
`return 0` on a miss or `set -e` aborts the decode.
|
||||||
(`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.
|
|
||||||
@@ -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
|
with the device for the first time. This tool takes that URI and shows you what
|
||||||
is inside.
|
is inside.
|
||||||
|
|
||||||
No build, no install, no package step. Two ways to use it: a browser app and a
|
No build, no install, no package step. A command-line script decodes the Matter
|
||||||
command-line script that share the same decode logic.
|
`MT:` payload for you.
|
||||||
|
|
||||||
There is also a separate, standalone command-line decoder for Apple **HomeKit**
|
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
|
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));
|
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
|
## What it decodes
|
||||||
|
|
||||||
@@ -34,11 +34,10 @@ split into these fields:
|
|||||||
|
|
||||||
| File | What it is |
|
| 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 decoder for the Matter `MT:` payload. No camera scanning. |
|
||||||
| [`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 companion tables. |
|
||||||
| [`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. Used by `matter-decode.sh`; optional. |
|
||||||
| [`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 you pass in). |
|
||||||
| [`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. |
|
| [`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. |
|
| [`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. |
|
| [`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
|
## Command line
|
||||||
|
|
||||||
`matter-decode.sh` decodes a URI from an argument or from stdin and prints the
|
`matter-decode.sh` decodes a URI from an argument or from stdin and prints these
|
||||||
same fields as the web UI:
|
fields:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./matter-decode.sh "MT:..."
|
./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
|
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
|
none of those groupings is an error. The shell script uses this scheme.
|
||||||
this same scheme, which is why the two stay in lockstep.
|
|
||||||
|
|
||||||
## Vendor name lookup
|
## 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
|
`manufacturers.tsv` (`vendor ID` in the first column, name in the second). The
|
||||||
table is derived from upstream
|
table is derived from upstream
|
||||||
`project-chip/connectedhomeip .../zcl/data-model/manufacturers.xml`
|
`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
|
python3 gen-manufacturers.py
|
||||||
```
|
```
|
||||||
|
|
||||||
The source is pinned to the same commit the HTML block was generated from, so a
|
The source is pinned to the same commit the committed `manufacturers.tsv` was
|
||||||
fresh pull reproduces the current `manufacturers.tsv` exactly. To refresh from a
|
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
|
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:
|
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
|
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
|
## Product name lookup
|
||||||
|
|
||||||
`matter-decode.sh` also maps a **Product ID** to a name, for any vendor that has
|
`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
|
`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*
|
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.
|
companion tables — the accessory-category names are inlined in the script.
|
||||||
|
|
||||||
```sh
|
```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`.
|
Apache-2.0. The LIFX product mapping is derived from LIFX's `products.json`.
|
||||||
Vendor equivalences (`vendor-equivalents.tsv`) are maintained locally. The
|
Vendor equivalences (`vendor-equivalents.tsv`) are maintained locally. The
|
||||||
HomeKit decoder's accessory-category names follow Apple's HomeKit
|
HomeKit decoder's accessory-category names follow Apple's HomeKit
|
||||||
`homekit_accessory_category` definitions. The app
|
`homekit_accessory_category` definitions. The decoders are small standalone bash
|
||||||
itself is a small single-file utility.
|
utilities.
|
||||||
Binary file not shown.
+4
-13
@@ -2,8 +2,8 @@
|
|||||||
# Regenerate manufacturers.tsv from the Matter/CHIP manufacturer ID map.
|
# 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
|
# 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
|
# is consumed by matter-decode.sh and is derived from upstream
|
||||||
# matter-qr-decoder.html. It is derived from upstream project-chip/connectedhomeip's
|
# project-chip/connectedhomeip's
|
||||||
# manufacturers.xml -- refresh from there rather than hand-editing.
|
# 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
|
# 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:
|
# 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 <url-or-file>
|
||||||
# python3 gen-manufacturers.py matter-qr-decoder.html # offline: embedded block
|
|
||||||
# python3 gen-manufacturers.py -o out.tsv <source>
|
# python3 gen-manufacturers.py -o out.tsv <source>
|
||||||
#
|
#
|
||||||
# The default network fetch uses curl (falling back to urllib if curl is
|
# 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.
|
# the Python 3 standard library.
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -105,13 +104,6 @@ def read_source(source):
|
|||||||
path = Path(source)
|
path = Path(source)
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
sys.exit(f"error: no such file: {path}")
|
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")
|
return path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
@@ -129,8 +121,7 @@ def main():
|
|||||||
ap.add_argument(
|
ap.add_argument(
|
||||||
"source", nargs="?", default=UPSTREAM_RAW,
|
"source", nargs="?", default=UPSTREAM_RAW,
|
||||||
help="a URL or local file (default: pinned upstream manufacturers.xml). "
|
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 "
|
"A GitHub blob/ URL is auto-converted to its raw form.")
|
||||||
'uses its embedded <script type="text/xml"> block.')
|
|
||||||
ap.add_argument(
|
ap.add_argument(
|
||||||
"-o", "--output", default=str(SCRIPT_DIR / "manufacturers.tsv"),
|
"-o", "--output", default=str(SCRIPT_DIR / "manufacturers.tsv"),
|
||||||
help="output path (default: manufacturers.tsv next to this script)")
|
help="output path (default: manufacturers.tsv next to this script)")
|
||||||
|
|||||||
+3
-3
@@ -2,9 +2,9 @@
|
|||||||
#
|
#
|
||||||
# matter-decode.sh — decode a Matter commissioning QR code (MT: URI) from the CLI.
|
# 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
|
# This decodes the Matter 84-bit payload (the format a Matter/CHIP commissioning
|
||||||
# app's camera/QR-scanning feature has no shell equivalent and is intentionally
|
# QR encodes) and prints the fields a commissioning app (Apple Home, Google Home,
|
||||||
# omitted). It decodes the 84-bit payload and prints the same fields the web UI shows.
|
# the Matter app) needs to pair with the device.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./matter-decode.sh "MT:..."
|
# ./matter-decode.sh "MT:..."
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user