Commit graph

11 commits

Author SHA1 Message Date
jedarden
9c56a3795b feat(fusion): 3D Fresnel zone weighted multi-link localization
New package mothership/internal/fusion implementing spatial localization
via Fresnel zone inverse-distance weighting across multiple TX-RX links:

- Grid3D: voxel grid (default 0.2m cells) with AddLinkInfluence using
  weight = activation / (1 + normalised_excess), where normalised_excess
  = excess_path_length / first_Fresnel_zone_radius
- Engine: fuses LinkMotion slices into a 3D activation map, normalises,
  and extracts peak blobs with confidence scores
- FresnelZoneRadius helper for callers choosing grid resolution
- 15 tests covering edge cases, ±1m position accuracy with 4+ links,
  off-centre target, and performance (<50ms for 20 links)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 23:34:52 -04:00
jedarden
bcfd1e3f16 feat(protocol): full bidirectional node protocol over WebSocket
- firmware: wire on-device motion hints to websocket_send_motion_hint()
  with 1s rate-limit; csi_set_rate() now writes to g_state.packet_rate
- firmware: websocket_send_motion_hint() sends variance + MAC + timestamp
  to mothership so rate controller ramps ahead of server-side detection
- mothership: RateController.OnMotionHint() preemptively ramps adjacent
  nodes via SetAdjacentNodesFn callback (topology-aware burst propagation)
- mothership: idle timeout extended to 30s; variance_threshold=0 in active
  mode (server handles detection), DefaultVarianceThreshold=1.0 in idle
- mothership: SendRoleToMAC() exposes dynamic role changes post-connect
- mothership: SendOTAToMAC() enables pushing firmware updates to nodes
- mothership: OTA status events are now logged with state and progress %

Protocol is backward-compatible: binary CSI frames work as in Phase 1;
JSON control messages are additive on the same single WebSocket per node.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 23:14:19 -04:00
jedarden
11ab29836e feat(recording): disk-backed circular buffer with 48h time-based retention
New package mothership/internal/recording implements a disk-backed circular
buffer for continuous CSI frame recording. Frames are persisted in the same
binary format used over WebSocket (raw frame bytes + 8-byte recv timestamp).

Key features:
- Time-based retention (default 48h) prunes expired records automatically
  on each Append and via an explicit Prune() method
- Configurable retention via SPAXEL_RECORDING_RETENTION env var (e.g. "24h")
- ScanRange(from, to time.Time, fn) for time-windowed read-back
- Space-bounded: fixed-size file with circular eviction prevents disk exhaustion
- Crash-safe: 32-byte header (magic + write/oldest/wrap positions) survives restarts
- 18 tests covering write, read-back, time-based pruning, wrap-around,
  crash recovery, ScanRange, env var configuration, and storage bounds

Foundation for Phase 8 time-travel replay.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 23:01:58 -04:00
jedarden
fb691904c6 feat(dashboard): per-link motion presence indicator with amplitude time series
- Dashboard hub broadcasts motion state changes immediately on transition
  (idle↔motion) via BroadcastMotionState; periodic state snapshots include
  motion_states for new client init
- Per-link presence badge (green CLEAR / red MOTION) rendered in link list
  alongside global presence indicator in status bar
- Amplitude mean time-series chart (60 s rolling window) for selected link,
  line segments colored by motion state at each sample
- Fix: links created from JSON link_active/state events now initialize
  ampHistory and lastAmpSample so time-series accumulates from first frame

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 22:55:33 -04:00
jedarden
de424a1f63 feat(mothership): complete Phase 2 signal processing & detection
- Dashboard presence indicator: per-link MOTION/CLEAR badges with global
  status bar indicator, motion_state WebSocket messages, amplitude chart
- CSI recording buffer: disk-backed circular buffer (replay/store.go) with
  magic-tagged binary format, wrap/eviction, 360 MB default (~48 h at 20 Hz)
- Adaptive sensing rate: RateController ramps nodes to 20 Hz on motion,
  drops to 2 Hz after 10 s idle; wires to SendConfigToMAC over WebSocket
- Fix: alias internal/signal as sigproc to avoid conflict with os/signal
- Fix: add GetAllMotionStates() to MockIngestionState in dashboard tests

All tests pass (signal, ingestion, replay, dashboard).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 22:27:24 -04:00
jedarden
973b0a0b62 feat(mothership): signal processing package with phase/baseline/motion detection
- Phase sanitization: complex CSI computation, RSSI normalization, spatial
  phase unwrapping, linear regression for STO/CFO removal
- HT20 subcarrier map: 64 total, 46 data subcarriers (null/guard/pilot excluded)
- Baseline system: EMA with motion-gated updates, confidence scoring,
  snapshot/restore for SQLite persistence
