123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- import { connect, ESPLoader, Logger } from "esp-web-flasher";
- import { Build, FlashError, FlashState, Manifest, State } from "./const";
- import { fireEvent, getChipFamilyName, sleep } from "./util";
- export const flash = async (
- eventTarget: EventTarget,
- logger: Logger,
- manifestPath: string,
- eraseFirst: boolean
- ) => {
- let manifest: Manifest;
- let build: Build | undefined;
- let chipFamily: ReturnType<typeof getChipFamilyName>;
- const fireStateEvent = (stateUpdate: FlashState) => {
- fireEvent(eventTarget, "state-changed", {
- ...stateUpdate,
- manifest,
- build,
- chipFamily,
- });
- };
- const manifestURL = new URL(manifestPath, location.toString()).toString();
- const manifestProm = fetch(manifestURL).then(
- (resp): Promise<Manifest> => resp.json()
- );
- let esploader: ESPLoader | undefined;
- try {
- esploader = await connect(logger);
- } catch (err) {
- // User pressed cancel on web serial
- return;
- }
- // For debugging
- (window as any).esploader = esploader;
- fireStateEvent({
- state: State.INITIALIZING,
- message: "Initializing...",
- details: { done: false },
- });
- try {
- await esploader.initialize();
- } catch (err) {
- logger.error(err);
- if (esploader.connected) {
- fireStateEvent({
- state: State.ERROR,
- message:
- "Failed to initialize. Try resetting your device or holding the BOOT button while selecting your serial port.",
- details: { error: FlashError.FAILED_INITIALIZING, details: err },
- });
- await esploader.disconnect();
- }
- return;
- }
- chipFamily = getChipFamilyName(esploader);
- fireStateEvent({
- state: State.INITIALIZING,
- message: `Initialized. Found ${chipFamily}`,
- details: { done: true },
- });
- fireStateEvent({
- state: State.MANIFEST,
- message: "Fetching manifest...",
- details: { done: false },
- });
- try {
- manifest = await manifestProm;
- } catch (err) {
- fireStateEvent({
- state: State.ERROR,
- message: `Unable to fetch manifest: ${err.message}`,
- details: { error: FlashError.FAILED_MANIFEST_FETCH, details: err },
- });
- await esploader.disconnect();
- return;
- }
- build = manifest.builds.find((b) => b.chipFamily === chipFamily);
- fireStateEvent({
- state: State.MANIFEST,
- message: `Found manifest for ${manifest.name}`,
- details: { done: true },
- });
- if (!build) {
- fireStateEvent({
- state: State.ERROR,
- message: `Your ${chipFamily} board is not supported.`,
- details: { error: FlashError.NOT_SUPPORTED, details: chipFamily },
- });
- await esploader.disconnect();
- return;
- }
- fireStateEvent({
- state: State.PREPARING,
- message: "Preparing installation...",
- details: { done: false },
- });
- const filePromises = build.parts.map(async (part) => {
- const url = new URL(part.path, manifestURL).toString();
- const resp = await fetch(url);
- if (!resp.ok) {
- throw new Error(
- `Downlading firmware ${part.path} failed: ${resp.status}`
- );
- }
- return resp.arrayBuffer();
- });
- // Run the stub while we wait for files to download
- const espStub = await esploader.runStub();
- const files: ArrayBuffer[] = [];
- let totalSize = 0;
- for (const prom of filePromises) {
- try {
- const data = await prom;
- files.push(data);
- totalSize += data.byteLength;
- } catch (err) {
- fireStateEvent({
- state: State.ERROR,
- message: err,
- details: { error: FlashError.FAILED_FIRMWARE_DOWNLOAD, details: err },
- });
- await esploader.disconnect();
- return;
- }
- }
- fireStateEvent({
- state: State.PREPARING,
- message: "Installation prepared",
- details: { done: true },
- });
- if (eraseFirst) {
- fireStateEvent({
- state: State.ERASING,
- message: "Erasing device...",
- details: { done: false },
- });
- await espStub.eraseFlash();
- fireStateEvent({
- state: State.ERASING,
- message: "Device erased",
- details: { done: true },
- });
- }
- let lastPct = 0;
- fireStateEvent({
- state: State.WRITING,
- message: `Writing progress: ${lastPct}%`,
- details: {
- bytesTotal: totalSize,
- bytesWritten: 0,
- percentage: lastPct,
- },
- });
- let totalWritten = 0;
- for (const part of build.parts) {
- const file = files.shift()!;
- try {
- await espStub.flashData(
- file,
- (bytesWritten: number) => {
- const newPct = Math.floor(
- ((totalWritten + bytesWritten) / totalSize) * 100
- );
- if (newPct === lastPct) {
- return;
- }
- lastPct = newPct;
- fireStateEvent({
- state: State.WRITING,
- message: `Writing progress: ${newPct}%`,
- details: {
- bytesTotal: totalSize,
- bytesWritten: totalWritten + bytesWritten,
- percentage: newPct,
- },
- });
- },
- part.offset,
- true
- );
- } catch (err) {
- fireStateEvent({
- state: State.ERROR,
- message: err,
- details: { error: FlashError.WRITE_FAILED, details: err },
- });
- await esploader.disconnect();
- return;
- }
- totalWritten += file.byteLength;
- }
- fireStateEvent({
- state: State.WRITING,
- message: "Writing complete",
- details: {
- bytesTotal: totalSize,
- bytesWritten: totalWritten,
- percentage: 100,
- },
- });
- await sleep(100);
- await esploader.hardReset();
- await esploader.disconnect();
- fireStateEvent({
- state: State.FINISHED,
- message: "All done!",
- });
- };
|