Files
matterdecode/gen-manufacturers.py
T

142 lines
4.9 KiB
Python
Executable File

#!/usr/bin/env python3
# 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 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 <url-or-file>
# python3 gen-manufacturers.py -o out.tsv <source>
#
# 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"<map\b.*?</map>", 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 <mapping> 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()