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.
@@ -3,10 +3,10 @@ export declare class ESPLoader extends EventTarget {
3
3
  port: SerialPort;
4
4
  logger: Logger;
5
5
  private _parent?;
6
- chipFamily: ChipFamily;
7
- chipName: string | null;
8
- chipRevision: number | null;
9
- chipVariant: string | null;
6
+ __chipFamily?: ChipFamily;
7
+ __chipName: string | null;
8
+ __chipRevision: number | null;
9
+ __chipVariant: string | null;
10
10
  _efuses: any[];
11
11
  _flashsize: number;
12
12
  debug: boolean;
@@ -30,6 +30,14 @@ export declare class ESPLoader extends EventTarget {
30
30
  private __lastAdaptiveAdjustment;
31
31
  private __isCDCDevice;
32
32
  constructor(port: SerialPort, logger: Logger, _parent?: ESPLoader | undefined);
33
+ get chipFamily(): ChipFamily;
34
+ set chipFamily(value: ChipFamily);
35
+ get chipName(): string | null;
36
+ set chipName(value: string | null);
37
+ get chipRevision(): number | null;
38
+ set chipRevision(value: number | null);
39
+ get chipVariant(): string | null;
40
+ set chipVariant(value: string | null);
33
41
  private get _inputBuffer();
34
42
  private get _inputBufferReadIndex();
35
43
  private set _inputBufferReadIndex(value);
@@ -75,6 +83,10 @@ export declare class ESPLoader extends EventTarget {
75
83
  chipId: number;
76
84
  apiVersion: number;
77
85
  }>;
86
+ /**
87
+ * Get MAC address from efuses
88
+ */
89
+ getMacAddress(): Promise<string>;
78
90
  /**
79
91
  * @name readLoop
80
92
  * Reads data from the input stream and places it in the inputBuffer
@@ -333,9 +345,14 @@ declare class EspStubLoader extends ESPLoader {
333
345
  */
334
346
  memBegin(size: number, _blocks: number, _blocksize: number, offset: number): Promise<[number, number[]]>;
335
347
  /**
336
- * @name getEraseSize
337
- * depending on flash chip model the erase may take this long (maybe longer!)
348
+ * @name eraseFlash
349
+ * Erase entire flash chip
338
350
  */
339
351
  eraseFlash(): Promise<void>;
352
+ /**
353
+ * @name eraseRegion
354
+ * Erase a specific region of flash
355
+ */
356
+ eraseRegion(offset: number, size: number): Promise<void>;
340
357
  }
341
358
  export {};
@@ -1,5 +1,5 @@
1
1
  /// <reference types="@types/w3c-web-serial" />
2
- import { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, CHIP_FAMILY_ESP32C2, CHIP_FAMILY_ESP32C3, CHIP_FAMILY_ESP32C5, CHIP_FAMILY_ESP32C6, CHIP_FAMILY_ESP32C61, CHIP_FAMILY_ESP32H2, CHIP_FAMILY_ESP32H4, CHIP_FAMILY_ESP32H21, CHIP_FAMILY_ESP32P4, CHIP_FAMILY_ESP32S31, CHIP_FAMILY_ESP8266, MAX_TIMEOUT, DEFAULT_TIMEOUT, ERASE_REGION_TIMEOUT_PER_MB, ESP_CHANGE_BAUDRATE, ESP_CHECKSUM_MAGIC, ESP_FLASH_BEGIN, ESP_FLASH_DATA, ESP_FLASH_END, ESP_MEM_BEGIN, ESP_MEM_DATA, ESP_MEM_END, ESP_READ_REG, ESP_WRITE_REG, ESP_SPI_ATTACH, ESP_SYNC, ESP_GET_SECURITY_INFO, FLASH_SECTOR_SIZE, FLASH_WRITE_SIZE, STUB_FLASH_WRITE_SIZE, MEM_END_ROM_TIMEOUT, ROM_INVALID_RECV_MSG, SYNC_PACKET, SYNC_TIMEOUT, USB_RAM_BLOCK, ESP_ERASE_FLASH, ESP_READ_FLASH, CHIP_ERASE_TIMEOUT, FLASH_READ_TIMEOUT, timeoutPerMb, ESP_ROM_BAUD, USB_JTAG_SERIAL_PID, ESP_FLASH_DEFL_BEGIN, ESP_FLASH_DEFL_DATA, ESP_FLASH_DEFL_END, getSpiFlashAddresses, DETECTED_FLASH_SIZES, CHIP_DETECT_MAGIC_REG_ADDR, CHIP_DETECT_MAGIC_VALUES, CHIP_ID_TO_INFO, ESP32P4_EFUSE_BLOCK1_ADDR, SlipReadError, } from "./const";
2
+ import { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, CHIP_FAMILY_ESP32C2, CHIP_FAMILY_ESP32C3, CHIP_FAMILY_ESP32C5, CHIP_FAMILY_ESP32C6, CHIP_FAMILY_ESP32C61, CHIP_FAMILY_ESP32H2, CHIP_FAMILY_ESP32H4, CHIP_FAMILY_ESP32H21, CHIP_FAMILY_ESP32P4, CHIP_FAMILY_ESP32S31, CHIP_FAMILY_ESP8266, MAX_TIMEOUT, DEFAULT_TIMEOUT, ERASE_REGION_TIMEOUT_PER_MB, ESP_CHANGE_BAUDRATE, ESP_CHECKSUM_MAGIC, ESP_FLASH_BEGIN, ESP_FLASH_DATA, ESP_FLASH_END, ESP_MEM_BEGIN, ESP_MEM_DATA, ESP_MEM_END, ESP_READ_REG, ESP_WRITE_REG, ESP_SPI_ATTACH, ESP_SYNC, ESP_GET_SECURITY_INFO, FLASH_SECTOR_SIZE, FLASH_WRITE_SIZE, STUB_FLASH_WRITE_SIZE, MEM_END_ROM_TIMEOUT, ROM_INVALID_RECV_MSG, SYNC_PACKET, SYNC_TIMEOUT, USB_RAM_BLOCK, ESP_ERASE_FLASH, ESP_ERASE_REGION, ESP_READ_FLASH, CHIP_ERASE_TIMEOUT, FLASH_READ_TIMEOUT, timeoutPerMb, ESP_ROM_BAUD, USB_JTAG_SERIAL_PID, ESP_FLASH_DEFL_BEGIN, ESP_FLASH_DEFL_DATA, ESP_FLASH_DEFL_END, getSpiFlashAddresses, DETECTED_FLASH_SIZES, CHIP_DETECT_MAGIC_REG_ADDR, CHIP_DETECT_MAGIC_VALUES, CHIP_ID_TO_INFO, ESP32P4_EFUSE_BLOCK1_ADDR, SlipReadError, } from "./const";
3
3
  import { getStubCode } from "./stubs";
4
4
  import { hexFormatter, sleep, slipEncode, toHex } from "./util";
5
5
  import { deflate } from "pako";
@@ -10,9 +10,9 @@ export class ESPLoader extends EventTarget {
10
10
  this.port = port;
11
11
  this.logger = logger;
12
12
  this._parent = _parent;
13
- this.chipName = null;
14
- this.chipRevision = null;
15
- this.chipVariant = null;
13
+ this.__chipName = null;
14
+ this.__chipRevision = null;
15
+ this.__chipVariant = null;
16
16
  this._efuses = new Array(4).fill(0);
17
17
  this._flashsize = 4 * 1024 * 1024;
18
18
  this.debug = false;
@@ -35,6 +35,51 @@ export class ESPLoader extends EventTarget {
35
35
  this.state_DTR = false;
36
36
  this.__writeChain = Promise.resolve();
37
37
  }
38
+ // Chip properties with parent delegation
39
+ get chipFamily() {
40
+ return this._parent ? this._parent.chipFamily : this.__chipFamily;
41
+ }
42
+ set chipFamily(value) {
43
+ if (this._parent) {
44
+ this._parent.chipFamily = value;
45
+ }
46
+ else {
47
+ this.__chipFamily = value;
48
+ }
49
+ }
50
+ get chipName() {
51
+ return this._parent ? this._parent.chipName : this.__chipName;
52
+ }
53
+ set chipName(value) {
54
+ if (this._parent) {
55
+ this._parent.chipName = value;
56
+ }
57
+ else {
58
+ this.__chipName = value;
59
+ }
60
+ }
61
+ get chipRevision() {
62
+ return this._parent ? this._parent.chipRevision : this.__chipRevision;
63
+ }
64
+ set chipRevision(value) {
65
+ if (this._parent) {
66
+ this._parent.chipRevision = value;
67
+ }
68
+ else {
69
+ this.__chipRevision = value;
70
+ }
71
+ }
72
+ get chipVariant() {
73
+ return this._parent ? this._parent.chipVariant : this.__chipVariant;
74
+ }
75
+ set chipVariant(value) {
76
+ if (this._parent) {
77
+ this._parent.chipVariant = value;
78
+ }
79
+ else {
80
+ this.__chipVariant = value;
81
+ }
82
+ }
38
83
  get _inputBuffer() {
39
84
  return this._parent ? this._parent._inputBuffer : this.__inputBuffer;
40
85
  }
@@ -390,6 +435,18 @@ export class ESPLoader extends EventTarget {
390
435
  apiVersion,
391
436
  };
392
437
  }
438
+ /**
439
+ * Get MAC address from efuses
440
+ */
441
+ async getMacAddress() {
442
+ if (!this._initializationSucceeded) {
443
+ throw new Error("getMacAddress() requires initialize() to have completed successfully");
444
+ }
445
+ const macBytes = this.macAddr(); // chip-family-aware
446
+ return macBytes
447
+ .map((b) => b.toString(16).padStart(2, "0").toUpperCase())
448
+ .join(":");
449
+ }
393
450
  /**
394
451
  * @name readLoop
395
452
  * Reads data from the input stream and places it in the inputBuffer
@@ -422,6 +479,12 @@ export class ESPLoader extends EventTarget {
422
479
  catch {
423
480
  this.logger.error("Read loop got disconnected");
424
481
  }
482
+ finally {
483
+ // Always reset reconfiguring flag when read loop ends
484
+ // This prevents "Cannot write during port reconfiguration" errors
485
+ // when the read loop dies unexpectedly
486
+ this._isReconfiguring = false;
487
+ }
425
488
  // Disconnected!
426
489
  this.connected = false;
427
490
  // Check if this is ESP32-S2 Native USB that needs port reselection
@@ -2064,52 +2127,45 @@ export class ESPLoader extends EventTarget {
2064
2127
  this.logger.debug("Port already closed, skipping disconnect");
2065
2128
  return;
2066
2129
  }
2130
+ // Wait for pending writes to complete
2067
2131
  try {
2068
- // Wait for pending writes to complete
2132
+ await this._writeChain;
2133
+ }
2134
+ catch (err) {
2135
+ this.logger.debug(`Pending write error during disconnect: ${err}`);
2136
+ }
2137
+ // Release persistent writer before closing
2138
+ if (this._writer) {
2069
2139
  try {
2070
- await this._writeChain;
2140
+ await this._writer.close();
2141
+ this._writer.releaseLock();
2071
2142
  }
2072
2143
  catch (err) {
2073
- this.logger.debug(`Pending write error during disconnect: ${err}`);
2144
+ this.logger.debug(`Writer close/release error: ${err}`);
2074
2145
  }
2075
- // Block new writes during disconnect
2076
- this._isReconfiguring = true;
2077
- // Release persistent writer before closing
2078
- if (this._writer) {
2079
- try {
2080
- await this._writer.close();
2081
- this._writer.releaseLock();
2082
- }
2083
- catch (err) {
2084
- this.logger.debug(`Writer close/release error: ${err}`);
2085
- }
2086
- this._writer = undefined;
2146
+ this._writer = undefined;
2147
+ }
2148
+ else {
2149
+ // No persistent writer exists, close stream directly
2150
+ // This path is taken when no writes have been queued
2151
+ try {
2152
+ const writer = this.port.writable.getWriter();
2153
+ await writer.close();
2154
+ writer.releaseLock();
2087
2155
  }
2088
- else {
2089
- // No persistent writer exists, close stream directly
2090
- // This path is taken when no writes have been queued
2091
- try {
2092
- const writer = this.port.writable.getWriter();
2093
- await writer.close();
2094
- writer.releaseLock();
2095
- }
2096
- catch (err) {
2097
- this.logger.debug(`Direct writer close error: ${err}`);
2098
- }
2156
+ catch (err) {
2157
+ this.logger.debug(`Direct writer close error: ${err}`);
2099
2158
  }
2100
- await new Promise((resolve) => {
2101
- if (!this._reader) {
2102
- resolve(undefined);
2103
- return;
2104
- }
2105
- this.addEventListener("disconnect", resolve, { once: true });
2106
- this._reader.cancel();
2107
- });
2108
- this.connected = false;
2109
- }
2110
- finally {
2111
- this._isReconfiguring = false;
2112
2159
  }
2160
+ await new Promise((resolve) => {
2161
+ if (!this._reader) {
2162
+ resolve(undefined);
2163
+ return;
2164
+ }
2165
+ this.addEventListener("disconnect", resolve, { once: true });
2166
+ this._reader.cancel();
2167
+ });
2168
+ this.connected = false;
2113
2169
  }
2114
2170
  /**
2115
2171
  * @name reconnectAndResume
@@ -2412,8 +2468,9 @@ export class ESPLoader extends EventTarget {
2412
2468
  newResp.set(resp);
2413
2469
  newResp.set(packetData, resp.length);
2414
2470
  resp = newResp;
2415
- // Send acknowledgment after receiving maxInFlight bytes
2416
- // This unblocks the stub to send the next batch of packets
2471
+ // Send acknowledgment when we've received maxInFlight bytes
2472
+ // The stub sends packets until (num_sent - num_acked) >= max_in_flight
2473
+ // We MUST wait for all packets before sending ACK
2417
2474
  const shouldAck = resp.length >= chunkSize || // End of chunk
2418
2475
  resp.length >= lastAckedLength + maxInFlight; // Received all packets
2419
2476
  if (shouldAck) {
@@ -2589,10 +2646,46 @@ class EspStubLoader extends ESPLoader {
2589
2646
  return [0, []];
2590
2647
  }
2591
2648
  /**
2592
- * @name getEraseSize
2593
- * depending on flash chip model the erase may take this long (maybe longer!)
2649
+ * @name eraseFlash
2650
+ * Erase entire flash chip
2594
2651
  */
2595
2652
  async eraseFlash() {
2596
2653
  await this.checkCommand(ESP_ERASE_FLASH, [], 0, CHIP_ERASE_TIMEOUT);
2597
2654
  }
2655
+ /**
2656
+ * @name eraseRegion
2657
+ * Erase a specific region of flash
2658
+ */
2659
+ async eraseRegion(offset, size) {
2660
+ // Validate inputs
2661
+ if (offset < 0) {
2662
+ throw new Error(`Invalid offset: ${offset} (must be non-negative)`);
2663
+ }
2664
+ if (size < 0) {
2665
+ throw new Error(`Invalid size: ${size} (must be non-negative)`);
2666
+ }
2667
+ // No-op for zero size
2668
+ if (size === 0) {
2669
+ this.logger.log("eraseRegion: size is 0, skipping erase");
2670
+ return;
2671
+ }
2672
+ // Check for sector alignment
2673
+ if (offset % FLASH_SECTOR_SIZE !== 0) {
2674
+ throw new Error(`Offset ${offset} (0x${offset.toString(16)}) is not aligned to flash sector size ${FLASH_SECTOR_SIZE} (0x${FLASH_SECTOR_SIZE.toString(16)})`);
2675
+ }
2676
+ if (size % FLASH_SECTOR_SIZE !== 0) {
2677
+ throw new Error(`Size ${size} (0x${size.toString(16)}) is not aligned to flash sector size ${FLASH_SECTOR_SIZE} (0x${FLASH_SECTOR_SIZE.toString(16)})`);
2678
+ }
2679
+ // Check for reasonable bounds (prevent wrapping in pack)
2680
+ const maxValue = 0xffffffff; // 32-bit unsigned max
2681
+ if (offset > maxValue) {
2682
+ throw new Error(`Offset ${offset} exceeds maximum value ${maxValue}`);
2683
+ }
2684
+ if (size > maxValue) {
2685
+ throw new Error(`Size ${size} exceeds maximum value ${maxValue}`);
2686
+ }
2687
+ const timeout = timeoutPerMb(ERASE_REGION_TIMEOUT_PER_MB, size);
2688
+ const buffer = pack("<II", offset, size);
2689
+ await this.checkCommand(ESP_ERASE_REGION, buffer, 0, timeout);
2690
+ }
2598
2691
  }