Updated matter decoder to show more info, and added a homekit decoder.

This commit is contained in:
Scott Garrett committed 2026-08-31 19:46:47 -04:00
1 parent 548f2e701f
commit 27ebf13f6f
8 files changed
+831 -6

No files matched your search

+89 -2
View File
@@ -21,6 +21,25 @@ flow, discovery capabilities, discriminator, passcode). Three artifacts:
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.
- `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
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.
- `vendor-equivalents.tsv` — 2-column `alias vendor ID → canonical vendor ID`
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
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`.
- `README.md` — user-facing docs; this file documents the internals.
## Commands
@@ -74,6 +93,64 @@ Everything lives in `matter-qr-decoder.html` (~1500 lines). Two regions matter:
`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)
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.
`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)
`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
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.
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).
- `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.
## Gotchas
- The `BarcodeDetector` scanner polls the camera via `setInterval` every 200ms and
@@ -86,8 +163,9 @@ the lookup re-runs when the decoded vendor ID changes.
- `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 only extra is a `manufacturers.tsv`
lookup for the vendor name; without that file it still decodes everything else.
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.
- `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
@@ -99,3 +177,12 @@ the lookup re-runs when the decoded vendor ID changes.
`<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.