- Added SettingsHandler with GET/PATCH /api/settings for configurable settings
- Added ZonesHandler with full CRUD for zones (GET/POST/PUT/DELETE /api/zones)
- Added PortalsHandler with full CRUD for portals (GET/POST/PUT/DELETE /api/portals)
- Added TriggersHandler with full CRUD for automation triggers including test endpoint
- Added NotificationsHandler for notification channel config and test notifications
- Added ReplayHandler for CSI replay sessions (start/stop/seek/tune endpoints)
- All endpoints follow OpenAPI-style patterns with appropriate godoc comments
- Settings persist to SQLite across restarts
- Zone/portal updates reflected in live 3D view via WebSocket
Endpoints implemented:
GET/PATCH /api/settings
GET/POST/PUT/DELETE /api/zones
GET/POST/PUT/DELETE /api/portals
GET/POST/PUT/DELETE /api/triggers
POST /api/triggers/{id}/test
GET/PATCH /api/notifications/config
POST /api/notifications/test
GET/POST /api/replay/sessions
POST /api/replay/start
POST /api/replay/stop
POST /api/replay/seek
POST /api/replay/tune
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixed a deadlock in the Migrator where Pending() and Migrate() methods
called CurrentVersion() while holding the lock, but CurrentVersion()
also tried to acquire the same lock. Created internal currentVersionLocked()
method for lock-safe access.
Also fixed unused imports in migrations.go and fixed NOT NULL constraint
in TestMigrateFromV1 test.
All tests pass including:
- TestMigrateIdempotent: running migrations on already-migrated DB is no-op
- TestMigrateFromV1: migrating from v1 to current version
- TestMigrationRollback: failed migrations roll back cleanly
- TestPreMigrationBackup: backups created before schema changes
- TestPendingMigrations: pending migrations correctly identified
- TestCurrentVersion: version queries work for all DB states
- TestBackupPruning: old backups pruned correctly
- TestOpenDBFullSequence: full 7-phase startup sequence
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add schema_migrations table for version tracking (version INT PK, applied_at INT, description TEXT)
- Implement Migration registry with Migration structs (Version, Description, Up func)
- Startup phase reads current schema_ver and runs pending migrations in transactions
- Pre-migration backup using SQLite VACUUM INTO to /data/backups/pre-upgrade-v<old>-to-v<new>-<timestamp>.sqlite
- Create /data/backups/ directory automatically; prune backups older than 90 days
- Initial migrations: v001 initial schema, v002 diurnal_baselines, v003 anomaly_patterns, v004 prediction_models, v005 ble_device_aliases
- Failed migration exits cleanly with error; backup preserved; no partial schema state
- Idempotent: running migrations on already-migrated DB is a no-op
- Standalone migrate.go CLI tool for running migrations outside the mothership process
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add BroadcastEvent for presence transitions, zone entries/exits, portal crossings
- Add BroadcastAlert for anomaly detections and security mode triggers
- Add BroadcastBLEScan for BLE device list updates (5s interval)
- Add BroadcastTriggerState for automation trigger state changes
- Add BroadcastSystemHealth for periodic system stats (60s interval)
- Add BLEState, TriggerState, and SystemHealthProvider interfaces
- Update hub.Run with new tickers for BLE (5s) and system health (60s)
- Update dashboard app.js with handlers for new message types
- Add bleDevices map to state for BLE device tracking
- Log unhandled message types for future debugging
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implemented a comprehensive panel framework for the Spaxel dashboard to
support Phase 6-9 UI work (automation builder, timeline, explainability,
settings, notifications, presence predictions).
- Panel System (dashboard/js/panels.js):
- Slide-in sidebar (right, 360px) with close button and title
- Modal overlay (centered, 600px wide) for forms and wizards
- Toast notification stack (bottom-right) with type variants
- Panel registry: panels can be opened by name from anywhere
- Route/Mode Navigation (dashboard/js/router.js):
- Hash-based routing: #live (default), #timeline, #automations, #settings
- Mode toggle bar in header with active state styling
- Active mode persisted across page refresh (localStorage)
- State Management (dashboard/js/state.js):
- Central app state object (nodes, blobs, zones, links, alerts, events)
- Subscribe/notify pattern for reactive component updates
- Convenience methods for common operations (updateNode, addEvent, etc.)
- Settings Panel (dashboard/js/settings-panel.js):
- Motion threshold slider (deltaRMS threshold)
- Fusion rate selection (5/10/20 Hz)
- Grid cell size and Fresnel decay rate controls
- Subcarrier count and baseline time constant settings
- Notification channel config (Ntfy URL/token, Pushover keys)
- System info display (version, uptime, detection quality, node count)
- Updated index.html with:
- CSS/JS includes for panel framework
- Settings button in status bar
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add build tag to main_phase6.go so default builds use phases 1-5 only.
Fix dashboard/hub.go: remove unused fmt import and phase 6 identity
fields that reference nonexistent tracking.Blob struct members.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add FFT-based breathing detection with long-dwell presence tracking:
- FFTBreathingDetector: Rolling 30-second buffer, Hann window, FFT analysis
to detect periodic breathing signals in 0.2-1.0 Hz band
- DwellTracker: State machine with CLEAR → MOTION_DETECTED →
POSSIBLY_PRESENT → STATIONARY_DETECTED transitions
- Health gating: Detection disabled when link health < 0.7
- Dashboard: Slow-pulsing blue dot for stationary detection with BPM tooltip
- Timeline: Event logging on transition to STATIONARY_DETECTED
Physics: CSI phase oscillates at 2x breathing rate due to round-trip
path length change. 15 BPM breathing → 0.5 Hz CSI phase oscillation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement a complete self-improving localization system that uses BLE
RSSI triangulation as ground truth to learn per-link, per-zone weights
for the Fresnel zone fusion engine.
Key components:
- Ground truth sample collection with confidence > 0.7 and distance < 0.5m gates
- Spatial weight learner using online SGD with L2 regularization
- Validation gate that rejects updates without 5% improvement on holdout set
- Bilinear interpolation for smooth weight transitions between zones
- SQLite persistence for weights with 10,000 sample cap per person
- Position accuracy trend visualization in dashboard Accuracy panel
Backend (mothership/internal/localization/):
- groundtruth.go: BLE trilateration provider with Gauss-Newton optimization
- groundtruth_store.go: SQLite storage with weekly accuracy rollups
- spatial_weights.go: SpatialWeightLearner with SGD, validation, interpolation
- weightlearner.go: WeightLearner with error history tracking
- weightstore.go: Weight persistence to SQLite
Frontend (dashboard/js/accuracy.js):
- fetchPositionAccuracy/fetchPositionHistory functions
- drawPositionSparkline for weekly median error trend
- Position accuracy section with median error, trend indicator, sample count
Tests cover:
- Sample collection gates (confidence/distance thresholds)
- SGD weight updates after 100 samples
- Validation gate rejection of adversarial samples
- Bilinear interpolation smoothness
- SQLite sample cap enforcement
- Weight persistence across restarts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add GetFeedbackInTimeRange() to query feedback by timestamp for accuracy computation
- Add GetUniqueScopeIDs() to extract unique link/zone/person IDs from feedback
- Update accuracy.go to use the new store methods for proper scope filtering
- Add zone breakdown visualization in accuracy panel with clickable zone items
- Add blob hover tooltips with thumbs up/down feedback buttons in 3D view
- Wire up feedback processor and accuracy computer in main startup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add comprehensive sleep quality monitoring system:
- Sleep analyzer with breathing and motion sample processing
- Sleep state tracking (awake, light_sleep, deep_sleep, restless)
- Quality scoring with breathing (40%), motion (30%), continuity (30%)
- Morning report generation with summaries and recommendations
- REST API endpoints at /api/sleep/* for status and reports
- Integration with signal processor for automated data collection
- Notification callbacks for morning report delivery
Deliverables:
- Breathing analysis during sleep hours via BreathingSample
- Motion scoring integration via MotionSample with DeltaRMS
- Morning summary generation at configurable hour (default 7 AM)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add showToast to SpaxelApp public API so the Feedback module can
display toast notifications when users submit feedback on detections.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add trajectory accumulation, directional flow maps, and dwell time
hotspot visualization for occupancy pattern analysis.
Backend:
- FlowAccumulator records trajectory segments and dwell time in SQLite
- REST endpoints for flow map, dwell heatmap, and detected corridors
- Bresenham rasterization for flow vector aggregation
- Connected component analysis for corridor detection
Frontend:
- Pattern controls in dashboard sidebar (flow, dwell, corridors toggles)
- Time filter dropdown (7d, 30d, all time)
- 3D visualization with ArrowHelper for flow, PlaneGeometry for heatmaps
- Pulsating animation on flow arrows
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add feedback_store.go with SQLite storage for detection feedback and accuracy metrics
- Add feedback_processor.go for background processing of user feedback
- Add accuracy.go for weekly precision/recall/F1 metric computation
- Add handler.go with REST API routes for feedback submission and accuracy retrieval
- Wire learning package into main_phase6.go with background processing
- Add dashboard/js/feedback.js with thumbs-up/down UI components
- Add dashboard/js/accuracy.js with accuracy panel rendering and sparkline trends
- Add comprehensive tests for feedback storage and accuracy computation
Feedback UI provides:
- Thumbs-up/down buttons for detection events
- Feedback form with false positive/negative options
- Missed detection reporting with position/zone selection
- Motivational counter showing improvement from user corrections
Accuracy panel shows:
- Circular gauge with F1 score
- Week-over-week trend sparkline
- Per-zone breakdown of precision/recall
- Total corrections count and improvement percentage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add comprehensive tests for HealthStore (healthpersist_test.go)
- Add link health panel CSS styles and sparkline visualization
- Add ghost node rendering for repositioning advice in viz3d.js
- Wire up all diagnostic API routes in main.go
- Minor test cleanup in linkweather_test.go
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements per-link health metrics that gate and weight detection algorithms:
Per-Link Health Metrics (LinkHealthScorer in ambient.go):
- SNR Estimate (40% weight): motion/quiet deltaRMS ratio via log10 mapping
- Phase Stability (30% weight): phase variance with 0.5 rad threshold
- Packet Rate Health (20% weight): actual vs configured rate
- Baseline Drift (10% weight): hourly normalized L2 change
Gating Effects:
- BreathingDetector: disabled when health_score < 0.7
- FusionEngine: link contributions weighted by health_score
Dashboard Visualization:
- 3D link line color: green (1.0) → yellow (0.5) → red (0.0)
- 3D link line thickness: 2px (>0.7), 1px (0.4-0.7), 0.5px (<0.4)
- System-wide Detection Quality gauge in header
- Link Health panel with per-metric breakdown and sparklines
API: GET /api/links returns health_score and health_details for each link
Tests:
- Health score computation with weighted sub-metrics
- SNR mapping: SNR=100 → 1.0, SNR=10 → 0.5
- Phase stability: variance=0 → 1.0, variance=0.5 → 0.0
- Breathing health gating at 0.7 threshold
- Fusion engine link weight verification
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Interactive onboarding wizard:
- 8-step Web Serial-based provisioning flow
- Firmware flashing via esp-web-install-button (CDN)
- Live CSI waveform feedback during guided calibration
- Server-side provisioning with client-side fallback
- Serial JSON response handling with error mapping
- Post-calibration reinforcement card with link count
OTA firmware management:
- Firmware list with SHA-256 hashes and size display
- Per-node progress tracking (idle/pending/downloading/rebooting/verified/failed/rollback)
- Rolling update orchestration via REST API
- Status bar button with state indicators (normal/in-progress/has-update)
- Node list badges for OTA status and rollback warnings
Guided troubleshooting:
- First-time feature tooltips with 8s auto-dismiss
- Sequential tooltip tour triggered on first node connection
- Node offline cards with step-by-step recovery instructions
- Factory reset instructions modal
- Client-side link health check (60s no-frame threshold)
- Captive portal recovery documentation
Exit criteria: New ESP32-S3 from unboxed to streaming CSI in under 5 minutes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add git-based version header generation for firmware builds
- Implement SHA-256 hash verification for OTA downloads with mbedtls
- Add URL decoding for captive portal form parsing (spaces, special chars)
- Add mbedtls dependency for SHA-256 verification
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds TransformControls for dragging nodes in the 3D scene, a room dimension
editor (width/depth/height), a GDOP (Geometric Dilution of Precision) coverage
overlay on the ground plane that updates in real-time during node drag, virtual
node support for planning optimal placement before hardware purchase, and REST
API integration to persist positions and room config to the mothership.
Backend adds PUT /api/nodes/{mac}/position, POST /api/nodes/virtual,
DELETE /api/nodes/{mac}, PUT /api/room endpoints. Also wires OTA status
handler and improves firmware manifest to use actual latest binary filename.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add troubleshooting infrastructure for non-technical users:
- TroubleshootManager: node offline cards with stepped recovery
guidance, factory reset modal, and detection quality banners
- TooltipManager: first-time feature tooltips with localStorage
persistence, auto-dismiss, and sequential tour
- Onboarding failure guidance: human-readable error messages for
browser compatibility, USB connection, WiFi provisioning, and
node detection failures
- Post-calibration reinforcement card with summary and next steps
- Client-side link health check with auto-recovery
- CSS for offline cards, tooltips, quality banners, and modals
- Script tags wired in index.html for troubleshoot.js and tooltips.js
- 30 tests covering all troubleshooting and tooltip functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix WebSocket mock to use factory function so resetAllMocks doesn't
break it (state.ws.close is not a function errors)
- Fix TextEncoderStream mock to provide functional readable/writable
for pipeTo (needed by provisioning serial send tests)
- Fix flash_firmware test to check wizard-nav for "Skip Flashing"
button instead of wizard-content
- Fix provisionAndSend "no port" test to use mockResolvedValue
instead of mockResolvedValueOnce so both primary and fallback
paths fail consistently
All 60 tests now pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Web Serial-based wizard that takes a non-technical user from unboxed
ESP32-S3 to streaming CSI in under 5 minutes. 8-step state machine:
browser check → connect → flash firmware (esp-web-tools) → provision
WiFi → detect node → guided calibration → placement guidance → complete.
Includes sessionStorage persistence for resumability across page refreshes,
live CSI waveform during calibration, human-friendly error messages for
all failure modes, and comprehensive Jest test coverage (34 tests).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements the CSI recording buffer for time-travel debugging. Each link
writes to append-only 1-hour segment files under data/csi/{linkID}/
with configurable 48-hour retention and 1GB/link max size guard. The
recorder uses buffered channels (capacity 1000) per link so Write never
blocks the ingestion goroutine. Background cleanup sweeps hourly to
delete expired segment files.
New package: mothership/internal/recorder/
- segment.go: append-only segment file I/O with [length][timestamp][frame] records
- manager.go: Manager with Write/ReadFrom/AvailableRange/Close, per-link goroutines
- Full test coverage: 18 tests covering write/read, retention cleanup, max bytes,
concurrent writes, buffer overflow drops, segment rotation, and edge cases
Wire-up: recorder.Manager created in main.go and injected into ingestion server.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 500ms periodic presence_update broadcast from the dashboard hub with
per-link motion state (is_motion, delta_rms, confidence). Surface this in
a new Presence panel on the dashboard with coloured dot indicators
(green=clear, amber=motion, red=high-confidence) and deltaRMS values.
Includes a rolling 10s Canvas 2D line chart of deltaRMS with a threshold
line at 0.02 for the selected link.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements plan item 17 — full Three.js Phase 3 visualization layer:
- Room bounds: floor, ceiling, semi-transparent walls and wireframe edges
built from registry_state room config (width/depth/height/origin)
- Floor plan texture: upload any image via status-bar button; mapped to
ground plane via THREE.TextureLoader
- Humanoid figures: SkinnedMesh + AnimationMixer with 13-bone skeleton;
four AnimationClip postures (standing, walking, seated, lying); smooth
0.35 s crossfade transitions; walking speed scales animation timeScale;
figures orient in direction of travel
- Vertical pillar anchors: line from floor to ceiling at each blob position
- Footprint trails: floor-level Line geometry following blob.trail history
- Node meshes: OctahedronGeometry at registry node 3-D positions; link
lines redrawn whenever link_active/inactive events arrive
- View presets: 3D perspective, top-down orthographic, first-person follow
(lerps camera to track first active blob)
- WebSocket: handles registry_state, loc_update, link_inactive message types
newly routed through handleJSONMessage; existing CSI/motion pipeline intact
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New package mothership/internal/tracker/ implementing full 3-D
Unscented Kalman Filter tracking for human figures detected by the
fusion engine.
Key features:
- 6-D UKF state [x, y, z, vx, vy, vz] using gonum.org/v1/gonum/mat
- Biomechanical constraints: max horiz velocity 2 m/s, max vert 0.8 m/s,
max acceleration 3 m/s², minimum turning radius 0.3 m
- Gravity-consistent Z: separate vertical speed cap for natural motion
- Blob ID assignment with persistence through up to 3 s occlusion gaps
- Collision avoidance: repulsion nudge when blobs closer than 0.4 m
- Posture estimation: lying (<0.4 m), seated (<0.8 m), standing/walking
from centroid height + horizontal speed
- 11 unit tests covering single-person tracking, occlusion recovery,
gap persistence, posture transitions, and constraint enforcement
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- 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>
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>
- 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>
- 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>