Files
olimex_ESP32-C6-EVB/board_verify.py
2026-03-15 08:47:06 +02:00

253 lines
11 KiB
Python

#!/usr/bin/env python3
"""
Olimex ESP32-C6-EVB — Remote Board Verification Script
=========================================================
Queries the board's REST API and verifies every subsystem.
Usage:
python3 board_verify.py # uses default IP 192.168.0.181
python3 board_verify.py 192.168.0.200 # custom IP
python3 board_verify.py --json # machine-readable output
Requirements: pip install requests (already in location_managemet requirements)
What it tests:
1. Board reachability (GET /api/status)
2. All 4 relays (POST /relay/on, GET /relay/status, POST /relay/off)
3. All 4 digital inputs (GET /input/status)
4. LED (POST /led/on + /led/off)
5. NFC reader (GET /nfc/status)
6. NFC config API (GET /nfc/config)
NOTE: Relay tests cycle each relay ON→verify→OFF→verify.
You should hear/see the relay click during the test.
"""
import sys
import json
import time
import argparse
import requests
TIMEOUT = 5 # seconds per HTTP request
RELAY_DLY = 0.4 # seconds to wait between relay on/status/off
# ─────────────────────────────────────────────────────────────────────────────
# Result tracking
# ─────────────────────────────────────────────────────────────────────────────
results = []
def record(name: str, ok: bool, detail: str = "") -> bool:
results.append({"name": name, "pass": ok, "detail": detail})
icon = "\033[32m[PASS]\033[0m" if ok else "\033[31m[FAIL]\033[0m"
print(f" {icon} {name}" + (f"{detail}" if detail else ""))
return ok
def _get(url: str):
try:
r = requests.get(url, timeout=TIMEOUT)
r.raise_for_status()
return r.json()
except requests.exceptions.ConnectionError:
return None
except Exception as e:
return {"_error": str(e)}
def _post(url: str):
try:
r = requests.post(url, timeout=TIMEOUT)
r.raise_for_status()
return r.json()
except requests.exceptions.ConnectionError:
return None
except Exception as e:
return {"_error": str(e)}
# ─────────────────────────────────────────────────────────────────────────────
# Tests
# ─────────────────────────────────────────────────────────────────────────────
def test_reachability(base: str) -> bool:
print("\n── Connectivity ──────────────────────────────")
data = _get(f"{base}/api/status")
if data is None:
record("Board reachable", False, f"no response from {base}")
return False
if "_error" in data:
record("Board reachable", False, data["_error"])
return False
record("Board reachable", True,
f"IP {base.split('//')[1]} "
f"nfc_init={data.get('nfc_initialized','?')} "
f"nfc_uid={data.get('nfc_last_uid') or '(none)'}")
return True
def test_relays(base: str):
print("\n── Relay Tests ───────────────────────────────")
for relay in range(1, 5):
# Turn ON
r_on = _post(f"{base}/relay/on?relay={relay}")
if r_on is None:
record(f"Relay {relay} ON", False, "no response"); continue
time.sleep(RELAY_DLY)
# Verify state = true
r_st = _get(f"{base}/relay/status?relay={relay}")
on_ok = r_st is not None and r_st.get("state") is True
record(f"Relay {relay} ON", on_ok,
("state=true" if on_ok else f"got {r_st}"))
time.sleep(RELAY_DLY)
# Turn OFF
_post(f"{base}/relay/off?relay={relay}")
time.sleep(RELAY_DLY)
# Verify state = false
r_st2 = _get(f"{base}/relay/status?relay={relay}")
off_ok = r_st2 is not None and r_st2.get("state") is False
record(f"Relay {relay} OFF", off_ok,
("state=false" if off_ok else f"got {r_st2}"))
time.sleep(0.1)
def test_inputs(base: str):
print("\n── Digital Input Tests ───────────────────────")
for inp in range(1, 5):
data = _get(f"{base}/input/status?input={inp}")
if data is None:
record(f"Input {inp} readable", False, "no response"); continue
if "_error" in data:
record(f"Input {inp} readable", False, data["_error"]); continue
state = data.get("state")
record(f"Input {inp} readable", state is not None,
f"state={'HIGH' if state else 'LOW'}" if state is not None else f"got {data}")
def test_led(base: str):
print("\n── LED Test ──────────────────────────────────")
on_r = _post(f"{base}/led/on")
time.sleep(0.3)
off_r = _post(f"{base}/led/off")
led_ok = (on_r is not None and "status" in on_r
and off_r is not None and "status" in off_r)
record("LED on/off", led_ok,
"API responded OK — verify LED blinked" if led_ok else f"on={on_r} off={off_r}")
def test_nfc(base: str):
print("\n── NFC Test ──────────────────────────────────")
data = _get(f"{base}/nfc/status")
if data is None:
record("NFC endpoint", False, f"GET /nfc/status no response"); return
if "_error" in data:
record("NFC endpoint", False, data["_error"]); return
record("NFC endpoint reachable", True, "")
init = data.get("initialized", False)
record("NFC PN532 initialized", init,
f"last_uid={data.get('last_uid') or '(none)'} "
f"access_state={data.get('access_state','?')}" if init
else "PN532 not detected — check hardware")
# Config endpoint
cfg = _get(f"{base}/nfc/config")
if cfg and "_error" not in cfg:
record("NFC config endpoint", True,
f"auth_uid='{cfg.get('auth_uid') or 'any'}' "
f"relay={cfg.get('relay_num')} "
f"pulse={cfg.get('pulse_ms')} ms")
else:
record("NFC config endpoint", False, str(cfg))
# ─────────────────────────────────────────────────────────────────────────────
# Optional: read board_test sketch results directly
# ─────────────────────────────────────────────────────────────────────────────
def test_sketch_results(base: str):
"""If the board_test sketch is running it exposes /test.json — use it."""
data = _get(f"{base}/test.json")
if data is None or "_error" in data:
return # main firmware running — no /test.json endpoint
print("\n── Board-Test Sketch Results (from /test.json) ──")
for t in data.get("tests", []):
record(f"[sketch] {t['name']}", t["pass"], t.get("detail", ""))
# ─────────────────────────────────────────────────────────────────────────────
# Entry point
# ─────────────────────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(description="Olimex ESP32-C6-EVB board verifier")
parser.add_argument("ip", nargs="?", default="192.168.0.181",
help="Board IP address (default: 192.168.0.181)")
parser.add_argument("--json", action="store_true",
help="Output results as JSON")
parser.add_argument("--skip-relays", action="store_true",
help="Skip relay tests (no load wired)")
args = parser.parse_args()
base = f"http://{args.ip}"
print(f"\n╔══════════════════════════════════════════╗")
print(f"║ Olimex ESP32-C6-EVB Remote Verifier ║")
print(f"╚══════════════════════════════════════════╝")
print(f" Target: {base}\n")
# Try board_test sketch first (if deployed)
test_sketch_results(base)
# Connectivity gate — abort if board unreachable
if not test_reachability(base):
print("\n\033[31m Board unreachable — aborting remaining tests.\033[0m")
print(f" Try: ping {args.ip} or wget -qO- {base}/api/status\n")
sys.exit(1)
if not args.skip_relays:
test_relays(base)
else:
print("\n── Relay Tests SKIPPED (--skip-relays) ──────")
test_inputs(base)
test_led(base)
test_nfc(base)
# ── Summary ───────────────────────────────────────────────────────────────
passed = sum(1 for r in results if r["pass"])
failed = sum(1 for r in results if not r["pass"])
total = len(results)
all_ok = failed == 0
print(f"\n╔══════════════════════════════════════════╗")
print(f"║ PASSED: {passed:2d} FAILED: {failed:2d} TOTAL: {total:2d}")
print("\033[32m✓ ALL TESTS PASSED — board is OK\033[0m ║" if all_ok else
"\033[31m✗ FAILURES DETECTED — see above\033[0m ║")
print(f"╚══════════════════════════════════════════╝\n")
if args.json:
summary = {
"board_ip": args.ip,
"pass": passed,
"fail": failed,
"total": total,
"board_ok": all_ok,
"tests": results,
}
print(json.dumps(summary, indent=2))
sys.exit(0 if all_ok else 1)
if __name__ == "__main__":
main()