tasmota-webserial-esptool 9.1.4 → 9.1.6
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 +23 -6
- package/dist/esp_loader.js +139 -46
- 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 +156 -46
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,
|
|
@@ -77,10 +78,10 @@ interface WebUSBSerialPort extends SerialPort {
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
export class ESPLoader extends EventTarget {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
__chipFamily?: ChipFamily;
|
|
82
|
+
__chipName: string | null = null;
|
|
83
|
+
__chipRevision: number | null = null;
|
|
84
|
+
__chipVariant: string | null = null;
|
|
84
85
|
_efuses = new Array(4).fill(0);
|
|
85
86
|
_flashsize = 4 * 1024 * 1024;
|
|
86
87
|
debug = false;
|
|
@@ -116,6 +117,55 @@ export class ESPLoader extends EventTarget {
|
|
|
116
117
|
super();
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
// Chip properties with parent delegation
|
|
121
|
+
get chipFamily(): ChipFamily {
|
|
122
|
+
return this._parent ? this._parent.chipFamily : this.__chipFamily!;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
set chipFamily(value: ChipFamily) {
|
|
126
|
+
if (this._parent) {
|
|
127
|
+
this._parent.chipFamily = value;
|
|
128
|
+
} else {
|
|
129
|
+
this.__chipFamily = value;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get chipName(): string | null {
|
|
134
|
+
return this._parent ? this._parent.chipName : this.__chipName;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
set chipName(value: string | null) {
|
|
138
|
+
if (this._parent) {
|
|
139
|
+
this._parent.chipName = value;
|
|
140
|
+
} else {
|
|
141
|
+
this.__chipName = value;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
get chipRevision(): number | null {
|
|
146
|
+
return this._parent ? this._parent.chipRevision : this.__chipRevision;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
set chipRevision(value: number | null) {
|
|
150
|
+
if (this._parent) {
|
|
151
|
+
this._parent.chipRevision = value;
|
|
152
|
+
} else {
|
|
153
|
+
this.__chipRevision = value;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
get chipVariant(): string | null {
|
|
158
|
+
return this._parent ? this._parent.chipVariant : this.__chipVariant;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
set chipVariant(value: string | null) {
|
|
162
|
+
if (this._parent) {
|
|
163
|
+
this._parent.chipVariant = value;
|
|
164
|
+
} else {
|
|
165
|
+
this.__chipVariant = value;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
119
169
|
private get _inputBuffer(): number[] {
|
|
120
170
|
return this._parent ? this._parent._inputBuffer : this.__inputBuffer!;
|
|
121
171
|
}
|
|
@@ -564,6 +614,21 @@ export class ESPLoader extends EventTarget {
|
|
|
564
614
|
};
|
|
565
615
|
}
|
|
566
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
|
+
|
|
567
632
|
/**
|
|
568
633
|
* @name readLoop
|
|
569
634
|
* Reads data from the input stream and places it in the inputBuffer
|
|
@@ -598,7 +663,13 @@ export class ESPLoader extends EventTarget {
|
|
|
598
663
|
}
|
|
599
664
|
} catch {
|
|
600
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;
|
|
601
671
|
}
|
|
672
|
+
|
|
602
673
|
// Disconnected!
|
|
603
674
|
this.connected = false;
|
|
604
675
|
|
|
@@ -2559,50 +2630,43 @@ export class ESPLoader extends EventTarget {
|
|
|
2559
2630
|
return;
|
|
2560
2631
|
}
|
|
2561
2632
|
|
|
2633
|
+
// Wait for pending writes to complete
|
|
2562
2634
|
try {
|
|
2563
|
-
|
|
2635
|
+
await this._writeChain;
|
|
2636
|
+
} catch (err) {
|
|
2637
|
+
this.logger.debug(`Pending write error during disconnect: ${err}`);
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
// Release persistent writer before closing
|
|
2641
|
+
if (this._writer) {
|
|
2564
2642
|
try {
|
|
2565
|
-
await this.
|
|
2643
|
+
await this._writer.close();
|
|
2644
|
+
this._writer.releaseLock();
|
|
2566
2645
|
} catch (err) {
|
|
2567
|
-
this.logger.debug(`
|
|
2646
|
+
this.logger.debug(`Writer close/release error: ${err}`);
|
|
2568
2647
|
}
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
this.logger.debug(`Writer close/release error: ${err}`);
|
|
2580
|
-
}
|
|
2581
|
-
this._writer = undefined;
|
|
2582
|
-
} else {
|
|
2583
|
-
// No persistent writer exists, close stream directly
|
|
2584
|
-
// This path is taken when no writes have been queued
|
|
2585
|
-
try {
|
|
2586
|
-
const writer = this.port.writable.getWriter();
|
|
2587
|
-
await writer.close();
|
|
2588
|
-
writer.releaseLock();
|
|
2589
|
-
} catch (err) {
|
|
2590
|
-
this.logger.debug(`Direct writer close error: ${err}`);
|
|
2591
|
-
}
|
|
2648
|
+
this._writer = undefined;
|
|
2649
|
+
} else {
|
|
2650
|
+
// No persistent writer exists, close stream directly
|
|
2651
|
+
// This path is taken when no writes have been queued
|
|
2652
|
+
try {
|
|
2653
|
+
const writer = this.port.writable.getWriter();
|
|
2654
|
+
await writer.close();
|
|
2655
|
+
writer.releaseLock();
|
|
2656
|
+
} catch (err) {
|
|
2657
|
+
this.logger.debug(`Direct writer close error: ${err}`);
|
|
2592
2658
|
}
|
|
2593
|
-
|
|
2594
|
-
await new Promise((resolve) => {
|
|
2595
|
-
if (!this._reader) {
|
|
2596
|
-
resolve(undefined);
|
|
2597
|
-
return;
|
|
2598
|
-
}
|
|
2599
|
-
this.addEventListener("disconnect", resolve, { once: true });
|
|
2600
|
-
this._reader!.cancel();
|
|
2601
|
-
});
|
|
2602
|
-
this.connected = false;
|
|
2603
|
-
} finally {
|
|
2604
|
-
this._isReconfiguring = false;
|
|
2605
2659
|
}
|
|
2660
|
+
|
|
2661
|
+
await new Promise((resolve) => {
|
|
2662
|
+
if (!this._reader) {
|
|
2663
|
+
resolve(undefined);
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
this.addEventListener("disconnect", resolve, { once: true });
|
|
2667
|
+
this._reader!.cancel();
|
|
2668
|
+
});
|
|
2669
|
+
this.connected = false;
|
|
2606
2670
|
}
|
|
2607
2671
|
|
|
2608
2672
|
/**
|
|
@@ -2980,8 +3044,9 @@ export class ESPLoader extends EventTarget {
|
|
|
2980
3044
|
newResp.set(packetData, resp.length);
|
|
2981
3045
|
resp = newResp;
|
|
2982
3046
|
|
|
2983
|
-
// Send acknowledgment
|
|
2984
|
-
//
|
|
3047
|
+
// Send acknowledgment when we've received maxInFlight bytes
|
|
3048
|
+
// The stub sends packets until (num_sent - num_acked) >= max_in_flight
|
|
3049
|
+
// We MUST wait for all packets before sending ACK
|
|
2985
3050
|
const shouldAck =
|
|
2986
3051
|
resp.length >= chunkSize || // End of chunk
|
|
2987
3052
|
resp.length >= lastAckedLength + maxInFlight; // Received all packets
|
|
@@ -3225,10 +3290,55 @@ class EspStubLoader extends ESPLoader {
|
|
|
3225
3290
|
}
|
|
3226
3291
|
|
|
3227
3292
|
/**
|
|
3228
|
-
* @name
|
|
3229
|
-
*
|
|
3293
|
+
* @name eraseFlash
|
|
3294
|
+
* Erase entire flash chip
|
|
3230
3295
|
*/
|
|
3231
3296
|
async eraseFlash() {
|
|
3232
3297
|
await this.checkCommand(ESP_ERASE_FLASH, [], 0, CHIP_ERASE_TIMEOUT);
|
|
3233
3298
|
}
|
|
3299
|
+
|
|
3300
|
+
/**
|
|
3301
|
+
* @name eraseRegion
|
|
3302
|
+
* Erase a specific region of flash
|
|
3303
|
+
*/
|
|
3304
|
+
async eraseRegion(offset: number, size: number) {
|
|
3305
|
+
// Validate inputs
|
|
3306
|
+
if (offset < 0) {
|
|
3307
|
+
throw new Error(`Invalid offset: ${offset} (must be non-negative)`);
|
|
3308
|
+
}
|
|
3309
|
+
if (size < 0) {
|
|
3310
|
+
throw new Error(`Invalid size: ${size} (must be non-negative)`);
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
// No-op for zero size
|
|
3314
|
+
if (size === 0) {
|
|
3315
|
+
this.logger.log("eraseRegion: size is 0, skipping erase");
|
|
3316
|
+
return;
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
// Check for sector alignment
|
|
3320
|
+
if (offset % FLASH_SECTOR_SIZE !== 0) {
|
|
3321
|
+
throw new Error(
|
|
3322
|
+
`Offset ${offset} (0x${offset.toString(16)}) is not aligned to flash sector size ${FLASH_SECTOR_SIZE} (0x${FLASH_SECTOR_SIZE.toString(16)})`,
|
|
3323
|
+
);
|
|
3324
|
+
}
|
|
3325
|
+
if (size % FLASH_SECTOR_SIZE !== 0) {
|
|
3326
|
+
throw new Error(
|
|
3327
|
+
`Size ${size} (0x${size.toString(16)}) is not aligned to flash sector size ${FLASH_SECTOR_SIZE} (0x${FLASH_SECTOR_SIZE.toString(16)})`,
|
|
3328
|
+
);
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
// Check for reasonable bounds (prevent wrapping in pack)
|
|
3332
|
+
const maxValue = 0xffffffff; // 32-bit unsigned max
|
|
3333
|
+
if (offset > maxValue) {
|
|
3334
|
+
throw new Error(`Offset ${offset} exceeds maximum value ${maxValue}`);
|
|
3335
|
+
}
|
|
3336
|
+
if (size > maxValue) {
|
|
3337
|
+
throw new Error(`Size ${size} exceeds maximum value ${maxValue}`);
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
const timeout = timeoutPerMb(ERASE_REGION_TIMEOUT_PER_MB, size);
|
|
3341
|
+
const buffer = pack("<II", offset, size);
|
|
3342
|
+
await this.checkCommand(ESP_ERASE_REGION, buffer, 0, timeout);
|
|
3343
|
+
}
|
|
3234
3344
|
}
|