A full-stack human activity recognition (HAR) system that benchmarks classical machine learning (SVM) against deep learning (LSTM) on tri-axial accelerometer data, with a live inference pipeline driven by an ESP32 wearable.
The project covers the complete ML lifecycle: dataset preparation, model training and comparison in Jupyter, a Flask inference API, a real-time WebSocket ingestion server, a React/TypeScript dashboard, and embedded firmware for an ESP32-based wearable. The whole stack is dockerized — ./start.sh brings everything up.
Status: Research / academic project. Trained models, datasets, and a working end-to-end demo are included.
- Highlights
- System Architecture
- Tech Stack
- Repository Layout
- Getting Started
- REST API Reference
- Dataset
- Models
- Configuration & Secrets
- Troubleshooting
- Contributing
- License
- Acknowledgments
- Dual-model inference — switch between SVM and LSTM at runtime via a single REST endpoint.
- Real-time pipeline — ESP32 streams 50 Hz accelerometer data over WebSocket; predictions are written to Firestore and pushed to the dashboard live.
- One-command deploy —
./start.shboots api + websocket + dashboard via Docker Compose../stop.shtears it down. - Reproducible training — Jupyter notebooks cover dataset windowing, feature engineering, hyperparameter search (GridSearchCV), LSTM/CNN architectures, and evaluation.
- Six recognized activities — Walking, Jogging, Standing, Sitting, Upstairs, Downstairs.
- Polished frontend — React 18 + TypeScript + Vite + TailwindCSS + shadcn/ui dashboard with charts (Recharts) and live state from Firestore.
- Pluggable data source — the inference server accepts both simulated WISDM samples and live ESP32 data.
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ ESP32 board │ ─────▶ │ WebSocket │ ─────▶ │ Flask inference │
│ (3-axis │ WS │ server (ws) │ HTTP │ API (api) │
│ accel @50Hz)│ │ port 5002 │ │ port 5001 │
└──────────────┘ └──────────────────┘ └────────┬─────────┘
│
▼
┌──────────────────┐
│ Firestore │
│ (predictions) │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ React dashboard │
│ (frontend:8080) │
└──────────────────┘
Each box maps to a service in docker-compose.yml. A simulator service (--with-simulator) can replace the ESP32 entirely, replaying the WISDM dataset over WebSocket.
| Layer | Technology |
|---|---|
| ML / Models | scikit-learn (SVM, GridSearchCV), TensorFlow / Keras (LSTM, CNN) |
| Backend | Python 3.11, Flask 3, Flask-CORS, websockets, aiohttp, gunicorn |
| Persistence | Google Firestore (via firebase-admin) |
| Frontend | React 18, TypeScript, Vite 5, TailwindCSS, shadcn/ui, Recharts |
| Serving | nginx (alpine) for the production frontend bundle |
| Orchestration | Docker + Docker Compose |
| Firmware | Arduino C++ for ESP32 (WebSocketsClient, WiFi) |
| Notebooks | Jupyter, NumPy, pandas, matplotlib, seaborn, SciPy |
Kinesis/
├── docker-compose.yml # api + ws + frontend (+ simulator profile)
├── start.sh # Bring the stack up
├── stop.sh # Tear it down
├── .env.example # Compose-level env (Firebase web config, API base URL)
├── SVMvsDL.ipynb # Model training & comparison notebook
├── SaveDatasetCSV.ipynb # WISDM raw → tidy CSV conversion
├── requirements.txt # Notebook / training dependencies
├── datasets/
│ └── WISDM/ # Raw WISDM v1.1 data
└── app/
├── backend/ # Flask API + WebSocket ingestion
│ ├── Dockerfile # Shared image for api / ws / simulator
│ ├── app.py # Flask entrypoint (port 5001)
│ ├── websocket_server.py # Realtime ingestion (port 5002)
│ ├── routes/predict.py # /api/predict, /api/model
│ ├── model/ # Trained .h5 / .pkl artifacts + loader
│ ├── simulator/ # Replays WISDM data over WebSocket
│ ├── switch_model.py # Thread-safe active-model selector
│ ├── firebase_client.py # Firestore admin client
│ ├── credentials/ # Service-account JSON (gitignored)
│ └── requirements.txt # Backend Python dependencies
├── frontend/ # React + Vite dashboard
│ ├── Dockerfile # Multi-stage: vite build → nginx
│ └── nginx.conf
└── sketch_may16a/
└── sketch_may16a.ino # ESP32 firmware
- Docker ≥ 24 with the Compose v2 plugin (Docker Desktop bundles both)
- Arduino IDE or PlatformIO with the ESP32 board package and the
WebSocketslibrary by Markus Sattler — only required if you want to use real hardware - A Firebase project with Firestore enabled, plus:
- A service-account JSON key (for the backend)
- A web app config (for the frontend)
For non-Docker workflows you'll also want Python 3.10+ (3.10–3.12 supported by TensorFlow 2.19) and Node.js 18+.
git clone https://github.com/aakri0/Kinesis.git
cd Kinesis
# Three .env files — backend, frontend, and the compose-level one for build args.
cp app/backend/.env.example app/backend/.env
cp app/frontend/.env.example app/frontend/.env
cp .env.example .env
# Drop your Firebase service-account JSON into app/backend/credentials/.
# Anything matching app/backend/credentials/*.json is auto-discovered.
mkdir -p app/backend/credentials
mv ~/Downloads/your-firebase-key.json app/backend/credentials/Fill in the Firebase web config in both app/frontend/.env and the root .env (the compose file passes the latter into the frontend image as build args). See Configuration & Secrets for what each value does.
./start.sh # api + ws + frontend
./start.sh --with-simulator # also replay WISDM data (no hardware needed)
./start.sh --rebuild # force rebuild of images
./start.sh --logs # tail compose logs after start
./stop.sh # stop and remove containers
./stop.sh --volumes # also drop named volumes
./stop.sh --rmi # also remove built imagesAfter ./start.sh, browse to:
| URL | What |
|---|---|
| http://localhost:8080 | Dashboard |
| http://localhost:5001/health | API health check |
| ws://localhost:5002 | WebSocket ingest |
Compose service names (api, ws, frontend, simulator) can be addressed individually:
docker compose logs -f api
docker compose restart frontend
docker compose --profile simulator up -d simulatorIf you'd rather run each process directly on your host, the commands are:
python -m venv .venv && source .venv/bin/activate
pip install -r app/backend/requirements.txt
python -m app.backend.app # http://localhost:5001
python -m app.backend.websocket_server # ws://localhost:5002
python -m app.backend.simulator # optional WISDM replay
cd app/frontend && npm install && npm run dev # http://localhost:8080For production (still without Docker):
gunicorn -w 2 -b 0.0.0.0:5001 app.backend.app:appRun all backend commands from the repo root so the
app.backend...package paths resolve.
- Open
app/sketch_may16a/sketch_may16a.inoin the Arduino IDE. - Install the ESP32 board package (Boards Manager → "esp32") and the WebSockets library by Markus Sattler.
- Update the
ssid,password, andhostplaceholders at the top of the sketch:hostis the IP of the machine running thewsservice.portis5002to matchwebsocket_server.py. Keep these aligned if you change either.
- Wire a 3-axis analog accelerometer (e.g. ADXL335) to GPIOs 34 (X), 35 (Y), 32 (Z).
- Flash the board. Watch the serial monitor at 115200 baud for connection status.
No hardware? Use ./start.sh --with-simulator instead — it produces the same WebSocket message format from the WISDM CSV.
pip install -r requirements.txt
jupyter notebookSaveDatasetCSV.ipynb— convertsdatasets/WISDM/WISDM_raw.txtinto a tidy CSV.SVMvsDL.ipynb— windows the data, extracts statistical features, trains and evaluates SVM (withGridSearchCV) and an LSTM (and a CNN baseline), and produces confusion matrices and classification reports for each.
Re-running the final cells will regenerate LSTM_model_50.h5, SVM_model_50.pkl, and scaler_50.pkl under app/backend/model/.
All endpoints are served by Flask on http://localhost:5001.
Run inference on a 50-sample window of accelerometer readings.
Request:
{
"window": [[0.12, -0.98, 0.05], "...50 rows of [x, y, z]..."],
"user_id": "user_1",
"source": "simulated",
"actual_activity": "Walking"
}Response:
{ "activity": "Walking", "accuracy": 0.9712 }A document is also written to the predictions Firestore collection, including the active model, source, timestamp, and last sensor sample.
Returns the currently active model: {"active_model": "lstm"} or "svm".
Switch the active model at runtime.
{ "model_type": "svm" }Smoke-test the Firestore connection by writing a sentinel document. Disabled unless ENABLE_FIRESTORE_TEST=1.
Models are trained on the WISDM Activity Recognition v1.1 dataset:
- 36 subjects, smartphone accelerometers at 20 Hz
- Labels: Walking, Jogging, Standing, Sitting, Upstairs, Downstairs
- Raw data lives at
datasets/WISDM/WISDM_raw.txt
The training pipeline windows the signal (window size 50, stride 50), extracts per-axis statistical features (mean, std, min, max, median, energy = 18 features total), and z-scores them before fitting the SVM. The LSTM consumes the raw windowed signal directly.
| Model | Input shape | Notes |
|---|---|---|
| SVM | (18,) |
RBF kernel; C and gamma tuned with GridSearchCV. Scaler saved as scaler_50.pkl. |
| LSTM | (50, 3) |
64–128 hidden units, dropout 0.3–0.5, early stopping. |
| CNN | (50, 3) |
Conv1D + MaxPooling baseline (notebook only, not deployed). |
Trained artifacts are committed under app/backend/model/ so the API works out of the box.
This repo never contains real credentials. You will need to provide your own. See SECURITY.md for the full disclosure policy and history-rotation guidance.
There are three .env files:
| File | Used by | What goes in it |
|---|---|---|
app/backend/.env |
Flask app + websocket_server | FIREBASE_CREDENTIALS, CORS_ORIGINS, API_BASE_URL, ENABLE_FIRESTORE_TEST, etc. |
app/frontend/.env |
Vite dev server (npm run dev) |
VITE_FIREBASE_*, VITE_API_BASE_URL |
.env (repo root) |
docker-compose build args | Same VITE_* keys, baked into the frontend image at build time |
All three are gitignored. Each has an adjacent .env.example.
Backend — Firestore admin & runtime config:
- In the Firebase console, generate a service-account JSON key.
- Either:
- Save it to
app/backend/credentials/<anything>.json—firebase_client.pyauto-discovers any*.jsonin that folder (it is gitignored), or - Set
FIREBASE_CREDENTIALS=/absolute/path/to/key.jsonin your environment.
- Save it to
- In
app/backend/.env, set:CORS_ORIGINS— comma-separated allowlist of dashboard origins (e.g.https://your-dashboard.example.com). Defaults tolocalhost:8080for development.FLASK_DEBUG=0(default). Never set this to1in production — Flask debug mode allows arbitrary code execution via the Werkzeug debugger.API_BASE_URL— where the websocket server sends prediction requests. Compose overrides this tohttp://api:5001for inter-container traffic.ENABLE_FIRESTORE_TEST— leave unset; the/test-firestoreendpoint is disabled by default.
- Do not commit absolute paths, service-account keys, or
.envfiles.
Frontend — Firestore web SDK & API base URL:
- Fill in
VITE_FIREBASE_*from your Firebase web app config (Project settings → General → Your apps → Web app). VITE_API_BASE_URLdefaults tohttp://localhost:5001. Override it if you proxy the API behind a different hostname.- For Docker, the same keys also need to be in the root
.envsodocker compose buildcan inject them into the image. They become public JS once built — Firebase web SDK keys are designed for this.
ESP32 — WiFi credentials:
Edit the ssid, password, and host constants in sketch_may16a.ino locally; do not commit personal network credentials.
docker compose: unknown command— install the Compose v2 plugin (Docker Desktop ≥ 4 has it; on Linux:sudo apt install docker-compose-plugin).- Frontend renders but can't reach the API — check
VITE_API_BASE_URLin the root.env; remember that the browser, not the container, opens the connection, soapi(the compose service name) won't resolve from the browser. ModuleNotFoundError: app.backend...(manual mode) — run backend commands from the repo root, not from insideapp/backend/.- WebSocket port mismatch — the firmware ships pointing at port 5000 while
websocket_server.pylistens on 5002. Pick one and align both ends. - Firestore writes fail silently — confirm the service-account JSON exists at
app/backend/credentials/*.json(or the path inFIREBASE_CREDENTIALS) and that the project ID matches the frontend config. - TensorFlow install errors on Apple Silicon (manual mode) — install
tensorflow-macosandtensorflow-metalinstead oftensorflow. The Docker image uses linux/amd64 wheels and is unaffected.
Issues and pull requests are welcome. See CONTRIBUTING.md for development setup, branching, commit conventions, and the review checklist.
Released under the MIT License.