diff --git a/src/app/debug/page.tsx b/src/app/debug/page.tsx
index aa0bab2..e3d17aa 100644
--- a/src/app/debug/page.tsx
+++ b/src/app/debug/page.tsx
@@ -1,6 +1,6 @@
'use client';
-import React, { ReactNode, useState } from 'react';
+import React, { ReactNode, useEffect, useState } from 'react';
import {
Alert,
Badge,
@@ -27,6 +27,11 @@ import HexSpan from '@/components/HexSpan';
import HexViewer from '@/components/HexViewer';
import { downloadData } from '@/utils/download';
import { FirmwareInfo } from '@/utils/firmwareIdentifier';
+import FileUpload, { FileUploadHandle } from '@/components/FileUpload';
+import {
+ getOfficialFirmwareVersions,
+ getCommunityFirmwareRemoteData,
+} from '@/remote/firmwareFetcher';
function OtadataDebug({ otaPartition }: { otaPartition: OtaPartition }) {
const bootPartitionLabel = otaPartition.getCurrentBootPartitionLabel();
@@ -272,8 +277,24 @@ function FirmwareIdentificationDebug({
}
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);
+ const [officialFirmwareVersions, setOfficialFirmwareVersions] = useState<{
+ en: string;
+ ch: string;
+ } | null>(null);
+ const [communityFirmwareVersions, setCommunityFirmwareVersions] = useState<{
+ crossPoint: { version: string; releaseDate: string };
+ } | null>(null);
+
+ useEffect(() => {
+ getOfficialFirmwareVersions().then((versions) =>
+ setOfficialFirmwareVersions(versions),
+ );
+
+ getCommunityFirmwareRemoteData().then(setCommunityFirmwareVersions);
+ }, []);
return (
@@ -388,6 +409,86 @@ 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 the backup
+ partition unchanged. Proceed with caution.
+
+
+
+
+
+
+
+ Flash English firmware ({officialFirmwareVersions?.en ?? '...'})
+ to current
+
+
+ Flash Chinese firmware ({officialFirmwareVersions?.ch ?? '...'})
+ to current
+
+
+ Flash CrossPoint firmware (
+ {communityFirmwareVersions?.crossPoint.version}) -{' '}
+ {communityFirmwareVersions?.crossPoint.releaseDate} 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 bedc09c..0aa3611 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -120,42 +120,50 @@ export default function Home() {
device using Save full flash above.
- Flash English/Chinese firmware will download the firmware,
+ 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 fast and retains all settings, with the
+ 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
+ partition), see the Debug page.
-
- Flash English firmware ({officialFirmwareVersions?.en ?? '...'})
-
-
- Flash Chinese firmware ({officialFirmwareVersions?.ch ?? '...'})
-
-
- Flash CrossPoint firmware (
- {communityFirmwareVersions?.crossPoint.version}) -{' '}
- {communityFirmwareVersions?.crossPoint.releaseDate}
-
+
+
+ Flash English firmware ({officialFirmwareVersions?.en ?? '...'})
+ to backup
+
+
+ Flash Chinese firmware ({officialFirmwareVersions?.ch ?? '...'})
+ to backup
+
+
+ Flash CrossPoint firmware (
+ {communityFirmwareVersions?.crossPoint.version}) -{' '}
+ {communityFirmwareVersions?.crossPoint.releaseDate} to backup
+
+
@@ -164,13 +172,13 @@ export default function Home() {
variant="subtle"
flexGrow={1}
onClick={() =>
- actions.flashCustomFirmware(() =>
+ actions.flashCustomFirmwareToBackup(() =>
appPartitionFileInput.current?.getFile(),
)
}
disabled={isRunning}
>
- Flash firmware from file
+ Flash firmware from file to backup
{process.env.NODE_ENV === 'development' && (
diff --git a/src/esp/useEspOperations.ts b/src/esp/useEspOperations.ts
index eda15bf..ae8b36d 100644
--- a/src/esp/useEspOperations.ts
+++ b/src/esp/useEspOperations.ts
@@ -30,6 +30,54 @@ 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 flashRemoteFirmwareToBackup = async (
+ getFirmware: () => Promise,
) => {
initializeSteps([
'Connect to device',
@@ -89,6 +137,13 @@ export function useEspOperations() {
await runStep('Reset device', () => espController.disconnect());
};
+ const flashEnglishFirmwareToBackup = async () =>
+ flashRemoteFirmwareToBackup(() => getOfficialFirmware('en'));
+ const flashChineseFirmwareToBackup = async () =>
+ flashRemoteFirmwareToBackup(() => getOfficialFirmware('ch'));
+ const flashCrossPointFirmwareToBackup = async () =>
+ flashRemoteFirmwareToBackup(() => getCommunityFirmware('CrossPoint'));
+
const flashEnglishFirmware = async () =>
flashRemoteFirmware(() => getOfficialFirmware('en'));
const flashChineseFirmware = async () =>
@@ -97,6 +152,60 @@ export function useEspOperations() {
flashRemoteFirmware(() => 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',
@@ -482,6 +591,16 @@ 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),