Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `CrossDomainEvaluator` — 6-metric evaluation protocol (MPJPE in-domain/cross-domain/few-shot/cross-hardware, domain gap ratio, adaptation speedup)
- ADR-027: Cross-Environment Domain Generalization — 10 SOTA citations (PerceptAlign, X-Fi ICLR 2025, AM-FM, DGSense, CVPR 2024)
- **Cross-platform RSSI adapters** — macOS CoreWLAN (`MacosCoreWlanScanner`) and Linux `iw` (`LinuxIwScanner`) Rust adapters with `#[cfg(target_os)]` gating
- macOS CoreWLAN Python sensing adapter with Swift helper (`mac_wifi.swift`)
- macOS synthetic BSSID generation (FNV-1a hash) for Sonoma 14.4+ BSSID redaction
- macOS CoreWLAN bridge tooling with canonical Swift helper (`tools/macos-wifi-scan/main.swift`) and explicit `macos-bridge` UDP source
- macOS synthetic BSSID generation (SHA-256 with locally administered MACs) for Sonoma 14.4+ BSSID redaction
- Linux `iw dev <iface> scan` parser with freq-to-channel conversion and `scan dump` (no-root) mode
- ADR-025: macOS CoreWLAN WiFi Sensing (ORCA)

Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1614,6 +1614,10 @@ graph TB
# Start with Windows WiFi RSSI
./target/release/sensing-server --source wifi

# Start with the experimental macOS bridge fallback
python3 scripts/macos_wifi_bridge.py --interval-ms 100 &
./target/release/sensing-server --source macos-bridge --tick-ms 100

# Run vital sign benchmark
./target/release/sensing-server --benchmark

Expand All @@ -1629,7 +1633,7 @@ graph TB

