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. + + + +
+ + + + + + + + + + + + + +
+ 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.

- - - + + + + + @@ -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),