wifi-densepose/docs/user-guide-apple-homepod.md

19 KiB
Raw Blame History

RuView ↔ HomePod Integration Guide

Ambient intelligence for Apple Home. Run RuView as a native HomeKit accessory so your HomePod discovers it, Siri understands it, and Apple Home automations govern it — no Home Assistant required.


Architecture Overview

RuView turns WiFi radio reflections into spatial intelligence (presence, breathing, fall risk, activity patterns). When paired with a HomePod or Apple TV acting as your Home Hub, RuView becomes an invisible sensor that feeds Siri, automations, and scenes:

ESP32-C6 CSI node (living room)
  ↓ (UDP feature stream)
RuView Sensing Server (announces presence, vital signs, BFLD events)
  ↓ (HTTP polling)
HAP Bridge (advertises HomeKit accessory on mDNS)
  ↓ (Bonjour discovery)
HomePod or Apple TV (Home Hub)
  ↓ (forwards to Home app + Siri)
iPhone, iPad, Mac, Watch, Apple Home automations

The integration leverages HomeKit Accessory Protocol (HAP-1.1) — the same standard that Philips Hue, Eve, and Nanoleaf use. Your HomePod discovers the bridge within seconds of launch, pairing is one-tap from the Home app, and Siri queries work immediately: "Hey Siri, is anyone in the living room?"

For design rationale and privacy safeguards, see ADR-125 — RuView ↔ Apple Home native HAP bridge.


What's Shipped Today (Tier 1 + Tier 2)

Eight incremental iterations landed in PR #797 on the feat/adr-125-apple-fabric branch:

Iteration Capability Commit Status
1 Multi-characteristic HomeKit accessory (Motion + Occupancy + StatelessProgrammableSwitch) 48db60a65 Runtime-live
2 Sensing-server HTTP endpoints for bridge polling (/api/v1/vitals, /api/v1/bfld, /api/v1/semantic-events) 194a2e163 Runtime-live, curl-validated
3 HAP bridge with N child accessories; Siri-by-room (name each room, Siri voices it) 63b77f760 Runtime-live, two bridges advertising
4 Semantic-events endpoint per §2.1.d (Unknown Presence, Unexpected Occupancy, Unrecognized Activity Pattern) 3d30261e7 Runtime-live, privacy invariant I1 enforced
5 rvagent MCP consumer (agentic chain); 12 MCP tools for Claude Code integration c19742d71 Runtime-validated on real C6
6 PyO3 BFLD PrivacyClass binding (SOTA rust crate exposed to Python) de0712d43 Source-built (cargo check green)
7 Shortcuts-as-glue (launchd job + Speak Text on HomePod via iCloud Home graph, bypasses Bonjour blocker) d0525359d Runtime-validated, osascript trigger green
8 Custom characteristic UUID scaffold for Eve.app rendering (design complete; runtime HAP-python JSON-loader follow-up) 3bb8c1621 Design scaffolded

What you can do today:

  • Pair a RuView bridge into your Home app on iPhone, iPad, or Mac.
  • Ask Siri room-specific presence questions ("is anyone home", "is the office occupied", "did someone fall").
  • Trigger automations on presence detection, breathing presence, fall risk, or activity pattern anomalies.
  • Stream RuView events to HomePod announcements via the Shortcuts-as-glue path (Tier 2).
  • Query RuView data programmatically through the agentic MCP interface (Claude Code integration).

Quickstart (5 minutes)

Prerequisites

  • Hardware: ESP32-C6 running CSI firmware (rev v0.7.0+) on the same WiFi network as your Mac and HomePod.
  • Software: Python 3.8+ on a Mac that's already paired into your Home app (iCloud account).
  • Network: Mac, HomePod, and ESP32-C6 must all be on the same LAN subnet (e.g., 192.168.1.0/24).

Step 1: Provision the ESP32-C6

Connect the C6 via USB and run the provisioning script:

python firmware/esp32-csi-node/provision.py \
  --port /dev/ttyUSB0 \
  --ssid "YourWiFiSSID" \
  --password "YourWiFiPassword" \
  --target-ip 192.168.1.20

Verify the C6 boots on the network:

ping 192.168.1.20

Step 2: Create a Python venv on the Mac and install HAP-python

mkdir -p ~/ruview-hap
cd ~/ruview-hap
python3 -m venv venv
source venv/bin/activate
pip install HAP-python

Step 3: Copy the RuView bridge scripts to the Mac

From the repository (e.g., cloned on your Mac), copy these files:

