Flash the firmware
The Jellybox firmware is an Arduino sketch for the ESP32. The fastest way to get a stock Jellybox running is the in-browser flasher below — no toolchain required. If you'd rather build from source, follow the Arduino IDE steps further down.
Flash from your browser
Plug your ESP32 into a USB port, click the button below, and pick the serial port for your device. The latest released firmware will be downloaded and flashed in one step. After the flash completes, head to step 7 — first-time setup below.
Requires a Chromium-based browser (Chrome, Edge, Opera, Arc) on desktop. Firefox and Safari don't implement Web Serial. If the button stays disabled, check that you're on HTTPS (or localhost) and that no other process — Arduino IDE, screen, esptool — is holding the serial port open.
Build from source
Use this path if you want to modify the firmware, debug over serial, or pin a development build that opts out of OTA updates.
Install Arduino IDE 2
Download Arduino IDE 2.x from arduino.cc/en/software. Version 2 is required — the older 1.x IDE does not support all features used by the project.
Add ESP32 board support
Arduino IDE doesn't include ESP32 support out of the box. Add it via the Board Manager:
- Open Arduino IDE → Preferences (Mac:
Cmd+,/ Windows:Ctrl+,). - Find Additional boards manager URLs and paste:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
- Go to Tools → Board → Boards Manager.
- Search for
esp32and install esp32 by Espressif Systems. Version 2.x or 3.x both work.
Install required libraries
Open Tools → Manage Libraries and install each of the following. Search by the name in the rightmost column.
| Library | Author | Min version |
|---|---|---|
| WiFiManager | tzapu | 2.0.17+ |
| Adafruit NeoPixel | Adafruit | 1.12+ |
| Adafruit PN532 | Adafruit | 1.3+ |
| GxEPD2 | ZinggJM | 1.6+ |
| Adafruit GFX Library | Adafruit | 1.11+ |
| ArduinoJson | Benoit Blanchon | 7+ |
Get the firmware source
Clone the firmware repository:
git clone https://github.com/Nikorag/Jellybox-firmware.git
Open jellybox-firmware/jellybox-firmware.ino in Arduino IDE. The IDE will automatically load all the associated header files in the same directory.
Select board and port
- Connect the ESP32 to your computer with a USB cable.
- Go to Tools → Board → esp32 → ESP32 Dev Module.
- Go to Tools → Port and select the port that appeared when you plugged in the device. On macOS it looks like
/dev/cu.usbserial-xxxxx; on Windows it will be aCOMport. - Leave all other settings at their defaults. Upload speed of
921600is fine.
Upload the firmware
Click the Upload button (right-arrow icon) or press Ctrl+U / Cmd+U. The IDE will compile and flash the firmware. Compilation takes about 60–90 seconds the first time.
When complete you should see Done uploading in the output panel and the device will reboot automatically.
First-time setup
On first boot (or after a factory reset), the device broadcasts a WiFi access point called Jellybox-Setup and shows a setup screen on the eInk display.
- Connect your phone or laptop to the
Jellybox-SetupWiFi network. - A captive portal should open automatically. If it doesn't, navigate to
192.168.4.1in a browser. - Tap Configure WiFi and enter your home network credentials.
- Enter the Server URL — the full URL of your Jellybox server (e.g.
https://your-app.vercel.app). - Enter the API key from your Jellybox dashboard. Go to Devices → Pair Device to generate one.
- Tap Save. The device reboots and connects.
When pairing is successful the eInk display shows the device name and the LED ring breathes blue — ready to scan.
Factory reset
To clear all stored config (WiFi credentials, server URL, API key) and return to setup mode:
- Power off the device.
- Hold the BOOT button (GPIO0).
- Power on while still holding BOOT.
- Hold for 3 seconds, then release.
The device will clear its NVS storage and restart into setup mode.
Over-the-air updates
OTA updates are user-triggered from the dashboard, not automatic. Every 30 seconds the device polls /api/device/me, sending its currently-running version as a ?version= query param. The server records that against the device, and only returns a latestFirmwarefield if you've flagged the device for an update. Otherwise the field is omitted and the device keeps running what it's running.
To update a device, open its page in the dashboard. The Firmware card shows the running version and the latest version from GitHub. Click Update firmware and on the next poll the device will download the binary, write it to its second OTA partition, and reboot into the new firmware. No phone, no app, no USB cable.
While the new image is being written the eInk display shows Updating firmware…and the LED ring spins cyan. After the reboot, the device verifies the new image boots cleanly before committing it — if the new firmware crashes during startup, ESP32's rollback mechanism reverts to the previous version on the next power cycle. A bad release can't brick a device in the field.
Once the device reports back the new version, the server compares it to the manifest and clears the pending flag automatically — the dashboard then shows the device as up to date. Builds tagged dev (e.g. local Arduino IDE flashes) opt out of OTA, so you can keep an in-development device connected without it jumping back to the public release.
By default your server tracks the upstream firmware repo and treats whatever release is currently tagged latest as the available version. Two env vars override that: FIRMWARE_REPO points at a different GitHub owner/name (use this if you maintain your own firmware fork), and FIRMWARE_VERSION pins every device to a specific tag like v0.0.2 instead of always offering the newest release. See the Vercel and self-hosting guides for where to set them.
FIRMWARE_VERSIONon the server to the older tag, then trigger an update from the dashboard; the device will treat the older version as “different” and reflash to it.Device log streaming
While running, every Jellybox device broadcasts its serial-style log output over the LAN as UDP packets to 255.255.255.255:5514. Each packet is one line — <millis> <message>— so you can watch a device's internal state without a USB cable, even after it's sealed inside its case.
For a quick local tail from a machine on the same network, run npm run logs:device from inside apps/server. To stream logs into the dashboard at /dashboard/device-logsinstead — useful when the server is hosted on Vercel and can't see LAN broadcasts directly — you need to run the log bridge. See Device log bridge on the server guide for setup.
If your access point filters broadcast traffic (some mesh and guest networks do), edit UDP_LOG_HOST in Config.h to the unicast IP of the bridge machine and reflash.
LED state reference
| Colour | Pattern | Meaning |
|---|---|---|
| Blue | Breathing | Ready to scan |
| Cyan | Spinning | Connecting / HTTP request in progress |
| Yellow | Spinning | Contacting server (bootstrap) |
| Purple | Breathing | Scan-capture mode — next scan registers a tag |
| Green | Flash | Success — playback started or tag captured |
| Red | Flash | Error — check serial monitor |
| Amber | Breathing | Unpaired / invalid API key |
Everything working? Print a case →