6.0 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
A collection of Matter commissioning QR decoders (the MT: URI format used by
Matter/CHIP devices). 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). Three artifacts:
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).manufacturers.tsv—vendor ID → nametable (~900 rows), shared by both tools and optional (a missing file only drops the vendor-name lookup).gen-manufacturers.py— Python 3 stdlib script that regeneratesmanufacturers.tsv. Default source is the upstreammanufacturers.xmlfetched over the network (viacurl; a GitHubblob/URL is auto-rewritten to itsraw.githubusercontent.comform). A positional arg overrides with another URL or a local XML file; passingmatter-qr-decoder.htmluses the embedded<map>block as an offline fallback. Reproduces the committed TSV byte-for-byte.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:
- Run/develop: open
matter-qr-decoder.htmlin a modern browser and edit it directly. - Camera scanning (
getUserMedia) requires a secure context — serve over HTTPS orlocalhost(e.g.python3 -m http.serverthen openhttp://localhost:8000). - The "Scan QR" feature uses the
BarcodeDetectorAPI, 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.
Architecture
Everything lives in matter-qr-decoder.html (~1500 lines). Two regions matter:
- App logic (the
<script>before#appis mounted, ~lines 255-559): a singleVue.createApp({...})mounted to#app, with all state insetup()viaref/computed. The UI template (lines ~26-253) is plain HTML usingv-model/v-if/v-onbindings, no SFC or build step. - 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 upstreamproject-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)
decodeUri() (triggered live on input) runs, in order:
- Validate the
MT:prefix, then take the base38 payload after it. base38Decode(str)→Uint8Array. Charset is0-9A-Z-.(38 chars). Chunking is greedy: 5 chars→3 bytes, 4→2, 2→1; a length that doesn't fit those groupings throwsInvalid base38 string length. Bytes are extracted little-endian (value & 0xFF, then>>= 8).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): version(3), vendor ID(16), product ID(16), custom flow(2), discovery caps(8), discriminator(12), passcode(27).- Display helpers translate raw numbers:
getCustomFlowName,getDiscoveryCapabilities(bit flags: SoftAP / BLE / On IP Network),formatVendorId/formatProductId(decimal +0x-hex padded to 4).
Vendor name lookup
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.
Gotchas
- The
BarcodeDetectorscanner polls the camera viasetIntervalevery 200ms and auto-stops the first time it sees a QR starting withMT:;stopScanning()clears the interval and stops the media tracks — keep those paired when changing scanning. parseVendorMappingsshort-circuits on a non-emptyvendorMap; 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.shis 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 amanufacturers.tsvlookup for the vendor name; without that file it still decodes everything else.manufacturers.tsvis regenerated withgen-manufacturers.py(Python 3 stdlib). Default source is the upstreammanufacturers.xmlfetched over the network (viacurl; a GitHubblob/URL is auto-rewritten to itsraw.githubusercontent.comform). A positional arg overrides with another URL or a local XML file, and passingmatter-qr-decoder.htmluses 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.