chore(adr-125 iter 1): fix C6 COM port + ship HAP-python reference impl

Two changes from the ADR-125 e2e bootstrap session:

1. CLAUDE.md hardware table: COM4 -> COM12 for ESP32-C6 (the C6 +
   Seeed MR60BHA2 dev kit now enumerates on COM12 on ruvzen, not
   COM4 as previously documented). Same fix applied to the ESP32-S3
   row (COM7 -> COM9) which CLAUDE.local.md already covered but the
   top-level table had not been updated.

2. scripts/hap-test-sensor.py — the ~80 LOC HAP-python sidecar that
   ADR-125 §2.1.a names as the reference implementation. Already
   running on ruv-mac-mini, already paired with operator's iPhone
   (paired_clients: 1), already round-trips a MotionDetected
   characteristic from a touch-file toggle through the HomePod (as
   Home Hub) to the Home app.

Substrate validated for iter 2+:
  - C6 provisioned on ruv.net (IP 192.168.1.179, ch 5, RSSI -38)
  - UDP frames: 44 packets in 8s @ mac-mini:5005 (~5.5 pps)
  - HAP bridge paired and live

Refs ADR-125, #794.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv 2026-05-25 16:14:51 -04:00
parent 82fecbb5ad
commit 19b445f9bb
2 changed files with 84 additions and 2 deletions

View File

@ -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.

View File

@ -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())