#!/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()