| Flag | Description |
|------|-------------|
| `--source` | Data source: `auto`, `wifi`, `esp32`, `simulate` |
| `--source` | Data source: `auto`, `wifi`, `esp32`, `simulate`, `macos-bridge` |
| `--http-port` | HTTP port for UI and REST API (default: 8080) |
| `--ws-port` | WebSocket port (default: 8765) |
| `--udp-port` | UDP port for ESP32 CSI frames (default: 5005) |
Expand Down Expand Up @@ -1674,7 +1678,7 @@ WebSocket: `ws://localhost:3001/ws/sensing` (real-time sensing + vital signs)
| Intel 5300 | Firmware mod | ~$15 | Linux `iwl-csi` |
| Atheros AR9580 | ath9k patch | ~$20 | Linux only |
| Any Windows WiFi | RSSI only | $0 | [Tutorial #36](https://github.com/ruvnet/RuView/issues/36) |
| Any macOS WiFi | RSSI only (CoreWLAN) | $0 | [ADR-025](docs/adr/ADR-025-macos-corewlan-wifi-sensing.md) |
| Any macOS WiFi | RSSI only (CoreWLAN, breathing experimental) | $0 | [ADR-025](docs/adr/ADR-025-macos-corewlan-wifi-sensing.md) |
| Any Linux WiFi | RSSI only (`iw`) | $0 | Requires `iw` + `CAP_NET_ADMIN` |

</details>
Expand Down
76 changes: 40 additions & 36 deletions docs/adr/ADR-025-macos-corewlan-wifi-sensing.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ Implement a **macOS CoreWLAN sensing adapter** as a Swift helper binary + Rust a

1. **Subprocess isolation** — Swift binary is a standalone tool, built and versioned independently of the Rust workspace.
2. **Same domain types** — macOS adapter produces `Vec<BssidObservation>`, identical to the Windows path. All downstream processing reuses as-is.
3. **SSID:channel as synthetic BSSID** — When real BSSIDs are redacted (no Location Services), `sha256(ssid + channel)[:12]` generates a stable pseudo-BSSID. Documented limitation: same-SSID same-channel APs collapse to one observation.
4. **`#[cfg(target_os = "macos")]` gating** — macOS-specific code compiles only on macOS. Windows and Linux builds are unaffected.
3. **Synthetic locally administered BSSID** — When real BSSIDs are redacted (no Location Services), the helper derives a stable synthetic MAC from `interface + ssid + channel`, marks `bssid_synthetic=true`, and keeps the observation usable by the Rust pipeline. Hidden SSIDs use a fixed placeholder in the hash input.
4. **Runtime gating with cross-platform tests** — macOS helper execution remains runtime-gated to macOS, but the scanner/parser code compiles cross-platform so Linux CI can exercise contract and source-selection tests.
5. **Graceful degradation** — If the Swift helper is not found or fails, `--source auto` skips macOS WiFi and falls back to simulated mode with a clear warning.

---
Expand Down Expand Up @@ -107,53 +107,40 @@ Implement a **macOS CoreWLAN sensing adapter** as a Swift helper binary + Rust a

### 3.2 Swift Helper Binary

**File:** `rust-port/wifi-densepose-rs/tools/macos-wifi-scan/main.swift`
**File:** `tools/macos-wifi-scan/main.swift`

```swift
// Modes:
// (no args) → Full scan, output JSON array to stdout
// --probe → Quick availability check, output {"available": true/false}
// --connected → Connected network info only
// --probe → One NDJSON line for the current association, exits immediately
// --scan-once → One NDJSON line per visible AP
// --connected → One NDJSON line for the current association
// --stream --interval-ms <N> → Repeated connected-AP NDJSON records
//
// Output schema (scan mode):
// [
// {
// "ssid": "MyNetwork",
// "rssi": -52,
// "noise": -90,
// "channel": 36,
// "band": "5GHz",
// "phy_mode": "802.11ax",
// "bssid": "aa:bb:cc:dd:ee:ff" | null,
// "security": "wpa2_personal"
// }
// ]
// Output schema (scan/connected/stream):
// {"timestamp":1710000000.0,"interface":"en0","ssid":"MyNetwork","bssid":"aa:bb:cc:dd:ee:ff","bssid_synthetic":false,"rssi":-52,"noise":-90,"channel":36,"band":"5ghz","tx_rate_mbps":866.7,"is_connected":true}
```

**Build:**

```bash
# Requires Xcode Command Line Tools (xcode-select --install)
cd tools/macos-wifi-scan
swiftc -framework CoreWLAN -framework Foundation -O -o macos-wifi-scan main.swift
./scripts/build-mac-wifi.sh
```

**Build script:** `tools/macos-wifi-scan/build.sh`
**Build script:** `scripts/build-mac-wifi.sh`

### 3.3 Rust Adapter

**File:** `crates/wifi-densepose-wifiscan/src/adapter/macos_scanner.rs`

```rust
// #[cfg(target_os = "macos")]

pub struct MacosCoreWlanScanner {
helper_path: PathBuf, // Resolved at construction: $PATH or sibling of server binary
helper_path: PathBuf, // Resolved from env override, repo-local build output, then PATH
}

impl MacosCoreWlanScanner {
pub fn new() -> Result<Self, WifiScanError> // Finds helper or errors
pub fn probe() -> bool // Runs --probe, returns availability
pub fn new() -> Self // Resolves helper path at construction
pub fn probe_sync(&self) -> Result<(), WifiScanError>
pub fn scan_sync(&self) -> Result<Vec<BssidObservation>, WifiScanError>
pub fn connected_sync(&self) -> Result<Option<BssidObservation>, WifiScanError>
}
Expand Down Expand Up @@ -216,7 +203,7 @@ The existing 8-stage `WindowsWifiPipeline` (ADR-022) operates entirely on `Bssid
| File | Purpose | Lines (est.) |
|------|---------|-------------|
| `tools/macos-wifi-scan/main.swift` | CoreWLAN scanner, JSON output | ~120 |
| `tools/macos-wifi-scan/build.sh` | Build script (`swiftc` invocation) | ~15 |
| `scripts/build-mac-wifi.sh` | Build script (`swiftc` invocation) | ~15 |
| `crates/wifi-densepose-wifiscan/src/adapter/macos_scanner.rs` | Rust adapter: spawn helper, parse JSON, produce `BssidObservation` | ~200 |

### 4.2 Modified Files
Expand All @@ -231,7 +218,7 @@ The existing 8-stage `WindowsWifiPipeline` (ADR-022) operates entirely on `Bssid

- `std::process::Command` — subprocess spawning (stdlib)
- `serde_json` — JSON parsing (already in workspace)
- No changes to `Cargo.toml`
- `serde_json` in `wifi-densepose-wifiscan` for typed helper parsing

---

Expand All @@ -243,21 +230,30 @@ All verification on Mac Mini (M2 Pro, macOS 26.3).

| Test | Command | Expected |
|------|---------|----------|
| Build | `cd tools/macos-wifi-scan && ./build.sh` | Produces `macos-wifi-scan` binary |
| Probe | `./macos-wifi-scan --probe` | `{"available": true}` |
| Scan | `./macos-wifi-scan` | JSON array with real SSIDs, RSSI in dBm, channels |
| Connected | `./macos-wifi-scan --connected` | Single JSON object for connected network |
| No WiFi | Disable WiFi → `./macos-wifi-scan` | `{"available": false}` or empty array |
| Build | `./scripts/build-mac-wifi.sh` | Produces `rust-port/wifi-densepose-rs/target/tools/macos-wifi-scan/macos-wifi-scan` |
| Probe | `./rust-port/wifi-densepose-rs/target/tools/macos-wifi-scan/macos-wifi-scan --probe` | One status JSON line (`ok`, `interface`, `message`) or non-zero with clear stderr if Wi-Fi is unavailable |
| Scan | `./rust-port/wifi-densepose-rs/target/tools/macos-wifi-scan/macos-wifi-scan --scan-once` | NDJSON records with SSID/RSSI/channel/band |
| Connected | `./rust-port/wifi-densepose-rs/target/tools/macos-wifi-scan/macos-wifi-scan --connected` | Single NDJSON object for connected network |
| No WiFi | Disable WiFi → `... --probe` | Non-zero exit with clear stderr |

### 5.2 Rust Adapter

| Test | Method | Expected |
|------|--------|----------|
| Unit: JSON parsing | `#[test]` with fixture JSON | Correct `BssidObservation` values |
| Unit: synthetic BSSID | `#[test]` with nil bssid input | Stable `sha256(ssid:channel)[:12]` |
| Unit: helper discovery | `#[test]` with env override/repo-local/PATH fixtures | Resolution order matches contract |
| Unit: helper not found | `#[test]` with bad path | `WifiScanError::ProcessError` |
| Integration: real scan | `cargo test` on Mac Mini | Live observations from CoreWLAN |

### 5.2.1 Bridge Fallback

| Test | Command | Expected |
|------|---------|----------|
| Bridge CLI validation | `python3 scripts/macos_wifi_bridge.py --help` | Shows helper/host/port/interval arguments |
| Bridge syntax | `python3 -m py_compile scripts/macos_wifi_bridge.py` | Passes |
| Bridge startup order | `python3 scripts/macos_wifi_bridge.py --interval-ms 100 &` then `./target/release/sensing-server --source macos-bridge --tick-ms 100` | Server binds and labels source `wifi-bridge:macos` |
| Bridge payload rejection | Send ESP32 binary or malformed JSON | Server logs rejection and keeps waiting |

### 5.3 End-to-End

| Step | Command | Verify |
Expand All @@ -270,7 +266,15 @@ All verification on Mac Mini (M2 Pro, macOS 26.3).
| 6 | Open UI at `http://localhost:8080` | Signal field updates with real RSSI variation |
| 7 | `--source auto` | Auto-detects macOS WiFi, does not fall back to simulated |

### 5.4 Cross-Platform Regression
### 5.4 Review Checklist

- No compiled helper binaries are committed.
- Public docs describe macOS as RSSI-only presence/coarse-motion sensing, not CSI parity.
- Helper discovery order is documented as env override, repo-local build output, then `PATH`.
- `macos-bridge` stays explicit-only and is never auto-selected.
- PR includes manual macOS QA evidence because CI is Linux-centric.

### 5.5 Cross-Platform Regression

| Platform | Build | Expected |
|----------|-------|----------|
Expand Down
25 changes: 19 additions & 6 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,14 +238,23 @@ docker run --network host ruvnet/wifi-densepose:latest --source wifi --tick-ms 5

### macOS WiFi (RSSI Only)

Uses CoreWLAN via a Swift helper binary. macOS Sonoma 14.4+ redacts real BSSIDs; the adapter generates deterministic synthetic MACs so the multi-BSSID pipeline still works.
Uses CoreWLAN via the canonical Swift helper built from `tools/macos-wifi-scan/main.swift`. macOS Sonoma 14.4+ redacts real BSSIDs; the helper emits deterministic synthetic MACs so the multi-BSSID pipeline still works.

Native macOS mode is RSSI/scan-based sensing for presence and coarse motion. Breathing estimates are experimental because CoreWLAN scan rates are much slower than ESP32 CSI. This mode does not provide CSI parity and should not be described as pose-grade sensing.

```bash
# Compile the Swift helper (once)
swiftc -O v1/src/sensing/mac_wifi.swift -o mac_wifi
# Build the Swift helper (once, on macOS)
./scripts/build-mac-wifi.sh

# Optional: point the server at a custom helper location
export RUVIEW_MAC_WIFI_HELPER="$PWD/rust-port/wifi-densepose-rs/target/tools/macos-wifi-scan/macos-wifi-scan"

# Run natively
./target/release/sensing-server --source macos --http-port 3000 --ws-port 3001 --tick-ms 500
# Run native macOS Wi-Fi sensing
./target/release/sensing-server --source wifi --http-port 3000 --ws-port 3001 --tick-ms 500

# Experimental fallback bridge (explicit, never auto-selected)
python3 scripts/macos_wifi_bridge.py --interval-ms 100 &
./target/release/sensing-server --source macos-bridge --http-port 3000 --ws-port 3001 --tick-ms 100
```

See [ADR-025](adr/ADR-025-macos-corewlan-wifi-sensing.md) for details.
Expand Down Expand Up @@ -473,7 +482,7 @@ The Rust sensing server binary accepts the following flags:

| Flag | Default | Description |
|------|---------|-------------|
| `--source` | `auto` | Data source: `auto`, `simulate`, `wifi`, `esp32` |
| `--source` | `auto` | Data source: `auto`, `simulate`, `wifi`, `esp32`, `macos-bridge` |
| `--http-port` | `8080` | HTTP port for REST API and UI |
| `--ws-port` | `8765` | WebSocket port |
| `--udp-port` | `5005` | UDP port for ESP32 CSI frames |
Expand Down Expand Up @@ -502,6 +511,10 @@ The Rust sensing server binary accepts the following flags:
# Windows WiFi RSSI
./target/release/sensing-server --source wifi --tick-ms 500

# Experimental macOS bridge fallback
python3 scripts/macos_wifi_bridge.py --interval-ms 100 &
./target/release/sensing-server --source macos-bridge --tick-ms 100

# Run benchmark
./target/release/sensing-server --benchmark

Expand Down
2 changes: 2 additions & 0 deletions rust-port/wifi-densepose-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading