fix(macos-rssi-bridge): make `make start` launch the presence injector too

The 3D Observatory needs an explicit POST to /api/v1/pose/external to
render a single coherent figure — without it the UI shows the placeholder
five-skeleton fallback that doesn't respond to real RSSI variance, which
manifests as a "frozen" figure even when the bridge is emitting frames
and the sensing-server is processing them. presence_to_pose.py is the
component that closes that loop (polls /aps, POSTs one pose at 10 Hz).

`make start` now manages all three children (server, bridge, presence
injector) under one trap, and `make stop` kills the trio. New `make
run-presence` target for running the injector standalone against an
already-running stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
griffin 2026-05-26 09:50:44 -07:00
parent 82f15c112a
commit 672be201f4
No known key found for this signature in database
GPG Key ID: E2CBC5B01EFA35EB
2 changed files with 54 additions and 17 deletions

View File

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

View File

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