tasmota-webserial-esptool 9.2.9 → 9.2.11
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/const.js +1 -1
- package/dist/esp_loader.d.ts +30 -5
- package/dist/esp_loader.js +178 -82
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/util.d.ts +4 -0
- package/dist/util.js +8 -0
- package/dist/web/index.js +1 -1
- package/js/modules/esptool.js +1 -1
- package/js/script.js +207 -180
- package/js/webusb-serial.js +21 -1
- package/package.json +3 -3
- package/src/const.ts +1 -1
- package/src/esp_loader.ts +206 -87
- package/src/index.ts +3 -0
- package/src/util.ts +9 -0
package/src/esp_loader.ts
CHANGED
|
@@ -141,9 +141,13 @@ export class ESPLoader extends EventTarget {
|
|
|
141
141
|
__inputBuffer?: number[];
|
|
142
142
|
__inputBufferReadIndex?: number;
|
|
143
143
|
__totalBytesRead?: number;
|
|
144
|
-
|
|
144
|
+
public currentBaudRate: number = ESP_ROM_BAUD;
|
|
145
145
|
private _maxUSBSerialBaudrate?: number;
|
|
146
146
|
public __reader?: ReadableStreamDefaultReader<Uint8Array>;
|
|
147
|
+
private SLIP_END = 0xc0;
|
|
148
|
+
private SLIP_ESC = 0xdb;
|
|
149
|
+
private SLIP_ESC_END = 0xdc;
|
|
150
|
+
private SLIP_ESC_ESC = 0xdd;
|
|
147
151
|
private _isESP32S2NativeUSB: boolean = false;
|
|
148
152
|
private _initializationSucceeded: boolean = false;
|
|
149
153
|
private __commandLock: Promise<[number, number[]]> = Promise.resolve([0, []]);
|
|
@@ -757,17 +761,14 @@ export class ESPLoader extends EventTarget {
|
|
|
757
761
|
|
|
758
762
|
// Always read from browser's serial buffer immediately
|
|
759
763
|
// to prevent browser buffer overflow. Don't apply back-pressure here.
|
|
760
|
-
const chunk = Array.from(value);
|
|
764
|
+
const chunk = Array.from(value as Uint8Array);
|
|
761
765
|
Array.prototype.push.apply(this._inputBuffer, chunk);
|
|
762
766
|
|
|
763
767
|
// Track total bytes read from serial port
|
|
764
768
|
this._totalBytesRead += value.length;
|
|
765
769
|
}
|
|
766
770
|
} catch {
|
|
767
|
-
//
|
|
768
|
-
if (!this._consoleMode) {
|
|
769
|
-
this.logger.error("Read loop got disconnected");
|
|
770
|
-
}
|
|
771
|
+
// this.logger.error("Read loop got disconnected");
|
|
771
772
|
} finally {
|
|
772
773
|
// Always reset reconfiguring flag when read loop ends
|
|
773
774
|
// This prevents "Cannot write during port reconfiguration" errors
|
|
@@ -1462,9 +1463,9 @@ export class ESPLoader extends EventTarget {
|
|
|
1462
1463
|
}
|
|
1463
1464
|
} catch (error) {
|
|
1464
1465
|
lastError = error as Error;
|
|
1465
|
-
this.logger.debug(
|
|
1466
|
-
`${strategy.name} reset failed: ${(error as Error).message}`,
|
|
1467
|
-
);
|
|
1466
|
+
// this.logger.debug(
|
|
1467
|
+
// `${strategy.name} reset failed: ${(error as Error).message}`,
|
|
1468
|
+
// );
|
|
1468
1469
|
|
|
1469
1470
|
// Set abandon flag to stop any in-flight operations
|
|
1470
1471
|
this._abandonCurrentOperation = true;
|
|
@@ -2129,47 +2130,47 @@ export class ESPLoader extends EventTarget {
|
|
|
2129
2130
|
"Timed out waiting for packet " + waitingFor,
|
|
2130
2131
|
);
|
|
2131
2132
|
}
|
|
2132
|
-
const
|
|
2133
|
+
const byte = this._readByte()!;
|
|
2133
2134
|
|
|
2134
2135
|
if (partialPacket === null) {
|
|
2135
2136
|
// waiting for packet header
|
|
2136
|
-
if (
|
|
2137
|
+
if (byte == this.SLIP_END) {
|
|
2137
2138
|
partialPacket = [];
|
|
2138
2139
|
} else {
|
|
2139
2140
|
if (this.debug) {
|
|
2140
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
2141
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
2141
2142
|
this.logger.debug(
|
|
2142
2143
|
"Remaining data in serial buffer: " +
|
|
2143
2144
|
hexFormatter(this._inputBuffer),
|
|
2144
2145
|
);
|
|
2145
2146
|
}
|
|
2146
2147
|
throw new SlipReadError(
|
|
2147
|
-
"Invalid head of packet (" + toHex(
|
|
2148
|
+
"Invalid head of packet (" + toHex(byte) + ")",
|
|
2148
2149
|
);
|
|
2149
2150
|
}
|
|
2150
2151
|
} else if (inEscape) {
|
|
2151
2152
|
// part-way through escape sequence
|
|
2152
2153
|
inEscape = false;
|
|
2153
|
-
if (
|
|
2154
|
-
partialPacket.push(
|
|
2155
|
-
} else if (
|
|
2156
|
-
partialPacket.push(
|
|
2154
|
+
if (byte == this.SLIP_ESC_END) {
|
|
2155
|
+
partialPacket.push(this.SLIP_END);
|
|
2156
|
+
} else if (byte == this.SLIP_ESC_ESC) {
|
|
2157
|
+
partialPacket.push(this.SLIP_ESC);
|
|
2157
2158
|
} else {
|
|
2158
2159
|
if (this.debug) {
|
|
2159
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
2160
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
2160
2161
|
this.logger.debug(
|
|
2161
2162
|
"Remaining data in serial buffer: " +
|
|
2162
2163
|
hexFormatter(this._inputBuffer),
|
|
2163
2164
|
);
|
|
2164
2165
|
}
|
|
2165
2166
|
throw new SlipReadError(
|
|
2166
|
-
"Invalid SLIP escape (0xdb, " + toHex(
|
|
2167
|
+
"Invalid SLIP escape (0xdb, " + toHex(byte) + ")",
|
|
2167
2168
|
);
|
|
2168
2169
|
}
|
|
2169
|
-
} else if (
|
|
2170
|
+
} else if (byte == this.SLIP_ESC) {
|
|
2170
2171
|
// start of escape sequence
|
|
2171
2172
|
inEscape = true;
|
|
2172
|
-
} else if (
|
|
2173
|
+
} else if (byte == this.SLIP_END) {
|
|
2173
2174
|
// end of packet
|
|
2174
2175
|
if (this.debug)
|
|
2175
2176
|
this.logger.debug(
|
|
@@ -2180,7 +2181,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2180
2181
|
return partialPacket;
|
|
2181
2182
|
} else {
|
|
2182
2183
|
// normal byte in packet
|
|
2183
|
-
partialPacket.push(
|
|
2184
|
+
partialPacket.push(byte);
|
|
2184
2185
|
}
|
|
2185
2186
|
}
|
|
2186
2187
|
}
|
|
@@ -2214,46 +2215,46 @@ export class ESPLoader extends EventTarget {
|
|
|
2214
2215
|
this.logger.debug(
|
|
2215
2216
|
"Read " + readBytes.length + " bytes: " + hexFormatter(readBytes),
|
|
2216
2217
|
);
|
|
2217
|
-
for (const
|
|
2218
|
+
for (const byte of readBytes) {
|
|
2218
2219
|
if (partialPacket === null) {
|
|
2219
2220
|
// waiting for packet header
|
|
2220
|
-
if (
|
|
2221
|
+
if (byte == this.SLIP_END) {
|
|
2221
2222
|
partialPacket = [];
|
|
2222
2223
|
} else {
|
|
2223
2224
|
if (this.debug) {
|
|
2224
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
2225
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
2225
2226
|
this.logger.debug(
|
|
2226
2227
|
"Remaining data in serial buffer: " +
|
|
2227
2228
|
hexFormatter(this._inputBuffer),
|
|
2228
2229
|
);
|
|
2229
2230
|
}
|
|
2230
2231
|
throw new SlipReadError(
|
|
2231
|
-
"Invalid head of packet (" + toHex(
|
|
2232
|
+
"Invalid head of packet (" + toHex(byte) + ")",
|
|
2232
2233
|
);
|
|
2233
2234
|
}
|
|
2234
2235
|
} else if (inEscape) {
|
|
2235
2236
|
// part-way through escape sequence
|
|
2236
2237
|
inEscape = false;
|
|
2237
|
-
if (
|
|
2238
|
-
partialPacket.push(
|
|
2239
|
-
} else if (
|
|
2240
|
-
partialPacket.push(
|
|
2238
|
+
if (byte == this.SLIP_ESC_END) {
|
|
2239
|
+
partialPacket.push(this.SLIP_END);
|
|
2240
|
+
} else if (byte == this.SLIP_ESC_ESC) {
|
|
2241
|
+
partialPacket.push(this.SLIP_ESC);
|
|
2241
2242
|
} else {
|
|
2242
2243
|
if (this.debug) {
|
|
2243
|
-
this.logger.debug("Read invalid data: " + toHex(
|
|
2244
|
+
this.logger.debug("Read invalid data: " + toHex(byte));
|
|
2244
2245
|
this.logger.debug(
|
|
2245
2246
|
"Remaining data in serial buffer: " +
|
|
2246
2247
|
hexFormatter(this._inputBuffer),
|
|
2247
2248
|
);
|
|
2248
2249
|
}
|
|
2249
2250
|
throw new SlipReadError(
|
|
2250
|
-
"Invalid SLIP escape (0xdb, " + toHex(
|
|
2251
|
+
"Invalid SLIP escape (0xdb, " + toHex(byte) + ")",
|
|
2251
2252
|
);
|
|
2252
2253
|
}
|
|
2253
|
-
} else if (
|
|
2254
|
+
} else if (byte == this.SLIP_ESC) {
|
|
2254
2255
|
// start of escape sequence
|
|
2255
2256
|
inEscape = true;
|
|
2256
|
-
} else if (
|
|
2257
|
+
} else if (byte == this.SLIP_END) {
|
|
2257
2258
|
// end of packet
|
|
2258
2259
|
if (this.debug)
|
|
2259
2260
|
this.logger.debug(
|
|
@@ -2264,7 +2265,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2264
2265
|
return partialPacket;
|
|
2265
2266
|
} else {
|
|
2266
2267
|
// normal byte in packet
|
|
2267
|
-
partialPacket.push(
|
|
2268
|
+
partialPacket.push(byte);
|
|
2268
2269
|
}
|
|
2269
2270
|
}
|
|
2270
2271
|
}
|
|
@@ -2341,9 +2342,9 @@ export class ESPLoader extends EventTarget {
|
|
|
2341
2342
|
|
|
2342
2343
|
// Track current baudrate for reconnect
|
|
2343
2344
|
if (this._parent) {
|
|
2344
|
-
this._parent.
|
|
2345
|
+
this._parent.currentBaudRate = baud;
|
|
2345
2346
|
} else {
|
|
2346
|
-
this.
|
|
2347
|
+
this.currentBaudRate = baud;
|
|
2347
2348
|
}
|
|
2348
2349
|
|
|
2349
2350
|
// Warn if baudrate exceeds USB-Serial chip capability
|
|
@@ -2429,8 +2430,8 @@ export class ESPLoader extends EventTarget {
|
|
|
2429
2430
|
// Restart Readloop
|
|
2430
2431
|
this.readLoop();
|
|
2431
2432
|
} catch (e) {
|
|
2432
|
-
this.logger.error(`Reconfigure port error: ${e}`);
|
|
2433
|
-
throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
|
|
2433
|
+
// this.logger.error(`Reconfigure port error: ${e}`);
|
|
2434
|
+
// throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
|
|
2434
2435
|
} finally {
|
|
2435
2436
|
// Always reset flag, even on error or early return
|
|
2436
2437
|
this._isReconfiguring = false;
|
|
@@ -3138,20 +3139,6 @@ export class ESPLoader extends EventTarget {
|
|
|
3138
3139
|
}
|
|
3139
3140
|
}
|
|
3140
3141
|
|
|
3141
|
-
private get _currentBaudRate(): number {
|
|
3142
|
-
return this._parent
|
|
3143
|
-
? this._parent._currentBaudRate
|
|
3144
|
-
: this.__currentBaudRate;
|
|
3145
|
-
}
|
|
3146
|
-
|
|
3147
|
-
private set _currentBaudRate(value: number) {
|
|
3148
|
-
if (this._parent) {
|
|
3149
|
-
this._parent._currentBaudRate = value;
|
|
3150
|
-
} else {
|
|
3151
|
-
this.__currentBaudRate = value;
|
|
3152
|
-
}
|
|
3153
|
-
}
|
|
3154
|
-
|
|
3155
3142
|
async writeToStream(data: number[]) {
|
|
3156
3143
|
if (!this.port.writable) {
|
|
3157
3144
|
this.logger.debug("Port writable stream not available, skipping write");
|
|
@@ -3231,7 +3218,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3231
3218
|
return;
|
|
3232
3219
|
}
|
|
3233
3220
|
if (!this.port.writable) {
|
|
3234
|
-
this.logger.debug("Port already closed, skipping disconnect");
|
|
3221
|
+
// this.logger.debug("Port already closed, skipping disconnect");
|
|
3235
3222
|
return;
|
|
3236
3223
|
}
|
|
3237
3224
|
|
|
@@ -3239,7 +3226,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3239
3226
|
try {
|
|
3240
3227
|
await this._writeChain;
|
|
3241
3228
|
} catch (err) {
|
|
3242
|
-
this.logger.debug(`Pending write error during disconnect: ${err}`);
|
|
3229
|
+
// this.logger.debug(`Pending write error during disconnect: ${err}`);
|
|
3243
3230
|
}
|
|
3244
3231
|
|
|
3245
3232
|
// Release persistent writer before closing
|
|
@@ -3248,7 +3235,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3248
3235
|
await this._writer.close();
|
|
3249
3236
|
this._writer.releaseLock();
|
|
3250
3237
|
} catch (err) {
|
|
3251
|
-
this.logger.debug(`Writer close/release error: ${err}`);
|
|
3238
|
+
// this.logger.debug(`Writer close/release error: ${err}`);
|
|
3252
3239
|
}
|
|
3253
3240
|
this._writer = undefined;
|
|
3254
3241
|
} else {
|
|
@@ -3259,7 +3246,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3259
3246
|
await writer.close();
|
|
3260
3247
|
writer.releaseLock();
|
|
3261
3248
|
} catch (err) {
|
|
3262
|
-
this.logger.debug(`Direct writer close error: ${err}`);
|
|
3249
|
+
// this.logger.debug(`Direct writer close error: ${err}`);
|
|
3263
3250
|
}
|
|
3264
3251
|
}
|
|
3265
3252
|
|
|
@@ -3288,7 +3275,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3288
3275
|
try {
|
|
3289
3276
|
this._reader.cancel();
|
|
3290
3277
|
} catch (err) {
|
|
3291
|
-
this.logger.debug(`Reader cancel error: ${err}`);
|
|
3278
|
+
// this.logger.debug(`Reader cancel error: ${err}`);
|
|
3292
3279
|
// Reader already released, resolve immediately
|
|
3293
3280
|
clearTimeout(timeout);
|
|
3294
3281
|
resolve(undefined);
|
|
@@ -3328,7 +3315,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3328
3315
|
try {
|
|
3329
3316
|
await this._writeChain;
|
|
3330
3317
|
} catch (err) {
|
|
3331
|
-
this.logger.debug(`Pending write error during release: ${err}`);
|
|
3318
|
+
// this.logger.debug(`Pending write error during release: ${err}`);
|
|
3332
3319
|
}
|
|
3333
3320
|
|
|
3334
3321
|
// Release writer
|
|
@@ -3378,37 +3365,46 @@ export class ESPLoader extends EventTarget {
|
|
|
3378
3365
|
/**
|
|
3379
3366
|
* @name detectUsbConnectionType
|
|
3380
3367
|
* Detect if device is using USB-JTAG/Serial or USB-OTG (not external serial chip)
|
|
3381
|
-
*
|
|
3368
|
+
* Uses USB PID (Product ID) for reliable detection
|
|
3382
3369
|
* @returns true if USB-JTAG or USB-OTG, false if external serial chip
|
|
3383
|
-
* @throws Error if
|
|
3370
|
+
* @throws Error if chipFamily is not set
|
|
3384
3371
|
*/
|
|
3385
3372
|
private async detectUsbConnectionType(): Promise<boolean> {
|
|
3386
3373
|
if (!this.chipFamily) {
|
|
3387
3374
|
throw new Error("Cannot detect USB connection type: chipFamily not set");
|
|
3388
3375
|
}
|
|
3389
3376
|
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
} else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
3405
|
-
const isUsingUsbOtg = await this.usingUsbOtg();
|
|
3406
|
-
const isUsingUsbJtagSerial = await this.usingUsbJtagSerial();
|
|
3407
|
-
return isUsingUsbOtg || isUsingUsbJtagSerial;
|
|
3408
|
-
} else {
|
|
3409
|
-
// Other chips don't have USB-JTAG/OTG
|
|
3377
|
+
// Use PID-based detection (most reliable method)
|
|
3378
|
+
const portInfo = this.port.getInfo();
|
|
3379
|
+
const pid = portInfo.usbProductId;
|
|
3380
|
+
const vid = portInfo.usbVendorId;
|
|
3381
|
+
|
|
3382
|
+
this.logger.debug(
|
|
3383
|
+
`USB detection: VID=0x${vid?.toString(16)}, PID=0x${pid?.toString(16)}`,
|
|
3384
|
+
);
|
|
3385
|
+
|
|
3386
|
+
// Check if this is an Espressif device
|
|
3387
|
+
const isEspressif = vid === 0x303a;
|
|
3388
|
+
|
|
3389
|
+
if (!isEspressif) {
|
|
3390
|
+
this.logger.debug("Not Espressif VID - external serial chip");
|
|
3410
3391
|
return false;
|
|
3411
3392
|
}
|
|
3393
|
+
|
|
3394
|
+
// ESP32-S2/S3/C3/C6/H2 USB-JTAG/OTG PIDs
|
|
3395
|
+
// 0x1001 = ESP32-S3 USB-JTAG
|
|
3396
|
+
// 0x0002 = ESP32-S2 USB-JTAG
|
|
3397
|
+
// 0x1000 = ESP32-C3 USB-JTAG
|
|
3398
|
+
// 0x4008 = ESP32-C6 USB-JTAG
|
|
3399
|
+
// 0x4009 = ESP32-H2 USB-JTAG
|
|
3400
|
+
const usbJtagPids = [0x1001, 0x0002, 0x1000, 0x4008, 0x4009];
|
|
3401
|
+
const isUsbJtag = usbJtagPids.includes(pid || 0);
|
|
3402
|
+
|
|
3403
|
+
this.logger.debug(
|
|
3404
|
+
`USB-JTAG/OTG detection: ${isUsbJtag ? "YES" : "NO"} (PID=0x${pid?.toString(16)})`,
|
|
3405
|
+
);
|
|
3406
|
+
|
|
3407
|
+
return isUsbJtag;
|
|
3412
3408
|
}
|
|
3413
3409
|
|
|
3414
3410
|
/**
|
|
@@ -3442,6 +3438,8 @@ export class ESPLoader extends EventTarget {
|
|
|
3442
3438
|
isUsbJtag = this.isUsbJtagOrOtg;
|
|
3443
3439
|
}
|
|
3444
3440
|
|
|
3441
|
+
// Release reader/writer so console can create new ones
|
|
3442
|
+
// This is needed for Desktop (Web Serial) to unlock streams
|
|
3445
3443
|
if (isUsbJtag) {
|
|
3446
3444
|
// USB-JTAG/OTG devices: Use watchdog reset which closes port
|
|
3447
3445
|
const wasReset = await this._resetToFirmwareIfNeeded();
|
|
@@ -3463,6 +3461,18 @@ export class ESPLoader extends EventTarget {
|
|
|
3463
3461
|
this.logger.debug(`Could not reset device: ${err}`);
|
|
3464
3462
|
}
|
|
3465
3463
|
|
|
3464
|
+
// For WebUSB (Android), recreate streams after hardware reset
|
|
3465
|
+
if (this.isWebUSB()) {
|
|
3466
|
+
try {
|
|
3467
|
+
// Use the public recreateStreams() method to safely recreate streams
|
|
3468
|
+
// without closing the port (important after hardware reset)
|
|
3469
|
+
await (this.port as any).recreateStreams();
|
|
3470
|
+
this.logger.debug("WebUSB streams recreated for console mode");
|
|
3471
|
+
} catch (err) {
|
|
3472
|
+
this.logger.debug(`Failed to recreate WebUSB streams: ${err}`);
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3466
3476
|
return false; // Port stays open
|
|
3467
3477
|
}
|
|
3468
3478
|
}
|
|
@@ -3488,7 +3498,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3488
3498
|
: "USB-JTAG/Serial";
|
|
3489
3499
|
|
|
3490
3500
|
this.logger.log(
|
|
3491
|
-
`Resetting ${this.
|
|
3501
|
+
`Resetting ${this.chipName || "device"} (${resetMethod}) to boot into firmware...`,
|
|
3492
3502
|
);
|
|
3493
3503
|
|
|
3494
3504
|
// Set console mode flag before reset to prevent subsequent hardReset calls
|
|
@@ -3567,7 +3577,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3567
3577
|
|
|
3568
3578
|
try {
|
|
3569
3579
|
this.logger.log("Reconnecting serial port...");
|
|
3570
|
-
const savedBaudRate = this.
|
|
3580
|
+
const savedBaudRate = this.currentBaudRate;
|
|
3571
3581
|
|
|
3572
3582
|
this.connected = false;
|
|
3573
3583
|
this.__inputBuffer = [];
|
|
@@ -3616,7 +3626,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3616
3626
|
try {
|
|
3617
3627
|
await this.port.open({ baudRate: ESP_ROM_BAUD });
|
|
3618
3628
|
this.connected = true;
|
|
3619
|
-
this.
|
|
3629
|
+
this.currentBaudRate = ESP_ROM_BAUD;
|
|
3620
3630
|
} catch (err) {
|
|
3621
3631
|
throw new Error(`Failed to open port: ${err}`);
|
|
3622
3632
|
}
|
|
@@ -3758,7 +3768,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3758
3768
|
try {
|
|
3759
3769
|
await this.port.open({ baudRate: ESP_ROM_BAUD });
|
|
3760
3770
|
this.connected = true;
|
|
3761
|
-
this.
|
|
3771
|
+
this.currentBaudRate = ESP_ROM_BAUD;
|
|
3762
3772
|
} catch (err) {
|
|
3763
3773
|
throw new Error(`Failed to open port: ${err}`);
|
|
3764
3774
|
}
|
|
@@ -3805,6 +3815,115 @@ export class ESPLoader extends EventTarget {
|
|
|
3805
3815
|
}
|
|
3806
3816
|
}
|
|
3807
3817
|
|
|
3818
|
+
/**
|
|
3819
|
+
* @name exitConsoleMode
|
|
3820
|
+
* Exit console mode and return to bootloader
|
|
3821
|
+
* For ESP32-S2, uses reconnectToBootloader which will trigger port change
|
|
3822
|
+
* @returns true if manual reconnection is needed (ESP32-S2), false otherwise
|
|
3823
|
+
*/
|
|
3824
|
+
async exitConsoleMode(): Promise<boolean> {
|
|
3825
|
+
if (this._parent) {
|
|
3826
|
+
return await this._parent.exitConsoleMode();
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
// Clear console mode flag
|
|
3830
|
+
this._consoleMode = false;
|
|
3831
|
+
|
|
3832
|
+
// Check if this is ESP32-S2 with USB-JTAG/OTG
|
|
3833
|
+
const isESP32S2 = this.chipFamily === CHIP_FAMILY_ESP32S2;
|
|
3834
|
+
|
|
3835
|
+
// For ESP32-S2: if _isUsbJtagOrOtg is undefined, try to detect it
|
|
3836
|
+
// If detection fails or is undefined, assume USB-JTAG/OTG (conservative/safe path)
|
|
3837
|
+
let isUsbJtagOrOtg = this._isUsbJtagOrOtg;
|
|
3838
|
+
if (isESP32S2 && isUsbJtagOrOtg === undefined) {
|
|
3839
|
+
try {
|
|
3840
|
+
isUsbJtagOrOtg = await this.detectUsbConnectionType();
|
|
3841
|
+
} catch (err) {
|
|
3842
|
+
this.logger.debug(
|
|
3843
|
+
`USB detection failed, assuming USB-JTAG/OTG for ESP32-S2: ${err}`,
|
|
3844
|
+
);
|
|
3845
|
+
isUsbJtagOrOtg = true; // Conservative fallback for ESP32-S2
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
|
|
3849
|
+
if (isESP32S2 && isUsbJtagOrOtg) {
|
|
3850
|
+
// ESP32-S2 USB: Use reconnectToBootloader which handles the mode switch
|
|
3851
|
+
// This will close the port and the device will reboot to bootloader
|
|
3852
|
+
this.logger.log("ESP32-S2 USB detected - reconnecting to bootloader");
|
|
3853
|
+
|
|
3854
|
+
try {
|
|
3855
|
+
await this.reconnectToBootloader();
|
|
3856
|
+
} catch (err) {
|
|
3857
|
+
this.logger.debug(`Reconnect error (expected for ESP32-S2): ${err}`);
|
|
3858
|
+
}
|
|
3859
|
+
|
|
3860
|
+
// For ESP32-S2, port will change, so return true to indicate manual reconnection needed
|
|
3861
|
+
return true;
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3864
|
+
// For other devices, use standard reconnectToBootloader
|
|
3865
|
+
await this.reconnectToBootloader();
|
|
3866
|
+
return false; // No manual reconnection needed
|
|
3867
|
+
}
|
|
3868
|
+
|
|
3869
|
+
/**
|
|
3870
|
+
* @name isConsoleResetSupported
|
|
3871
|
+
* Check if console reset is supported for this device
|
|
3872
|
+
* ESP32-S2 USB-JTAG/CDC does not support reset in console mode
|
|
3873
|
+
* because any reset causes USB port to be lost (hardware limitation)
|
|
3874
|
+
*/
|
|
3875
|
+
isConsoleResetSupported(): boolean {
|
|
3876
|
+
if (this._parent) {
|
|
3877
|
+
return this._parent.isConsoleResetSupported();
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3880
|
+
// For ESP32-S2: if _isUsbJtagOrOtg is undefined, assume USB-JTAG/OTG (conservative)
|
|
3881
|
+
// This means console reset is NOT supported (safer default)
|
|
3882
|
+
const isS2UsbJtag =
|
|
3883
|
+
this.chipFamily === CHIP_FAMILY_ESP32S2 &&
|
|
3884
|
+
(this._isUsbJtagOrOtg === true || this._isUsbJtagOrOtg === undefined);
|
|
3885
|
+
return !isS2UsbJtag; // Not supported for ESP32-S2 USB-JTAG/CDC
|
|
3886
|
+
}
|
|
3887
|
+
|
|
3888
|
+
/**
|
|
3889
|
+
* @name resetInConsoleMode
|
|
3890
|
+
* Reset device while in console mode (firmware mode)
|
|
3891
|
+
*
|
|
3892
|
+
* NOTE: For ESP32-S2 USB-JTAG/CDC, ANY reset (hardware or software) causes
|
|
3893
|
+
* the USB port to be lost because the device switches USB modes during reset.
|
|
3894
|
+
* This is a hardware limitation - use isConsoleResetSupported() to check first.
|
|
3895
|
+
*/
|
|
3896
|
+
async resetInConsoleMode(): Promise<void> {
|
|
3897
|
+
if (this._parent) {
|
|
3898
|
+
return await this._parent.resetInConsoleMode();
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3901
|
+
if (!this.isConsoleResetSupported()) {
|
|
3902
|
+
this.logger.debug(
|
|
3903
|
+
"Console reset not supported for ESP32-S2 USB-JTAG/CDC",
|
|
3904
|
+
);
|
|
3905
|
+
return; // Do nothing
|
|
3906
|
+
}
|
|
3907
|
+
|
|
3908
|
+
// For other devices: Use standard firmware reset
|
|
3909
|
+
const isWebUSB = (this.port as any).isWebUSB === true;
|
|
3910
|
+
|
|
3911
|
+
try {
|
|
3912
|
+
this.logger.debug("Resetting device in console mode");
|
|
3913
|
+
|
|
3914
|
+
if (isWebUSB) {
|
|
3915
|
+
await this.hardResetToFirmwareWebUSB();
|
|
3916
|
+
} else {
|
|
3917
|
+
await this.hardResetToFirmware();
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3920
|
+
this.logger.debug("Device reset complete");
|
|
3921
|
+
} catch (err) {
|
|
3922
|
+
this.logger.error(`Reset failed: ${err}`);
|
|
3923
|
+
throw err;
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
|
|
3808
3927
|
/**
|
|
3809
3928
|
* @name drainInputBuffer
|
|
3810
3929
|
* Actively drain the input buffer by reading data for a specified time.
|
|
@@ -3820,7 +3939,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3820
3939
|
await sleep(bufferingTime);
|
|
3821
3940
|
|
|
3822
3941
|
// Unsupported command response is sent 8 times and has
|
|
3823
|
-
// 14 bytes length including delimiter 0xC0 bytes.
|
|
3942
|
+
// 14 bytes length including delimiter SLIP_END (0xC0) bytes.
|
|
3824
3943
|
// At least part of it is read as a command response,
|
|
3825
3944
|
// but to be safe, read it all.
|
|
3826
3945
|
const bytesToDrain = 14 * 8;
|
|
@@ -4040,7 +4159,7 @@ export class ESPLoader extends EventTarget {
|
|
|
4040
4159
|
// The stub expects 4 bytes (ACK), if we send less it will break out
|
|
4041
4160
|
try {
|
|
4042
4161
|
// Send SLIP frame with no data (just delimiters)
|
|
4043
|
-
const abortFrame = [
|
|
4162
|
+
const abortFrame = [this.SLIP_END, this.SLIP_END]; // Empty SLIP frame
|
|
4044
4163
|
await this.writeToStream(abortFrame);
|
|
4045
4164
|
this.logger.debug(`Sent abort frame to stub`);
|
|
4046
4165
|
|
package/src/index.ts
CHANGED
package/src/util.ts
CHANGED
|
@@ -45,5 +45,14 @@ export const toHex = (value: number, size = 2) => {
|
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Format MAC address array to string (e.g., [0xAA, 0xBB, 0xCC] -> "AA:BB:CC:DD:EE:FF")
|
|
50
|
+
*/
|
|
51
|
+
export const formatMacAddr = (macAddr: number[]): string => {
|
|
52
|
+
return macAddr
|
|
53
|
+
.map((value) => value.toString(16).toUpperCase().padStart(2, "0"))
|
|
54
|
+
.join(":");
|
|
55
|
+
};
|
|
56
|
+
|
|
48
57
|
export const sleep = (ms: number) =>
|
|
49
58
|
new Promise((resolve) => setTimeout(resolve, ms));
|