From 22f0ab2b5b71bb51873e979ddab33afd23122ea5 Mon Sep 17 00:00:00 2001
From: Jake Lyell
Date: Wed, 7 Jan 2026 11:08:55 +1100
Subject: [PATCH 1/6] Add option to flash to backup partition or current
partition
---
src/app/page.tsx | 104 ++++++++++++++++++++++--------
src/esp/useEspOperations.ts | 123 ++++++++++++++++++++++++++++++++++--
2 files changed, 194 insertions(+), 33 deletions(-)
diff --git a/src/app/page.tsx b/src/app/page.tsx
index c729991..59068ff 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -101,38 +101,76 @@ export default function Home() {
your device using Save full flash above.
- Flash English/Chinese firmware will download the firmware,
+ Flash to current partition will download the firmware and
+ overwrite your current running partition. The device will reboot
+ with the new firmware on the same partition. This is fast and
+ retains all settings.
+
+
+ Flash to backup partition will download the firmware,
overwrite the backup partition with the new firmware, and swap
over to using this partition (leaving your existing firmware as
- the new backup). This is significantly faster than a full flash
- write and will retain all your settings. If it goes wrong, it
- should be fine to run again.
+ the new backup). This is also fast and retains all settings, with
+ the option to swap back if needed.
-
- Flash English firmware (3.1.1)
-
-
- Flash Chinese firmware (3.1.5)
-
-
- Flash CrossPoint firmware (Community)
-
-
+
+
+ Flash English (3.1.1) to current
+
+
+ Flash English (3.1.1) to backup
+
+
+
+
+ Flash Chinese (3.1.5) to current
+
+
+ Flash Chinese (3.1.5) to backup
+
+
+
+
+ Flash CrossPoint to current
+
+
+ Flash CrossPoint to backup
+
+
+
@@ -146,7 +184,19 @@ export default function Home() {
}
disabled={isRunning}
>
- Flash firmware from file
+ Flash file to current
+
+
+ actions.flashCustomFirmwareToBackup(() =>
+ appPartitionFileInput.current?.getFile(),
+ )
+ }
+ disabled={isRunning}
+ >
+ Flash file to backup
{process.env.NODE_ENV === 'development' && (
diff --git a/src/esp/useEspOperations.ts b/src/esp/useEspOperations.ts
index 1d104e4..6e71140 100644
--- a/src/esp/useEspOperations.ts
+++ b/src/esp/useEspOperations.ts
@@ -25,6 +25,61 @@ export function useEspOperations() {
const flashRemoteFirmware = async (
getFirmware: () => Promise,
+ ) => {
+ initializeSteps([
+ 'Connect to device',
+ 'Download firmware',
+ 'Read otadata partition',
+ 'Flash app partition',
+ 'Reset device',
+ ]);
+
+ const espController = await runStep('Connect to device', async () => {
+ const c = await EspController.fromRequestedDevice();
+ await c.connect();
+ return c;
+ });
+
+ const firmwareFile = await runStep('Download firmware', getFirmware);
+
+ const currentPartitionLabel = await runStep(
+ 'Read otadata partition',
+ async (): Promise => {
+ const partition = await espController.readOtadataPartition((_, p, t) =>
+ updateStepData('Read otadata partition', {
+ progress: { current: p, total: t },
+ }),
+ );
+
+ return partition.getCurrentBootPartitionLabel();
+ },
+ );
+
+ const flashAppPartitionStepName = `Flash app partition (${currentPartitionLabel})`;
+ updateStepData('Flash app partition', { name: flashAppPartitionStepName });
+ await runStep(flashAppPartitionStepName, () =>
+ espController.writeAppPartition(
+ currentPartitionLabel,
+ firmwareFile,
+ (_, p, t) =>
+ updateStepData(flashAppPartitionStepName, {
+ progress: { current: p, total: t },
+ }),
+ ),
+ );
+
+ await runStep('Reset device', () => espController.disconnect());
+ };
+
+ const flashEnglishFirmware = async () =>
+ flashRemoteFirmware(() => getOfficialFirmware('3.1.1-EN'));
+ const flashChineseFirmware = async () =>
+ flashRemoteFirmware(() => getOfficialFirmware('3.1.5-CH'));
+ const flashCrossPointFirmware = async () =>
+ flashRemoteFirmware(() => getCommunityFirmware('CrossPoint'));
+
+ const flashRemoteFirmwareToBackup = async (
+ getFirmware: () => Promise,
) => {
initializeSteps([
'Connect to device',
@@ -84,14 +139,66 @@ export function useEspOperations() {
await runStep('Reset device', () => espController.disconnect());
};
- const flashEnglishFirmware = async () =>
- flashRemoteFirmware(() => getOfficialFirmware('3.1.1-EN'));
- const flashChineseFirmware = async () =>
- flashRemoteFirmware(() => getOfficialFirmware('3.1.5-CH'));
- const flashCrossPointFirmware = async () =>
- flashRemoteFirmware(() => getCommunityFirmware('CrossPoint'));
+ const flashEnglishFirmwareToBackup = async () =>
+ flashRemoteFirmwareToBackup(() => getOfficialFirmware('3.1.1-EN'));
+ const flashChineseFirmwareToBackup = async () =>
+ flashRemoteFirmwareToBackup(() => getOfficialFirmware('3.1.5-CH'));
+ const flashCrossPointFirmwareToBackup = async () =>
+ flashRemoteFirmwareToBackup(() => getCommunityFirmware('CrossPoint'));
const flashCustomFirmware = async (getFile: () => File | undefined) => {
+ initializeSteps([
+ 'Read file',
+ 'Connect to device',
+ 'Read otadata partition',
+ 'Flash app partition',
+ 'Reset device',
+ ]);
+
+ const fileData = await runStep('Read file', async () => {
+ const file = getFile();
+ if (!file) {
+ throw new Error('File not found');
+ }
+ return new Uint8Array(await file.arrayBuffer());
+ });
+
+ const espController = await runStep('Connect to device', async () => {
+ const c = await EspController.fromRequestedDevice();
+ await c.connect();
+ return c;
+ });
+
+ const currentPartitionLabel = await runStep(
+ 'Read otadata partition',
+ async (): Promise => {
+ const partition = await espController.readOtadataPartition((_, p, t) =>
+ updateStepData('Read otadata partition', {
+ progress: { current: p, total: t },
+ }),
+ );
+
+ return partition.getCurrentBootPartitionLabel();
+ },
+ );
+
+ const flashAppPartitionStepName = `Flash app partition (${currentPartitionLabel})`;
+ updateStepData('Flash app partition', { name: flashAppPartitionStepName });
+ await runStep(flashAppPartitionStepName, () =>
+ espController.writeAppPartition(
+ currentPartitionLabel,
+ fileData,
+ (_, p, t) =>
+ updateStepData(flashAppPartitionStepName, {
+ progress: { current: p, total: t },
+ }),
+ ),
+ );
+
+ await runStep('Reset device', () => espController.disconnect());
+ };
+
+ const flashCustomFirmwareToBackup = async (getFile: () => File | undefined) => {
initializeSteps([
'Read file',
'Connect to device',
@@ -382,6 +489,10 @@ export function useEspOperations() {
flashChineseFirmware: wrapWithRunning(flashChineseFirmware),
flashCrossPointFirmware: wrapWithRunning(flashCrossPointFirmware),
flashCustomFirmware: wrapWithRunning(flashCustomFirmware),
+ flashEnglishFirmwareToBackup: wrapWithRunning(flashEnglishFirmwareToBackup),
+ flashChineseFirmwareToBackup: wrapWithRunning(flashChineseFirmwareToBackup),
+ flashCrossPointFirmwareToBackup: wrapWithRunning(flashCrossPointFirmwareToBackup),
+ flashCustomFirmwareToBackup: wrapWithRunning(flashCustomFirmwareToBackup),
saveFullFlash: wrapWithRunning(saveFullFlash),
writeFullFlash: wrapWithRunning(writeFullFlash),
fakeWriteFullFlash: wrapWithRunning(fakeWriteFullFlash),
From 2b871c79a18ea5b9f1f427acd4c57d85deff417f Mon Sep 17 00:00:00 2001
From: Jake Lyell
Date: Wed, 7 Jan 2026 11:24:37 +1100
Subject: [PATCH 2/6] move flash to current to the debug page
---
src/app/debug/page.tsx | 83 +++++++++++++++++++++++++++++++++++++++++-
src/app/page.tsx | 50 +++----------------------
2 files changed, 88 insertions(+), 45 deletions(-)
diff --git a/src/app/debug/page.tsx b/src/app/debug/page.tsx
index d974b85..9d19b42 100644
--- a/src/app/debug/page.tsx
+++ b/src/app/debug/page.tsx
@@ -15,6 +15,7 @@ import {
Separator,
Stack,
Table,
+ Text,
} from '@chakra-ui/react';
import { useEspOperations } from '@/esp/useEspOperations';
import Steps from '@/components/Steps';
@@ -23,6 +24,7 @@ import OtaPartition, { OtaPartitionDetails } from '@/esp/OtaPartition';
import HexSpan from '@/components/HexSpan';
import HexViewer from '@/components/HexViewer';
import { downloadData } from '@/utils/download';
+import FileUpload, { FileUploadHandle } from '@/components/FileUpload';
function OtadataDebug({ otaPartition }: { otaPartition: OtaPartition }) {
const bootPartitionLabel = otaPartition.getCurrentBootPartitionLabel();
@@ -197,8 +199,9 @@ function AppDebug({
}
export default function Debug() {
- const { debugActions, stepData, isRunning } = useEspOperations();
+ const { actions, debugActions, stepData, isRunning } = useEspOperations();
const [debugOutputNode, setDebugOutputNode] = useState(null);
+ const appPartitionFileInput = React.useRef(null);
return (
@@ -288,6 +291,84 @@ export default function Debug() {
+
+
+
Overwrite current partition (Advanced)
+
+
+ These are advanced flashing options for users who want to flash
+ firmware directly to the currently selected partition, as opposed
+ to the backup partition.
+
+
+ Flash to current partition will download the firmware and
+ overwrite your current running firmware. The device will reboot
+ with the new firmware on the same partition.
+
+
+
+
+
+ Warning: Current firmware will be overwritten
+
+ Flashing to the current partition will overwrite the
+ currently used firmware and leave the backup partition unchanged.
+ Proceed with caution.
+
+
+
+
+
+
+
+ Flash English (3.1.1) to current
+
+
+
+
+ Flash Chinese (3.1.5) to current
+
+
+
+
+ Flash CrossPoint firmware (Community) to current
+
+
+
+
+
+
+
+ actions.flashCustomFirmware(() =>
+ appPartitionFileInput.current?.getFile(),
+ )
+ }
+ disabled={isRunning}
+ >
+ Flash file to current
+
+
+
+
+
Steps
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 59068ff..2b68c96 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -100,31 +100,21 @@ export default function Home() {
Before using this, I'd strongly recommend taking a backup of
your device using Save full flash above.
-
- Flash to current partition will download the firmware and
- overwrite your current running partition. The device will reboot
- with the new firmware on the same partition. This is fast and
- retains all settings.
-
Flash to backup partition will download the firmware,
overwrite the backup partition with the new firmware, and swap
over to using this partition (leaving your existing firmware as
- the new backup). This is also fast and retains all settings, with
+ the new backup). This is fast and retains all settings, with
the option to swap back if needed.
+
+ For more advanced flashing options (like flashing to the current
+ partition), see the Debug page.
+
-
- Flash English (3.1.1) to current
-
-
- Flash Chinese (3.1.5) to current
-
-
- Flash CrossPoint to current
-
- Flash CrossPoint to backup
+ Flash CrossPoint firmware (Community) to backup
-
- actions.flashCustomFirmware(() =>
- appPartitionFileInput.current?.getFile(),
- )
- }
- disabled={isRunning}
- >
- Flash file to current
-
Date: Thu, 15 Jan 2026 09:26:17 +1100
Subject: [PATCH 3/6] Update Chinese Firmware version on button
---
src/app/debug/page.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/app/debug/page.tsx b/src/app/debug/page.tsx
index 9d19b42..02e1c3e 100644
--- a/src/app/debug/page.tsx
+++ b/src/app/debug/page.tsx
@@ -336,7 +336,7 @@ export default function Debug() {
onClick={actions.flashChineseFirmware}
disabled={isRunning}
>
- Flash Chinese (3.1.5) to current
+ Flash Chinese (3.1.7) to current
From 5d2f8472e73105b75035c0179783419bec93fc9c Mon Sep 17 00:00:00 2001
From: Jake Lyell
Date: Thu, 15 Jan 2026 09:46:10 +1100
Subject: [PATCH 4/6] Accidental styling change fix
---
src/app/page.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/app/page.tsx b/src/app/page.tsx
index a6b2ff8..657ca82 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -116,7 +116,7 @@ export default function Home() {
actions.flashCustomFirmwareToBackup(() =>
From f1c47dfddd27fee592ded4811d68763a7ae4b0d1 Mon Sep 17 00:00:00 2001
From: Jake Lyell
Date: Tue, 20 Jan 2026 21:49:41 +1100
Subject: [PATCH 5/6] minor fixes from merge mistakes
---
src/app/debug/page.tsx | 20 +++++++++++++++++++-
src/app/page.tsx | 5 +++--
2 files changed, 22 insertions(+), 3 deletions(-)
diff --git a/src/app/debug/page.tsx b/src/app/debug/page.tsx
index ff74221..03c0def 100644
--- a/src/app/debug/page.tsx
+++ b/src/app/debug/page.tsx
@@ -287,7 +287,6 @@ export default function Debug() {
const [communityFirmwareVersions, setCommunityFirmwareVersions] = useState<{
crossPoint: { version: string; releaseDate: string };
} | null>(null);
- const fullFlashFileInput = useRef(null);
useEffect(() => {
getOfficialFirmwareVersions().then((versions) =>
@@ -388,6 +387,25 @@ export default function Debug() {
>
Swap boot partitions (app0 / app1)
+ {
+ debugActions
+ .readAndIdentifyAllFirmware()
+ .then((data) =>
+ setDebugOutputNode(
+ ,
+ ),
+ );
+ }}
+ disabled={isRunning}
+ >
+ Identify firmware in both partitions
+
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 84db74f..0aa3611 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -124,7 +124,8 @@ export default function Home() {
overwrite the backup partition with the new firmware, and swap
over to using this partition (leaving your existing firmware as
the new backup). This is fast and retains all settings, with the
- option to swap back if needed.
+ option to swap back if needed. If it goes wrong, it should be fine
+ to run again.
For more advanced flashing options (like flashing to the current
@@ -177,7 +178,7 @@ export default function Home() {
}
disabled={isRunning}
>
- Flash file to backup
+ Flash firmware from file to backup
{process.env.NODE_ENV === 'development' && (
From 2e768446f384b81045ea63db81c099d42aab196b Mon Sep 17 00:00:00 2001
From: Jake Lyell
Date: Tue, 20 Jan 2026 21:52:08 +1100
Subject: [PATCH 6/6] remove unused import
---
src/app/debug/page.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/app/debug/page.tsx b/src/app/debug/page.tsx
index 03c0def..e3d17aa 100644
--- a/src/app/debug/page.tsx
+++ b/src/app/debug/page.tsx
@@ -1,6 +1,6 @@
'use client';
-import React, { ReactNode, useRef, useEffect, useState } from 'react';
+import React, { ReactNode, useEffect, useState } from 'react';
import {
Alert,
Badge,