esp32tool 1.1.2 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/esp_loader.ts CHANGED
@@ -166,8 +166,6 @@ export class ESPLoader extends EventTarget {
166
166
  }
167
167
 
168
168
  async initialize() {
169
- await this.hardReset(true);
170
-
171
169
  if (!this._parent) {
172
170
  this.__inputBuffer = [];
173
171
  this.__totalBytesRead = 0;
@@ -196,13 +194,8 @@ export class ESPLoader extends EventTarget {
196
194
  this.readLoop();
197
195
  }
198
196
 
199
- // Drain input buffer first for CP210x compatibility on Windows
200
- // This helps clear any stale data before sync
201
- await this.drainInputBuffer(200);
202
-
203
- // Clear buffer again after starting read loop
204
- await this.flushSerialBuffers();
205
- await this.sync();
197
+ // Try to connect with different reset strategies
198
+ await this.connectWithResetStrategies();
206
199
 
207
200
  // Detect chip type
208
201
  await this.detectChip();
@@ -464,37 +457,11 @@ export class ESPLoader extends EventTarget {
464
457
  if (bootloader) {
465
458
  // enter flash mode
466
459
  if (this.port.getInfo().usbProductId === USB_JTAG_SERIAL_PID) {
467
- // esp32c3 esp32s3 etc. build-in USB serial.
468
- // when connect to computer direct via usb, using following signals
469
- // to enter flash mode automatically.
470
- await this.setDTR(false);
471
- await this.setRTS(false);
472
- await this.sleep(100);
473
-
474
- await this.setDTR(true);
475
- await this.setRTS(false);
476
- await this.sleep(100);
477
-
478
- await this.setRTS(true);
479
- await this.setDTR(false);
480
- await this.setRTS(true);
481
-
482
- await this.sleep(100);
483
- await this.setDTR(false);
484
- await this.setRTS(false);
485
- this.logger.log("USB MCU reset.");
460
+ await this.hardResetUSBJTAGSerial();
461
+ this.logger.log("USB-JTAG/Serial reset.");
486
462
  } else {
487
- // otherwise, esp chip should be connected to computer via usb-serial
488
- // bridge chip like ch340,CP2102 etc.
489
- // use normal way to enter flash mode.
490
- await this.setDTR(false);
491
- await this.setRTS(true);
492
- await this.sleep(100);
493
- await this.setDTR(true);
494
- await this.setRTS(false);
495
- await this.sleep(50);
496
- await this.setDTR(false);
497
- this.logger.log("DTR/RTS USB serial chip reset.");
463
+ await this.hardResetClassic();
464
+ this.logger.log("Classic reset.");
498
465
  }
499
466
  } else {
500
467
  // just reset
@@ -879,6 +846,135 @@ export class ESPLoader extends EventTarget {
879
846
  }
880
847
  }
881
848
 
849
+ /**
850
+ * @name connectWithResetStrategies
851
+ * Try different reset strategies to enter bootloader mode
852
+ * Similar to esptool.py's connect() method with multiple reset strategies
853
+ */
854
+ async connectWithResetStrategies() {
855
+ const portInfo = this.port.getInfo();
856
+ const isUSBJTAGSerial = portInfo.usbProductId === USB_JTAG_SERIAL_PID;
857
+ const isEspressifUSB = portInfo.usbVendorId === 0x303a;
858
+
859
+ this.logger.log(
860
+ `Detected USB: VID=0x${portInfo.usbVendorId?.toString(16) || "unknown"}, PID=0x${portInfo.usbProductId?.toString(16) || "unknown"}`,
861
+ );
862
+
863
+ // Define reset strategies to try in order
864
+ const resetStrategies: Array<{ name: string; fn: () => Promise<void> }> =
865
+ [];
866
+
867
+ // Strategy 1: USB-JTAG/Serial reset (for ESP32-C3, C6, S3, etc.)
868
+ // Try this first if we detect Espressif USB VID or the specific PID
869
+ if (isUSBJTAGSerial || isEspressifUSB) {
870
+ resetStrategies.push({
871
+ name: "USB-JTAG/Serial",
872
+ fn: async () => await this.hardResetUSBJTAGSerial(),
873
+ });
874
+ }
875
+
876
+ // Strategy 2: Classic reset (for USB-to-Serial bridges)
877
+ resetStrategies.push({
878
+ name: "Classic",
879
+ fn: async () => await this.hardResetClassic(),
880
+ });
881
+
882
+ // Strategy 3: If USB-JTAG/Serial was not tried yet, try it as fallback
883
+ if (!isUSBJTAGSerial && !isEspressifUSB) {
884
+ resetStrategies.push({
885
+ name: "USB-JTAG/Serial (fallback)",
886
+ fn: async () => await this.hardResetUSBJTAGSerial(),
887
+ });
888
+ }
889
+
890
+ let lastError: Error | null = null;
891
+
892
+ // Try each reset strategy
893
+ for (const strategy of resetStrategies) {
894
+ try {
895
+ this.logger.log(`Trying ${strategy.name} reset...`);
896
+
897
+ // Check if port is still open, if not, skip this strategy
898
+ if (!this.connected || !this.port.writable) {
899
+ this.logger.log(`Port disconnected, skipping ${strategy.name} reset`);
900
+ continue;
901
+ }
902
+
903
+ await strategy.fn();
904
+
905
+ // Try to sync after reset
906
+ await this.sync();
907
+
908
+ // If we get here, sync succeeded
909
+ this.logger.log(`Connected successfully with ${strategy.name} reset.`);
910
+ return;
911
+ } catch (error) {
912
+ lastError = error as Error;
913
+ this.logger.log(
914
+ `${strategy.name} reset failed: ${(error as Error).message}`,
915
+ );
916
+
917
+ // If port got disconnected, we can't try more strategies
918
+ if (!this.connected || !this.port.writable) {
919
+ this.logger.log(`Port disconnected during reset attempt`);
920
+ break;
921
+ }
922
+
923
+ // Clear buffers before trying next strategy
924
+ this._inputBuffer.length = 0;
925
+ await this.drainInputBuffer(200);
926
+ await this.flushSerialBuffers();
927
+ }
928
+ }
929
+
930
+ // All strategies failed
931
+ throw new Error(
932
+ `Couldn't sync to ESP. Try resetting manually. Last error: ${lastError?.message}`,
933
+ );
934
+ }
935
+
936
+ /**
937
+ * @name hardResetUSBJTAGSerial
938
+ * USB-JTAG/Serial reset sequence for ESP32-C3, ESP32-S3, ESP32-C6, etc.
939
+ */
940
+ async hardResetUSBJTAGSerial() {
941
+ await this.setRTS(false);
942
+ await this.setDTR(false); // Idle
943
+ await this.sleep(100);
944
+
945
+ await this.setDTR(true); // Set IO0
946
+ await this.setRTS(false);
947
+ await this.sleep(100);
948
+
949
+ await this.setRTS(true); // Reset. Calls inverted to go through (1,1) instead of (0,0)
950
+ await this.setDTR(false);
951
+ await this.setRTS(true); // RTS set as Windows only propagates DTR on RTS setting
952
+ await this.sleep(100);
953
+
954
+ await this.setDTR(false);
955
+ await this.setRTS(false); // Chip out of reset
956
+
957
+ // Wait for chip to boot into bootloader
958
+ await this.sleep(200);
959
+ }
960
+
961
+ /**
962
+ * @name hardResetClassic
963
+ * Classic reset sequence for USB-to-Serial bridge chips (CH340, CP2102, etc.)
964
+ */
965
+ async hardResetClassic() {
966
+ await this.setDTR(false); // IO0=HIGH
967
+ await this.setRTS(true); // EN=LOW, chip in reset
968
+ await this.sleep(100);
969
+ await this.setDTR(true); // IO0=LOW
970
+ await this.setRTS(false); // EN=HIGH, chip out of reset
971
+ await this.sleep(50);
972
+ await this.setDTR(false); // IO0=HIGH, done
973
+
974
+ // Wait for chip to boot into bootloader
975
+ await this.sleep(200);
976
+ }
977
+
882
978
  /**
883
979
  * @name sync
884
980
  * Put into ROM bootload mode & attempt to synchronize with the
@@ -0,0 +1,53 @@
1
+ const path = require('node:path');
2
+ const { PluginBase, namedHookWithTaskFn } = require('@electron-forge/plugin-base');
3
+
4
+ function getElectronExecutablePath({ appName, basePath, platform }) {
5
+ if (['darwin', 'mas'].includes(platform)) {
6
+ return path.join(basePath, 'MacOS', appName);
7
+ }
8
+
9
+ const suffix = platform === 'win32' ? '.exe' : '';
10
+ return path.join(basePath, `${appName}${suffix}`);
11
+ }
12
+
13
+ class FusesPlugin extends PluginBase {
14
+ constructor(fusesConfig) {
15
+ super(fusesConfig);
16
+ this.name = 'fuses';
17
+ this.fusesConfig = fusesConfig ?? {};
18
+ }
19
+
20
+ getHooks() {
21
+ return {
22
+ packageAfterCopy: namedHookWithTaskFn(
23
+ async (listrTask, resolvedForgeConfig, resourcesPath, electronVersion, platform, arch) => {
24
+ if (!Object.keys(this.fusesConfig).length) {
25
+ return;
26
+ }
27
+
28
+ const applePlatforms = ['darwin', 'mas'];
29
+ const pathToElectronExecutable = getElectronExecutablePath({
30
+ appName: applePlatforms.includes(platform) ? 'Electron' : 'electron',
31
+ basePath: path.resolve(resourcesPath, '../..'),
32
+ platform,
33
+ });
34
+
35
+ const osxSignConfig = resolvedForgeConfig.packagerConfig.osxSign;
36
+ const hasOSXSignConfig =
37
+ (typeof osxSignConfig === 'object' && Boolean(Object.keys(osxSignConfig).length)) ||
38
+ Boolean(osxSignConfig);
39
+
40
+ const { flipFuses } = await import('@electron/fuses');
41
+ await flipFuses(pathToElectronExecutable, {
42
+ resetAdHocDarwinSignature:
43
+ !hasOSXSignConfig && applePlatforms.includes(platform) && arch === 'arm64',
44
+ ...this.fusesConfig,
45
+ });
46
+ },
47
+ 'Flipping Fuses'
48
+ ),
49
+ };
50
+ }
51
+ }
52
+
53
+ module.exports = { FusesPlugin };