QRPickle is a simple, lightweight dashboard designed for portable, QRP, and field-day amateur radio operations. Built to run on a standard ESP32 Cheap Yellow Display (CYD) with no external PSRAM, it consolidates real-time tracking data and tools into a clean, easy-to-use touchscreen interface.
This project is inspired by cburns42/HamClockCYD and SP3KON/ESP32-HAM-CLOCK, which resulted in a group build at BARC with VU3GWN forking it to (coffeedev/BARC-HAM-CLOCK).
TL;DR: install QRPickle from your browser with the one-click web installer (Chrome or Edge, and a USB data cable).
This codebase, firmware architecture, network parsing engine, memory management schema, and layout optimization layers were designed, audited, and developed using a Large Language Model (LLM).
Screenshots are taken on the device (tools/device_screens.py, Classic theme); network names, addresses and positions are replaced or hidden in them. Smart sleep: after the screen timeout the backlight turns off; a touch wakes it (the first touch only wakes).
When on the same network, navigate to the device's IP address to access the integrated Web UI. The UI interacts with the C++ backend via asynchronous REST API endpoints.
| Basic Network & Station Config | Advanced Settings |
|---|---|
![]() |
![]() |
| Profiles, Backup & Restore | Editing a Profile in the Settings Form |
![]() |
![]() |
| Live Hardware Telemetry & Manual Update | Cloud OTA |
![]() |
![]() |
| APRS Messaging | |
![]() |
The web console and the QRPickle-Setup hotspot are password-protected.
- Username:
admin - Password: generated on first boot. It's shown on the device's Network screen (the serial boot log masks it). The same password is the WPA2 key for the setup hotspot.
- Changing it: Basic Settings → Admin Password (8-16 characters, no spaces), then Save Configuration. The browser then asks you to log in again, and the setup hotspot uses the new password.
Saved secrets (WiFi password, API keys, passcodes) are never shown again in the web console. Leave those fields blank to keep the stored values.
Note: saved profiles store their WiFi credentials on the device's filesystem, so they can switch networks. The flash is not encrypted. A profile without a stored password or key keeps the device's current one when applied. Backups never contain secrets.
What each screen shows and where its data comes from. Accounts and keys are entered in the web console and stay on the device.
| Screen | What it shows | Data from |
|---|---|---|
| Dashboard | Local and UTC time, indoor sensor and weather, band conditions for your chosen band groups | the screens below |
| DX Cluster | Live DX spots, filtered by band and mode | DX cluster over telnet (default dxspider.co.uk:7300, fallback dxc.w6bgr.com:7373; both configurable) |
| xOTA | POTA and SOTA activators on air, filtered by band, mode and QRP | POTA (api.pota.app); SOTA cluster (cluster.sota.org.uk, telnet) |
| HamAlert | Spots matching your own triggers | HamAlert (your account, telnet) |
| APRS | Stations heard (list and radar), messages, your position beacon | APRS-IS (rotate.aprs.net; your callsign and passcode) |
| Band Conditions | 11 bands (160 m to 6 m) rated for day and night at your location, greyline, next sunrise/sunset, solar indicators explained (how bands are rated) | HAMQSL.com solar data, courtesy of N0NBH (every 3 hours, hourly in storms), or your own source |
| Satellites | The next passes over your station of popular amateur satellites (FM, linear, SSTV, voice beacons, optionally APRS) with times, directions and frequencies; ISS SSTV events and ARISS school contacts | A feed built every 6 hours from SatNOGS DB (CC BY-SA 4.0), CelesTrak orbits and ARISS (about the feed); passes computed on the device, your location is never sent |
| Weather | Indoor temperature, humidity and pressure; current conditions and a 24-hour forecast | BME280 sensor (optional, wiring); OpenWeather (your free API key) |
| Network, Settings, Cloud OTA | WiFi, station and display settings on the device; updates | GitHub Pages for updates; time from pool.ntp.org |
On the device
- Screen cycling: chosen screens in turn (10 s each by default); a touch pauses it, a status-bar icon shows and toggles it.
- Status LED: setup and network states, a faint pulse when data arrives, a strobe for HamAlert or APRS messages, and a purple signal before ISS SSTV and school-contact passes (LED Colours).
- Themes: 7 themes in LVGL 9, including Tactical Field Red, Terminal Green and two E-Ink styles.
- Display: sleep timeout, manual or automatic brightness (light sensor).
- Privacy: passwords and network details are masked on screen and in the serial log.
Web console and updates
- Web console: every setting, profiles (save and switch complete setups for home, mobile and field; download/restore a backup without passwords and keys), APRS messaging and system information. Digest login; the setup hotspot (
192.168.4.1) takes over when no network is reachable. - Updates: firmware upload in the web console, or Cloud OTA from GitHub Pages with a mandatory SHA-256 and verified HTTPS. A new image that doesn't come up healthy within 3 boots is rolled back. First installs: the web installer.
How it fits on an ESP32 without PSRAM
- One network session at a time and streamed JSON parsing keep HTTPS (about 40 KB per session) within memory; long-running work runs in background tasks, never in the UI loop (memory budget).
- WiFi transmit power is capped (15 dBm) to avoid brownouts on the CYD's regulator.
- Guides for hardware, releasing, debugging and assets: docs/.
QRPickle uses a fixed UTC offset (in half-hour steps), with no automatic daylight saving. In regions that observe DST, change the offset in Settings twice a year. Most ham-radio activity is logged in UTC, which is always shown correctly.
QRPickle's own code is MIT-licensed (see License). The fonts are under the SIL Open Font License 1.1, the web server libraries under LGPL-3.0, and the CYD pinout image in the docs under CC BY-NC-ND 4.0. The full list, with copyright holders and licence texts, is in THIRD_PARTY_NOTICES.md; each release zip includes it.
- LVGL (Light and Versatile Graphics Library) v9.x: Core C-based UI framework handling all screens, layouts, animations, and touch interactions.
- TFT_eSPI: Highly optimized SPI driver for the ILI9341 display.
- XPT2046_Touchscreen: Driver for the resistive touch controller (on its own SPI bus).
- ESPAsyncWebServer: The web console and its REST API, with Digest authentication.
- Sgp4: Satellite orbit propagation (Vallado's SGP4) for the pass predictions.
- ArduinoJson v7: Zero-allocation stream parsing for OpenWeatherMap, POTA, and SOTA HTTPS payloads.
- ESP-IDF Native APIs: Utilized directly alongside the Arduino Core wrapper for strict Wi-Fi TX power limits, FreeRTOS task handling, and lwIP socket management.
The complete list, with licences, is in THIRD_PARTY_NOTICES.md.
This project utilizes a heavily customized partition table (partitions.csv) to fit a complex FreeRTOS application, LVGL graphics, a Web UI, and an Over-The-Air (OTA) update system into a standard 4MB ESP32 flash chip.
The full flash map, from the start of the chip. The bootloader and the partition table sit at fixed addresses before the first partition; the rest comes from partitions.csv:
| Address | Region | Size | Contents | Written at install |
|---|---|---|---|---|
0x001000 |
Bootloader | ESP32 second-stage bootloader | bootloader.bin |
|
0x008000 |
Partition table | The table below | partitions.bin |
|
0x009000 |
nvs |
0x005000 (20 KB) |
Settings: WiFi, callsign, keys, admin password | Kept (cleared only by "Erase device") |
0x00E000 |
otadata |
0x002000 (8 KB) |
Which app slot boots (app0 or app1) |
boot_app0.bin (boot app0) |
0x010000 |
app0 |
0x1C0000 (1.75 MB) |
Firmware slot 1 | firmware.bin |
0x1D0000 |
app1 |
0x1C0000 (1.75 MB) |
Firmware slot 2: updates (web upload or Cloud OTA) are written to whichever slot isn't running, so a failed update can roll back | Not written |
0x390000 |
spiffs |
0x070000 (448 KB) |
LittleFS: web console (www/), images, about text, saved profiles |
littlefs.bin |
The installer and the manual flashing steps below use exactly these addresses for every version. Changing partitions.csv breaks OTA for devices already in use.
The project is built entirely within PlatformIO (VS Code).
QRPickle targets the ESP32-2432S028R (2.8" CYD); no soldering is needed. The pins it uses and the wiring for the optional BME280 sensor are in Hardware and Wiring.
Before the firmware will function fully (especially the Web UI), you must write the data/ folder to the LittleFS partition.
pio run --target uploadfsWarning: writing the filesystem (by USB, web upload or web installer) replaces the whole LittleFS partition, which erases saved profiles. Settings stored in NVS (WiFi, callsign, keys, admin password) are kept, unless you tick "Erase device" in the web installer.
Compile the C++ source and burn the binary to the ESP32 via USB.
pio run --target uploadOnce deployed in the field, further updates can be pushed wirelessly. Compile your firmware in PlatformIO to generate firmware.bin or littlefs.bin, open the System Info tab on the Web UI, and push the payload directly over your local network.
Designed for operational reliability out in the field. Keep the signal clean, the power consumption managed, and the heap layout stable. 73!
You do not need to install VS Code or PlatformIO to use QRPickle. Pre-compiled binaries for the CYD are available in the repository releases.
Download the latest release binaries (every release also lists firmware.bin.sha256 for integrity checks)
The release folder contains the following files:
firmware.bin(The core C++ application)littlefs.bin(The Web UI and filesystem data)bootloader.bin&partitions.bin(Low-level ESP32 boot structures)License,THIRD_PARTY_NOTICES.mdand the font licences (OFL-*.txt) (in the ZIP)boot_app0.bin(marks the first app slot as the one to start)
Flashing from a terminal (when a browser can't connect): install
esptool (pipx install esptool, or use PlatformIO's copy), connect the CYD and run, from the folder with
the downloaded files:
# Firmware only: settings, profiles and the web console files stay
esptool --chip esp32 --port /dev/ttyUSB0 write_flash 0xe000 boot_app0.bin 0x10000 firmware.bin
# Everything (a new board); add "erase_flash" first for a clean start, which also clears settings
esptool --chip esp32 --port /dev/ttyUSB0 write_flash 0x1000 bootloader.bin 0x8000 partitions.bin \
0xe000 boot_app0.bin 0x10000 firmware.bin 0x390000 littlefs.binThe port is /dev/ttyUSB0 on Linux, /dev/cu.usbserial-… on macOS, COM3 (or similar) on Windows. Older
esptool versions are called esptool.py.
Web installer on Linux. Chromium 154 on Linux (seen with the Flatpak and Fedora builds) doesn't set the serial
port's speed: the port keeps its previous speed, 9600 after plugging in, and the installer reports "Failed to
initialize". Run stty -F /dev/ttyUSB0 115200 before installing, or set it automatically whenever a CH340 board
is plugged in with a udev rule (/etc/udev/rules.d/99-ch340.rules, then sudo udevadm control --reload-rules
and replug):
SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", MODE="0660", TAG+="uaccess", RUN+="/usr/bin/stty -F /dev/%k 115200"
Then hold BOOT while clicking Install and release it once installing starts (the browser can't switch the CYD into download mode by itself there). Other Chromium-based browsers on Linux may behave the same.
One-click installer (from v0.2.0): open quixotic.bharathpalavalli.com/QRPickle/install in Chrome or Edge, pick a version (the latest is preselected; the last five releases are listed), connect the CYD by USB and click Install. It writes everything, including the filesystem. If you're updating a device that already has profiles, download a backup first (web console → Profiles → Download Backup).
Manual alternative: flash the release files with the Espressif Web Flasher.
- Connect your CYD to your computer via USB.
- Open the Espressif Web Flasher Tool.
- Set your baud rate to
921600and click Connect. - Add the release files to the flash tool using their exact partition offsets:
0x1000➔bootloader.bin0x8000➔partitions.bin0x10000➔firmware.bin0x390000➔littlefs.bin
- Click Program and wait for the process to finish. The device will automatically reboot into QRPickle!
- And the device should boot up, and create a WiFi access-point called
QRPickle-Setupprotected with the admin password shown on the device's Network screen (see Web Console Login).































