tasmota-webserial-esptool 9.1.8 → 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 {
@@ -993,7 +999,7 @@ export class ESPLoader extends EventTarget {
993
999
  // Strategy 1: USB-JTAG/Serial (works in CDC mode on Desktop)
994
1000
  resetStrategies.push({
995
1001
  name: "USB-JTAG/Serial (WebUSB) - ESP32-S2",
996
- fn: async function () {
1002
+ fn: async () => {
997
1003
  return await self.hardResetUSBJTAGSerialWebUSB();
998
1004
  },
999
1005
  });
@@ -1001,7 +1007,7 @@ export class ESPLoader extends EventTarget {
1001
1007
  // Strategy 2: USB-JTAG/Serial Inverted DTR (works in JTAG mode)
1002
1008
  resetStrategies.push({
1003
1009
  name: "USB-JTAG/Serial Inverted DTR (WebUSB) - ESP32-S2",
1004
- fn: async function () {
1010
+ fn: async () => {
1005
1011
  return await self.hardResetUSBJTAGSerialInvertedDTRWebUSB();
1006
1012
  },
1007
1013
  });
@@ -1009,7 +1015,7 @@ export class ESPLoader extends EventTarget {
1009
1015
  // Strategy 3: UnixTight (CDC fallback)
1010
1016
  resetStrategies.push({
1011
1017
  name: "UnixTight (WebUSB) - ESP32-S2 CDC",
1012
- fn: async function () {
1018
+ fn: async () => {
1013
1019
  return await self.hardResetUnixTightWebUSB();
1014
1020
  },
1015
1021
  });
@@ -1017,7 +1023,7 @@ export class ESPLoader extends EventTarget {
1017
1023
  // Strategy 4: Classic reset (CDC fallback)
1018
1024
  resetStrategies.push({
1019
1025
  name: "Classic (WebUSB) - ESP32-S2 CDC",
1020
- fn: async function () {
1026
+ fn: async () => {
1021
1027
  return await self.hardResetClassicWebUSB();
1022
1028
  },
1023
1029
  });
@@ -1025,19 +1031,19 @@ export class ESPLoader extends EventTarget {
1025
1031
  // Other USB-JTAG chips: Try Inverted DTR first - works best for ESP32-H2 and other JTAG chips
1026
1032
  resetStrategies.push({
1027
1033
  name: "USB-JTAG/Serial Inverted DTR (WebUSB)",
1028
- fn: async function () {
1034
+ fn: async () => {
1029
1035
  return await self.hardResetUSBJTAGSerialInvertedDTRWebUSB();
1030
1036
  },
1031
1037
  });
1032
1038
  resetStrategies.push({
1033
1039
  name: "USB-JTAG/Serial (WebUSB)",
1034
- fn: async function () {
1040
+ fn: async () => {
1035
1041
  return await self.hardResetUSBJTAGSerialWebUSB();
1036
1042
  },
1037
1043
  });
1038
1044
  resetStrategies.push({
1039
1045
  name: "Inverted DTR Classic (WebUSB)",
1040
- fn: async function () {
1046
+ fn: async () => {
1041
1047
  return await self.hardResetInvertedDTRWebUSB();
1042
1048
  },
1043
1049
  });
@@ -1050,31 +1056,31 @@ export class ESPLoader extends EventTarget {
1050
1056
  // CH340/CH343: UnixTight works best (like CP2102)
1051
1057
  resetStrategies.push({
1052
1058
  name: "UnixTight (WebUSB) - CH34x",
1053
- fn: async function () {
1059
+ fn: async () => {
1054
1060
  return await self.hardResetUnixTightWebUSB();
1055
1061
  },
1056
1062
  });
1057
1063
  resetStrategies.push({
1058
1064
  name: "Classic (WebUSB) - CH34x",
1059
- fn: async function () {
1065
+ fn: async () => {
1060
1066
  return await self.hardResetClassicWebUSB();
1061
1067
  },
1062
1068
  });
1063
1069
  resetStrategies.push({
1064
1070
  name: "Inverted Both (WebUSB) - CH34x",
1065
- fn: async function () {
1071
+ fn: async () => {
1066
1072
  return await self.hardResetInvertedWebUSB();
1067
1073
  },
1068
1074
  });
1069
1075
  resetStrategies.push({
1070
1076
  name: "Inverted RTS (WebUSB) - CH34x",
1071
- fn: async function () {
1077
+ fn: async () => {
1072
1078
  return await self.hardResetInvertedRTSWebUSB();
1073
1079
  },
1074
1080
  });
1075
1081
  resetStrategies.push({
1076
1082
  name: "Inverted DTR (WebUSB) - CH34x",
1077
- fn: async function () {
1083
+ fn: async () => {
1078
1084
  return await self.hardResetInvertedDTRWebUSB();
1079
1085
  },
1080
1086
  });
@@ -1084,35 +1090,35 @@ export class ESPLoader extends EventTarget {
1084
1090
 
1085
1091
  resetStrategies.push({
1086
1092
  name: "UnixTight (WebUSB) - CP2102",
1087
- fn: async function () {
1093
+ fn: async () => {
1088
1094
  return await self.hardResetUnixTightWebUSB();
1089
1095
  },
1090
1096
  });
1091
1097
 
1092
1098
  resetStrategies.push({
1093
1099
  name: "Classic (WebUSB) - CP2102",
1094
- fn: async function () {
1100
+ fn: async () => {
1095
1101
  return await self.hardResetClassicWebUSB();
1096
1102
  },
1097
1103
  });
1098
1104
 
1099
1105
  resetStrategies.push({
1100
1106
  name: "Inverted Both (WebUSB) - CP2102",
1101
- fn: async function () {
1107
+ fn: async () => {
1102
1108
  return await self.hardResetInvertedWebUSB();
1103
1109
  },
1104
1110
  });
1105
1111
 
1106
1112
  resetStrategies.push({
1107
1113
  name: "Inverted RTS (WebUSB) - CP2102",
1108
- fn: async function () {
1114
+ fn: async () => {
1109
1115
  return await self.hardResetInvertedRTSWebUSB();
1110
1116
  },
1111
1117
  });
1112
1118
 
1113
1119
  resetStrategies.push({
1114
1120
  name: "Inverted DTR (WebUSB) - CP2102",
1115
- fn: async function () {
1121
+ fn: async () => {
1116
1122
  return await self.hardResetInvertedDTRWebUSB();
1117
1123
  },
1118
1124
  });
@@ -1120,7 +1126,7 @@ export class ESPLoader extends EventTarget {
1120
1126
  // For other USB-Serial chips, try UnixTight first, then multiple strategies
1121
1127
  resetStrategies.push({
1122
1128
  name: "UnixTight (WebUSB)",
1123
- fn: async function () {
1129
+ fn: async () => {
1124
1130
  return await self.hardResetUnixTightWebUSB();
1125
1131
  },
1126
1132
  });
@@ -1198,7 +1204,6 @@ export class ESPLoader extends EventTarget {
1198
1204
  }
1199
1205
  }
1200
1206
  } else {
1201
- // Web Serial (Desktop) strategies
1202
1207
  // Strategy: USB-JTAG/Serial reset
1203
1208
  if (isUSBJTAGSerial || isEspressifUSB) {
1204
1209
  resetStrategies.push({
@@ -1309,9 +1314,9 @@ export class ESPLoader extends EventTarget {
1309
1314
  // just reset (no bootloader mode)
1310
1315
  if (this.isWebUSB()) {
1311
1316
  // WebUSB: Use longer delays for better compatibility
1312
- await this.setRTS(true); // EN->LOW
1317
+ await this.setRTSWebUSB(true); // EN->LOW
1313
1318
  await this.sleep(200);
1314
- await this.setRTS(false);
1319
+ await this.setRTSWebUSB(false);
1315
1320
  await this.sleep(200);
1316
1321
  this.logger.log("Hard reset (WebUSB).");
1317
1322
  } else {
@@ -1560,7 +1565,6 @@ export class ESPLoader extends EventTarget {
1560
1565
  "Timed out waiting for packet " + waitingFor,
1561
1566
  );
1562
1567
  }
1563
-
1564
1568
  const b = this._readByte()!;
1565
1569
 
1566
1570
  if (partialPacket === null) {
@@ -2676,7 +2680,21 @@ export class ESPLoader extends EventTarget {
2676
2680
  resolve(undefined);
2677
2681
  return;
2678
2682
  }
2679
- 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
+ );
2680
2698
 
2681
2699
  // Only cancel if reader is still active
2682
2700
  try {
@@ -2684,10 +2702,19 @@ export class ESPLoader extends EventTarget {
2684
2702
  } catch (err) {
2685
2703
  this.logger.debug(`Reader cancel error: ${err}`);
2686
2704
  // Reader already released, resolve immediately
2705
+ clearTimeout(timeout);
2687
2706
  resolve(undefined);
2688
2707
  }
2689
2708
  });
2690
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
+ }
2691
2718
  }
2692
2719
 
2693
2720
  /**
@@ -3357,6 +3384,12 @@ class EspStubLoader extends ESPLoader {
3357
3384
  if (size > maxValue) {
3358
3385
  throw new Error(`Size ${size} exceeds maximum value ${maxValue}`);
3359
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
+ }
3360
3393
 
3361
3394
  const timeout = timeoutPerMb(ERASE_REGION_TIMEOUT_PER_MB, size);
3362
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
  };