Skip to content

Enhance T-Deck support with CI build, flasher, and keyboard features#302

Closed
3rdIteration wants to merge 21 commits into
Blockstream:masterfrom
3rdIteration:T-Deck-Plus
Closed

Enhance T-Deck support with CI build, flasher, and keyboard features#302
3rdIteration wants to merge 21 commits into
Blockstream:masterfrom
3rdIteration:T-Deck-Plus

Conversation

@3rdIteration

Copy link
Copy Markdown
Contributor

This pull request adds support for the LilyGO T-Deck hardware variant to the project. It introduces new configuration files for both normal and debug builds, updates the build system to handle T-Deck firmware, and extends the Kconfig system with the necessary board-specific settings and options. The changes ensure that T-Deck is properly recognized and configured in both the build pipeline and device configuration.

T-Deck hardware support:

Build and deployment pipeline:

  • Introduced a new GitHub Actions workflow .github/workflows/build.yml to automate building and artifact deployment for both normal and debug T-Deck firmware variants.

Board-specific configuration files:

  • Added configs/sdkconfig_display_tdeck.defaults for standard T-Deck builds and configs/sdkconfig_display_tdeck_debug.defaults for debug builds, specifying all relevant hardware and feature flags. [1] [2]

3rdIteration and others added 21 commits July 7, 2026 03:53
…loop)

Passing explicit flashMode/flashFreq/flashSize makes esptool-js patch the
bootloader image header in flash without recomputing the appended SHA-256
digest. The ESP32-S3 ROM verifies that digest when loading the second-stage
bootloader, rejects the image (ets_loader.c 78) and the device bootloops.
Use 'keep' so the image is written byte-identical to the build output.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
power_backlight_on() scaled brightness as a 0-255 value, but Jade passes
levels BACKLIGHT_MIN..BACKLIGHT_MAX (1..5), so the result was always 0 and
the backlight switched off as soon as the stored brightness was applied
after boot. Clamp and map levels 1..5 linearly onto dimmer steps 1..16.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the pulse-train step dimmer; drive the backlight GPIO high for any
requested brightness level and low for off. Level-based dimming can be
reinstated once the display path is proven out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Restore the pulse-step dimmer, mapping Jade brightness levels
BACKLIGHT_MIN..BACKLIGHT_MAX (1..5) linearly onto dimmer steps 1..16
(the earlier code scaled as if brightness were 0-255, always yielding 0).
Add short delays around the step pulses to respect dimmer timing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The trackball direction lines toggle level once per step of ball travel;
driving them through iot_button meant that whenever a line came to rest
low the driver saw a held button and fired LONG_PRESS_HOLD forever,
causing runaway scrolling until the ball was nudged. Handle all four
direction lines with ANYEDGE GPIO interrupts feeding a queue/watcher
task (same pattern as the Jade v1 wheel), with a 2ms debounce.

Also wire up the previously unused up/down lines: up scrolls prev,
down scrolls next, matching forward/back in vertical UI contexts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…vity

Mnemonic word entry only understood the on-screen keyboard:
- letters: the screen posts uppercase A-Z, but the T-Deck keyboard sends
  lowercase ascii, so typed letters were ignored; accept either case
- enter: the on-screen enter button is inactive during mnemonic entry,
  but a physical keyboard can post BTN_KEYBOARD_ENTER at any time, which
  set done_entering_words early and tripped the words_entered==nwords
  assert (mnemonic.c:865); only honour enter when the on-screen button
  would be active
- letter validity: the on-screen keyboard disables letters that match no
  word, a physical keyboard cannot - reject letters yielding a stem with
  no matching words (previously would trip the possible_words>0 assert)

Also halve trackball sensitivity: require 2 edges per scroll step, with
the accumulator reset on direction change or a 300ms pause.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add HAS_KEYBOARD config (selected by the T-Deck board) and lay out the
on-screen keyboards qwerty to match the physical keys: the mnemonic
word-entry keyboard and the upper/lower-case passphrase keyboards.
The passphrase space key moves to the end of the middle row as qwerty
needs all ten bottom-row columns for zxcvbnm plus the control keys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Passphrase: replace the show-and-confirm (Yes/No) screen with a second,
deliberately blank, keyboard entry titled Confirm Passphrase; the two
entries are compared and a mismatch restarts entry from scratch. The
old display-confirm activity is removed, and both entry buffers are
registered with the sensitive-data stack.

