CognitiveSense is a multi-client fatigue and cognitive-state monitoring project with:
server/for the Python analysis pipeline and control-plane APIfrontend/for the Electron desktop clientmirror/for the Raspberry Pi native frame streamer
The current architecture is a thin-client model:
- desktop and mirror devices capture media locally
- both stream into the Python backend on TCP
9000 - the backend runs state detection and LLM feedback generation
- the control website on
8080handles registration, device claiming, snapshots, and timeline views
main.py: top-level entrypoint for the Python appserver/: analysis pipeline, remote media receiver, control API, storage, and UI helpersfrontend/: Electron + React desktop shellmirror/: Raspberry Pi camera capture and frame streaming binariestests/: Python regression testsDEPLOYMENT.md: backend deployment notesDISTRIBUTION.md: desktop packaging and release notesscripts/setup_wsl_mirror_proxy.ps1: Windows-to-WSL port forwarding helper for Raspberry Pi connectivity
- Python
3.11 uv
- Node.js
20+ - npm
- Docker
- Docker Compose
- CMake
3.16+ - a C compiler
rpicam-vidavailable on the Pi path
The Python backend reads configuration from .env.
Common variables:
OPENAI_API_KEY: enables OpenAI-backed state tracking and feedbackCOGNITIVESENSE_SERVER_HOST: override backend bind host for remote media ingressCOGNITIVESENSE_SERVER_PORT: override backend media port, default9000COGNITIVESENSE_LOG_LEVEL: logging level, defaultDEBUGSTATE_TRACKER_TYPE/state_tracker_type: selectllmorruleTARGET_FPS/target_fps: target analysis FPSLLM_STATE_TRACKER_MODEL/llm_state_tracker_model: model used for state inferenceLLM_FEEDBACK_MODEL/llm_feedback_model: model used for feedback generation
The main settings live in settings.py. Current defaults include:
state_tracker_type = "llm"target_fps = 15llm_state_tracker_model = "gpt-5-mini"llm_feedback_model = "gpt-4.1-nano"
Start the backend services from the repo root:
docker compose up --buildThis starts:
analysison TCP9000controlon HTTP8080
Open:
http://127.0.0.1:8080/
Then start the desktop app:
cd frontend
npm ci
npm run devIf npm run dev fails with missing commands such as concurrently, run npm ci first in frontend/.
Install Python dependencies:
uv sync --all-groupsStart the unified Python server:
uv run python main.py serverUseful equivalents:
uv run cognitivesense server
uv run cognitivesense mirrorNotes:
serverandmirrorboth start the same unified remote media receiverfrontendis still accepted as a deprecated alias- the default desktop mode is
uv run python main.py
Start the control website directly if needed:
uv run python -m uvicorn server.control.api:app --host 0.0.0.0 --port 8080The Electron app is a thin client. It captures camera and microphone data locally and forwards them to the backend.
Development:
cd frontend
npm ci
npm run devBuild desktop bundles:
cd frontend
npm ci
npm run distArtifacts are written to frontend/release/.
Currently configured packaging targets:
- Windows:
nsis - macOS:
dmg - Linux:
AppImageanddeb
The desktop app stores runtime connection info in a per-user client-config.json inside the Electron user-data directory.
Stored fields:
mediaHostmediaPortapiBaseUrldeviceIddeviceName
Defaults:
- media host:
127.0.0.1 - media port:
9000 - API base URL:
http://127.0.0.1:8080
Build the Raspberry Pi binaries from the repo root:
cmake -S mirror -B mirror/build
cmake --build mirror/buildRun the capture smoke test on the Pi:
./mirror/build/mirror_capture_smoke
./mirror/build/mirror_capture_smoke 1280 720 8000
./mirror/build/mirror_capture_smoke 1280 720 8000 /home/pi/frame.bmpArguments are width height timeout_ms [output_path].
To continuously stream frames to the backend:
./mirror/build/mirror_frame_streamer 192.168.1.10
./mirror/build/mirror_frame_streamer 192.168.1.10 9000 640 480 15Arguments are server_ip [port] [width] [height] [fps].
The streamer uses rpicam-vid when available and sends MJPEG frames over TCP.
To make a mirror device claimable with a stable identity:
COGNITIVESENSE_DEVICE_ID=bathroom-mirror ./mirror/build/mirror_frame_streamer 192.168.1.10If the backend runs inside WSL2, Raspberry Pi devices on your LAN usually need Windows to forward the port into WSL first. Run this once from an elevated Windows PowerShell:
powershell -ExecutionPolicy Bypass -File .\scripts\setup_wsl_mirror_proxy.ps1 -Port 9000Then use the printed Windows LAN IP as the Raspberry Pi target address.
Open the control site at:
http://127.0.0.1:8080/
The website currently supports:
- registration and login
- device discovery for newly connected but unclaimed devices
- claiming desktop and mirror devices
- renaming claimed devices
- recent feedback history
- overview metrics and timeline views
- device snapshot previews
- live feed updates for claimed devices
The current model is local and self-hosted:
- each desktop or mirror client sends a stable
deviceId - the backend records devices automatically when they connect
- unclaimed devices appear in the control website
- a signed-in user can claim a device from the website
- user-level summaries aggregate across claimed devices
Persisted data currently includes:
- users
- auth tokens
- devices
- monitoring sessions
- state samples
- feedback events
- snapshot images
Raw audio and raw frame archives are not stored by default.
Python:
uv run pyright .
uv run ruff check .Frontend:
cd frontend
npm ci
npm run buildFor more detailed docs:
- DEPLOYMENT.md: remote backend deployment
- DISTRIBUTION.md: desktop packaging
- deploy.env.example: example deployment environment file