- 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>
AnomalyDetector is initialized in main() with periodic model updates.
Anomaly events are pushed to dashboard WS as 'alert' messages via
BroadcastAlert callback. Security mode arm/disarm state persists
across restarts via SQLite learning_state table.
Endpoints:
- GET /api/anomalies?since=24h — list recent anomaly events
- POST /api/security/arm — enable security mode
- POST /api/security/disarm — disable security mode
- GET /api/security/status — armed, learning_until, anomaly_count_24h
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix migration 001 floorplan schema: use distance_m instead of cal_distance_m,
and rotation_deg instead of room_bounds_json
- Update migration 010 to ALTER existing floorplan tables for databases
that already ran migration 001
- Create /data/floorplan directory in db.OpenDB for storing floor plan images
- GET/POST /api/zones - list and create zones with JSON responses
- PUT /api/zones/{id} - update existing zone
- DELETE /api/zones/{id} - delete zone
- All endpoints return JSON with proper HTTP status codes
- OpenAPI/Swagger annotations present (@Summary, @Description, @Tags, @Router, etc.)
- Table-driven tests for all CRUD operations
Acceptance criteria met:
- Endpoints respond correctly to HTTP requests
- Godoc annotations present for API documentation
The provisioning.NewServer call was using cfg.InstallSecretHex which
doesn't exist. The correct field name in config.Config is InstallSecret.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All acceptance criteria verified:
- AnomalyDetector initialized in main() with providers wired
- Anomaly events pushed to dashboard WS feed as 'alert' messages
- GET /api/anomalies?since=24h returns active + history anomalies
- POST /api/security/arm + /api/security/disarm endpoints
- GET /api/security/status with armed, learning_until, anomaly_count_24h
- Security mode persists across restarts via learning_state table
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Firmware (already implemented):
- ntp.c: Call esp_sntp_setservername() before esp_sntp_init()
- ntp.c: 10-minute periodic resync via esp_timer
- main.c: Read ntp_server from NVS (default: pool.ntp.org)
- main.c: 10-second sync attempt after WiFi connect with WARN on failure
- websocket.c: Include ntp_synced status in health JSON
Mothership (added):
- message.go: Add NTPSynced field to HealthMessage struct
- message.go: Add NTPServer field to ConfigMessage struct
- server.go: Add SendNTPServerToMAC() method for runtime NTP config
- server.go: Update sendConfig() to accept NTP server parameter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename health endpoint JSON field from 'load_level' to 'shedding_level'
- Add GetShedLevel callback to health checker for direct ProcessorManager access
- Dashboard WebSocket alerts now broadcast on Level 3 trigger and recovery
- Level 3 actively pushes 10Hz rate cap to all connected nodes
- Recovery from Level 3 restores adaptive rate control automatically
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- AnomalyDetector initialized and running in main() with periodic updates
- Anomaly events pushed to dashboard WS feed as 'alert' messages
- GET /api/anomalies?since=24h lists recent anomaly events
- POST /api/security/arm + /api/security/disarm endpoints
- GET /api/security/status returns armed, learning_until, anomaly_count_24h
- Arm/disarm state persists via learning_state SQLite table
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ntp.c to CMakeLists.txt SRCS so it's compiled and linked
- Load ntp_server from NVS in load_nvs_config() (default: pool.ntp.org)
- Add ntp_server field to spaxel_state_t
- Initialize NTP after WiFi connects with 10s sync timeout, WARN on failure
- Re-sync NTP after WiFi reconnect (WIFI_LOST state)
- Start periodic 10-minute resync timer via esp_timer
- Add ntp_synced boolean to health JSON message
- Handle ntp_server field in downstream config message
- Fix periodic resync callback to properly stop/restart SNTP
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Connect pm.GetShedLevel to health checker (exposes load_level in /healthz)
- Wire OnShedLevelChange callback to broadcast dashboard WS alert on Level 3
- Log rate reduction push and recovery messages for Level 3 transitions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Register zones and portals API handler in main.go
- GET /api/zones - list all zones with occupancy
- POST /api/zones - create a new zone
- PUT /api/zones/{id} - update existing zone
- DELETE /api/zones/{id} - delete a zone
- OpenAPI-style godoc comments already in place
- Zone changes reflect in live 3D view within one WebSocket cycle (10 Hz polling)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a 5-iteration rolling average timer to Process() with automatic
load-shedding levels (0-3) based on pipeline duration thresholds
(80ms/90ms/95ms). Recovery steps down when avg drops below 60ms for
10 consecutive iterations. Includes GetShedLevel() getter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add TimestampMs field to eventbus.Event
- Add event type constants (detection, zone_entry, zone_exit, etc.)
- Add severity level constants
- Add global Default() bus instance for shared access
- Add convenience functions: PublishDefault, PublishDefaultSync, SubscribeDefault
- Integrate with events.InsertEvent to publish to eventbus
- Add comprehensive table-driven tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add StartArchiveScheduler function that runs RunArchiveJob nightly at 02:00 local time
- Scheduler respects local timezone and gracefully stops on done channel signal
- Add table-driven tests for scheduler start and stop behavior
- Add AnomalyType, SystemMode, and sleep session event types to types.go
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements a comprehensive e2e test system that:
- Starts mothership container/binary
- Waits for /healthz with 15s timeout
- Handles PIN auth setup if needed
- Runs CSI simulator against mothership
- Asserts during run (health, nodes online, blob detection)
- Validates frame rate doesn't drop >20%
- Asserts detection events recorded
Components added:
- mothership/cmd/sim: CSI simulator that generates synthetic frames
- mothership/tests/e2e: Go test suite with WebSocket assertions
- tests/e2e/run.sh: Shell script with comprehensive assertions
- .github/workflows/e2e.yml: CI workflow for automated testing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements exponential backoff (1s→10s cap) with ±500ms jitter,
blob position extrapolation during disconnects (capped at 2s),
three visual states (silent <5s, dimming 5-30s, modal >30s),
and automatic scene restoration on reconnect.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace httptest.Server with raw net.Listen to avoid Close() blocking
on active connections after a timeout, which caused the test to hang
indefinitely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>