Two boards, one firmware project:
| LilyGO T-Deck Plus | Waveshare ESP32-S3-GEEK | |
|---|---|---|
![]() |
![]() |
|
| What it is | A pocket Meshtastic node with a keyboard, trackball, LoRa, GPS, WiFi, BLE and a microSD slot, running this firmware | A USB-A stick with a screen and a TF card slot. Plugged into a computer it is the T-Deck's keyboard, mouse, mesh terminal and shared drive |
| Firmware | src/, tdeck-plus-<version>-merged.bin |
dongle/, tdeck-dongle-<version>.bin |
| Flash | at 0x0, see Flash it | at 0x0 with BOOT held while plugging in, see Flash the GEEK |
Both images, with checksums and ESP Web Tools manifests for flashing from a browser, are on the releases page.
The T-Deck talks to stock Meshtastic nodes on its own. The GEEK is what makes it a desk tool as well: type on the T-Deck and the computer receives it over an encrypted ESP-NOW link, chat on the mesh from a terminal on the computer, and move notes, to-do, music and map tiles through the GEEK's TF card, which is a USB drive on the computer and a folder on the T-Deck at the same time. It can also lock the computer when you walk away with the T-Deck.
It is not a fork of Meshtastic. The radio protocol is reimplemented from the published source (see docs/research) on an ESP32-S3 Arduino build with LVGL, so the apps are free to be whatever a pocket deck should have: chat, nodes, an offline map, GPS and compass, WiFi, BLE and LoRa scanners, notes, to-do, files, a timer, music, and the Keyboard app. The look is phosphor green on black.
Status: in daily use on a T-Deck Plus since 2026-10-22. Channel text, direct messages, relaying and map tiles are confirmed against stock Meshtastic nodes (#26, #129). What is not done is listed at the end.
More screens in docs/screenshots/; the longer write-up is docs/tdeck.md.
This is built and tested with two boards:
| Board | Role | What it has |
|---|---|---|
| LilyGO T-Deck Plus | the device | ESP32-S3, 2.8" 320×240 touch screen, keyboard, trackball, SX1262 LoRa (915 MHz, internal antenna), GPS, microSD, speaker, 2000 mAh battery |
| Waveshare ESP32-S3-GEEK | the dongle | ESP32-S3 in a USB-A stick with a 1.14" screen and a TF card slot |
The original T-Deck (no GPS, no battery) uses the same pin map and should run the same image, but only the Plus has been tried.
The dongle firmware is dongle/, a PlatformIO project of its own in this repo, with an env per board:
| Env | Board | Gets |
|---|---|---|
geek |
Waveshare ESP32-S3-GEEK | everything: keyboard, mouse, mesh terminal, the TF card as a shared drive, link status and the last keys on its screen, lock-when-away |
s3 |
any ESP32-S3 board with the USB port on the native pins | keyboard, mouse, mesh terminal, lock-when-away; no drive, no screen |
s2 |
any ESP32-S2 board | the same as s3 |
The GEEK is the one to get: the shared drive is what makes notes, to-do, music and map tiles flow between the T-Deck and the computer, and it sits in a USB port all day. A plain ESP32-S3 board is fine for the keyboard alone.
| App | What it does |
|---|---|
| Chat | #LongFast channel plus direct messages (New DM or n picks a node from the ones heard). Delivery status (… queued, ✓ sent, ✓✓ relayed/acked, ✕ failed). DMs are PKI-encrypted (X25519 + AES-256-CCM), which Meshtastic 2.7+ requires. A DM to a node whose key isn't known yet waits until its NodeInfo arrives. |
| Nodes | Every node heard: name, last heard, hops, SNR/RSSI, battery, distance and bearing. Actions: DM, request position, request info, show on map. |
| Map | Offline map from SD tiles in Meshtastic MUI's layout, or a distance grid without tiles. Shows you and every node with a position, with a breadcrumb trail of where each has been since boot. s pulls new tiles from map/ on the GEEK's card. |
| GPS | Auto-detects the L76K (9600) or u-blox M10 (38400) module. Shows fix, sats, HDOP, position, altitude, speed and UTC. Sets the clock. Has a "share my position" toggle (off by default). |
| Compass | Heading-up rose from the GPS course (no magnetometer, so no heading while stopped), speed, altitude, and a trip that keeps counting with the app closed: distance, moving time, max speed (r resets). m marks a waypoint, kept across reboots; the rose shows its direction and the page its distance and bearing. c clears it. |
| WiFi | Scanner: SSID, signal, channel, security, BSSID, networks per channel. |
| BLE | Scanner that flags item trackers (Apple Find My / AirTag, Tile, Samsung SmartTag, Google Find) and Meshtastic devices. |
| Spectrum | Three waterfalls. LoRa sweeps 902–928 MHz in 160 steps (about 3 rows/s) with an auto noise floor; mesh traffic pauses while it runs. WiFi hops channels 1–13 in promiscuous mode and draws each channel's 22 MHz hump. BLE gives each nearby device a lane, with time scrolling right to left and trackers in amber. Click: next mode. c: matrix or SDR colours. Up/down: brightness (LoRa, WiFi) or pick a device (BLE). |
| Keyboard | The T-Deck as a wireless keyboard and mouse for a computer, over Bluetooth or the dongle. Its own section is below. |
| Notes | Plain .txt notes in /notes/ on the SD card (internal flash without a card). |
| To-do | A check list in /todo.txt ([ ] item / [x] item lines). Click toggles, n adds, d deletes, c clears the done ones. With the dongle online it syncs with tdeck/todo.txt on the GEEK's card: edit that file on the computer, s (or opening the app) pulls and merges it, every change pushes. A line deleted on either side stays deleted after the next sync. |
| Files | SD browser with sizes and free space. Views text files; deletes after pressing d twice; p pushes a file to the dongle's card. With a dongle paired, a Dongle card row opens the GEEK's TF card over the link: browse it, click a text file to read it, g pulls any file to /dongle/ on the T-Deck. |
| Timer | Stopwatch with laps and a countdown. Left/right switch; click starts and pauses; l lap, r reset; up/down set the countdown. Both keep running with the app closed, and the countdown rings the speaker and wakes the screen from any app. |
| Music | MP3 and WAV from /music on the SD card through the speaker. Every folder in /music is a playlist, loose files are the Music playlist. Click = play/pause, left/right = track, up/down = volume, p = playlists, x = stop. Keeps playing with the app closed. s pulls everything new under music/ on the GEEK's card. |
| Settings | Names, modem preset, channel name, TX power, hop limit, relaying, shared-position precision, brightness, screen timeout, US time zone, message tone and tone volume, the computer-lock shortcut, radio stats, power off. |
| About | Version, MAC, battery, memory, detected peripherals. |
Mesh behaviour matches a Meshtastic CLIENT:
- Relays other nodes' packets using the SNR-weighted contention window, and drops its relay if another node relays first.
- Dedupes on (from, id).
- ACKs DMs and retries its own up to 3 times.
- Broadcasts NodeInfo (with its public key) at boot and every 3 h.
- Sends battery telemetry every 30 min, and its position every 15 min if sharing is on.
Two ways to reach a computer:
- Bluetooth: pair "T-Deck Keyboard" from a computer or phone (no PIN). Keyboard and mouse only.
- USB dongle: the dongle plugs into the computer and types what the T-Deck sends over ESP-NOW. Pairing is an X25519 key exchange; after that every packet is AES-CCM encrypted and carries an HMAC tag and a counter, so a recorded packet cannot be replayed. Keys travel as press-and-release taps, so a lost packet never leaves a key stuck down.
On the typing screen the trackball is a mouse (click = left button, press and hold = drag, double-click = scroll mode until the next click) or arrow keys. Hold the trackball still for 3 s to stop typing (a long click does it in arrow-key mode). The screen is an editor with a caret: a tap in the text puts it on that letter and the computer's cursor follows, typing inserts there, backspace deletes before it, Clear empties the box (the computer keeps its text).
What the dongle link carries besides keys:
- Mesh chat on the desk (
Mesh chat: To dongle): every chat message is relayed to the dongle's USB serial port as a JSON line and lines sent to it go out on the mesh, even with the app closed.tools/meshterm.pymakes the computer a Meshtastic terminal with no app. Messages that arrive while the dongle is unplugged are forwarded when it returns (the last 20). - Shared drive (GEEK only): the GEEK's TF card is a USB drive on the computer and a folder on the T-Deck at the same time. Sync copies the notes to it, To-do syncs both ways, Music and Map pull from it, Files browses it over the air.
- Lock the computer: Lock when away (Off, 10, 20 or 60 s) has the dongle lock the computer that long after the T-Deck goes quiet, and the Lock button locks it now. The shortcut (Mac Ctrl+Cmd+Q, PC Win+L, or Report only, which just logs it on the dongle's serial port) and whether Power off locks the computer are in Settings. Over Bluetooth the T-Deck taps the shortcut itself, and only on the button. With the lock on, powering the T-Deck off or reflashing it locks the computer too; set it to Off first when that is not what you want.
Pairing: plug the dongle in with its screen saying "Pairing" (hold its BOOT button for 2 s to get there from a paired one), then on the T-Deck open Keyboard, set Send to USB dongle and press Pair. Forget needs a second press.
Versions: both halves include the same src/link/link_proto.h, so a release's two images always match. Mixing builds is fine as long as LINK_VERSION is the same; a mismatch is logged on the dongle's serial port and shown on the Keyboard page.
| Input | Action |
|---|---|
| Trackball up / down | Move focus (scroll in Chat, Files viewer, Map) |
| Trackball left / right | Adjust sliders and dropdowns, move the text cursor, pan the map |
| Trackball click | Select / Enter |
| Trackball long click | Back |
| Trackball held 2 s | Lock: screen off, keys ignored |
| Trackball held 1 s while locked | Unlock |
| Keyboard | Type; Enter sends a message |
Alt+B |
Keyboard backlight (handled by the keyboard itself) |
Map: + - c n |
Zoom in / out, centre on me, jump to the next node |
Notes: n · Files: d d · WiFi/BLE: r |
New note · delete · rescan |
| Touch | Tap anything |
The screen fades out after the timeout (default 10 s) and the T-Deck locks: keys, taps and trackball rolls do nothing, so a pocket press changes nothing. Hold the trackball button about a second to unlock, or two seconds while awake to lock at once. An incoming message or a finished timer lights the screen for a look but keeps the lock.
You need PlatformIO (uv tool install platformio or pip install platformio) and a USB-C data cable.
scripts/build_release.shscripts/flash.shbuild_release.sh writes release/tdeck-plus-<version>-merged.bin (bootloader, partitions and app; flash at 0x0), an app-only -app.bin (0x10000, for Launcher/OTA), a .sha256 and a -manifest.json. flash.sh flashes the newest merged image at 0x0 and refuses anything else. Writing a merged image at 0x10000 is a known way to brick a T-Deck. Prebuilt images are on the releases page.
From the browser: the manifest is for ESP Web Tools. Put the merged image and the manifest in the same folder on any web site and add
<script type="module" src="https://unpkg.com/esp-web-tools@10/dist/web/install-button.js?module"></script>
<esp-web-install-button manifest="tdeck-plus-v0.2.0-manifest.json"></esp-web-install-button>Chrome or Edge on a computer, a data cable, and the download-mode steps below if it does not connect. GitHub's release downloads do not allow cross-site fetches, so the files have to be copied to the site rather than linked from the release.
If flashing can't connect (common when Meshtastic or another firmware is running), enter download mode:
- Turn the T-Deck off (left-side switch).
- Hold the trackball down.
- Turn it on and keep holding for 2–3 s.
- Release. The screen stays black: that's download mode.
- Run
scripts/flash.sh, then press reset or power-cycle.
To name a port: scripts/flash.sh release/<file>-merged.bin /dev/cu.usbmodemXXXX.
Going back to stock Meshtastic: use flasher.meshtastic.org with download mode as above. The LilyGO factory test images are in the upstream repo.
The release carries tdeck-dongle-<version>.bin, the companion firmware from dongle/ built for the Waveshare ESP32-S3-GEEK, with a -manifest.json for the browser button and a .sha256. scripts/build_release.sh builds it next to the T-Deck image; cd dongle && pio run -e geek builds it alone.
- Hold the GEEK's BOOT button while plugging it into the computer. It shows up as a new serial port and its screen stays dark.
- Flash the image at 0x0:
uvx esptool --chip esp32s3 write-flash 0x0 tdeck-dongle-<version>.bin- Unplug and plug it back in. The screen says Pairing.
- On the T-Deck open Keyboard, set Send to USB dongle and press Pair. The GEEK's screen says Connected and the T-Deck's status line shows the dongle online.
The image is a factory image: it wipes the GEEK's settings, so a GEEK that was already paired comes back in pairing mode. dongle/tools/geekflash.py reflashes a running GEEK without losing the pairing, and dongle/README.md covers the s3 and s2 envs for other boards.
Copy 256 px PNG slippy tiles to the SD card (FAT32 or exFAT) as either:
/map/{z}/{x}/{y}.png
/maps/<style>/{z}/{x}/{y}.png
This is the same layout Meshtastic MUI uses, so MUI tile packs work as-is. With a GEEK paired, put the same map/ tree on its card (it is a USB drive on the computer) and press s in the Map app to pull it over the air. 8-bit palette PNGs decode fastest. Respect the tile provider's terms; OpenStreetMap's public tile servers forbid bulk downloads.
At 115200 baud, over the USB port, one command per line:
| Command | Does |
|---|---|
open <app> |
Opens an app by name, e.g. open Spectrum |
key <k> |
Injects a key: enter, esc, up, down, left, right or one character |
tap <x> <y> |
Presses the screen at that pixel for 80 ms and releases, as a finger would |
home |
Back to the launcher |
lock / unlock |
Lock or unlock the screen (a locked T-Deck ignores key and tap) |
ls <dir> / cat <file> |
List a directory / print a file from the SD card (or internal flash without one; flash:/x forces flash) |
shot |
Screenshot: SHOT <w> <h>, base64 RGB565 little-endian lines, then END (the active screen; toasts are not in it) |
rssi |
SX1262 instantaneous RSSI at several settle times (sweep diagnostics) |
Output only arrives if the host opened the port before the device booted, so reset after connecting. The dongle answers {"get":"link"} and {"get":"screen"} on its own port.
pio runtest/host/run.shtest/host/run_crypto.sh /path/to/mbedtls-v2.28.8test/host/run.shchecks the frequency slot (906.875 MHz), channel hash (0x08), nonce layout, header flags, protobuf codec, and AES-CTR against OpenSSL.test/host/run_crypto.shbuildssrc/mesh/crypto.cppagainst upstream mbedtls and checks X25519 against OpenSSL, plus the PKI round trip and tamper rejection. It needs an mbedtls 2.28 checkout (Mbed-TLS/mbedtls, tagv2.28.8).scripts/qemu_smoke.shboots the merged image in Espressif QEMU for 45 s and fails on any panic.
src/board.h pin map (from LilyGO utilities.h, cross-checked with Meshtastic's variant)
src/hal/ display + backlight, keyboard/trackball/touch, battery, GPS, BLE HID
src/mesh/ protocol constants, protobuf codec, crypto, router + radio
src/link/ the dongle link: pairing, encryption, the packet types, the companion relay
src/core/ settings (NVS), node DB and message log (LittleFS), SD, serial console
src/ui/ launcher, status bar, toasts, helpers
src/apps/ one file per app
lib/ LilyGO's preconfigured TFT_eSPI and SensorLib (don't replace with registry copies)
tools/meshterm.py the computer as a Meshtastic terminal through the dongle
dongle/ the GEEK companion firmware: its own PlatformIO project, board support, flash and screenshot tools
docs/ the write-up, screenshots, and the protocol and hardware research
Every change is one issue, one pull request, one review and one squash commit.
GPL-3.0. The firmware links ESP8266Audio (GPL-3.0), so a build of it is GPL either way; the rest of the code is offered under the same terms. LilyGO's TFT_eSPI and SensorLib copies in lib/ keep their own licenses.
- Meshtastic 2.8 XEdDSA packet signatures are not implemented. Nodes accept unsigned packets by default, and chat with stock nodes (an M5Stack among them) works without them.
- GPS detection is confirmed on the u-blox M10 only; the L76K path is written from its documentation.
- Battery voltage was never checked against a meter (the pack is sealed in the case); the divider ratio comes from the schematic (#5).
- Only the T-Deck Plus has been flashed. The original T-Deck shares the pin map but is untried.