PIN/number entry: accept physical keyboard input alongside the wheel.
Adds gui_activity_wait_event_of_either_base() so the digit-entry loop
can wait on gui navigation and keyboard button events simultaneously.
Digits accepted as ascii 0-9 plus the letter keys with digits printed
on them on the T-Deck (w,e,r,s,d,f,z,x,c = 1-9); backspace steps back
(abandoning entry on the first digit), enter completes index entry.

Also log raw keyboard bytes at DEBUG level to identify unmapped keys
(eg. the mic key, which carries the printed 0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The CI shallow/tagless checkout (plus the git dubious-ownership error
inside the IDF container) made git describe fail, so JADE_VERSION in
get_version_info was "1". Companion apps parse this as semver against
a minimum firmware version and refuse to proceed (endless
get_version_info retries / "please reconnect your hardware wallet").
Fetch full history and tags, mark the workspace safe for git in the
container, and log the version at build time.

Also report BOARD_TYPE "TDECK" instead of "UNKNOWN", and simplify PIN
entry keys to actual ascii digits only - the T-Deck keyboard sends
these with the SYM modifier held, so the letter-alias mapping is
unnecessary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Send the keyboard controller brightness command (0x01 <level>) so the
keyboard backlight turns on/off and dims with the screen (levels 1-5
map to 51-255). The request is latched and applied by the keyboard
task, which owns the i2c device, retrying until the write succeeds.
The Alt+B manual toggle in the keyboard firmware still works.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Average 8 adc samples and use a 2.11 divider multiplier (2.0 nominal
  plus ~10% load correction, matching other t-deck firmwares) instead
  of a single noisy sample scaled by 2.
- usb/charging detection used tud_mounted(), which is always false on
  the debug (usb-serial-jtag) build where TinyUSB is never initialised;
  infer usb connection from usb_serial_jtag_is_connected() there.
- Report charging as usb present while below 4300mV - with no charger
  status line, a higher reading means the charger is holding up the
  sense node with the battery full.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
handle_display_battery_volts() only formats a reading for a hardcoded
set of boards and falls back to "NO BAT" otherwise; add the tdeck
(power_get_vbat() already returns divider-corrected millivolts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 7, 2026 00:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds LilyGO T-Deck (ESP32-S3) as a first-class board target, extending the firmware configuration, input/power drivers, UI keyboard behavior, and introducing a CI-driven web flasher + GitHub Actions build/deploy pipeline.

Changes:

  • Add BOARD_TYPE_TDECK with board-specific defaults (display, I2C, input pins) and new HAS_KEYBOARD capability in Kconfig + sdkconfig defaults.
  • Implement T-Deck-specific power (battery ADC + backlight + keyboard backlight) and input (I2C keyboard + GPIO trackball) integrations.
  • Add GitHub Actions build + artifact publishing and a browser-based flasher UI that can flash latest CI artifacts or uploaded ZIP bundles.

Reviewed changes

Copilot reviewed 19 out of 23 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tools/switch_to.sh Adds tdeck config selection for build switching script.
main/ui/mnemonic.c Adjusts word-entry keyboard layout for devices with a physical keyboard; removes passphrase confirm UI activity.
main/ui/keyboard.c Updates on-screen keyboard row layout to match physical QWERTY when CONFIG_HAS_KEYBOARD.
main/ui/digit_entry.c Adds physical keyboard support for digit entry and introduces dual-base event waiting.
main/ui/dashboard.c Enables display brightness/orientation options for T-Deck in display settings menu.
main/process/ota_defines.h Adds OTA board-type string mapping for T-Deck.
main/process/mnemonic.c Improves physical keyboard handling for mnemonic entry and changes passphrase confirmation to blind re-entry with sensitive buffers.
main/process/dashboard.c Enables brightness settings and battery voltage display paths for T-Deck.
main/power/tdeck.inc Adds T-Deck power implementation: backlight control, battery ADC reading, USB power inference, keyboard backlight linkage.
main/power.c Wires T-Deck power implementation into board selection.
main/Kconfig.projbuild Adds BOARD_TYPE_TDECK, HAS_KEYBOARD, and various T-Deck hardware defaults.
main/input/tdeck.inc Adds T-Deck navigation implementation: I2C keyboard reader task + trackball ISR/task + press handling.
main/input.c Includes T-Deck input implementation when CONFIG_BOARD_TYPE_TDECK.
main/gui.h Declares new helper to wait for either of two event bases.
main/gui.c Implements gui_activity_wait_event_of_either_base.
flasher/vendor/jszip-3.10.1.min.js Adds JSZip dependency for reading firmware ZIP bundles in the browser.
flasher/index.html Adds a full web flasher UI (CI firmware fetch + custom ZIP) and optional serial monitor.
flasher/branding/logo.svg Adds branding asset for the web flasher.
configs/sdkconfig_display_tdeck.defaults Adds standard T-Deck sdkconfig defaults for building firmware.
configs/sdkconfig_display_tdeck_debug.defaults Adds debug T-Deck sdkconfig defaults (USB-Serial-JTAG console).
.gitignore Ignores generated flasher/firmware/ content.
.github/workflows/build.yml Adds CI build matrix for T-Deck + deploy-to-gh-pages of the flasher site and firmware artifacts.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread main/gui.c
Comment on lines +2772 to +2793
bool gui_activity_wait_event_of_either_base(gui_activity_t* activity, const char* event_base1,
const char* event_base2, esp_event_base_t* trigger_event_base, int32_t* trigger_event_id,
void** trigger_event_data, const TickType_t max_wait)
{
JADE_ASSERT(activity);
JADE_ASSERT(event_base1);
JADE_ASSERT(event_base2);

// create a new wait-event-data structure and attach to the activity, which takes ownership
wait_event_data_t* const wait_event_data = gui_activity_make_wait_event_data(activity);
JADE_ASSERT(wait_event_data);

// register both bases against the same wait data so either can wake the caller
gui_activity_register_event(activity, event_base1, ESP_EVENT_ANY_ID, sync_wait_event_handler, wait_event_data);
gui_activity_register_event(activity, event_base2, ESP_EVENT_ANY_ID, sync_wait_event_handler, wait_event_data);

// immediately start waiting
const esp_err_t ret
= sync_wait_event(wait_event_data, trigger_event_base, trigger_event_id, trigger_event_data, max_wait);

return ret == ESP_OK;
}
Comment thread main/ui/dashboard.c
@@ -394,7 +394,7 @@ gui_activity_t* make_display_settings_activity(void)

// NOTE: Only Jade v1.1's and v2's have brightness controls
Comment thread main/process/dashboard.c
@@ -1703,7 +1703,7 @@ static void handle_view_otps(void)

// NOTE: Only Jade v1.1's and v2's have brightness controls
Comment thread main/process/dashboard.c
@@ -2281,7 +2281,7 @@ static void handle_settings(const bool startup_menu)

// NOTE: Only Jade v1.1's and v2's have brightness controls
Comment thread main/Kconfig.projbuild
BOARD_TYPE_M5_BLACK_GRAY || BOARD_TYPE_TTGO_TWATCHS3 || BOARD_TYPE_WS_TOUCH_LCD2
BOARD_TYPE_M5_BLACK_GRAY || BOARD_TYPE_TTGO_TWATCHS3 || BOARD_TYPE_WS_TOUCH_LCD2 || BOARD_TYPE_TDECK
help
Invert the colors in the dipslay. Some devices may have inverted colors depending on revision
Comment on lines +54 to +56
deploy:
if: github.event_name == 'push'
needs: build
Comment thread flasher/index.html
Comment on lines +146 to +148
<p style="font-size:13px;color:#8b949e;margin-bottom:12px">
Downloads the latest firmware built from the <code>T-Deck-Plus</code> branch.
</p>
Comment thread flasher/index.html
Comment on lines +299 to +310
function serialWrite(text) {
const last = serialDiv.lastChild;
if (last && last.nodeType === Node.TEXT_NODE) {
last.textContent += text;
} else {
serialDiv.appendChild(document.createTextNode(text));
}
serialDiv.scrollTop = serialDiv.scrollHeight;
if (serialDiv.children.length > 500) {
serialDiv.removeChild(serialDiv.firstChild);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants