#!/usr/bin/env python3 # Regenerate manufacturers.tsv from the Matter/CHIP manufacturer ID map. # # The TSV has two columns, "\t", sorted by vendor id. It # 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 # # By default the script pulls the upstream file over the network (via curl) and # writes manufacturers.tsv: # # python3 gen-manufacturers.py # # A GitHub blob/ (HTML) URL is auto-rewritten to its raw.githubusercontent.com # form, so the URL from the repo's "From ..." comment works as-is. # # Override the source with a positional argument that is a URL or a local file: # # python3 gen-manufacturers.py # python3 gen-manufacturers.py -o out.tsv # # The default network fetch uses curl (falling back to urllib if curl is # missing); a local XML file needs no network. Requires only # the Python 3 standard library. import argparse import re import shutil import subprocess import sys import urllib.request import xml.etree.ElementTree as ET from pathlib import Path SCRIPT_DIR = Path(__file__).resolve().parent # Pinned upstream source -- the same commit the HTML block was generated from, so # a fresh pull reproduces the committed TSV byte-for-byte until upstream moves. UPSTREAM_RAW = ("https://raw.githubusercontent.com/project-chip/connectedhomeip/" "7986ee3d82b26403fc2d0f70bdfbc9e8ff650df1/" "src/app/zap-templates/zcl/data-model/manufacturers.xml") def by_local_tag(root, local_name): for el in root.iter(): tag = el.tag.rsplit("}", 1)[-1] if "}" in el.tag else el.tag if tag == local_name: yield el def mappings_from_xml_text(xml_text): m = re.search(r"", xml_text, re.DOTALL | re.IGNORECASE) frag = m.group(0) if m else xml_text root = ET.fromstring(frag) rows = [] for el in by_local_tag(root, "mapping"): code = el.get("code") name = el.get("translation", "") if code is None: continue # Upstream codes are hex with a 0x prefix ("0x1026"); strip it. hexpart = code[2:] if code[:2].lower() == "0x" else code try: dec = int(hexpart, 16) except ValueError: sys.stderr.write(f"warning: skipping unparseable code {code!r}\n") continue rows.append((dec, name)) return rows def to_raw_url(url): m = re.match(r"https://github\.com/([^/]+)/([^/]+)/blob/(.+)", url) if m: owner, repo, path = m.groups() return f"https://raw.githubusercontent.com/{owner}/{repo}/{path}" return url def fetch_url(url): url = to_raw_url(url) if shutil.which("curl"): try: out = subprocess.run(["curl", "-fsSL", url], capture_output=True, check=True).stdout return out.decode("utf-8") except subprocess.CalledProcessError as e: sys.stderr.write(e.stderr.decode("utf-8", "replace")) sys.exit(f"error: failed to fetch {url}") req = urllib.request.Request(url, headers={"User-Agent": "gen-manufacturers.py"}) try: with urllib.request.urlopen(req) as resp: return resp.read().decode("utf-8") except Exception as e: sys.exit(f"error: failed to fetch {url}: {e}") def read_source(source): if source.startswith(("http://", "https://")): return fetch_url(source) path = Path(source) if not path.exists(): sys.exit(f"error: no such file: {path}") return path.read_text(encoding="utf-8") def write_tsv(path, rows): rows.sort(key=lambda r: r[0]) with open(path, "w", encoding="utf-8", newline="") as f: for dec, name in rows: f.write(f"{dec}\t{name}\n") def main(): ap = argparse.ArgumentParser( description="Regenerate manufacturers.tsv from the Matter/CHIP " "manufacturer ID map (fetches upstream by default).") 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.") ap.add_argument( "-o", "--output", default=str(SCRIPT_DIR / "manufacturers.tsv"), help="output path (default: manufacturers.tsv next to this script)") args = ap.parse_args() xml_text = read_source(args.source) rows = mappings_from_xml_text(xml_text) if not rows: sys.exit("error: no entries found in the source") out = Path(args.output) write_tsv(out, rows) sys.stderr.write(f"wrote {len(rows)} mappings to {out} (from {args.source})\n") if __name__ == "__main__": main()