Files
magnus919_agent-skills/esp32-development/scripts/esp32-preflight.py
T
Magnus HedemarkandGitHub 36638256a8 feat: add portable ESP32 development skill (#53)
* feat: add portable ESP32 development skill

Add source-backed workflows, safe templates, native CLI routing, and a read-only preflight for ESP32 hardware and firmware work.\n\nAI assistance: research, drafting, implementation, and review used OpenAI Codex and delegated DeepSeek agents under human direction.

* docs: harden ESP32 family and security guidance

Add source-backed family traps, brownout, calibration, USB recovery, and security-mode boundaries found during independent review.\n\nAI assistance: independent audits and drafting used delegated DeepSeek agents and OpenAI Codex under human direction.
2026-07-15 21:45:53 -04:00

111 lines
3.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Report ESP32-related host tools and candidate serial ports without opening them."""
from __future__ import annotations
import argparse
import glob
import json
import platform
import shutil
from pathlib import Path
TOOLS = (
"idf.py",
"esptool",
"esptool.py",
"arduino-cli",
"pio",
"mpremote",
"circup",
"esphome",
"west",
"espflash",
"cargo-espflash",
)
PORT_PATTERNS = {
"Darwin": ("/dev/cu.usb*", "/dev/cu.SLAB*", "/dev/cu.wchusb*"),
"Linux": ("/dev/ttyUSB*", "/dev/ttyACM*", "/dev/serial/by-id/*"),
}
def candidate_ports(system: str) -> list[str]:
ports = {
str(Path(path))
for pattern in PORT_PATTERNS.get(system, ())
for path in glob.glob(pattern)
}
return sorted(ports)
def report(system: str | None = None) -> dict[str, object]:
system = system or platform.system()
port_enumeration = (
"filesystem patterns"
if system in PORT_PATTERNS
else "not available without a native device-list tool on this platform"
)
return {
"host": {"system": system, "machine": platform.machine()},
"tools": {name: shutil.which(name) for name in TOOLS},
"candidate_ports": candidate_ports(system),
"port_enumeration": port_enumeration,
"opened_ports": False,
"next": "Identify the exact board before opening or flashing a port.",
}
def self_test() -> None:
assert "/dev/cu.Bluetooth-Incoming-Port" not in candidate_ports("Darwin")
assert candidate_ports("Windows") == []
windows = report("Windows")
assert str(windows["port_enumeration"]).startswith("not available")
data = report("TestOS")
assert data["opened_ports"] is False
tools = data["tools"]
assert isinstance(tools, dict)
assert set(tools) == set(TOOLS)
print("self-test: PASS")
def print_text(data: dict[str, object]) -> None:
host = data["host"]
assert isinstance(host, dict)
print(f"host: {host['system']} {host['machine']}")
print("tools:")
tools = data["tools"]
assert isinstance(tools, dict)
for name, path in tools.items():
print(f" {name}: {path or 'missing'}")
print(f"port enumeration: {data['port_enumeration']}")
print("candidate ports:")
ports = data["candidate_ports"]
assert isinstance(ports, list)
if ports:
for port in ports:
print(f" {port}")
else:
print(" none found (use the framework's native device-list command)")
print(f"next: {data['next']}")
def main() -> None:
parser = argparse.ArgumentParser(
description="List ESP32-related tools and candidate ports without opening serial devices."
)
parser.add_argument("--json", action="store_true", help="emit JSON")
parser.add_argument("--self-test", action="store_true", help="run deterministic checks")
args = parser.parse_args()
if args.self_test:
self_test()
return
data = report()
if args.json:
print(json.dumps(data, indent=2, sort_keys=True))
else:
print_text(data)
if __name__ == "__main__":
main()