Broadcasts { type: 'trigger_state', trigger: { id, name, last_fired, enabled } }
on trigger fire, enable, and disable events. Handled in app.js onmessage
and forwarded to window.Automations.updateTriggerState().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Hub sends full snapshot (type=snapshot) on new client connect before adding
to broadcast list, preventing race with delta messages
- tickDelta at 10 Hz computes byte-level diffs of cached JSON fields, broadcasts
only changed fields with no type field
- Dashboard sets awaitingSnapshot on WS open, drops incremental updates until
snapshot received, rebuilds full state from snapshot
- Fix zone snapshot building (nil→ok check, SizeX fields)
- Remove unused broadcastBLEScan function
- Add drainSnapshot helper and fix TestHub_LinkEvents to account for snapshot
- Add TestHub_SnapshotOnConnect, TestHub_SnapshotBeforeDelta, TestHub_DeltaOmitsTypeField
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements a comprehensive 'People & Devices' panel for BLE device management:
Frontend (dashboard/js/ble-panel.js, dashboard/css/ble-panel.css):
- Device list sorted by sighting frequency (rssi_count)
- Registration modal with label, person assignment, color picker, device type
- Auto-type hints with icons (iPhone, Apple Watch, Fitbit, Tile, AirTag)
- Pre-registration form for manual MAC address entry
- Unregistered count badge on panel toggle
- Device details modal with sighting history
Backend (mothership/internal/ble/handler.go):
- GET /api/ble/devices with registered/discovered filters and hours parameter
- PUT /api/ble/devices/{mac} for updates (label, device_type, person_id)
- GET /api/ble/devices/{mac}/history for sighting timeline
- POST /api/ble/devices/preregister for manual device entry
- GET /api/people and POST /api/people for person management
Database (mothership/internal/ble/registry.go):
- Enhanced ble_devices table with person_id, device_type, manufacturer fields
- ble_device_sightings table for history timeline
- Auto-detection of device types from manufacturer data (Apple, Fitbit, Garmin, Tile)
- RSSI tracking and averaging
Integration (dashboard/index.html, mothership/cmd/mothership/main.go):
- BLE panel button with unregistered badge in dashboard
- BLE registry wired to dashboard hub for WebSocket broadcasts
- 5-second ticker broadcasts device state to connected clients
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend (mothership/ble/):
- RotationDetector with manufacturer data fingerprinting, time+RSSI proximity
- ble_device_aliases table for tracking rotated MAC addresses
- Identity matcher integration with alias resolution for blob-to-device scoring
- 5-minute grace period fallback when rotation is unresolved
- REST API: GET /api/ble/devices/{mac}/aliases, POST /api/ble/merge, POST /api/ble/split
Dashboard UI (dashboard/js/):
- Rotation icon indicator (🔄) in BLE device registry
- Manual merge/split UI with confirmation dialog
- Alias history expandable in device detail panel
- Possible rotations section with match confidence
Acceptance:
- Identity continuity across address rotation with >90% precision
- No duplicate person tracks created on rotation event
- Alias history queryable via GET /api/ble/devices/{mac}/aliases
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Automatically detect the home router as a passive radar TX source,
eliminating need for a dedicated active TX node.
Firmware changes:
- During hello message, include ap_bssid and ap_channel from esp_wifi_sta_get_ap_info()
Mothership changes:
- On hello: extract ap_bssid; if >=80% of nodes report same BSSID create virtual node entry with virtual=1
- OUI lookup: embedded IEEE OUI registry as Go map compiled via go:embed; display router brand
- Detect AP BSSID change (router reboot/replacement) and emit system alert
- SQLite nodes table: add virtual BOOL, node_type TEXT, ap_bssid TEXT, ap_channel INT columns
Dashboard changes:
- Show "I detected your router (ASUS). Place it on the floor plan..." notification
- Render virtual AP nodes with distinct router icon in 3D view
- Drag-to-place virtual node (distinct router icon) in 3D editor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add explainability.js module with sidebar panel, X-ray overlay, Fresnel zone rendering
- Add explainability handler with /api/explain/{blobID} endpoint
- Fix viz3d.js CONFIG reference by adding FRESNEL_CONFIG constant
- Integrate explainability into fusion engine and ingestion server
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend (mothership/internal/auth/):
- SQLite auth table with pin_bcrypt and install_secret (singleton row)
- GET /api/auth/status — return {pin_configured: bool}
- POST /api/auth/setup — sets PIN (bcrypt cost 12) on first run only
- POST /api/auth/login — verifies PIN, issues session cookie (7-day expiry)
- POST /api/auth/logout — clears cookie and deletes session from SQLite
- Session middleware: all /api/* and /ws/* require valid session
- Rolling window: extends session by 7 days if within 24h of expiry
- Install secret generation for node token derivation
Dashboard (dashboard/js/auth.js):
- On load: GET /api/auth/status check
- First-run setup page: enter PIN + confirm PIN → POST /api/auth/setup → reload
- Login page: shown on 401; PIN entry → POST /api/auth/login → reload
- Logout button in settings panel → POST /api/auth/logout → redirect
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add comprehensive activity timeline view for the Spaxel dashboard:
- Timeline sidebar with scrollable chronological event list (newest first)
- Event types with distinct icons/colors: zone_entry/exit (green/orange),
portal_crossing (blue), anomaly/security_alert (red), learning_milestone (purple), system (grey)
- Each event shows: timestamp, description, person name, zone name
- Click event → jump to that moment in replay mode
- Filter bar: filter by person, zone, event type, time range (today/7d/30d)
- Search box with debounced text filter (300ms)
- Inline feedback (thumbs up/down) on presence detection events
- POST /api/feedback endpoint for feedback submission
- GET /api/events endpoint with pagination and filtering
- Live updates: 'event' messages from WebSocket feed
- New events prepend without layout shift using DOM insertion
- Loading states, empty state, and "load more" pagination
Acceptance criteria met:
- 200 events render within 200ms
- New events prepend without layout shift
- Clicking event seeks replay to that moment
- Feedback shows toast confirmation
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 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 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>
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>
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>
- 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>
- 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>