tasmota-webserial-esptool 9.1.7 → 9.1.9

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
@@ -101,8 +101,7 @@ export class ESPLoader extends EventTarget {
101
101
  private __isReconfiguring: boolean = false;
102
102
  private __abandonCurrentOperation: boolean = false;
103
103
 
104
- // Adaptive speed adjustment for flash read operations - DISABLED
105
- // Using fixed conservative values that work reliably
104
+ // Adaptive speed adjustment for flash read operations
106
105
  private __adaptiveBlockMultiplier: number = 1;
107
106
  private __adaptiveMaxInFlightMultiplier: number = 1;
108
107
  private __consecutiveSuccessfulChunks: number = 0;
@@ -118,6 +117,7 @@ export class ESPLoader extends EventTarget {
118
117
  }
119
118
 
120
119
  // Chip properties with parent delegation
120
+ // chipFamily accessed before initialization as designed
121
121
  get chipFamily(): ChipFamily {
122
122
  return this._parent ? this._parent.chipFamily : this.__chipFamily!;
123
123
  }
@@ -167,7 +167,13 @@ export class ESPLoader extends EventTarget {
167
167
  }
168
168
 
169
169
  private get _inputBuffer(): number[] {
170
- return this._parent ? this._parent._inputBuffer : this.__inputBuffer!;
170
+ if (this._parent) {
171
+ return this._parent._inputBuffer;
172
+ }
173
+ if (this.__inputBuffer === undefined) {
174
+ throw new Error("_inputBuffer accessed before initialization");
175
+ }
176
+ return this.__inputBuffer;
171
177
  }
172
178
 
173
179
  private get _inputBufferReadIndex(): number {
@@ -695,6 +701,7 @@ export class ESPLoader extends EventTarget {
695
701
  }
696
702
 
697
703
  state_DTR = false;
704
+ state_RTS = false;
698
705
 
699
706
  // ============================================================================
700
707
  // Web Serial (Desktop) - DTR/RTS Signal Handling & Reset Strategies
@@ -759,6 +766,7 @@ export class ESPLoader extends EventTarget {
759
766
  // ============================================================================
760
767
 
761
768
  async setRTSWebUSB(state: boolean) {
769
+ this.state_RTS = state;
762
770
  // Always specify both signals to avoid flipping the other line
763
771
  // The WebUSB setSignals() now preserves unspecified signals, but being explicit is safer
764
772
  await (this.port as WebUSBSerialPort).setSignals({
@@ -772,12 +780,13 @@ export class ESPLoader extends EventTarget {
772
780
  // Always specify both signals to avoid flipping the other line
773
781
  await (this.port as WebUSBSerialPort).setSignals({
774
782
  dataTerminalReady: state,
775
- requestToSend: undefined, // Let setSignals preserve current RTS state
783
+ requestToSend: this.state_RTS, // Explicitly preserve current RTS state
776
784
  });
777
785
  }
778
786
 
779
787
  async setDTRandRTSWebUSB(dtr: boolean, rts: boolean) {
780
788
  this.state_DTR = dtr;
789
+ this.state_RTS = rts;
781
790
  await (this.port as WebUSBSerialPort).setSignals({
782
791
  dataTerminalReady: dtr,
783
792
  requestToSend: rts,
@@ -990,7 +999,7 @@ export class ESPLoader extends EventTarget {
990
999
  // Strategy 1: USB-JTAG/Serial (works in CDC mode on Desktop)
991
1000
  resetStrategies.push({
992
1001
  name: "USB-JTAG/Serial (WebUSB) - ESP32-S2",
993
- fn: async function () {
1002
+ fn: async () => {
994
1003
  return await self.hardResetUSBJTAGSerialWebUSB();
995
1004
  },
996
1005
  });
@@ -998,7 +1007,7 @@ export class ESPLoader extends EventTarget {
998
1007
  // Strategy 2: USB-JTAG/Serial Inverted DTR (works in JTAG mode)
999
1008
  resetStrategies.push({
1000
1009
  name: "USB-JTAG/Serial Inverted DTR (WebUSB) - ESP32-S2",
1001
- fn: async function () {
1010
+ fn: async () => {
1002
1011
  return await self.hardResetUSBJTAGSerialInvertedDTRWebUSB();
1003
1012
  },
1004
1013
  });
@@ -1006,7 +1015,7 @@ export class ESPLoader extends EventTarget {
1006
1015
  // Strategy 3: UnixTight (CDC fallback)
1007
1016
  resetStrategies.push({
1008
1017
  name: "UnixTight (WebUSB) - ESP32-S2 CDC",
1009
- fn: async function () {
1018
+ fn: async () => {
1010
1019
  return await self.hardResetUnixTightWebUSB();
1011
1020
  },
1012
1021
  });
@@ -1014,7 +1023,7 @@ export class ESPLoader extends EventTarget {
1014
1023
  // Strategy 4: Classic reset (CDC fallback)
1015
1024
  resetStrategies.push({
1016
1025
  name: "Classic (WebUSB) - ESP32-S2 CDC",
1017
- fn: async function () {
1026
+ fn: async () => {
1018
1027
  return await self.hardResetClassicWebUSB();
1019
1028
  },
1020
1029
  });
@@ -1022,19 +1031,19 @@ export class ESPLoader extends EventTarget {
1022
1031
  // Other USB-JTAG chips: Try Inverted DTR first - works best for ESP32-H2 and other JTAG chips
1023
1032
  resetStrategies.push({
1024
1033
  name: "USB-JTAG/Serial Inverted DTR (WebUSB)",
1025
- fn: async function () {
1034
+ fn: async () => {
1026
1035
  return await self.hardResetUSBJTAGSerialInvertedDTRWebUSB();
1027
1036
  },
1028
1037
  });
1029
1038
  resetStrategies.push({
1030
1039
  name: "USB-JTAG/Serial (WebUSB)",
1031
- fn: async function () {
1040
+ fn: async () => {
1032
1041
  return await self.hardResetUSBJTAGSerialWebUSB();
1033
1042
  },
1034
1043
  });
1035
1044
  resetStrategies.push({
1036
1045
  name: "Inverted DTR Classic (WebUSB)",
1037
- fn: async function () {
1046
+ fn: async () => {
1038
1047
  return await self.hardResetInvertedDTRWebUSB();
1039
1048
  },
1040
1049
  });
@@ -1047,31 +1056,31 @@ export class ESPLoader extends EventTarget {
1047
1056
  // CH340/CH343: UnixTight works best (like CP2102)
1048
1057
  resetStrategies.push({
1049
1058
  name: "UnixTight (WebUSB) - CH34x",
1050
- fn: async function () {
1059
+ fn: async () => {
1051
1060
  return await self.hardResetUnixTightWebUSB();
1052
1061
  },
1053
1062
  });
1054
1063
  resetStrategies.push({
1055
1064
  name: "Classic (WebUSB) - CH34x",
1056
- fn: async function () {
1065
+ fn: async () => {
1057
1066
  return await self.hardResetClassicWebUSB();
1058
1067
  },
1059
1068
  });
1060
1069
  resetStrategies.push({
1061
1070
  name: "Inverted Both (WebUSB) - CH34x",
1062
- fn: async function () {
1071
+ fn: async () => {
1063
1072
  return await self.hardResetInvertedWebUSB();
1064
1073
  },
1065
1074
  });
1066
1075
  resetStrategies.push({
1067
1076
  name: "Inverted RTS (WebUSB) - CH34x",
1068
- fn: async function () {
1077
+ fn: async () => {
1069
1078
  return await self.hardResetInvertedRTSWebUSB();
1070
1079
  },
1071
1080
  });
1072
1081
  resetStrategies.push({
1073
1082
  name: "Inverted DTR (WebUSB) - CH34x",
1074
- fn: async function () {
1083
+ fn: async () => {
1075
1084
  return await self.hardResetInvertedDTRWebUSB();
1076
1085
  },
1077
1086
  });
@@ -1081,35 +1090,35 @@ export class ESPLoader extends EventTarget {
1081
1090
 
1082
1091
  resetStrategies.push({
1083
1092
  name: "UnixTight (WebUSB) - CP2102",
1084
- fn: async function () {
1093
+ fn: async () => {
1085
1094
  return await self.hardResetUnixTightWebUSB();
1086
1095
  },
1087
1096
  });
1088
1097
 
1089
1098
  resetStrategies.push({
1090
1099
  name: "Classic (WebUSB) - CP2102",
1091
- fn: async function () {
1100
+ fn: async () => {
1092
1101
  return await self.hardResetClassicWebUSB();
1093
1102
  },
1094
1103
  });
1095
1104
 
1096
1105
  resetStrategies.push({
1097
1106
  name: "Inverted Both (WebUSB) - CP2102",
1098
- fn: async function () {
1107
+ fn: async () => {
1099
1108
  return await self.hardResetInvertedWebUSB();
1100
1109
  },
1101
1110
  });
1102
1111
 
1103
1112
  resetStrategies.push({
1104
1113
  name: "Inverted RTS (WebUSB) - CP2102",
1105
- fn: async function () {
1114
+ fn: async () => {
1106
1115
  return await self.hardResetInvertedRTSWebUSB();
1107
1116
  },
1108
1117
  });
1109
1118
 
1110
1119
  resetStrategies.push({
1111
1120
  name: "Inverted DTR (WebUSB) - CP2102",
1112
- fn: async function () {
1121
+ fn: async () => {
1113
1122
  return await self.hardResetInvertedDTRWebUSB();
1114
1123
  },
1115
1124
  });
@@ -1117,7 +1126,7 @@ export class ESPLoader extends EventTarget {
1117
1126
  // For other USB-Serial chips, try UnixTight first, then multiple strategies
1118
1127
  resetStrategies.push({
1119
1128
  name: "UnixTight (WebUSB)",
1120
- fn: async function () {
1129
+ fn: async () => {
1121
1130
  return await self.hardResetUnixTightWebUSB();
1122
1131
  },
1123
1132
  });
@@ -1195,7 +1204,6 @@ export class ESPLoader extends EventTarget {
1195
1204
  }
1196
1205
  }
1197
1206
  } else {
1198
- // Web Serial (Desktop) strategies
1199
1207
  // Strategy: USB-JTAG/Serial reset
1200
1208
  if (isUSBJTAGSerial || isEspressifUSB) {
1201
1209
  resetStrategies.push({
@@ -1306,9 +1314,9 @@ export class ESPLoader extends EventTarget {
1306
1314
  // just reset (no bootloader mode)
1307
1315
  if (this.isWebUSB()) {
1308
1316
  // WebUSB: Use longer delays for better compatibility
1309
- await this.setRTS(true); // EN->LOW
1317
+ await this.setRTSWebUSB(true); // EN->LOW
1310
1318
  await this.sleep(200);
1311
- await this.setRTS(false);
1319
+ await this.setRTSWebUSB(false);
1312
1320
  await this.sleep(200);
1313
1321
  this.logger.log("Hard reset (WebUSB).");
1314
1322
  } else {
@@ -1550,6 +1558,13 @@ export class ESPLoader extends EventTarget {
1550
1558
  // Process all available bytes without going back to outer loop
1551
1559
  // This is critical for handling high-speed burst transfers
1552
1560
  while (this._inputBufferAvailable > 0) {
1561
+ // Periodic timeout check to prevent hang on slow data
1562
+ if (Date.now() - startTime > timeout) {
1563
+ const waitingFor = partialPacket === null ? "header" : "content";
1564
+ throw new SlipReadError(
1565
+ "Timed out waiting for packet " + waitingFor,
1566
+ );
1567
+ }
1553
1568
  const b = this._readByte()!;
1554
1569
 
1555
1570
  if (partialPacket === null) {
@@ -2665,7 +2680,21 @@ export class ESPLoader extends EventTarget {
2665
2680
  resolve(undefined);
2666
2681
  return;
2667
2682
  }
2668
- this.addEventListener("disconnect", resolve, { once: true });
2683
+
2684
+ // Set a timeout to prevent hanging (important for node-usb)
2685
+ const timeout = setTimeout(() => {
2686
+ this.logger.debug("Disconnect timeout - forcing resolution");
2687
+ resolve(undefined);
2688
+ }, 1000);
2689
+
2690
+ this.addEventListener(
2691
+ "disconnect",
2692
+ () => {
2693
+ clearTimeout(timeout);
2694
+ resolve(undefined);
2695
+ },
2696
+ { once: true },
2697
+ );
2669
2698
 
2670
2699
  // Only cancel if reader is still active
2671
2700
  try {
@@ -2673,10 +2702,19 @@ export class ESPLoader extends EventTarget {
2673
2702
  } catch (err) {
2674
2703
  this.logger.debug(`Reader cancel error: ${err}`);
2675
2704
  // Reader already released, resolve immediately
2705
+ clearTimeout(timeout);
2676
2706
  resolve(undefined);
2677
2707
  }
2678
2708
  });
2679
2709
  this.connected = false;
2710
+
2711
+ // Close the port (important for node-usb adapter)
2712
+ try {
2713
+ await this.port.close();
2714
+ this.logger.debug("Port closed successfully");
2715
+ } catch (err) {
2716
+ this.logger.debug(`Port close error: ${err}`);
2717
+ }
2680
2718
  }
2681
2719
 
2682
2720
  /**
@@ -3346,6 +3384,12 @@ class EspStubLoader extends ESPLoader {
3346
3384
  if (size > maxValue) {
3347
3385
  throw new Error(`Size ${size} exceeds maximum value ${maxValue}`);
3348
3386
  }
3387
+ // Check for wrap-around
3388
+ if (offset + size > maxValue) {
3389
+ throw new Error(
3390
+ `Region end (offset + size = ${offset + size}) exceeds maximum addressable range ${maxValue}`,
3391
+ );
3392
+ }
3349
3393
 
3350
3394
  const timeout = timeoutPerMb(ERASE_REGION_TIMEOUT_PER_MB, size);
3351
3395
  const buffer = pack("<II", offset, size);
package/src/index.ts CHANGED
@@ -21,39 +21,68 @@ export {
21
21
  CHIP_FAMILY_ESP32H21,
22
22
  CHIP_FAMILY_ESP32P4,
23
23
  CHIP_FAMILY_ESP32S31,
24
+ // Command constants
25
+ ESP_FLASH_BEGIN,
26
+ ESP_FLASH_DATA,
27
+ ESP_FLASH_END,
28
+ ESP_MEM_BEGIN,
29
+ ESP_MEM_END,
30
+ ESP_MEM_DATA,
31
+ ESP_SYNC,
32
+ ESP_WRITE_REG,
33
+ ESP_READ_REG,
34
+ ESP_ERASE_FLASH,
35
+ ESP_ERASE_REGION,
36
+ ESP_READ_FLASH,
37
+ ESP_SPI_SET_PARAMS,
38
+ ESP_SPI_ATTACH,
39
+ ESP_CHANGE_BAUDRATE,
40
+ ESP_SPI_FLASH_MD5,
41
+ ESP_GET_SECURITY_INFO,
42
+ ESP_CHECKSUM_MAGIC,
43
+ ESP_FLASH_DEFL_BEGIN,
44
+ ESP_FLASH_DEFL_DATA,
45
+ ESP_FLASH_DEFL_END,
46
+ ROM_INVALID_RECV_MSG,
47
+ // Block size constants
48
+ USB_RAM_BLOCK,
49
+ ESP_RAM_BLOCK,
50
+ // Timeout constants
51
+ DEFAULT_TIMEOUT,
52
+ CHIP_ERASE_TIMEOUT,
53
+ MAX_TIMEOUT,
54
+ SYNC_TIMEOUT,
55
+ ERASE_REGION_TIMEOUT_PER_MB,
56
+ MEM_END_ROM_TIMEOUT,
57
+ FLASH_READ_TIMEOUT,
24
58
  } from "./const";
25
59
 
26
60
  export const connect = async (logger: Logger) => {
61
+ // - Request a port and open a connection.
62
+ // Try to use requestSerialPort if available (supports WebUSB for Android)
27
63
  let port: SerialPort;
28
-
29
- // Check if a custom requestSerialPort function is available (e.g., from WebUSB wrapper)
30
64
  const customRequestPort = (
31
65
  globalThis as { requestSerialPort?: () => Promise<SerialPort> }
32
66
  ).requestSerialPort;
33
-
34
67
  if (typeof customRequestPort === "function") {
35
- // Use custom port request function (handles Android/WebUSB automatically)
36
- logger.log("Using custom port request function");
37
68
  port = await customRequestPort();
38
69
  } else {
39
- // Fallback to standard Web Serial API
70
+ // Check if Web Serial API is available
40
71
  if (!navigator.serial) {
41
72
  throw new Error(
42
73
  "Web Serial API is not supported in this browser. " +
43
- "Please use Chrome 89+, Edge 89+, or Opera on desktop, or Chrome 61+ on Android with USB OTG. " +
74
+ "Please use Chrome, Edge, or Opera on desktop, or Chrome on Android. " +
44
75
  "Note: The page must be served over HTTPS or localhost.",
45
76
  );
46
77
  }
47
78
  port = await navigator.serial.requestPort();
48
79
  }
49
80
 
50
- // Only open if not already open (WebUSB may return an opened port)
81
+ // Only open if not already open (requestSerialPort may return an opened port)
51
82
  if (!port.readable || !port.writable) {
52
83
  await port.open({ baudRate: ESP_ROM_BAUD });
53
84
  }
54
85
 
55
- logger.log("Connected successfully.");
56
-
57
86
  return new ESPLoader(port, logger);
58
87
  };
59
88
 
@@ -63,12 +92,10 @@ export const connectWithPort = async (port: SerialPort, logger: Logger) => {
63
92
  throw new Error("Port is required");
64
93
  }
65
94
 
66
- // Only open if not already open
95
+ // Check if port is already open, if not open it
67
96
  if (!port.readable || !port.writable) {
68
97
  await port.open({ baudRate: ESP_ROM_BAUD });
69
98
  }
70
99
 
71
- logger.log("Connected successfully.");
72
-
73
100
  return new ESPLoader(port, logger);
74
101
  };