Repository navigation
Firmware Build and Flash
How to build the three firmware projects, flash them onto boards, set each board up, and read its logs. Reference: docs/guides/development-setup.md.
- The three projects
- Toolchain
- Building
- Configuring before you flash
- Flashing
- Setting up a room, step by step
- Reading logs
- Version numbers
- Signed and TLS builds
- When serial flashing is required
| Project | Board | Image | Notes |
|---|---|---|---|
firmware/class_c6 |
gateway (ESP32-C6) | build/impress_class_c6.bin |
updates itself over the air |
firmware/class_s3 |
hub (ESP32-S3) | build/impress_class_s3.bin |
updates itself over the air (Wi-Fi hop) |
firmware/student |
student module (ESP32) | build/impress_student.bin |
updated over USB only |
firmware/protocol |
(a shared component) | – | wire formats used by all three |
All three are ESP-IDF v6.1 applications in C, on FreeRTOS.
You can build with either:
-
Docker (no installation): the pinned image
espressif/idf:v6.1contains everything. -
Native ESP-IDF v6.1: follow Espressif's installation guide, then
. $IDF_PATH/export.shin each shell.
Docker is the easiest way to build. Flashing from inside Docker needs USB pass-through, which is awkward on macOS and Windows. So the usual setup is to build in Docker and flash with esptool or a native IDF.
From the repository root:
docker run --rm -v "$PWD/firmware":/project -w /project/class_c6 espressif/idf:v6.1 idf.py build
docker run --rm -v "$PWD/firmware":/project -w /project/class_s3 espressif/idf:v6.1 idf.py build
docker run --rm -v "$PWD/firmware":/project -w /project/student espressif/idf:v6.1 idf.py buildOr let the test runner do it, which uses Docker when IDF isn't installed:
python run_tests.py --with-idf -s idf. $IDF_PATH/export.sh
cd firmware/class_c6
idf.py build
idf.py size # flash and RAM usageBuilds use each project's committed sdkconfig, so the result matches CI.
Each board's settings are compiled in as defaults, then copied into NVS on its first boot. After that, NVS wins (see Configuration Reference). So set the defaults before the first flash:
cd firmware/class_c6
idf.py menuconfig| Board | Menu | Set at least |
|---|---|---|
| Gateway | imPress C6 Gateway Configuration | Wi-Fi SSID and password; backend host and port; device API key (= the server's IMPRESS_DEVICE_API_KEY) |
| Hub | imPress S3 Class Module Configuration | the same Wi-Fi, backend and key (used for its OTA hop); the mesh channel |
| Student | no menu: edit firmware/student/main/config.h
|
the mesh channel (= the hub's) and the board variant (display, LED) |
Tips:
-
Backend address: use the server's LAN IP or DNS name, never
localhost. -
Committed
sdkconfig:menuconfigchanges it. Don't commit site-specific values (passwords, keys) to the repository; use a private copy, or set NVS per board (see Configuration Reference).
Connect the board over USB and find its port:
| System | Port |
|---|---|
| Linux |
/dev/ttyUSB0 or /dev/ttyACM0
|
| macOS |
/dev/cu.usbserial-* or /dev/cu.usbmodem*
|
| Windows |
COM3 and so on |
Then flash everything (bootloader, partition table, OTA data and app) and open the monitor:
cd firmware/class_c6
idf.py -p /dev/ttyUSB0 flash monitorQuit the monitor with Ctrl+].
The gateway's USB port:
- Use the UART bridge. The C6 uses GPIO12/13 as SPI ready lines, and those are its native USB pins. So once the firmware runs, the native USB port disappears. Flash and monitor through the board's UART (USB-to-serial bridge) port; the console is on GPIO16/17.
- If the native port is the only one you have: hold BOOT while resetting to enter the ROM bootloader, flash, then switch to the UART port to watch the logs.
Erasing a board completely (NVS too: Wi-Fi, keys, identity, class):
idf.py -p <port> erase-flash flashEach release has impress-<version>-firmware.zip: one folder per board with the images and their flash layout. Flash a board with esptool only (no ESP-IDF):
pip install "esptool>=5"
cd class_c6 # or class_s3 (--chip esp32s3), student (--chip esp32)
python -m esptool --chip esp32c6 -p <port> erase-flash
python -m esptool --chip esp32c6 -p <port> -b 460800 write-flash @flash_argsThe prebuilt images contain the default Wi-Fi, server and key settings. They are right for over-the-air uploads, and for boards that already have their settings in NVS. A new gateway or hub also needs a settings partition at 0x9000. The exact settings files (the key names differ between the gateway and the hub) are in releases → flash prebuilt images. Student modules need no settings: enter the enrollment number on the module.
-
Server ready: installed and reachable (Installation). Create the class, with the room's
classroom_code(e.g.RM-201) set, and activate it (Administrator Guide). - Wire the hub to the gateway (Hardware Guide) and power both from a stable 5 V supply.
-
Gateway: set Wi-Fi, backend and key in
menuconfig, then flash. On boot it:- connects to Wi-Fi;
- registers;
- receives its own device key, and the class id when the backend links it;
- opens the WebSocket.
-
Linking: with no
classroom_codein its registration, the gateway links itself to the first active class that has no gateway. To pin it to a room, register it with that room's code (the smoke test does this), or link it as an admin. - Hub: set the same Wi-Fi, backend and key, plus the mesh channel. Flash it over serial (it carries the rollback bootloader).
-
Check:
- Modules page: the gateway is online, its health is online, and its diagnostics show the hub link up.
- Class page: the Live Devices panel lists it.
- Student modules: set the mesh channel, flash, and give each module its student's enrollment number (Hardware Guide).
- Test: start a quiz. The question appears on the modules; pressing a button raises the live count.
| Board | What healthy looks like |
|---|---|
| Gateway |
Adopted class_id N from register, Device key stored; it replaces the shared key, Batch flush N items → /api/device/batch: HTTP 200
|
| Hub |
Mesh master initialized, channel=N, New student: enroll=…, Active students: N every heartbeat |
| Student | the display shows the connection, then the question during a quiz |
Every boot prints rst:0x… (REASON). The gateway also reports its reset reason, uptime and boot count to the backend, where the Modules page shows them (Device Management).
Crash backtraces: idf.py monitor decodes them when it can find the matching .elf. Keep the build/ folder of the firmware you deployed.
Each project's version is in firmware/<project>/version.txt (2.1.0). ESP-IDF writes it into the image, and the firmware reports exactly that value. The repository's VERSION file holds the release version, and a test requires all four to match. To release a new version, bump all four, rebuild and upload (Firmware Management and OTA).
| Build | How | Details |
|---|---|---|
| Signed (devices accept only images signed with the site key) | IMPRESS_SIGNING_KEY=… scripts/build_signed.sh class_c6 |
Security → Signed firmware |
| Backend over TLS |
menuconfig → Backend Server → Use HTTPS / WSS to reach the backend
|
Security → Device traffic over TLS |
Over-the-air updates replace only the app. These need a USB cable once:
| Situation | Why |
|---|---|
| The first v2.1 install on every gateway and hub | the hub–gateway SPI format changed (#29), and the gateway's bootloader gained rollback (#34) |
| A different partition table, flash size or flash mode (e.g. the reduced-flash hub profile) | those aren't part of the app |
| Every student module update | student modules have no over-the-air path |
| A board that boots neither image, or a lost signing key | see Recovery Procedures |
imPress v2.1 · Repository · Issues · Source docs: docs/ · Not yet verified on hardware: see Testing and CI
Understand
Set up
Use
Operate
Develop
More