diff --git a/scripts/macos-rssi-bridge/Makefile b/scripts/macos-rssi-bridge/Makefile index bfa28350..f5bf7bf6 100644 --- a/scripts/macos-rssi-bridge/Makefile +++ b/scripts/macos-rssi-bridge/Makefile @@ -1,4 +1,4 @@ -.PHONY: all build build-server clean start stop test scan run run-verbose listen install-helper dashboard help +.PHONY: all build build-server clean start stop test scan run run-verbose run-presence listen install-helper dashboard help HELPER := mac_wifi BRIDGE := target/release/macos-rssi-bridge @@ -20,13 +20,14 @@ all: build help: @echo "macos-rssi-bridge — Mac WiFi card → RuView sensing-server bridge" @echo "" - @echo " make start build + run everything: sensing-server + bridge + open UIs" - @echo " make stop kill any running sensing-server / bridge processes" + @echo " make start build + run everything: sensing-server + bridge + presence + open UIs" + @echo " make stop kill any running sensing-server / bridge / presence processes" @echo " make build compile mac_wifi (Swift) + macos-rssi-bridge (Rust)" @echo " make build-server compile the v2 sensing-server (release)" @echo " make scan run a single multi-BSSID scan, print JSON" @echo " make run start the bridge → udp://$(TARGET_HOST):$(TARGET_PORT) + http://localhost:9090" @echo " make run-verbose same, with per-frame stats on stderr" + @echo " make run-presence poll bridge /aps, POST single pose to sensing-server /api/v1/pose/external" @echo " make dashboard open the multistatic RF tomography UI in your browser" @echo " make listen debug: print frames arriving on $(TARGET_PORT) (no sensing-server)" @echo " make test run Rust unit tests" @@ -45,20 +46,27 @@ $(BRIDGE): src/main.rs Cargo.toml build-server: cd $(SENSING_SERVER_DIR) && cargo build --release -p wifi-densepose-sensing-server --no-default-features -# One-shot: builds everything, starts sensing-server + bridge under a single -# process group, opens both UIs, waits for Ctrl-C, and kills both children -# cleanly. The sensing-server is run with cwd=v2/ so its default --ui-path of -# ../ui resolves; the bridge stays in this directory so ./mac_wifi works. +# One-shot: builds everything, starts sensing-server + bridge + presence +# injector under a single process group, opens both UIs, waits for Ctrl-C, +# and kills all three children cleanly. The sensing-server is run with +# cwd=v2/ so its default --ui-path of ../ui resolves; the bridge stays in +# this directory so ./mac_wifi works. # # --source esp32 is required: the server's "auto" probe only binds UDP if it # detects frames *before* the bridge starts sending. The bridge IS the ESP32 # source (synthetic, but same wire format), so pin it explicitly. Otherwise # the server falls back to `simulate`, never binds UDP, and you get # ECONNREFUSED on the bridge + a UI showing simulated (fake) motion. +# +# presence_to_pose.py is required for the 3D Observatory: the sensing-server +# computes motion stats from CSI frames, but rendering a single coherent +# figure (instead of the placeholder five-skeleton fallback) needs an +# explicit POST to /api/v1/pose/external. The script polls the bridge's +# /aps endpoint and translates per-AP RSSI variance into one honest pose. start: build build-server @$(MAKE) -s stop >/dev/null 2>&1 || true - @echo "[start] sensing-server + macos-rssi-bridge → $(SENSING_UI_URL)" - @trap 'echo; echo "[start] stopping…"; kill $$SERVER_PID $$BRIDGE_PID 2>/dev/null; wait 2>/dev/null; exit 0' INT TERM; \ + @echo "[start] sensing-server + macos-rssi-bridge + presence injector → $(SENSING_UI_URL)" + @trap 'echo; echo "[start] stopping…"; kill $$SERVER_PID $$BRIDGE_PID $$PRESENCE_PID 2>/dev/null; wait 2>/dev/null; exit 0' INT TERM; \ ( cd $(SENSING_SERVER_DIR) && ./$(SENSING_SERVER_BIN) \ --source esp32 --udp-port $(TARGET_PORT) ) & \ SERVER_PID=$$!; \ @@ -67,14 +75,20 @@ start: build build-server ./$(BRIDGE) --helper ./$(HELPER) --target-host $(TARGET_HOST) \ --target-port $(TARGET_PORT) --interval $(INTERVAL) & \ BRIDGE_PID=$$!; \ - echo "[start] bridge pid=$$BRIDGE_PID — dashboard: $(BRIDGE_DASHBOARD)"; \ + echo "[start] bridge pid=$$BRIDGE_PID — dashboard: $(BRIDGE_DASHBOARD) — waiting 2s for /aps…"; \ + sleep 2; \ + python3 presence_to_pose.py --bridge-url http://127.0.0.1:9090/aps \ + --server-url $(SENSING_UI_URL)/api/v1/pose/external & \ + PRESENCE_PID=$$!; \ + echo "[start] presence injector pid=$$PRESENCE_PID — feeding /api/v1/pose/external @ 10 Hz"; \ ( sleep 2; open $(SENSING_UI_URL) 2>/dev/null; open $(BRIDGE_DASHBOARD) 2>/dev/null ) & \ - echo "[start] press Ctrl-C to stop both"; \ + echo "[start] press Ctrl-C to stop all three"; \ wait stop: -@pkill -f "$(SENSING_SERVER_BIN)" 2>/dev/null && echo "[stop] sensing-server killed" || echo "[stop] no sensing-server" -@pkill -f "$(BRIDGE)" 2>/dev/null && echo "[stop] bridge killed" || echo "[stop] no bridge" + -@pkill -f "presence_to_pose.py" 2>/dev/null && echo "[stop] presence injector killed" || echo "[stop] no presence injector" test: cargo test --release @@ -90,6 +104,12 @@ run-verbose: build ./$(BRIDGE) --helper ./$(HELPER) --target-host $(TARGET_HOST) \ --target-port $(TARGET_PORT) --interval $(INTERVAL) --verbose +# Standalone presence injector. Requires the bridge to already be running +# (so /aps responds) and the sensing-server to be up on $(SENSING_UI_URL). +run-presence: + python3 presence_to_pose.py --bridge-url http://127.0.0.1:9090/aps \ + --server-url $(SENSING_UI_URL)/api/v1/pose/external --verbose + # Open the RF tomography dashboard. Assumes the bridge is already running # (start it with `make run` or `make run-verbose` in another terminal). dashboard: diff --git a/scripts/macos-rssi-bridge/README.md b/scripts/macos-rssi-bridge/README.md index 2fe4f3ae..cea1a8d7 100644 --- a/scripts/macos-rssi-bridge/README.md +++ b/scripts/macos-rssi-bridge/README.md @@ -39,19 +39,30 @@ Toolchain: `swiftc` 6.0+ and Cargo 1.80+. Tested on macOS 26 (Tahoe) arm64. ## Run -One command — builds both, launches the sensing-server and the bridge, +One command — builds everything, launches the full three-process pipeline, opens both UIs, and cleans up on Ctrl-C: ```bash -make start # → sensing-server (:8080) + bridge (:9090/dashboard) -make stop # kill any leftover sensing-server / bridge process +make start # → sensing-server (:8080) + bridge (:9090/dashboard) + presence injector +make stop # kill any leftover process from the trio ``` -Or run them manually in two terminals if you want to see logs side-by-side. -In one terminal, the sensing-server (provides UI + UDP receiver): +The pipeline is three processes: + +1. **sensing-server** — UDP receiver, motion stats, WebSocket stream, UI + (pinned to `--source esp32` so it binds UDP and consumes the bridge's + frames instead of falling back to its simulation generator). +2. **macos-rssi-bridge** — Swift scan → Rust UDP emitter → ESP32 frame format. +3. **presence_to_pose.py** — polls the bridge's `/aps` endpoint and POSTs a + single honest pose to `/api/v1/pose/external` so the Observatory renders + one figure modulated by RSSI variance instead of the placeholder + five-skeleton fallback. + +Or run each manually if you want logs side-by-side. In one terminal, the +sensing-server (UI + UDP receiver): ```bash -cd ../../v2 && cargo run --release -p wifi-densepose-sensing-server --no-default-features +cd ../../v2 && cargo run --release -p wifi-densepose-sensing-server --no-default-features -- --source esp32 # UI: http://localhost:8080 # WS: ws://localhost:8765/ws/sensing ``` @@ -63,6 +74,12 @@ make run # default: target 127.0.0.1:5005, 1.5 s scan interval # or: make run-verbose for per-frame stats on stderr ``` +In a third, the presence injector (only needed for the 3D Observatory pose): + +```bash +make run-presence # polls :9090/aps, posts a pose to :8080/api/v1/pose/external +``` + Open http://localhost:8080 and walk around. Motion should register within a few seconds (the pipeline needs ~5 frames of baseline before it'll classify).