#!/usr/bin/env python3 """ Olimex ESP32-C5-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.240 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. Both relays (POST /relay/on, GET /relay/status, POST /relay/off) 3. Both 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) return r.status_code, r.text, None except requests.RequestException as e: return None, None, str(e) def _post(url: str, json_data=None): try: r = requests.post(url, json=json_data, timeout=TIMEOUT) return r.status_code, r.text, None except requests.RequestException as e: return None, None, str(e) # ───────────────────────────────────────────────────────────────────────────── # Main verification # ───────────────────────────────────────────────────────────────────────────── def verify_board(ip: str) -> int: """Test all board subsystems. Returns count of failures.""" base = f"http://{ip}" failures = 0 print(f"\n{'='*70}") print(f"Olimex ESP32-C5-EVB Verification") print(f"{'='*70}") print(f"Target IP: {ip}\n") # ─ 1. Board Reachability ────────────────────────────────────────────────── print("1. Board Status") status_code, resp_text, err = _get(f"{base}/api/status") if err: record("Board reachable", False, err) return 1 # Can't proceed without board connectivity if not (status_code and status_code == 200): record("Board reachable", False, f"HTTP {status_code}") return 1 try: status_json = json.loads(resp_text) record("Board reachable", True, f"HTTP 200") except json.JSONDecodeError: record("Board reachable (partial)", False, "Response not JSON") status_json = {} # ─ 2. Relays ────────────────────────────────────────────────────────────── print("\n2. Relays (2x 250V/10A)") for relay_num in [1, 2]: relay_name = f"Relay {relay_num}" # Turn ON _, _, err = _post(f"{base}/relay/on?relay={relay_num}") if err: record(f"{relay_name} ON", False, err) failures += 1 continue time.sleep(RELAY_DLY) status_code, resp_text, err = _get(f"{base}/relay/status?relay={relay_num}") if err or status_code != 200: record(f"{relay_name} ON verify", False, err or f"HTTP {status_code}") failures += 1 else: try: r = json.loads(resp_text) if r.get("state") == True: record(f"{relay_name} ON", True) else: record(f"{relay_name} ON verify", False, "State mismatch") failures += 1 except: record(f"{relay_name} ON verify", False, "Invalid response") failures += 1 time.sleep(RELAY_DLY) # Turn OFF _, _, err = _post(f"{base}/relay/off?relay={relay_num}") if err: record(f"{relay_name} OFF", False, err) failures += 1 continue time.sleep(RELAY_DLY) status_code, resp_text, err = _get(f"{base}/relay/status?relay={relay_num}") if err or status_code != 200: record(f"{relay_name} OFF verify", False, err or f"HTTP {status_code}") failures += 1 else: try: r = json.loads(resp_text) if r.get("state") == False: record(f"{relay_name} OFF", True) else: record(f"{relay_name} OFF verify", False, "State mismatch") failures += 1 except: record(f"{relay_name} OFF verify", False, "Invalid response") failures += 1 # ─ 3. Digital Inputs ────────────────────────────────────────────────────── print("\n3. Opto-Isolated Inputs (110VAC-240VAC)") for input_num in [1, 2]: status_code, resp_text, err = _get(f"{base}/input/status?input={input_num}") if err or status_code != 200: record(f"Input {input_num}", False, err or f"HTTP {status_code}") failures += 1 else: try: r = json.loads(resp_text) state = "HIGH" if r.get("state") else "LOW" record(f"Input {input_num}", True, f"State: {state}") except: record(f"Input {input_num}", False, "Invalid response") failures += 1 # ─ 4. LED ───────────────────────────────────────────────────────────────── print("\n4. LED Control") _, _, err = _post(f"{base}/led/on") if err: record("LED ON", False, err) failures += 1 else: record("LED ON", True) time.sleep(0.2) _, _, err = _post(f"{base}/led/off") if err: record("LED OFF", False, err) failures += 1 else: record("LED OFF", True) # ─ 5. NFC Status ────────────────────────────────────────────────────────── print("\n5. NFC Module (PN532 via UEXT, optional)") status_code, resp_text, err = _get(f"{base}/nfc/status") if err or status_code != 200: record("NFC Status API", False, err or f"HTTP {status_code}") else: try: nfc_status = json.loads(resp_text) init = nfc_status.get("initialized", False) record("NFC Status API", True, f"Init: {init}") except: record("NFC Status API", False, "Invalid response") # ─ 6. NFC Config ────────────────────────────────────────────────────────── status_code, resp_text, err = _get(f"{base}/nfc/config") if err or status_code != 200: record("NFC Config API", False, err or f"HTTP {status_code}") else: try: config = json.loads(resp_text) record("NFC Config API", True, f"Relay: {config.get('relay_num')}, Pulse: {config.get('pulse_ms')}ms") except: record("NFC Config API", False, "Invalid response") # ───────────────────────────────────────────────────────────────────────── # Summary # ───────────────────────────────────────────────────────────────────────── print(f"\n{'='*70}") passed = len([r for r in results if r["pass"]]) total = len(results) print(f"Result: {passed}/{total} tests passed") if failures == 0: print("✓ All systems functional!") else: print(f"✗ {failures} failures detected") print(f"{'='*70}\n") return failures if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("ip", nargs="?", default="192.168.0.240", help="Board IP (default: 192.168.0.240)") parser.add_argument("--json", action="store_true", help="Output results as JSON") args = parser.parse_args() failures = verify_board(args.ip) if args.json: print(json.dumps(results, indent=2)) sys.exit(min(failures, 1))