253 lines
11 KiB
Python
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()
|