From 6486ce1f91be4303520f9415c518dc047092e00c Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Thu, 23 Apr 2026 13:03:52 -0600 Subject: [PATCH] feat: add LilyGo T-Beam and T-Beam Supreme board support --- src/components/BoardSelector.svelte | 18 ++++++++++++++++++ src/components/EspFlashFlow.svelte | 2 +- src/components/Flasher.svelte | 4 ++-- src/lib/boards.ts | 22 ++++++++++++++++++++++ src/lib/esptool.ts | 24 ++++++++++++++++++++---- src/lib/types.ts | 2 +- 6 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/components/BoardSelector.svelte b/src/components/BoardSelector.svelte index 6daf9ce..64fd5e0 100644 --- a/src/components/BoardSelector.svelte +++ b/src/components/BoardSelector.svelte @@ -9,6 +9,7 @@ let { selected, onselect }: Props = $props(); + const esp32Boards = boards.filter((b) => b.family === "esp32"); const espBoards = boards.filter((b) => b.family === "esp32s3"); const rpBoards = boards.filter((b) => b.family === "rp2040"); const nrfBoards = boards.filter((b) => b.family === "nrf52840"); @@ -39,6 +40,23 @@

Select Board

+

ESP32

+
+ {#each esp32Boards as board} + + {/each} +
+

ESP32-S3

{#each espBoards as board} diff --git a/src/components/EspFlashFlow.svelte b/src/components/EspFlashFlow.svelte index b198e23..55b08a0 100644 --- a/src/components/EspFlashFlow.svelte +++ b/src/components/EspFlashFlow.svelte @@ -38,7 +38,7 @@ let session; try { - session = await connect(callbacks); + session = await connect(callbacks, board.family === "esp32" ? "esp32" : "esp32s3"); } catch (err) { const msg = err instanceof DOMException && err.name === "NotFoundError" ? "Port selection cancelled." diff --git a/src/components/Flasher.svelte b/src/components/Flasher.svelte index b6a72c2..1a19500 100644 --- a/src/components/Flasher.svelte +++ b/src/components/Flasher.svelte @@ -59,7 +59,7 @@ {#if selectedBoard && selectedRelease}

- {#if selectedBoard.family === "esp32s3"} + {#if (selectedBoard.family === "esp32" || selectedBoard.family === "esp32s3")} Flash Firmware {:else} Download Firmware @@ -72,7 +72,7 @@

{/if} - {#if selectedBoard.family === "esp32s3"} + {#if (selectedBoard.family === "esp32" || selectedBoard.family === "esp32s3")} {:else} diff --git a/src/lib/boards.ts b/src/lib/boards.ts index 4843b4d..8591cd2 100644 --- a/src/lib/boards.ts +++ b/src/lib/boards.ts @@ -1,6 +1,17 @@ import type { BoardDef } from "./types"; export const boards: BoardDef[] = [ + { + id: "lilygo_tbeam", + name: "LilyGo T-Beam (classic)", + mcu: "ESP32", + radio: "SX1276", + family: "esp32", + flashMethod: "web-serial", + assetPattern: "donglora-lilygo_tbeam-v{version}.bin", + notes: + "Original T-Beam with ESP32 + SX1276 + SSD1306 OLED + AXP192 PMIC. Flashes over the on-board CP2102 USB-to-UART bridge. Clients connect via serial (BLE planned).", + }, { id: "elecrow_thinknode_m2", name: "ELECROW ThinkNode-M2", @@ -43,6 +54,17 @@ export const boards: BoardDef[] = [ flashMethod: "web-serial", assetPattern: "donglora-heltec_v4-v{version}.bin", }, + { + id: "lilygo_tbeam_supreme", + name: "LilyGo T-Beam S3 Supreme", + mcu: "ESP32-S3", + radio: "SX1262", + family: "esp32s3", + flashMethod: "web-serial", + assetPattern: "donglora-lilygo_tbeam_supreme-v{version}.bin", + notes: + "T-Beam Supreme with ESP32-S3 + SX1262 + SH1106 OLED + AXP2101 PMIC. Flashes over native USB CDC-ACM — no UART bridge.", + }, { id: "rak_wisblock_4631", name: "RAK WisBlock 4631", diff --git a/src/lib/esptool.ts b/src/lib/esptool.ts index e6cb4d3..845b878 100644 --- a/src/lib/esptool.ts +++ b/src/lib/esptool.ts @@ -1,5 +1,7 @@ import type { FlashProgress } from "./types"; +export type EspChipFamily = "esp32" | "esp32s3"; + export function isWebSerialSupported(): boolean { return typeof navigator !== "undefined" && "serial" in navigator; } @@ -19,7 +21,20 @@ async function loadEsptool() { return { ESPLoader: mod.ESPLoader, Transport: mod.Transport }; } -export async function connect(callbacks: FlashCallbacks): Promise { +function chipFamilyLabel(family: EspChipFamily): string { + return family === "esp32s3" ? "ESP32-S3" : "ESP32"; +} + +function isChipCompatible(detected: string, expected: EspChipFamily): boolean { + if (expected === "esp32s3") return detected.includes("ESP32-S3"); + // Classic ESP32: esptool-js reports "ESP32". Exclude S2/S3/C3/C6/H2/P4 variants. + return /^ESP32(?![-\s]?(S|C|H|P)\d)/i.test(detected); +} + +export async function connect( + callbacks: FlashCallbacks, + expectedChip: EspChipFamily, +): Promise { if (!isWebSerialSupported()) { throw new Error("Web Serial API is not supported in this browser. Use Chrome, Edge, or Opera."); } @@ -45,16 +60,17 @@ export async function connect(callbacks: FlashCallbacks): Promise }, }); - callbacks.onLog("Connecting to ESP32..."); + const expectedLabel = chipFamilyLabel(expectedChip); + callbacks.onLog(`Connecting to ${expectedLabel}...`); callbacks.onProgress({ phase: "Connecting", percent: 0, message: "Detecting chip..." }); await loader.main(); const chip = loader.chip.CHIP_NAME; callbacks.onLog(`Detected: ${chip}`); - if (!chip.includes("ESP32-S3")) { + if (!isChipCompatible(chip, expectedChip)) { await transport.disconnect(); - throw new Error(`Expected ESP32-S3 but detected ${chip}. Make sure you selected the correct board.`); + throw new Error(`Expected ${expectedLabel} but detected ${chip}. Make sure you selected the correct board.`); } return { transport, loader }; diff --git a/src/lib/types.ts b/src/lib/types.ts index dbb7eaa..bf1a48d 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,4 +1,4 @@ -export type BoardFamily = "esp32s3" | "nrf52840" | "rp2040"; +export type BoardFamily = "esp32" | "esp32s3" | "nrf52840" | "rp2040"; export type FlashMethod = "web-serial" | "uf2" | "download-only"; export interface BoardDef {