velxio/test/test-esp32-cam/tests/debug_worker_direct.py

183 lines
5.7 KiB
Python

"""
Spawn esp32_worker.py directly (bypassing the WS+uvicorn stack) so we can
SEE the QEMU stderr where our fprintf debug lines from esp32_i2s_cam.c go.
Usage:
python test/test-esp32-cam/tests/debug_worker_direct.py
Compiles webcam_demo.ino, launches the worker, pushes a JPEG every 100 ms,
prints worker stderr + stdout to terminal in real time. Kill with Ctrl+C.
"""
from __future__ import annotations
import base64
import json
import os
import pathlib
import subprocess
import sys
import threading
import time
THIS_DIR = pathlib.Path(__file__).resolve().parent
TEST_ROOT = THIS_DIR.parent
REPO_ROOT = TEST_ROOT.parent.parent
SKETCH = TEST_ROOT / "sketches" / "webcam_demo" / "webcam_demo.ino"
WORKER_SCRIPT = REPO_ROOT / "backend" / "app" / "services" / "esp32_worker.py"
LIB_XTENSA = REPO_ROOT / "backend" / "app" / "services" / "libqemu-xtensa.dll"
sys.path.insert(0, str(THIS_DIR))
def compile_sketch() -> str:
"""POST sketch to /api/compile/ and return base64-encoded firmware.
Caches the result in /tmp/webcam_demo_fw.b64 keyed by sketch SHA256."""
import hashlib
import httpx
sketch = SKETCH.read_text(encoding="utf-8")
h = hashlib.sha256(sketch.encode()).hexdigest()[:16]
cache = pathlib.Path(os.environ.get("TEMP", "C:/temp")) / f"webcam_demo_fw_{h}.b64"
cache.parent.mkdir(parents=True, exist_ok=True)
if cache.exists():
fw = cache.read_text().strip()
print(f"[compile] cache HIT ({h}): {len(fw)} chars b64")
return fw
print(f"[compile] cache MISS ({h}), compiling via /api/compile/...")
r = httpx.post(
"http://localhost:8001/api/compile/",
json={
"files": [{"name": "webcam_demo.ino", "content": sketch}],
"board_fqbn": "esp32:esp32:esp32cam",
},
timeout=300.0,
)
r.raise_for_status()
body = r.json()
if not body.get("success"):
raise RuntimeError(f"compile failed: {body.get('error', body)[:600]}")
fw = body.get("binary_content") or body.get("firmware_b64")
if not fw:
raise RuntimeError("compile OK but no firmware")
cache.write_text(fw)
print(f"[compile] OK, firmware: {len(fw)} chars b64 "
f"(~{len(fw) * 3 // 4 // 1024} KiB raw), cached to {cache}")
return fw
def main() -> int:
print(f"[paths] worker={WORKER_SCRIPT}")
print(f"[paths] dll={LIB_XTENSA} exists={LIB_XTENSA.exists()}")
print(f"[paths] sketch={SKETCH} exists={SKETCH.exists()}")
fw = compile_sketch()
from webcam_helper import get_test_jpeg
jpeg, src = get_test_jpeg()
print(f"[jpeg] source={src}, {len(jpeg)} bytes, head={jpeg[:8].hex()}")
jpeg_b64 = base64.b64encode(jpeg).decode("ascii")
config = {
"lib_path": str(LIB_XTENSA),
"firmware_b64": fw,
"machine": "esp32-picsimlab",
"sensors": [],
"wifi_enabled": False,
}
print(f"[spawn] launching worker via {sys.executable}")
proc = subprocess.Popen(
[sys.executable, str(WORKER_SCRIPT)],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
# Send config
proc.stdin.write((json.dumps(config) + "\n").encode())
proc.stdin.flush()
print("[spawn] config sent")
# Threads to stream stderr + stdout
stop = threading.Event()
def stream_stderr():
for line in proc.stderr:
if stop.is_set():
return
sys.stderr.write("[STDERR] " + line.decode(errors="replace"))
sys.stderr.flush()
def stream_stdout():
line_buf = bytearray()
if proc.stdout is None:
return
for line in proc.stdout:
if stop.is_set():
return
try:
msg = json.loads(line)
except Exception:
continue
t = msg.get("type", "?")
if t == "uart_tx" and msg.get("uart") == 0:
b = msg.get("byte", 0)
line_buf.append(b)
if b == ord("\n") or len(line_buf) >= 200:
text = line_buf.decode(errors="replace").rstrip()
line_buf.clear()
if text:
sys.stdout.write(f"[SERIAL] {text}\n")
sys.stdout.flush()
elif t == "system":
sys.stdout.write(f"[SYSTEM] {msg}\n"); sys.stdout.flush()
t1 = threading.Thread(target=stream_stderr, daemon=True)
t2 = threading.Thread(target=stream_stdout, daemon=True)
t1.start(); t2.start()
# Wait for boot
time.sleep(6.0)
# Attach + push frames
print("[push] sending camera_attach")
proc.stdin.write((json.dumps({"cmd": "camera_attach"}) + "\n").encode())
proc.stdin.flush()
print("[push] starting frame push loop (10 fps)")
push_count = 0
try:
while True:
time.sleep(0.1)
if proc.stdin is None:
break
proc.stdin.write((json.dumps({
"cmd": "camera_frame",
"b64": jpeg_b64,
"fmt": "jpeg",
"w": 320,
"h": 240,
}) + "\n").encode())
proc.stdin.flush()
push_count += 1
if push_count == 1 or push_count % 50 == 0:
print(f"[push] {push_count} frames pushed")
if push_count > 300:
print("[push] hit 300 frames, stopping")
break
except KeyboardInterrupt:
print("\n[main] Ctrl+C received")
finally:
stop.set()
try:
proc.terminate()
proc.wait(timeout=3)
except Exception:
proc.kill()
return 0
if __name__ == "__main__":
sys.exit(main())