Skip to content

Firmware Build and Flash

Varun Karthic edited this page Oct 10, 2026 · 2 revisions

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.

Contents


The three projects

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.

Toolchain

You can build with either:

  • Docker (no installation): the pinned image espressif/idf:v6.1 contains everything.
  • Native ESP-IDF v6.1: follow Espressif's installation guide, then . $IDF_PATH/export.sh in 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.

Building

With Docker

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 build

Or let the test runner do it, which uses Docker when IDF isn't installed:

python run_tests.py --with-idf -s idf

Native

. $IDF_PATH/export.sh
cd firmware/class_c6
idf.py build
idf.py size          # flash and RAM usage

Builds use each project's committed sdkconfig, so the result matches CI.

Configuring before you flash

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: menuconfig changes it. Don't commit site-specific values (passwords, keys) to the repository; use a private copy, or set NVS per board (see Configuration Reference).

Flashing

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 monitor

Quit 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 flash

Flashing prebuilt release images

Each 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_args

The 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.

Setting up a room, step by step

  1. 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).
  2. Wire the hub to the gateway (Hardware Guide) and power both from a stable 5 V supply.
  3. Gateway: set Wi-Fi, backend and key in menuconfig, then flash. On boot it:
    1. connects to Wi-Fi;
    2. registers;
    3. receives its own device key, and the class id when the backend links it;
    4. opens the WebSocket.
  4. Linking: with no classroom_code in 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.
  5. Hub: set the same Wi-Fi, backend and key, plus the mesh channel. Flash it over serial (it carries the rollback bootloader).
  6. 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.
  7. Student modules: set the mesh channel, flash, and give each module its student's enrollment number (Hardware Guide).
  8. Test: start a quiz. The question appears on the modules; pressing a button raises the live count.

Reading logs

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.

Version numbers

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).

Signed and TLS builds

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

When serial flashing is required

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

Clone this wiki locally