diff --git a/CLAUDE.md b/CLAUDE.md index 05f3bf63..33ed0713 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,9 +73,9 @@ All 5 ruvector crates integrated in workspace: | Device | Port | Chip | Role | Cost | |--------|------|------|------|------| -| ESP32-S3 (8MB flash) | COM7 | Xtensa dual-core | WiFi CSI sensing node | ~$9 | +| ESP32-S3 (8MB flash) | COM9 (ruvzen, was COM7) | Xtensa dual-core | WiFi CSI sensing node | ~$9 | | ESP32-S3 SuperMini (4MB) | — | Xtensa dual-core | WiFi CSI (compact) | ~$6 | -| ESP32-C6 + Seeed MR60BHA2 | COM4 | RISC-V + 60 GHz FMCW | mmWave HR/BR/presence | ~$15 | +| ESP32-C6 + Seeed MR60BHA2 | COM12 (ruvzen, was COM4) | RISC-V + 60 GHz FMCW | mmWave HR/BR/presence + WiFi CSI | ~$15 | | HLK-LD2410 | — | 24 GHz FMCW | Presence + distance | ~$3 | **Not supported:** ESP32 (original), ESP32-C3 — single-core, can't run CSI DSP pipeline. diff --git a/scripts/hap-test-sensor.py b/scripts/hap-test-sensor.py new file mode 100644 index 00000000..fa319389 --- /dev/null +++ b/scripts/hap-test-sensor.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +""" +hap-test-sensor.py — ADR-125 §2.1.a smoke test. + +Stands up a single HomeKit Accessory Protocol (HAP-1.1) bridge with one +child MotionSensor named "RuView Test Motion". Once paired in the Apple +Home app, the HomePod (acting as Home Hub) sees state changes when +TOGGLE_FILE (default /tmp/ruview-motion) is touched / removed. + +Usage: + python3 hap-test-sensor.py + +Pair from iPhone: Home app -> Add Accessory -> More Options -> "RuView Test Bridge". +The setup code is printed on stdout AND written to ~/.ruview-hap/setup-code.txt. + +Trigger motion: touch /tmp/ruview-motion +Clear motion: rm /tmp/ruview-motion + +State persists across restarts in ~/.ruview-hap/accessory.state. +""" + +from pathlib import Path +import os +import sys +import time +import signal + +from pyhap.accessory import Accessory, Bridge +from pyhap.accessory_driver import AccessoryDriver +from pyhap.const import CATEGORY_SENSOR, CATEGORY_BRIDGE + +STATE_DIR = Path(os.path.expanduser("~/.ruview-hap")) +STATE_DIR.mkdir(exist_ok=True) +STATE_FILE = STATE_DIR / "accessory.state" +SETUP_CODE_FILE = STATE_DIR / "setup-code.txt" +TOGGLE_FILE = Path(os.environ.get("RUVIEW_MOTION_TOGGLE", "/tmp/ruview-motion")) + + +class RuViewMotion(Accessory): + category = CATEGORY_SENSOR + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + serv = self.add_preload_service("MotionSensor") + self.char_motion = serv.configure_char("MotionDetected") + self._last = False + + @Accessory.run_at_interval(1.0) + def run(self): + present = TOGGLE_FILE.exists() + if present != self._last: + self.char_motion.set_value(present) + self._last = present + print( + f"[hap-test] MotionDetected -> {present} (toggle file: {TOGGLE_FILE})", + flush=True, + ) + + +def main() -> int: + driver = AccessoryDriver(port=51826, persist_file=str(STATE_FILE)) + + bridge = Bridge(driver, "RuView Test Bridge") + bridge.category = CATEGORY_BRIDGE + bridge.add_accessory(RuViewMotion(driver, "RuView Test Motion")) + driver.add_accessory(accessory=bridge) + + setup_code = driver.state.pincode.decode() if hasattr(driver.state.pincode, "decode") else driver.state.pincode + SETUP_CODE_FILE.write_text(str(setup_code) + "\n") + print(f"[hap-test] HAP bridge advertising as 'RuView Test Bridge'") + print(f"[hap-test] iPhone pair flow: Home app -> Add Accessory -> More Options") + print(f"[hap-test] Setup code (also in {SETUP_CODE_FILE}): {setup_code}") + print(f"[hap-test] Motion toggle file: {TOGGLE_FILE}") + print(f"[hap-test] State persists in: {STATE_FILE}") + + signal.signal(signal.SIGTERM, lambda *_: driver.stop()) + driver.start() + return 0 + + +if __name__ == "__main__": + sys.exit(main())