- Add simple.html: mobile-first card-based UI without 3D scene
- Room cards show occupancy count, person names, and status color
- Activity feed displays chronological events from timeline
- Alert banner for fall detection, anomaly alerts, system warnings
- Quick actions: arm/disarm security, re-baseline, silence alerts
- Sleep summary card showing last night's sleep data
- Simple/expert mode toggle button in all views
- Mobile-responsive layout with touch-friendly interface
- Per-user default mode stored in localStorage
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Raycast against both wireframe and fill meshes for reliable detection
- Wireframe is always present even when fill has 0 opacity (zones 2+)
- Sort intersections by distance to get the closest intersection
- Fixes hover detection not working properly for multi-zone Fresnel ellipsoids
- Fixed onFresnelMouseMove to properly iterate through arrays of ellipsoids
- Fixed showFresnelTooltip to access ellipsoid data from array correctly
- Enhanced tooltip to show zone count and improved units display
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Rebuilt spaxel-sim binary with latest code
- Copied binary to mothership directory for use in integration tests
- All tests pass (go test ./... && go vet ./...)
Implemented the CSI simulator CLI tool for testing Spaxel without
hardware. The simulator connects to a running mothership via WebSocket
and streams synthetic CSI binary frames.
Features:
- Virtual nodes positioned at corners, evenly distributed
- Walker random walk with Gaussian velocity updates (σ=0.3 m/s per axis per 50ms)
- Synthetic CSI generation using propagation model (path-loss + Fresnel zones)
- Binary frame format matching ingestion/frame.go (24-byte header + n_sub*2 payload)
- RSSI calculation: clamp(-30 - path_loss_dB, -90, -30)
- BLE advertisement simulation every 5s when --ble flag is set
- Reject detection: exits non-zero on {type:'reject'} from mothership
- Per-second stats: frame counts and blob count (from GET /api/blobs poll)
CLI Interface:
spaxel-sim --mothership ws://localhost:8080/ws/node --token <token> \
--nodes 4 --walkers 1 --rate 20 --duration 60s --ble \
--seed 42 --space '6x5x2.5'
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add fetchBlobCount() function that queries the mothership's /api/blobs
endpoint and updates reportStats() to include blob count in periodic
statistics. Also updates printFinalStats() to show final blob count.
This completes the acceptance criteria for printing per-second frame
counts AND blob counts from GET /api/blobs poll.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Fix GetShoppingList to use proper GDOP-based accuracy estimation
instead of simplified implementation
- Fix simulation flow to run synchronously and display results
immediately instead of polling
- Update GDOP legend HTML structure with proper styling hooks
- Add shopping list container with "add node at worst spot" button
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Fix infinity handling in accuracy_map for GDOP overlay JSON output
- Convert average_gdop infinity to string for JSON compatibility
- Use 9999.0 sentinel value for infinity in accuracy_map array
This fixes the 'unsupported value: +Inf' error when using --gdop-overlay flag.
Both --gdop-overlay and --shopping-list now output valid JSON.
- Implements full CSI simulator CLI at cmd/sim/
- Synthetic CSI frame generation using two-ray propagation model (direct + first-order reflection)
- Walker types: random-walk, path-following, node-to-node traversal
- User-defined paths via JSON file (--path-file)
- Wall attenuation with material properties (drywall, brick, concrete, glass, metal)
- BLE advertisement simulation (--ble flag)
- Ground truth CSV output with walker positions and per-link deltaRMS
- Scenario support: fall detection, OTA, bag-on-couch
- Blob count verification via --verify flag
- Seed-based reproducibility for deterministic tests
- Full test coverage (33 tests passing)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The test was using a wall at x=10 from y=0 to y=5, which still creates
a valid reflection geometry for TX at (1,1) and RX at (3,3). The
reflection point calculation correctly finds that the reflected ray
would intersect the wall at y≈2.125, which is within the wall's bounds.
Fixed the test to use a wall that truly cannot create a valid reflection:
vertical wall at x=2 from y=10 to y=15. The reflection of TX (1,1) across
x=2 would be at (3,1). The line from (3,1) to RX (3,3) is vertical at x=3,
which never intersects the wall at x=2.
The simulator implements:
- Synthetic walkers (random walk, path-following, node-to-node)
- CSI generation with two-ray propagation model
- Wall attenuation (drywall: 3dB, brick/concrete: 10dB, glass: 2dB, metal: 20dB)
- Fresnel zone-based deltaRMS computation
- BLE simulation and CSV ground truth output
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cleaned up unused variables in ray-based propagation model:
- Removed unused directPathLoss in computeReflectionPower
- Removed unused reflectionZ in floor/ceiling reflection functions
The propagation engine implements:
- Direct path computation (TX -> walker -> RX)
- First-order reflections (walls, floor, ceiling)
- Path loss model using log-distance
- Wall attenuation based on material type
- Fresnel zone modulation
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Press space bar to place a new virtual node at camera target position
- Node is placed at intersection of camera ray with ground plane
- Position is clamped to room bounds
- Height defaults to 80% of room height or 1.5m minimum
- Virtual nodes are rendered as ghost wireframe nodes (via viz3d.js)
- Links to/from virtual nodes are dashed (via viz3d.js)
This completes the 'space + virtual node placement' feature for the
simulator, reusing the 3D editor with ghost wireframe nodes and dashed links.
- Refactored simulate.js to properly use existing HTML structure from simulator.html
- Added ghost wireframe node visualization using THREE.OctahedronGeometry with wireframe material
- Implemented dashed links between virtual nodes using THREE.LineDashedMaterial
- Added CSS for selected state highlighting and proper panel positioning
- Reused TransformControls from 3D editor for node placement
- Virtual nodes appear as teal-colored wireframe octahedrons
- Links between virtual nodes render as teal dashed lines
- GDOP overlay updates in real-time as nodes are dragged
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The simulator page now includes the viz3d.js 3D visualization layer
which provides the scene management API needed by the simulator.
This ensures the simulator 3D scene is properly initialized.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Component 17 pre-deployment simulator is fully implemented. This commit
updates the test file to match the current API after refactoring:
- Changed from PropagationModel.PathLoss() to PhysicsModel.PathLossdB()
- Changed from PropagationModel.WallLoss() to PhysicsModel.WallAttenuation()
- Changed from PropagationModel.ReceivedPower() to PropagationModel.ExpectedRSSI()
- Changed from PropagationModel.PhaseAt() to PhaseAtSubcarrier()
- Changed from PropagationModel.DeltaRMS() to PhysicsModel.DeltaRMS()
- Removed non-existent IsInFirstFresnelZone() - use FresnelZoneNumber() instead
- Removed non-existent SimulateCSIData(), GenerateCSIFrame(), GenerateCSIFrames(), ComputeLinkMetrics()
All simulator tests now pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add 'Pause Live' button that freezes 3D view and reveals timeline scrubber
- Implement scrubbing backward/forward through recorded history (1×, 2×, 5×)
- 3D scene renders blobs exactly as detected at scrubbed time, including trails
- Add parameter tuning overlay with sliders for detection parameters:
- Detection threshold (deltaRMS)
- Baseline time constant (tau)
- Fresnel weight decay rate
- Subcarrier selection count (NBVI)
- Breathing sensitivity
- Adjusting slider re-runs pipeline on recorded CSI with new parameters
- Add 'Apply to Live' button that writes tuned parameters to running pipeline
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add hourly zone history snapshot recording that runs every hour
- Returns [{timestamp, count, people:[]}] in hourly buckets
- Supports period query parameter: 24h (default), 7d, 30d
- Zone history is persisted in zone_history SQLite table
- Initial snapshot recorded on startup for the current hour
Add TestGetZoneHistoryWithData which verifies:
- Response format with timestamp, count, and people array
- Period query parameter (24h, 7d, 30d)
- Correct number of hourly buckets returned
- People array parsing from JSON
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implement hourly occupancy history endpoint that returns zone_history
records from the database. The endpoint returns hourly snapshots with
count and people list, supporting period query parameter (24h, 7d, 30d).
Changes:
- Modified GetZoneHistory to query zone_history table instead of
computing from crossing_events
- Added encoding/json import to parse people JSON field
- Split database schema creation into manager_migrate.go for editability
- Split zone history snapshot logic into manager_history.go
- Updated tests to insert zone_history data instead of crossing_events
The zone_history table stores pre-aggregated hourly snapshots written
by RecordZoneHistorySnapshot, which should be called periodically.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add crossing_events table (migration 16) for portal crossing log with
cursor pagination support. The endpoint returns [{timestamp, direction,
person, blob_id, from_zone, to_zone}] with ?limit and ?before cursor
pagination parameters.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add ID field to CrossingEvent struct for database row ID
- Update GetPortalCrossings to query and return the database id column
- Fix getPortalCrossings handler to populate all response fields:
- ID: database row ID
- PortalID: portal ID from the crossing event
- BlobID: blob identifier
- Direction: a_to_b or b_to_a
- FromZone: source zone name
- ToZone: destination zone name
- Timestamp: crossing timestamp
- Person: BLE identity if available
- Update tests to verify all fields are properly set
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Added "idle" to validRoles map for consistency with disable/enable flow
- Added comprehensive tests for disableNode and enableNode handlers
- Tests cover: disable from tx/rx/tx_rx, already idle, node not found
- Tests cover: enable with saved role, no saved role (defaults to rx),
already enabled, node not found
- Added round-trip test for full disable/enable cycle
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements baseline read/capture endpoints for the dashboard. GET /api/baseline
returns [{link_id, snapshot_time_ms, confidence, n_sub}] for all links.
POST /api/baseline/capture starts a 60s quiet-room capture with optional
links filter.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements two REST API endpoints as defined in the plan's REST API spec:
1. GET /api/status - Returns system status including:
- version: Application version string
- nodes: Number of online nodes
- blobs: Number of currently tracked blobs
- uptime_s: Uptime in seconds
- detection_quality: System-wide detection quality (0-100)
2. GET /api/occupancy - Returns zone occupancy data:
- zones: Map of zone names to {count, people[]}
- count: Number of people in the zone
- people: List of person names (BLE-identified)
These are simple read-only endpoints for dashboard and Home Assistant
integration for quick system checks and occupancy queries without WebSocket.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The GET /api/notifications/preview endpoint was already implemented
in internal/api/notifications.go but was never registered in main.go.
This commit wires up the NotificationsHandler to enable the test
thumbnail endpoint for UI development and QA.
The endpoint accepts query parameters:
- type: notification type (fall, anomaly, zone_enter, sleep)
- person: person name (optional, defaults to "Alice")
It calls the appropriate Generate*Thumbnail function from the
render package and returns PNG bytes with Content-Type: image/png.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add SubscribeToSecurityMode handler that calls anomalyDetector.SetSecurityMode()
with SecurityModeArmed for 'arm' and SecurityModeDisarmed for 'disarm'
- Add SubscribeToRebaseline handler that publishes event bus events for baseline capture
- Both subscriptions allow Home Assistant automations to control security and rebaseline