Add REST API for diurnal baseline data:
- GET /api/diurnal/status - learning status for all links
- GET /api/diurnal/slots/{linkID} - slot data for specific link
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Floor plan upload panel with image selection and preview
- Two-point calibration UI with pixel distance measurement
- Real-world distance input for scale computation
- Pixel-to-meter scale factor calculation and storage
- Fixed floor plan image serving at /floorplan/image.png
- Integration with Viz3D ground plane texture
- CSS styling for floor plan setup panel
- Image persists across server restart via SQLite
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update .beads/issues.jsonl and .needle-predispatch-sha after
remote rebase to maintain tracking state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Backend: Add POST /api/auth/change-pin endpoint
- Requires valid session; body: {old_pin, new_pin}
- Verifies old PIN against bcrypt hash; returns 403 on mismatch
- Hashes new PIN with bcrypt cost=12
- Existing sessions remain valid after PIN change
- Returns {ok:true} on success
- Dashboard: Security section in settings panel
- Add "Security" section with Change PIN button
- Modal form: old PIN → new PIN → confirm new PIN → Submit
- Inline error display for incorrect current PIN (403)
- Success toast notification on PIN change
- Validation: 4-8 digits, numeric only, PINs must match, new ≠ old
- Tests: Add comprehensive tests for change PIN endpoint
- Success case: old PIN verified, new PIN works
- Wrong old PIN: returns 403, original PIN still works
- Unauthenticated: returns 401
- Invalid new PIN: validation for length, digits, etc.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All components complete:
- Firmware: LED blink at 5Hz via FreeRTOS task, configurable GPIO
- Mothership: POST /api/nodes/{mac}/identify endpoint, WebSocket sending
- Dashboard: Identify button in fleet panel and 3D view context menu
Acceptance criteria met:
✓ LED blinks at ~5 Hz for specified duration
✓ Blink stops automatically when duration_ms expires
✓ REST endpoint returns 404 for disconnected nodes
✓ LED GPIO configurable via sdkconfig (default GPIO8)
✓ Dashboard integration complete
- Fleet status page now has 'Identify' button per row that POSTs to /api/nodes/{mac}/identify
- 3D view right-click context menu has 'Identify (blink LED)' option for nodes
- Both options trigger LED blink on the selected node for identification
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add 'Identify' button to fleet health panel role list for online nodes
- Add CSS styling for fleet identify button
- Add identifyNode function to FleetPanel public API
- 3D view context menu with 'Identify (blink LED)' already implemented
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 'Identify (blink LED)' option to the right-click context menu
in the 3D view that POSTs to /api/nodes/{mac}/identify.
The context menu appears when right-clicking on a node mesh in the
3D visualization. Uses recursive raycasting to properly detect
both standard node meshes and router node groups.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added tests for:
- Auto-away activation when all registered BLE devices absent for 15+ minutes
- Auto-disarm triggering when registered BLE device returns with strong RSSI
- Manual override pausing auto-away detection
- System mode GET/PUT REST API endpoints
- Edge cases: no registered devices, weak BLE signals, unregistered devices
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a lightning bolt (⚡) Identify button per node row in the
fleet status page that POSTs to /api/nodes/{mac}/identify.
- Added CSS styling for .node-identify-btn with hover/active states
- Implemented identifyNode() function that sends identify command
- Button only shows for online nodes
- Toast notifications for success/failure feedback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- POST /api/nodes/{mac}/identify endpoint with {duration_ms: 5000} body
- Forwards identify message as WebSocket JSON to target node
- Returns 404 if node not connected; 200 on success
- Includes table-driven tests for all edge cases
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add led_init() call during firmware initialization in app_main()
- Add led_stop_blink() call on WebSocket disconnect
- LED GPIO configurable via CONFIG_SPAXEL_LED_GPIO (default GPIO8)
- Blink runs in FreeRTOS task at ~5Hz (100ms on/100ms off)
- Any running blink is cancelled when new identify message received
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add manufacturer display to AP detection banner
- Show "your router (Manufacturer)" when OUI is known
- Show "your router" as fallback when OUI unknown
- Expand OUI database with more manufacturer entries
The AP detection banner now shows the router manufacturer
when available from OUI lookup, making it easier for users
to identify their router during the onboarding process.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create mothership/internal/oui/oui.go with LookupOUI function:
- Accepts net.HardwareAddr (MAC address)
- Extracts first 3 bytes as uint32 in big-endian order (OUI)
- Returns manufacturer name from ouiMap or empty string if not found
- Handles edge cases gracefully (short MAC returns "", no panic)
Acceptance criteria met:
- LookupOUI returns correct vendor for known OUIs
- Unknown OUI returns empty string (no panic)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add anomaly.css and sleep.css to dashboard includes
- Add sleep.js for sleep quality monitoring
- Implement analytics API handler (flow, dwell, corridors)
- Add tracks API and tests for time-based data queries
- Add sleep monitor tests
- AnomalyDetector initialized and running in main()
- Anomaly events broadcast via WebSocket to dashboard
- Security mode arm/disarm persists across restarts (learning_state table)
- Learning progress tracking and display
- Alert banner with acknowledge functionality
- All API endpoints wired: /api/anomalies, /api/security/*, /api/analytics/*
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The load shedding system was already fully implemented with 4 levels,
rolling average timing, and all integration points wired. This change
extracts the state machine evaluation into a separate evaluate() method
so tests can inject controlled timing values without depending on wall
clock time, and fixes the recovery counter reset test accordingly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire anomaly detection backend into dashboard WebSocket feed as
typed 'anomaly_detected' and 'alert' messages. Add security mode
state to snapshot/delta broadcasts via SecurityStateProvider.
Include load shedding integration for crowd flow, detection event
logging, identity matching improvements, and sleep integration
updates. All acceptance criteria met: arm/disarm persists,
learning progress refreshes, alert banner <2s, acknowledge flow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The shell script was using GET /api/auth/setup to check auth status,
but that endpoint only accepts POST requests. The correct endpoint for
checking auth status is /api/auth/status which returns the
pin_configured field.
This fix ensures the E2E test properly detects when PIN authentication
is enabled and configures it accordingly.
AnomalyDetector initialized in main() with periodic model updates.
Anomaly events broadcast to dashboard WS as 'alert' messages via
BroadcastAlert. GET /api/anomalies?since=24h lists recent events.
POST /api/security/arm and /api/security/disarm manage security mode.
GET /api/security/status returns armed state, learning progress, and
24h anomaly count. Arm/disarm state persisted to learning_state table
and restored on restart.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Zones CRUD (GET/POST/PUT/DELETE /api/zones) and portals CRUD already
implemented with OpenAPI godoc comments, WebSocket broadcasting via
ZoneChangeBroadcaster for live 3D view updates, and 31 table-driven
tests covering all endpoints, edge cases, and broadcast behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fixed currentTimestamp() to return time.Now().UnixMilli() instead of constant 1e9
- Added 'time' package import to support the fix
- This ensures calibration data gets correct timestamps when persisted to SQLite
Confirm AnomalyDetector initialization in main(), wire anomaly event
broadcasts to dashboard WS as alert messages, and verify all security
mode endpoints (arm/disarm/status) return correct JSON with persistent
state across restarts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>