Quellcode durchsuchen

Use esptool-js for installation (#269)

Paulus Schoutsen vor 2 Jahren
Ursprung
Commit
8c17d20aea
7 geänderte Dateien mit 257 neuen und 343 gelöschten Zeilen
  1. 130 211
      package-lock.json
  2. 4 2
      package.json
  3. 16 0
      patches/esploader.patch
  4. 0 7
      src/const.ts
  5. 98 93
      src/flash.ts
  6. 9 2
      src/install-dialog.ts
  7. 0 28
      src/util/chip-family-name.ts

Datei-Diff unterdrückt, da er zu groß ist
+ 130 - 211
package-lock.json


+ 4 - 2
package.json

@@ -7,7 +7,8 @@
   "author": "ESPHome maintainers",
   "author": "ESPHome maintainers",
   "license": "Apache-2.0",
   "license": "Apache-2.0",
   "scripts": {
   "scripts": {
-    "prepublishOnly": "script/build"
+    "prepublishOnly": "script/build",
+    "postinstall": "patch -Ntu node_modules/esptool-js/ESPLoader.js -i patches/esploader.patch || true"
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@rollup/plugin-json": "^4.1.0",
     "@rollup/plugin-json": "^4.1.0",
@@ -28,9 +29,10 @@
     "@material/mwc-formfield": "^0.26.1",
     "@material/mwc-formfield": "^0.26.1",
     "@material/mwc-icon-button": "^0.26.1",
     "@material/mwc-icon-button": "^0.26.1",
     "@material/mwc-textfield": "^0.26.1",
     "@material/mwc-textfield": "^0.26.1",
-    "esp-web-flasher": "^5.1.4",
+    "esptool-js": "github:espressif/esptool-js#0c1b972a05d691c85da23fcc937d91dcf7e283eb",
     "improv-wifi-serial-sdk": "^2.2.2",
     "improv-wifi-serial-sdk": "^2.2.2",
     "lit": "^2.0.0",
     "lit": "^2.0.0",
+    "pako": "^2.0.4",
     "tslib": "^2.3.1"
     "tslib": "^2.3.1"
   }
   }
 }
 }

+ 16 - 0
patches/esploader.patch

@@ -0,0 +1,16 @@
+--- node_modules/esptool-js/ESPLoader.js	2022-07-19 09:17:05.000000000 -0700
++++ node_modules/esptool-js/ESPLoader.fixed.js	2022-07-19 09:19:04.000000000 -0700
+@@ -1,3 +1,4 @@
++import pako from 'pako';
+ import {ESPError, TimeoutError} from "./error.js";
+ 
+ const MAGIC_TO_CHIP = {
+@@ -680,7 +681,7 @@
+ 
+         await this.run_stub();
+ 
+-        await this.change_baud();
++        // await this.change_baud();
+         return chip;
+     }
+ 

+ 0 - 7
src/const.ts

@@ -38,11 +38,6 @@ export interface InitializingState extends BaseFlashState {
   details: { done: boolean };
   details: { done: boolean };
 }
 }
 
 