- Motion detection: NBVI subcarrier selection (Welford's algorithm),
  deltaRMS computation, exponential smoothing, threshold-based detection
- LinkProcessor/ProcessorManager: thread-safe per-link processing

Complete: phase sanitization, baseline, motion detection (items 7-9 of Phase 2)
Remaining: dashboard presence indicator, CSI recording buffer, adaptive rate

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 08:08:46 -04:00
jedarden
3f2962f945 feat(deploy): Docker packaging with multi-stage build and compose orchestration
- Dockerfile: golang:1.23-bookworm builder → distroless/static-debian12:nonroot
- docker-compose.yml: host networking (required for mDNS), Traefik labels, resource limits
- VERSION: 0.1.0 for image tagging
- .dockerignore: excludes docs, build artifacts, IDE files
- .gitignore: standard Go/ESP-IDF ignores

Key decisions:
- Host networking required: Docker bridge blocks mDNS multicast 224.0.0.251
- distroless/static-debian12:nonroot: no shell, minimal attack surface, UID 65532
- Firmware via volume mount: users provide their own binaries for OTA
- Traefik labels disabled by default: enable SPAXEL_TRAEFIK_ENABLE=true for TLS

Complete: Phase 1 Docker packaging — all foundation items now done
Remaining: Phase 2 signal processing (baseline, deltaRMS, Fresnel zones)
2026-03-26 07:46:15 -04:00
jedarden
8230ef4222 feat(dashboard): web dashboard skeleton with Three.js 3D scene and CSI visualization
- Static HTML/JS dashboard served by mothership at /
- Three.js 3D scene with ground grid, OrbitControls (pan/zoom/rotate)
- WebSocket connection at /ws/dashboard for real-time CSI streaming
- Binary CSI frame parsing (24-byte header + I/Q payload)
- Amplitude bar chart (64 subcarriers) as 2D Canvas overlay
- Node/link panels with live status and click-to-select
- Dashboard Go package with Hub for client broadcasting
- Ingestion server broadcasts CSI frames and node events to dashboard
- 5 new tests for dashboard hub operations

Complete: 3D scene, WebSocket, amplitude chart, node/link panels
Remaining: Docker packaging (Phase 1 final item)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 07:40:53 -04:00
jedarden
f178496c83 feat(firmware): ESP32-S3 firmware skeleton with CSI/BLE/WebSocket
- State machine: 7-state lifecycle (BOOT→WIFI→DISCOVERY→CONNECTED,
  degraded modes WIFI_LOST/MOTHERSHIP_UNAVAILABLE/CAPTIVE_PORTAL)
- WiFi: STA connection with exponential backoff, mDNS discovery with
  cached IP fallback, captive portal AP for provisioning
- WebSocket: bidirectional comms, binary CSI frames (24B header + I/Q),
  JSON hello/health/ble/ota_status upstream, role/config/ota/reboot
  downstream, OTA task with progress reporting
- CSI: promiscuous mode capture, queue-based processing, passive BSSID
  filter, on-device variance tracking (Welford's) for motion hints
- BLE: passive scanning on Core 0, 60-device cache, 5s reporting
- NVS: 15-key schema with versioning, role/rate persistence

Complete: ESP32 firmware skeleton, passive radar, BLE scanning
Remaining: Dashboard skeleton, Docker packaging (Phase 1 items 5-6)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 07:20:55 -04:00
jedarden
3937dbe06e feat(mothership): WebSocket ingestion server with binary/JSON frame parsing
- /ws/node endpoint: one goroutine per connection, bidirectional
- Binary frames: 24-byte header → CSIFrame struct; payload as []int8 I/Q pairs
- JSON frames: dispatched by "type" field (hello, health, ble, motion_hint, ota_status)
- Per-link ring buffer: 256-sample circular, keyed by (node_mac, peer_mac)
- Node identity from first "hello" — no pre-registration required
- Used gorilla/websocket: mature, SetReadDeadline, binary frame support
- mDNS via hashicorp/mdns at _spaxel._tcp.local:8080
- Ping/pong keepalive: 30s ping interval, 60s read deadline
- Malformed frame tracking: warn at 100/min, close at 1000/min

Complete: frame parsing, ring buffers, mDNS, hello/health/ble dispatch, tests (22 passing)
Remaining: OTA command dispatch, /ws/dashboard publisher, SQLite fleet manager (Phase 2)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 06:55:22 -04:00
jedarden
948c966226 init: spaxel project — docs, plan, and marathon infrastructure
- WiFi CSI-based indoor positioning system for self-hosted home environments
- docs/plan/plan.md: full 9-phase implementation plan (65 gaps closed by analysis)
- docs/research/: CSI fundamentals, physics, algorithms, signal processing, mesh topology, accuracy limits, literature
- docs/notes/: recovery mechanisms, simulation testing, UX visualization
- .marathon/instruction.md: per-iteration marathon instructions with detailed commit format
- .marathon/start.sh: GLM-5 tmux launcher via ZAI proxy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 06:43:25 -04:00