cp scripts/c6-presence-watcher.py ~/ruview-hap/
cp scripts/ruview-sensing-server.py ~/ruview-hap/
cp scripts/ruview-hap-bridge.py ~/ruview-hap/

Step 4: Start the three daemons in order

Terminal 1: Start the C6 presence watcher (reads UDP packets from the C6, applies BFLD privacy gate)

cd ~/ruview-hap
source venv/bin/activate
python c6-presence-watcher.py --node-id 1 --esp32-ip 192.168.1.20 --privacy-class 2

Output: Writes presence events to /tmp/ruview-state.json.

Terminal 2: Start the sensing server (HTTP polling interface for the HAP bridge)

cd ~/ruview-hap
source venv/bin/activate
python ruview-sensing-server.py --port 3000

Output: Listening on http://127.0.0.1:3000/api/v1/....

Terminal 3: Start the HAP bridge (advertises HomeKit accessory on mDNS)

cd ~/ruview-hap
source venv/bin/activate
python ruview-hap-bridge.py --port 51826 --pin 200-70-910

Output: Look for setup code in the terminal output, e.g., Setup code: 200-70-910.

Step 5: Pair the bridge from your iPhone

  1. Open the Home app on your iPhone.
  2. Tap the + icon (top right) → Add Accessory.
  3. Scan the setup code (or tap Don't Have a Code or Can't Scan?More Options).
  4. Select the RuView Sense bridge from the list (should appear within 10 seconds).
  5. Assign to a room (e.g., "Living Room").
  6. Tap Done.

Step 6: Test with Siri

Once paired, ask Siri:

"Hey Siri, is anyone in the living room?"

Siri will respond with the current occupancy state. Walk past the C6 and ask again — the presence value should update within 12 seconds.


Per-Room Expansion

To monitor multiple rooms, run multiple C6 nodes, each with its own c6-presence-watcher.py instance:

# Terminal: Room 1 (Living Room, node_id=1)
python c6-presence-watcher.py --node-id 1 --esp32-ip 192.168.1.20 \
  --output /tmp/ruview-state.living-room.json

# Terminal: Room 2 (Bedroom, node_id=2)
python c6-presence-watcher.py --node-id 2 --esp32-ip 192.168.1.21 \
  --output /tmp/ruview-state.bedroom.json

# Terminal: HAP bridge (auto-discovers both state files)
python ruview-hap-bridge.py --port 51826 --rooms "Living Room,Bedroom"

The HAP bridge auto-discovers *.json files in /tmp/ruview-state* and creates a child HomeKit accessory per room. Each room appears separately in the Home app and can be assigned to its physical location.


Privacy Semantics

RuView's BFLD (Beamforming Feedback Layer for Detection) uses a privacy class gate that enforces what data can cross the HomeKit boundary. Only Classes 2 and 3 (Anonymous and Restricted) are eligible; Class 0/1 (Raw identity information) is never exposed.

The Three Semantic Events

HomeKit exposes thresholded events, not raw probabilities:

Event HomeKit Characteristic Meaning Example Automation
Unknown Presence MotionSensor (stateful) Person detected + no matching identity record for >30s "Turn on porch light when Unknown Presence detected after 9pm"
Unexpected Occupancy OccupancySensor Occupancy outside the operator's defined schedule "Send notification if office is occupied on weekends"
Unrecognized Activity Pattern ProgrammableSwitch (momentary) Activity drift or recalibration gate fires "Run a re-learning sequence when activity changes"

What's Deliberately Hidden

The following are never exposed to HomeKit:

  • identity_risk_score (numeric 01 confidence) — only thresholded semantic events cross the boundary
  • Soul-Signature match probability — internal to BFLD
  • rf_signature_hash — cryptographic internal state

This enforces ADR-125 §2.1.d invariant I1: raw identity information never exits the node. The semantic framing is intentional — "Unknown Presence" reads as who's-here-and-it's-fine-but-worth-noting, not as an accusation.

For the technical definition, see ADR-118 — Beamforming Feedback Layer for Detection.


Siri-by-Room

Name each HomeKit accessory after its room. The HAP bridge pulls room names from the state file prefixes:

python c6-presence-watcher.py --node-id 1 \
  --output /tmp/ruview-state.LIVING_ROOM.json

# HAP bridge sees this and names the accessory "Living Room"

When paired in the Home app, Siri knows the room:

Query Result
"Is anyone in the living room?" Queries the Living Room accessory's motion sensor
"Is anyone home?" Queries all room accessories; returns true if any motion is detected
"Turn on the bedroom lights when occupancy is detected" Automation triggers on the Bedroom accessory only

StatelessProgrammableSwitch for Automations

Each room also exposes a StatelessProgrammableSwitch that fires on semantic-event boundaries (Unrecognized Activity Pattern, Recalibration, etc.). This is the HomeKit primitive for momentary triggers:

  1. In the Home app, go to AutomationCreate New AutomationWhen an Accessory is Controlled.
  2. Select Living RoomProgrammable SwitchSingle Press.
  3. Add an action: Turn on scene, Send notification, Set HomeKit Secure Video recording, etc.

HomePod Announcements via Shortcuts (Tier 2 Path)

The easiest way to announce RuView events on a HomePod is through Shortcuts-as-glue — a native macOS launchd job that watches RuView's semantic events and triggers a Shortcut you define.

This path bypasses the Bonjour reflector blocker that can prevent HomePod discovery in some mesh networks. Instead of direct mDNS, the Mac uses the Home graph (iCloud-paired) to reach the HomePod.

One-Time Setup

1. Create the Shortcut in Shortcuts.app

  1. Open Shortcuts.app on your Mac.
  2. Click + (top left) → Create Shortcut.
  3. Click Add Action → search for "Speak Text" → add it.
  4. In the "Speak Text" action, click the speaker icon → select your HomePod (or HomePod mini).
  5. Name the Shortcut RuView Announce (exact name).
  6. Save (top right).

2. Test the Shortcut from the terminal

osascript -e 'tell application "Shortcuts Events" to run shortcut "RuView Announce" with input "Test from RuView"'

Your HomePod should speak "Test from RuView" in your chosen voice.

3. Install the launchd job

Copy the launchd plist from the repository:

cp scripts/macos-shortcuts/ruview-watcher.plist \
  ~/Library/LaunchAgents/com.ruvnet.ruview.watcher.plist

launchctl load ~/Library/LaunchAgents/com.ruvnet.ruview.watcher.plist

launchctl list | grep ruvnet  # Confirm it's loaded

4. Verify it works

Tail the log in one terminal:

tail -f /tmp/ruview-watcher.log

In another terminal, walk past the C6 and trigger a presence detection. The log should show:

[17:10:12] unknown_presence rising-edge → running 'RuView Announce'

And your HomePod should announce the event in its configured voice.

Extending to Multiple Rooms

To announce different events in different rooms, create multiple Shortcuts in Shortcuts.app:

  • RuView Announce Kitchen
  • RuView Announce Bedroom

Then run multiple watcher jobs with different --shortcut-name flags:

# Kitchen events on HomePod mini in kitchen
scripts/macos-shortcuts/announce-via-homepod.sh \
  --node-id 1 --event unknown_presence \
  --shortcut-name "RuView Announce Kitchen" \
  --poll-interval 2 &

# Bedroom events on HomePod in bedroom
scripts/macos-shortcuts/announce-via-homepod.sh \
  --node-id 2 --event unknown_presence \
  --shortcut-name "RuView Announce Bedroom" \
  --poll-interval 2 &

Going Further

Because the Shortcut is operator-editable in Shortcuts.app, you can extend it to do anything:

  • Activate a scene ("turn on bedtime scene when fall risk detected")
  • Send a notification to your Apple Watch
  • Call a Webhook to integrate with other systems
  • Send a message to another person's iPhone
  • Trigger a HomeKit secure camera recording

This is the flexibility of the Shortcuts-as-glue approach — no code change needed in RuView, all customization in the operator's own Shortcuts library.

For complete setup details and troubleshooting, see scripts/macos-shortcuts/README.md.


Agentic Consumption via MCP

RuView's sensing stream is also available through Model Context Protocol (MCP) — the standard interface for Claude Code and other AI agents to query RuView data.

The @ruvnet/rvagent npm package (v0.1.0)

The package exposes 12 MCP tools that let Claude Code agents:

  • Query presence and occupancy per room
  • Read breathing rate and heart rate telemetry
  • Monitor BFLD semantic events
  • Inspect the app registry (edge modules)
  • Kickstart background training jobs

Installation

In your Claude Code project:

npm install -D @ruvnet/rvagent@0.1.0

# Or, add via MCP:
claude mcp add rvagent -- npx -y @ruvnet/rvagent@0.1.0

Then in your Claude Code chat:

/claude-flow-help  # Lists all available MCP tools

Tool Reference

