Repository navigation
Development Guide
How to work on imPress: setting up, the repository layout, the workflow, conventions, and recipes for common changes. References: docs/guides/development-setup.md, docs/design/decisions.md.
- Setting up
- Repository layout
- Workflow
- Conventions
- Simulating a gateway without hardware
- Recipes
- Design decisions worth knowing
| Tool | Version | For |
|---|---|---|
| Python | 3.12+ (3.14 tested locally) | the backend and tests |
| C compiler (clang or gcc) | any recent | firmware host tests (no ESP-IDF needed) |
| ESP-IDF or Docker | v6.1 / any | firmware builds |
| Node.js | 20 | the React UI (optional) |
| Chromium or Google Chrome | any | browser tests (optional; Playwright can download Chromium) |
cd backend
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements-dev.txt # runtime + pytest, httpx, ruff, pip-audit
cat > .env <<'EOF'
IMPRESS_DEBUG=true # allows the published default secrets locally
IMPRESS_INITIAL_ADMIN_PASSWORD=dev-admin
EOF
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000-
Web app: http://localhost:8000; sign in as
admin/dev-admin. - API explorer: http://localhost:8000/docs.
-
Database:
backend/impress.db. Delete it for a fresh start; tables and the admin are recreated on boot. -
Working directory: always start from
backend/. The database path,.envandfirmware_bins/are relative to it.
TOKEN=$(curl -s localhost:8000/api/auth/login -H 'content-type: application/json' \
-d '{"username":"admin","password":"dev-admin"}' | python -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
H="Authorization: Bearer $TOKEN"
curl -s localhost:8000/api/admin/classes -H "$H" -H 'content-type: application/json' \
-d '{"name":"Physics","code":"PHY101","classroom_code":"RM-1"}'
curl -s -X POST localhost:8000/api/classes/1/activate -H "$H"
curl -s localhost:8000/api/students/ -H "$H" -H 'content-type: application/json' \
-d '{"roll_number":"ABCDE12345","student_name":"Asha Rao"}'cd frontend
npm ci
npm run dev # http://localhost:5173, proxying /api and /ws to :8000The React UI is a development dashboard and doesn't cover every feature. The built-in web app is the product UI.
firmware/run_host_tests.sh # all host C suites with ASan/UBSan
python -m pytest -q firmware/tests # structural tests
docker run --rm -v "$PWD/firmware":/project -w /project/class_c6 espressif/idf:v6.1 idf.py buildMore in Firmware: Build and Flash.
imPress/
├── backend/
│ ├── app/
│ │ ├── main.py app, startup, /health, the web app
│ │ ├── config.py settings (IMPRESS_*)
│ │ ├── auth.py passwords, sessions, roles
│ │ ├── device_auth.py shared and per-device keys
│ │ ├── database.py engine, commit helpers, foreign keys
│ │ ├── migrations.py versioned migrations
│ │ ├── models.py, schemas.py
│ │ ├── routers/ auth, admin, classes, courses, students, quizzes, polls, device, firmware, deployments
│ │ ├── services/ presence, health, student_modules, deployments, firmware_*, *_import, records, sessions
│ │ ├── ws/ WebSocket rooms
│ │ ├── static/, templates/ the web app
│ ├── tests/ backend tests, gateway simulator, fixtures
│ ├── requirements*.txt runtime, dev, and the hash-locked set
│ └── .env.example
├── firmware/
│ ├── protocol/ shared wire formats (+ host tests)
│ ├── class_c6/ gateway (+ host tests)
│ ├── class_s3/ hub
│ ├── student/ student module (+ host tests)
│ ├── contract/ the backend → gateway WebSocket fixture
│ ├── tests/ structural tests
│ └── sdkconfig.defaults.signed
├── scripts/ install.sh, start.sh, init_env.py, smoke_test.py, firmware_key.sh, build_signed.sh, verify_firmware.py
├── ui_tests/ Playwright browser tests
├── tests/ repository, CI and documentation checks
├── frontend/ React development UI
├── docs/ architecture, design, API, guides, reference, engineering, hardware, firmware, testing, deployment
├── run_tests.py the test runner
├── VERSION release version
└── .github/workflows/ci.yml
- Open or pick an issue. Bigger changes get a detailed issue first.
-
Branch from
varun/v2.1:feat/<summary>,fix/<summary>ordocs/<summary>, one branch per issue. Never commit tomain,v2orv3, and never force-push. -
Change code and tests together. Run
python run_tests.py(add--with-ui/--with-idfwhen you touched those parts). -
Update the docs in
docs/. Repository checks fail on broken links and anchors, undocumented routes or settings, and an incomplete README. -
Commit with conventional messages (
feat(scope): …,fix(…),docs(…),test(…),ci(…),security(…)). -
Open a pull request into
varun/v2.1with a detailed description:- summary and linked issue;
- technical explanation;
- files and tests;
- CI;
- compatibility;
- database, firmware and security impact;
- screenshots;
- rollback;
- hardware verification, stated honestly.
- Merge only when the CI result check is green.
| Area | Convention |
|---|---|
| Python | ruff (pyflakes rules); type hints; comments explain why, not what |
| C | no blocking work in radio callbacks; driver-owned buffers outlive the operation; one owner per cJSON tree; every wire format through firmware/protocol
|
| Errors |
HTTPException with a clear message; commit_or_conflict() for writes that can race |
| Time |
timeutil.istnow(); store naive IST |
| Activity |
log_activity(db, …) before the commit, so the log row commits with the change |
| Broadcasts | after the commit |
| Settings | new IMPRESS_* settings go in config.py, docs/guides/configuration.md and, if important, .env.example
|
| Secrets | never in the repository; .gitignore excludes *.pem, *.key, .env, *.db
|
| Line endings | LF (.gitattributes) |
K='X-API-Key: impress-device-key-2024' # the DEBUG default; else your IMPRESS_DEVICE_API_KEY
curl -s localhost:8000/api/device/register -H "$K" -H 'content-type: application/json' \
-d '{"mac_address":"48:F6:EE:00:00:01","device_type":"c6","device_name":"sim","classroom_code":"RM-1"}'
# → {"device_id":1,"status":"registered","class_id":1,"device_key":"…"} (you may keep using the shared key)
curl -s localhost:8000/api/device/batch -H "$K" -H 'content-type: application/json' -d '{"device_type":"c6","messages":[
{"type":"student_join","enrollment_number":"ABCDE12345","device_mac":"48:F6:EE:00:00:01","device_id":4660}]}'To receive commands like a gateway, connect a WebSocket client with the key header, then start a quiz in the web app:
websocat -H "X-API-Key: impress-device-key-2024" ws://localhost:8000/ws/class/1?role=deviceIn tests, use backend/tests/gateway_sim.Gateway: it registers, sends heartbeats, batches with the firmware's retry rules, and runs OTA updates, with faults on demand.
- Add it to the right router in
backend/app/routers/, with an auth dependency (require_admin,require_teacher_or_admin, a class-access check, ordevice_caller+authorizefor devices). - Add the request and response schemas to
schemas.py. - Write tests in
backend/tests/, including access control. - Document it in
docs/api/rest-api.md(ordevice-api.md).test_every_api_route_is_documentedfails otherwise. -
test_route_guards.pychecks it refuses unauthenticated callers. If it must be public, add it to the explicit list there, with a reason.
-
A new table: add it to
models.py. It is created at startup. -
A new column on an existing table: add it to
models.pyand append a migration step (Database and Migrations). Add the table to the legacy-database test's drop list if it's new in this release.
Add the field to Settings in config.py (it becomes IMPRESS_<NAME>), document it in docs/guides/configuration.md (a test checks), and add it to .env.example if operators should see it.
See Protocols and Mesh → Changing a protocol.
- Put pure logic in its own file with a host test (
firmware/<project>/test_host/), asota_logic.candstudent_set.cdo. - Put ordering and safety rules in a structural test (
firmware/tests/). - Build with ESP-IDF (
python run_tests.py --with-idf -s idf) and check that there are no new warnings. - If it changes what the device sends, update the backend schema in the same pull request.
Every pull request adds an entry to CHANGELOG.md under ## [Unreleased], in one of Added, Changed, Removed, Fixed, Security (CONTRIBUTING §14.4). CI fails the pull request without one, unless a maintainer adds the label no-changelog.
Patch releases need no action. Every merge to an integration line that passes CI is released as <owner>/vX.Y.<n>, with the CHANGELOG entries new since the previous release on its page (docs/guides/releases.md).
A new release line (for example varun/v2.2):
- Create the branch
varun/v2.2. Its first green push becomesvarun/v2.2.0and creates the milestonevarun/v2.2. The branch name decides the line, and the release rules come from the default branch. - In a pull request, set
VERSIONand everyfirmware/*/version.txtto2.2.0. Adddocs/releases/v2.2.mdif the page should differ from the newest template, keeping the page structure. InCHANGELOG.md, rename## [Unreleased]to the old line's last release and start a new one. - Put the issues that must pass before the line is Stable in the milestone
varun/v2.2(labeltesting). Issues invarun/v2.1keep gating the new line, because it is built on 2.1. - Deploy the firmware to the rooms (Firmware Management and OTA).
| Decision | Why |
|---|---|
| ESP-NOW flooding mesh for students | no access point; robust to students moving; bounded by TTL (Protocols and Mesh) |
| Two boards per room (hub + gateway) | one radio can't serve the mesh channel and a Wi-Fi association at once |
| Enrollment number as the student identity, never the device | modules can be swapped or re-assigned; no physical tracking of students |
| One backend process, SQLite | simplicity for a campus; WebSocket rooms live in memory |
| In-repo migration runner instead of Alembic | one SQLite file that must also adopt databases with no migration history |
| The backend → gateway JSON contract is a committed fixture | the backend and firmware tests read the same file, so they can't drift |
| Firmware facts come from the image | file names and forms lie; the binary doesn't |
| Opt-in signing and TLS | the key and the certificate must be each site's own |
| Health computed, not stored | the rules can change without a migration |
The full decision records are in docs/design/decisions.md.
imPress v2.1 · Repository · Issues · Source docs: docs/ · Not yet verified on hardware: see Testing and CI
Understand
Set up
Use
Operate
Develop
More