-export interface ManifestState extends BaseFlashState {
-  state: FlashStateType.MANIFEST;
-  details: { done: boolean };
-}
-
 export interface PreparingState extends BaseFlashState {
 export interface PreparingState extends BaseFlashState {
   state: FlashStateType.PREPARING;
   state: FlashStateType.PREPARING;
   details: { done: boolean };
   details: { done: boolean };
@@ -69,7 +64,6 @@ export interface ErrorState extends BaseFlashState {
 
 
 export type FlashState =
 export type FlashState =
   | InitializingState
   | InitializingState
-  | ManifestState
   | PreparingState
   | PreparingState
   | ErasingState
   | ErasingState
   | WritingState
   | WritingState
@@ -78,7 +72,6 @@ export type FlashState =
 
 
 export const enum FlashStateType {
 export const enum FlashStateType {
   INITIALIZING = "initializing",
   INITIALIZING = "initializing",
-  MANIFEST = "manifest",
   PREPARING = "preparing",
   PREPARING = "preparing",
   ERASING = "erasing",
   ERASING = "erasing",
   WRITING = "writing",
   WRITING = "writing",

+ 98 - 93
src/flash.ts

@@ -1,4 +1,7 @@
-import { ESPLoader, Logger } from "esp-web-flasher";
+// @ts-ignore-next-line
+import { Transport } from "esptool-js/webserial.js";
+// @ts-ignore-next-line
+import { ESPLoader } from "esptool-js/esploader.js";
 import {
 import {
   Build,
   Build,
   FlashError,
   FlashError,
@@ -6,19 +9,28 @@ import {
   Manifest,
   Manifest,
   FlashStateType,
   FlashStateType,
 } from "./const";
 } from "./const";
-import { getChipFamilyName } from "./util/chip-family-name";
 import { sleep } from "./util/sleep";
 import { sleep } from "./util/sleep";
 
 
+const resetTransport = async (transport: Transport) => {
+  await transport.device.setSignals({
+    dataTerminalReady: false,
+    requestToSend: true,
+  });
+  await transport.device.setSignals({
+    dataTerminalReady: false,
+    requestToSend: false,
+  });
+};
+
 export const flash = async (
 export const flash = async (
   onEvent: (state: FlashState) => void,
   onEvent: (state: FlashState) => void,
   port: SerialPort,
   port: SerialPort,
-  logger: Logger,
   manifestPath: string,
   manifestPath: string,
+  manifest: Manifest,
   eraseFirst: boolean
   eraseFirst: boolean
 ) => {
 ) => {
-  let manifest: Manifest;
   let build: Build | undefined;
   let build: Build | undefined;
-  let chipFamily: ReturnType<typeof getChipFamilyName>;
+  let chipFamily: Build["chipFamily"];
 
 
   const fireStateEvent = (stateUpdate: FlashState) =>
   const fireStateEvent = (stateUpdate: FlashState) =>
     onEvent({
     onEvent({
@@ -28,12 +40,8 @@ export const flash = async (
       chipFamily,
       chipFamily,
     });
     });
 
 
-  const manifestURL = new URL(manifestPath, location.toString()).toString();
-  const manifestProm = fetch(manifestURL).then(
-    (resp): Promise<Manifest> => resp.json()
-  );
-
-  const esploader = new ESPLoader(port, logger);
+  const transport = new Transport(port);
+  const esploader = new ESPLoader(transport, 115200);
 
 
   // For debugging
   // For debugging
   (window as any).esploader = esploader;
   (window as any).esploader = esploader;
@@ -45,61 +53,53 @@ export const flash = async (
   });
   });
 
 
   try {
   try {
-    await esploader.initialize();
+    await esploader.main_fn();
+    await esploader.flash_id();
   } catch (err: any) {
   } catch (err: any) {
-    logger.error(err);
+    console.error(err);
     fireStateEvent({
     fireStateEvent({
       state: FlashStateType.ERROR,
       state: FlashStateType.ERROR,
       message:
       message:
         "Failed to initialize. Try resetting your device or holding the BOOT button while clicking INSTALL.",
         "Failed to initialize. Try resetting your device or holding the BOOT button while clicking INSTALL.",
       details: { error: FlashError.FAILED_INITIALIZING, details: err },
       details: { error: FlashError.FAILED_INITIALIZING, details: err },
     });
     });
-    if (esploader.connected) {
-      await esploader.disconnect();
-    }
+    await resetTransport(transport);
+    await transport.disconnect();
     return;
     return;
   }
   }
 
 
-  chipFamily = getChipFamilyName(esploader);
+  chipFamily = await esploader.chip.CHIP_NAME;
 
 
-  fireStateEvent({
-    state: FlashStateType.INITIALIZING,
-    message: `Initialized. Found ${chipFamily}`,
-    details: { done: true },
-  });
-  fireStateEvent({
-    state: FlashStateType.MANIFEST,
-    message: "Fetching manifest...",
-    details: { done: false },
-  });
-
-  try {
-    manifest = await manifestProm;
-  } catch (err: any) {
+  if (!esploader.chip.ROM_TEXT) {
     fireStateEvent({
     fireStateEvent({
       state: FlashStateType.ERROR,
       state: FlashStateType.ERROR,
-      message: `Unable to fetch manifest: ${err}`,
-      details: { error: FlashError.FAILED_MANIFEST_FETCH, details: err },
+      message: `Chip ${chipFamily} is not supported`,
+      details: {
+        error: FlashError.NOT_SUPPORTED,
+        details: `Chip ${chipFamily} is not supported`,
+      },
     });
     });
-    await esploader.disconnect();
+    await resetTransport(transport);
+    await transport.disconnect();
     return;
     return;
   }
   }
 
 
-  build = manifest.builds.find((b) => b.chipFamily === chipFamily);
-
   fireStateEvent({
   fireStateEvent({
-    state: FlashStateType.MANIFEST,
-    message: `Found manifest for ${manifest.name}`,
+    state: FlashStateType.INITIALIZING,
+    message: `Initialized. Found ${chipFamily}`,
     details: { done: true },
     details: { done: true },
   });
   });
 
 
+  build = manifest.builds.find((b) => b.chipFamily === chipFamily);
+
   if (!build) {
   if (!build) {
     fireStateEvent({
     fireStateEvent({
       state: FlashStateType.ERROR,
       state: FlashStateType.ERROR,
       message: `Your ${chipFamily} board is not supported.`,
       message: `Your ${chipFamily} board is not supported.`,
       details: { error: FlashError.NOT_SUPPORTED, details: chipFamily },
       details: { error: FlashError.NOT_SUPPORTED, details: chipFamily },
     });
     });
-    await esploader.disconnect();
+    await resetTransport(transport);
+    await transport.disconnect();
     return;
     return;
   }
   }
 
 
@@ -109,6 +109,7 @@ export const flash = async (
     details: { done: false },
     details: { done: false },
   });
   });
 
 
+  const manifestURL = new URL(manifestPath, location.toString()).toString();
   const filePromises = build.parts.map(async (part) => {
   const filePromises = build.parts.map(async (part) => {
     const url = new URL(part.path, manifestURL).toString();
     const url = new URL(part.path, manifestURL).toString();
     const resp = await fetch(url);
     const resp = await fetch(url);
@@ -117,20 +118,24 @@ export const flash = async (
         `Downlading firmware ${part.path} failed: ${resp.status}`
         `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 reader = new FileReader();
+    const blob = await resp.blob();
+
+    return new Promise<string>((resolve) => {
+      reader.addEventListener("load", () => resolve(reader.result as string));
+      reader.readAsBinaryString(blob);
+    });
+  });
 
 
-  const files: ArrayBuffer[] = [];
+  const fileArray: Array<{ data: string; address: number }> = [];
   let totalSize = 0;
   let totalSize = 0;
 
 
-  for (const prom of filePromises) {
+  for (let part = 0; part < filePromises.length; part++) {
     try {
     try {
-      const data = await prom;
-      files.push(data);
-      totalSize += data.byteLength;
+      const data = await filePromises[part];
+      fileArray.push({ data, address: build.parts[part].offset });
+      totalSize += data.length;
     } catch (err: any) {
     } catch (err: any) {
       fireStateEvent({
       fireStateEvent({
         state: FlashStateType.ERROR,
         state: FlashStateType.ERROR,
@@ -140,7 +145,8 @@ export const flash = async (
           details: err.message,
           details: err.message,
         },
         },
       });
       });
-      await esploader.disconnect();
+      await resetTransport(transport);
+      await transport.disconnect();
       return;
       return;
     }
     }
   }
   }
@@ -157,7 +163,7 @@ export const flash = async (
       message: "Erasing device...",
       message: "Erasing device...",
       details: { done: false },
       details: { done: false },
     });
     });
-    await espStub.eraseFlash();
+    await esploader.erase_flash();
     fireStateEvent({
     fireStateEvent({
       state: FlashStateType.ERASING,
       state: FlashStateType.ERASING,
       message: "Device erased",
       message: "Device erased",
@@ -165,56 +171,55 @@ export const flash = async (
     });
     });
   }
   }
 
 
-  let lastPct = 0;
-
   fireStateEvent({
   fireStateEvent({
     state: FlashStateType.WRITING,
     state: FlashStateType.WRITING,
-    message: `Writing progress: ${lastPct}%`,
+    message: `Writing progress: 0%`,
     details: {
     details: {
       bytesTotal: totalSize,
       bytesTotal: totalSize,
       bytesWritten: 0,
       bytesWritten: 0,
-      percentage: lastPct,
+      percentage: 0,
     },
     },
   });
   });
 
 
   let totalWritten = 0;
   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: FlashStateType.WRITING,
-            message: `Writing progress: ${newPct}%`,
-            details: {
-              bytesTotal: totalSize,
-              bytesWritten: totalWritten + bytesWritten,
-              percentage: newPct,
-            },
-          });
-        },
-        part.offset,
-        true
-      );
-    } catch (err: any) {
-      fireStateEvent({
-        state: FlashStateType.ERROR,
-        message: err.message,
-        details: { error: FlashError.WRITE_FAILED, details: err },
-      });
-      await esploader.disconnect();
-      return;
-    }
-    totalWritten += file.byteLength;
+  try {
+    await esploader.write_flash({
+      fileArray,
+      reportProgress(fileIndex: number, written: number, total: number) {
+        const uncompressedWritten =
+          (written / total) * fileArray[fileIndex].data.length;
+
+        const newPct = Math.floor(
+          ((totalWritten + uncompressedWritten) / totalSize) * 100
+        );
+
+        // we're done with this file
+        if (written === total) {
+          totalWritten += uncompressedWritten;
+          return;
+        }
+
+        fireStateEvent({
+          state: FlashStateType.WRITING,
+          message: `Writing progress: ${newPct}%`,
+          details: {
+            bytesTotal: totalSize,
+            bytesWritten: totalWritten + written,
+            percentage: newPct,
+          },
+        });
+      },
+    });
+  } catch (err: any) {
+    fireStateEvent({
+      state: FlashStateType.ERROR,
+      message: err.message,
+      details: { error: FlashError.WRITE_FAILED, details: err },
+    });
+    await resetTransport(transport);
+    await transport.disconnect();
+    return;
   }
   }
 
 
   fireStateEvent({
   fireStateEvent({
@@ -228,10 +233,10 @@ export const flash = async (
   });
   });
 
 
   await sleep(100);
   await sleep(100);
-  console.log("DISCONNECT");
-  await esploader.disconnect();
   console.log("HARD RESET");
   console.log("HARD RESET");
-  await esploader.hardReset();
+  await resetTransport(transport);
+  console.log("DISCONNECT");
+  await transport.disconnect();
 
 
   fireStateEvent({
   fireStateEvent({
     state: FlashStateType.FINISHED,
     state: FlashStateType.FINISHED,

+ 9 - 2
src/install-dialog.ts

@@ -571,7 +571,6 @@ export class EwtInstallDialog extends LitElement {
     } else if (
     } else if (
       !this._installState ||
       !this._installState ||
       this._installState.state === FlashStateType.INITIALIZING ||
       this._installState.state === FlashStateType.INITIALIZING ||
-      this._installState.state === FlashStateType.MANIFEST ||
       this._installState.state === FlashStateType.PREPARING
       this._installState.state === FlashStateType.PREPARING
     ) {
     ) {
       heading = "Installing";
       heading = "Installing";
@@ -826,19 +825,27 @@ export class EwtInstallDialog extends LitElement {
     }
     }
     this._client = undefined;
     this._client = undefined;
 
 
+    // Close port. ESPLoader likes opening it.
+    await this.port.close();
     flash(
     flash(
       (state) => {
       (state) => {
         this._installState = state;
         this._installState = state;
 
 
         if (state.state === FlashStateType.FINISHED) {
         if (state.state === FlashStateType.FINISHED) {
           sleep(100)
           sleep(100)
+            // Flashing closes the port
+            .then(() => this.port.open({ baudRate: 115200 }))
             .then(() => this._initialize(true))
             .then(() => this._initialize(true))
             .then(() => this.requestUpdate());
             .then(() => this.requestUpdate());
+        } else if (state.state === FlashStateType.ERROR) {
+          sleep(100)
+            // Flashing closes the port
+            .then(() => this.port.open({ baudRate: 115200 }));
         }
         }
       },
       },
       this.port,
       this.port,
-      this.logger,
       this.manifestPath,
       this.manifestPath,
+      this._manifest,
       this._installErase
       this._installErase
     );
     );
   }
   }

+ 0 - 28
src/util/chip-family-name.ts

@@ -1,28 +0,0 @@
-import {
-  CHIP_FAMILY_ESP32,
-  CHIP_FAMILY_ESP32S2,
-  CHIP_FAMILY_ESP32S3,
-  CHIP_FAMILY_ESP8266,
-  CHIP_FAMILY_ESP32C3,
-  ESPLoader,
-} from "esp-web-flasher";
-import type { BaseFlashState } from "../const";
-
-export const getChipFamilyName = (
-  esploader: ESPLoader
-): NonNullable<BaseFlashState["chipFamily"]> => {
-  switch (esploader.chipFamily) {
-    case CHIP_FAMILY_ESP32:
-      return "ESP32";
-    case CHIP_FAMILY_ESP8266:
-      return "ESP8266";
-    case CHIP_FAMILY_ESP32S2:
-      return "ESP32-S2";
-    case CHIP_FAMILY_ESP32S3:
-      return "ESP32-S3";
-    case CHIP_FAMILY_ESP32C3:
-      return "ESP32-C3";
-    default:
-      return "Unknown Chip";
-  }
-};

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.