Tool Input Output
ruview_csi_latest node_id Latest CSI window (1024 subcarriers, 30 OFDM symbols)
ruview_pose_infer CSI window 17-keypoint skeleton (x, y, confidence per joint)
ruview_count_infer CSI window Person count + 95% CI
ruview_registry_list query (optional) List of 105+ available edge modules
ruview_train_count epochs, learning_rate Kickoff training job ID
ruview_job_status job_id Progress, ETA, current loss
ruview.bfld.last_scan node_id Latest BFLD scan: privacy_class, person_count (identity_risk_score=null per I1 invariant)
ruview.bfld.subscribe node_id, event_filter Stream BFLD windows until you close the stream
ruview.presence.now room (optional) Current occupancy per room
ruview.vitals.get_breathing node_id Breathing rate (BPM) + confidence
ruview.vitals.get_heart_rate node_id Heart rate (BPM) + confidence
ruview.vitals.get_all node_id Breathing + heart rate + metadata

Example: Claude Code Agent Workflow

# Claude-flow agent pseudocode
import claude_code

tools = claude_code.mcp_tools("rvagent")

# Query latest presence
presence = tools["ruview.presence.now"](room="living room")
print(f"Living room occupancy: {presence.occupancy}")  # True/False

# Check vitals
vitals = tools["ruview.vitals.get_all"](node_id=1)
print(f"Breathing: {vitals.breathing_bpm} BPM")

# Stream BFLD events in real-time
for event in tools["ruview.bfld.subscribe"](node_id=1, event_filter="unknown_presence"):
    print(f"Unknown presence detected: privacy_class={event.privacy_class}")

For the full MCP specification, see ADR-124 — rvagent MCP / RuVector npm integration.


Troubleshooting

HomePod Not Visible on dns-sd -B _airplay._tcp local. from the Mac

Likely cause: HomePod and Mac are on different subnets despite being on the same SSID. Some mesh networks segment 2.4 GHz and 5 GHz bands onto different /24 subnets, or place guest devices on a separate VLAN.

Check:

  1. Open your router admin page and confirm both the HomePod and Mac are in the same subnet range (e.g., both 192.168.1.x).
  2. If they're on different subnets (e.g., 192.168.1.x vs 192.168.100.x), enable IGMP Proxying in your router settings (common on Netgear Nighthawk). If available, enable Bonjour Repeater or mDNS Reflector instead.
  3. Restart the HomePod and Mac.

Note: The Shortcuts-as-glue path (Tier 2) doesn't need this fix — it routes announcements through the iCloud Home graph, not mDNS.

iPhone Pairing Fails with "Couldn't Add Accessory"

Likely cause: The HAP bridge's pairing state is corrupt or out of sync with mDNS.

Fix:

  1. Stop the HAP bridge daemon.
  2. Delete the pairing state file:
    rm -rf ~/.ruview-hap-prod/accessory.state
    
  3. Restart the HAP bridge — it regenerates a new setup code.
  4. From the Home app, retry Add AccessoryMore Options with the new setup code.

The Setup Code Regenerates on Restart

Expected behavior. HAP-python regenerates the setup code if the pairing persist file is missing or corrupt. Once you've paired successfully, the pairing key is stored separately in ~/.ruview-hap-prod/ and survives restarts — the setup code itself is transient and only matters during initial pairing.

If you lose the setup code before pairing, simply delete the state and restart to get a new one.

Presence Updates Are Slow or Stuck

Likely cause: The HTTP polling loop in ruview-sensing-server.py is blocked, or the C6 is not sending UDP packets.

Check:

  1. Verify the C6 is booting: ping 192.168.1.20.
  2. Verify packets are reaching the sensing server:
    nc -u -l 5005 &  # Listen on UDP 5005
    # You should see occasional packets from the C6
    
  3. Manually query the sensing server:
    curl http://127.0.0.1:3000/api/v1/vitals/latest
    
    Should return JSON with breathing and heart rate fields.
  4. If the HAP bridge doesn't reflect the changes after polling, restart it.

What's NOT in Scope

These items are intentionally deferred or beyond the current release:

Item Status Timeline
Matter Protocol (P3) Deferred Waiting for matter-rs SDK stabilization; HAP-1.1 covers 95% of the UX today
Rust-native HAP (P2) Planned Replaces Python HAP-python sidecar; expected after operator feedback from 5+ real pairings
PyO3 BFLD wheel deployment (ADR-117 P5) Pending Runtime import flip so Python scripts use the Rust BFLD crate; source-built ( cargo check green) but wheel not yet published
Custom characteristic UUIDs for Eve.app (Iter 8 runtime) Scaffolded Design complete; awaiting HAP-python JSON-loader implementation (small follow-up PR)
AirPlay 2 voice synthesis (pyatv) Network-pending Requires HomePod visible on Bonjour from the Mac; Shortcuts-as-glue (Tier 2) is the working alternative

References