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/dist/esp_loader.d.ts +1 -0
- package/dist/esp_loader.js +60 -26
- package/dist/index.d.ts +1 -1
- package/dist/index.js +13 -10
- package/dist/web/index.js +1 -1
- package/js/modules/esptool.js +1 -1
- package/js/script.js +8 -1
- package/js/webusb-serial.js +9 -19
- package/package.json +1 -1
- package/src/esp_loader.ts +70 -26
- package/src/index.ts +40 -13
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
1317
|
+
await this.setRTSWebUSB(true); // EN->LOW
|
|
1310
1318
|
await this.sleep(200);
|
|
1311
|
-
await this.
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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 (
|
|
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
|
-
//
|
|
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
|
};
|