tasmota-webserial-esptool 9.1.5 → 9.1.7
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 +11 -2
- package/dist/esp_loader.js +101 -44
- package/dist/web/index.js +1 -1
- package/js/modules/esptool.js +1 -1
- package/js/webusb-serial.js +7 -10
- package/package.json +1 -1
- package/src/esp_loader.ts +113 -42
package/src/esp_loader.ts
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
USB_RAM_BLOCK,
|
|
43
43
|
ChipFamily,
|
|
44
44
|
ESP_ERASE_FLASH,
|
|
45
|
+
ESP_ERASE_REGION,
|
|
45
46
|
ESP_READ_FLASH,
|
|
46
47
|
CHIP_ERASE_TIMEOUT,
|
|
47
48
|
FLASH_READ_TIMEOUT,
|
|
@@ -613,6 +614,21 @@ export class ESPLoader extends EventTarget {
|
|
|
613
614
|
};
|
|
614
615
|
}
|
|
615
616
|
|
|
617
|
+
/**
|
|
618
|
+
* Get MAC address from efuses
|
|
619
|
+
*/
|
|
620
|
+
async getMacAddress(): Promise<string> {
|
|
621
|
+
if (!this._initializationSucceeded) {
|
|
622
|
+
throw new Error(
|
|
623
|
+
"getMacAddress() requires initialize() to have completed successfully",
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
const macBytes = this.macAddr(); // chip-family-aware
|
|
627
|
+
return macBytes
|
|
628
|
+
.map((b) => b.toString(16).padStart(2, "0").toUpperCase())
|
|
629
|
+
.join(":");
|
|
630
|
+
}
|
|
631
|
+
|
|
616
632
|
/**
|
|
617
633
|
* @name readLoop
|
|
618
634
|
* Reads data from the input stream and places it in the inputBuffer
|
|
@@ -647,7 +663,13 @@ export class ESPLoader extends EventTarget {
|
|
|
647
663
|
}
|
|
648
664
|
} catch {
|
|
649
665
|
this.logger.error("Read loop got disconnected");
|
|
666
|
+
} finally {
|
|
667
|
+
// Always reset reconfiguring flag when read loop ends
|
|
668
|
+
// This prevents "Cannot write during port reconfiguration" errors
|
|
669
|
+
// when the read loop dies unexpectedly
|
|
670
|
+
this._isReconfiguring = false;
|
|
650
671
|
}
|
|
672
|
+
|
|
651
673
|
// Disconnected!
|
|
652
674
|
this.connected = false;
|
|
653
675
|
|
|
@@ -1256,7 +1278,9 @@ export class ESPLoader extends EventTarget {
|
|
|
1256
1278
|
}
|
|
1257
1279
|
}
|
|
1258
1280
|
|
|
1259
|
-
// All strategies failed
|
|
1281
|
+
// All strategies failed - reset abandon flag before throwing
|
|
1282
|
+
this._abandonCurrentOperation = false;
|
|
1283
|
+
|
|
1260
1284
|
throw new Error(
|
|
1261
1285
|
`Couldn't sync to ESP. Try resetting manually. Last error: ${lastError?.message}`,
|
|
1262
1286
|
);
|
|
@@ -2608,50 +2632,51 @@ export class ESPLoader extends EventTarget {
|
|
|
2608
2632
|
return;
|
|
2609
2633
|
}
|
|
2610
2634
|
|
|
2635
|
+
// Wait for pending writes to complete
|
|
2611
2636
|
try {
|
|
2612
|
-
|
|
2637
|
+
await this._writeChain;
|
|
2638
|
+
} catch (err) {
|
|
2639
|
+
this.logger.debug(`Pending write error during disconnect: ${err}`);
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
// Release persistent writer before closing
|
|
2643
|
+
if (this._writer) {
|
|
2613
2644
|
try {
|
|
2614
|
-
await this.
|
|
2645
|
+
await this._writer.close();
|
|
2646
|
+
this._writer.releaseLock();
|
|
2615
2647
|
} catch (err) {
|
|
2616
|
-
this.logger.debug(`
|
|
2648
|
+
this.logger.debug(`Writer close/release error: ${err}`);
|
|
2617
2649
|
}
|
|
2650
|
+
this._writer = undefined;
|
|
2651
|
+
} else {
|
|
2652
|
+
// No persistent writer exists, close stream directly
|
|
2653
|
+
// This path is taken when no writes have been queued
|
|
2654
|
+
try {
|
|
2655
|
+
const writer = this.port.writable.getWriter();
|
|
2656
|
+
await writer.close();
|
|
2657
|
+
writer.releaseLock();
|
|
2658
|
+
} catch (err) {
|
|
2659
|
+
this.logger.debug(`Direct writer close error: ${err}`);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2618
2662
|
|
|
2619
|
-
|
|
2620
|
-
this.
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
if (this._writer) {
|
|
2624
|
-
try {
|
|
2625
|
-
await this._writer.close();
|
|
2626
|
-
this._writer.releaseLock();
|
|
2627
|
-
} catch (err) {
|
|
2628
|
-
this.logger.debug(`Writer close/release error: ${err}`);
|
|
2629
|
-
}
|
|
2630
|
-
this._writer = undefined;
|
|
2631
|
-
} else {
|
|
2632
|
-
// No persistent writer exists, close stream directly
|
|
2633
|
-
// This path is taken when no writes have been queued
|
|
2634
|
-
try {
|
|
2635
|
-
const writer = this.port.writable.getWriter();
|
|
2636
|
-
await writer.close();
|
|
2637
|
-
writer.releaseLock();
|
|
2638
|
-
} catch (err) {
|
|
2639
|
-
this.logger.debug(`Direct writer close error: ${err}`);
|
|
2640
|
-
}
|
|
2663
|
+
await new Promise((resolve) => {
|
|
2664
|
+
if (!this._reader) {
|
|
2665
|
+
resolve(undefined);
|
|
2666
|
+
return;
|
|
2641
2667
|
}
|
|
2668
|
+
this.addEventListener("disconnect", resolve, { once: true });
|
|
2642
2669
|
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
}
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
}
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
this._isReconfiguring = false;
|
|
2654
|
-
}
|
|
2670
|
+
// Only cancel if reader is still active
|
|
2671
|
+
try {
|
|
2672
|
+
this._reader.cancel();
|
|
2673
|
+
} catch (err) {
|
|
2674
|
+
this.logger.debug(`Reader cancel error: ${err}`);
|
|
2675
|
+
// Reader already released, resolve immediately
|
|
2676
|
+
resolve(undefined);
|
|
2677
|
+
}
|
|
2678
|
+
});
|
|
2679
|
+
this.connected = false;
|
|
2655
2680
|
}
|
|
2656
2681
|
|
|
2657
2682
|
/**
|
|
@@ -3029,8 +3054,9 @@ export class ESPLoader extends EventTarget {
|
|
|
3029
3054
|
newResp.set(packetData, resp.length);
|
|
3030
3055
|
resp = newResp;
|
|
3031
3056
|
|
|
3032
|
-
// Send acknowledgment
|
|
3033
|
-
//
|
|
3057
|
+
// Send acknowledgment when we've received maxInFlight bytes
|
|
3058
|
+
// The stub sends packets until (num_sent - num_acked) >= max_in_flight
|
|
3059
|
+
// We MUST wait for all packets before sending ACK
|
|
3034
3060
|
const shouldAck =
|
|
3035
3061
|
resp.length >= chunkSize || // End of chunk
|
|
3036
3062
|
resp.length >= lastAckedLength + maxInFlight; // Received all packets
|
|
@@ -3274,10 +3300,55 @@ class EspStubLoader extends ESPLoader {
|
|
|
3274
3300
|
}
|
|
3275
3301
|
|
|
3276
3302
|
/**
|
|
3277
|
-
* @name
|
|
3278
|
-
*
|
|
3303
|
+
* @name eraseFlash
|
|
3304
|
+
* Erase entire flash chip
|
|
3279
3305
|
*/
|
|
3280
3306
|
async eraseFlash() {
|
|
3281
3307
|
await this.checkCommand(ESP_ERASE_FLASH, [], 0, CHIP_ERASE_TIMEOUT);
|
|
3282
3308
|
}
|
|
3309
|
+
|
|
3310
|
+
/**
|
|
3311
|
+
* @name eraseRegion
|
|
3312
|
+
* Erase a specific region of flash
|
|
3313
|
+
*/
|
|
3314
|
+
async eraseRegion(offset: number, size: number) {
|
|
3315
|
+
// Validate inputs
|
|
3316
|
+
if (offset < 0) {
|
|
3317
|
+
throw new Error(`Invalid offset: ${offset} (must be non-negative)`);
|
|
3318
|
+
}
|
|
3319
|
+
if (size < 0) {
|
|
3320
|
+
throw new Error(`Invalid size: ${size} (must be non-negative)`);
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
// No-op for zero size
|
|
3324
|
+
if (size === 0) {
|
|
3325
|
+
this.logger.log("eraseRegion: size is 0, skipping erase");
|
|
3326
|
+
return;
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
// Check for sector alignment
|
|
3330
|
+
if (offset % FLASH_SECTOR_SIZE !== 0) {
|
|
3331
|
+
throw new Error(
|
|
3332
|
+
`Offset ${offset} (0x${offset.toString(16)}) is not aligned to flash sector size ${FLASH_SECTOR_SIZE} (0x${FLASH_SECTOR_SIZE.toString(16)})`,
|
|
3333
|
+
);
|
|
3334
|
+
}
|
|
3335
|
+
if (size % FLASH_SECTOR_SIZE !== 0) {
|
|
3336
|
+
throw new Error(
|
|
3337
|
+
`Size ${size} (0x${size.toString(16)}) is not aligned to flash sector size ${FLASH_SECTOR_SIZE} (0x${FLASH_SECTOR_SIZE.toString(16)})`,
|
|
3338
|
+
);
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
// Check for reasonable bounds (prevent wrapping in pack)
|
|
3342
|
+
const maxValue = 0xffffffff; // 32-bit unsigned max
|
|
3343
|
+
if (offset > maxValue) {
|
|
3344
|
+
throw new Error(`Offset ${offset} exceeds maximum value ${maxValue}`);
|
|
3345
|
+
}
|
|
3346
|
+
if (size > maxValue) {
|
|
3347
|
+
throw new Error(`Size ${size} exceeds maximum value ${maxValue}`);
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
const timeout = timeoutPerMb(ERASE_REGION_TIMEOUT_PER_MB, size);
|
|
3351
|
+
const buffer = pack("<II", offset, size);
|
|
3352
|
+
await this.checkCommand(ESP_ERASE_REGION, buffer, 0, timeout);
|
|
3353
|
+
}
|
|
3283
3354
|
}
|