AnasSarkiz/magnetic-shutter-remote-r8

A USB‑C rechargeable battery-powered ESP32‑C3 wireless controller with Li‑ion charging, protected 3.3‑V buck‑boost regulation, thermal/safety monitoring, status LEDs, buttons, programming UART, crystal clock, and 2.4‑GHz antenna matching network.

Version
0.4.0-prototype
License
unset
Stars
0

scripts/audit-r8-module.py

"""Compare unmodified supplier source/import against the manufacturer reference.

This produces qualification evidence, never component definitions or PCB geometry.
"""
import json
import re
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
RAW = ROOT / "evidence/R8-components/C2934560-supplier-raw.json"
IMPORT = ROOT / "imports/ESP32_C3_WROOM_02_N4/ESP32_C3_WROOM_02_N4.tsx"


def supplier_pads(supplier_path=RAW):
    supplier = json.loads(supplier_path.read_text())
    package = supplier["packageDetail"]["dataStr"]
    if isinstance(package, str):
        package = json.loads(package)
    origin = package["head"]
    pads = []
    for shape in package["shape"]:
        if not shape.startswith("PAD~"):
            continue
        fields = shape.split("~")
        rotation_degrees = float(fields[11]) % 360
        width_mm = float(fields[4]) * 0.254
        height_mm = float(fields[5]) * 0.254
        if rotation_degrees in (90, 270):
            width_mm, height_mm = height_mm, width_mm
        elif rotation_degrees not in (0, 180):
            raise ValueError("Pad audit supports only axis-aligned supplier rectangles")
        pads.append({
            "pin": int(fields[8]),
            "x_mm": (float(fields[2]) - origin["x"]) * 0.254,
            "y_mm": (origin["y"] - float(fields[3])) * 0.254,
            "width_mm": width_mm,
            "height_mm": height_mm,
        })
    return pads


def imported_pads(import_path=IMPORT):
    pads = []
    pattern = re.compile(
        r'<smtpad portHints=\{\["pin(\d+)"\]\} pcbX="([\d.\-]+)mm" '
        r'pcbY="([\d.\-]+)mm" width="([\d.]+)mm" height="([\d.]+)mm"'
    )
    for pin, x, y, width, height in pattern.findall(import_path.read_text()):
        pads.append({"pin": int(pin), "x_mm": float(x), "y_mm": float(y),
                     "width_mm": float(width), "height_mm": float(height)})
    return pads


def audit():
    supplier = supplier_pads()
    imported = imported_pads()
    if len(supplier) != len(imported):
        raise ValueError("Supplier/import pad count differs")
    maximum_conversion_difference_mm = max(
        abs(original[axis] - converted[axis])
        for original, converted in zip(supplier, imported, strict=True)
        for axis in ("x_mm", "y_mm", "width_mm", "height_mm")
    )
    if any(original["pin"] != converted["pin"]
           for original, converted in zip(supplier, imported, strict=True)):
        raise ValueError("Supplier/import physical terminal association differs")
    contacts = [pad for pad in imported if pad["pin"] != 19]
    return {
        "part": "ESP32-C3-WROOM-02-N4", "jlcpcb": "C2934560",
        "supplier_package_uuid": json.loads(RAW.read_text())["packageDetail"]["uuid"],
        "supplier_pad_count": len(supplier), "imported_pad_count": len(imported),
        "conversion_fidelity": "PASS" if maximum_conversion_difference_mm < 1e-9 else "FAIL",
        "maximum_conversion_difference_mm": maximum_conversion_difference_mm,
        "manufacturer_reference": "Espressif datasheet v1.7, Figure 11-1, p38",
        "recommended_contact_width_mm": 1.5, "recommended_contact_height_mm": 0.9,
        "actual_contact_width_mm": sorted(set(pad["width_mm"] for pad in contacts)),
        "actual_contact_height_mm": sorted(set(pad["height_mm"] for pad in contacts)),
        "manufacturer_land_pattern_qualification": "BLOCKED — UNQUALIFIED SUPPLIER VARIATION",
        "recommendation_is_not_a_published_tolerance": True,
        "pads": imported,
    }


if __name__ == "__main__":
    report = audit()
    target = ROOT / "evidence/R8-components/ESP32-footprint-audit.json"
    target.write_text(json.dumps(report, indent=2) + "\n")
    print(json.dumps({key: report[key] for key in (
        "conversion_fidelity", "maximum_conversion_difference_mm",
        "manufacturer_land_pattern_qualification")}, indent=2))