236 lines
9.8 KiB
Python
236 lines
9.8 KiB
Python
#!/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))
|