142 lines
4.9 KiB
Python
Executable File
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()
|