tasmota-webserial-esptool 7.0.1 → 7.2.0

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/src/esp_loader.ts CHANGED
@@ -62,7 +62,7 @@ import {
62
62
  } from "./const";
63
63
  import { getStubCode } from "./stubs";
64
64
  import { hexFormatter, sleep, slipEncode, toHex } from "./util";
65
- // @ts-ignore
65
+ // @ts-expect-error pako ESM module doesn't have proper type definitions
66
66
  import { deflate } from "pako/dist/pako.esm.mjs";
67
67
  import { pack, unpack } from "./struct";
68
68
 
@@ -83,6 +83,8 @@ export class ESPLoader extends EventTarget {
83
83
  private _currentBaudRate: number = ESP_ROM_BAUD;
84
84
  private _maxUSBSerialBaudrate?: number;
85
85
  private _reader?: ReadableStreamDefaultReader<Uint8Array>;
86
+ private _isESP32S2NativeUSB: boolean = false;
87
+ private _initializationSucceeded: boolean = false;
86
88
 
87
89
  constructor(
88
90
  public port: SerialPort,
@@ -121,8 +123,13 @@ export class ESPLoader extends EventTarget {
121
123
  > = {
122
124
  0x1a86: {
123
125
  // QinHeng Electronics
126
+ 0x7522: { name: "CH340", maxBaudrate: 460800 },
124
127
  0x7523: { name: "CH340", maxBaudrate: 460800 },
128
+ 0x7584: { name: "CH340", maxBaudrate: 460800 },
129
+ 0x5523: { name: "CH341", maxBaudrate: 2000000 },
130
+ 0x55d3: { name: "CH343", maxBaudrate: 6000000 },
125
131
  0x55d4: { name: "CH9102", maxBaudrate: 6000000 },
132
+ 0x55d8: { name: "CH9101", maxBaudrate: 3000000 },
126
133
  },
127
134
  0x10c4: {
128
135
  // Silicon Labs
@@ -179,6 +186,10 @@ export class ESPLoader extends EventTarget {
179
186
  this._maxUSBSerialBaudrate = chipInfo.maxBaudrate;
180
187
  this.logger.log(`Max baudrate: ${chipInfo.maxBaudrate}`);
181
188
  }
189
+ // Detect ESP32-S2 Native USB
190
+ if (portInfo.usbVendorId === 0x303a && portInfo.usbProductId === 0x2) {
191
+ this._isESP32S2NativeUSB = true;
192
+ }
182
193
  }
183
194
 
184
195
  // Don't await this promise so it doesn't block rest of method.
@@ -193,8 +204,8 @@ export class ESPLoader extends EventTarget {
193
204
  await this.detectChip();
194
205
 
195
206
  // Read the OTP data for this chip and store into this.efuses array
196
- let FlAddr = getSpiFlashAddresses(this.getChipFamily());
197
- let AddrMAC = FlAddr.macFuse;
207
+ const FlAddr = getSpiFlashAddresses(this.getChipFamily());
208
+ const AddrMAC = FlAddr.macFuse;
198
209
  for (let i = 0; i < 4; i++) {
199
210
  this._efuses[i] = await this.readRegister(AddrMAC + 4 * i);
200
211
  }
@@ -202,6 +213,9 @@ export class ESPLoader extends EventTarget {
202
213
  this.logger.debug(
203
214
  `Bootloader flash offset: 0x${FlAddr.flashOffs.toString(16)}`,
204
215
  );
216
+
217
+ // Mark initialization as successful
218
+ this._initializationSucceeded = true;
205
219
  }
206
220
 
207
221
  /**
@@ -241,10 +255,10 @@ export class ESPLoader extends EventTarget {
241
255
  this.logger.debug(
242
256
  `Unknown IMAGE_CHIP_ID: ${chipId}, falling back to magic value detection`,
243
257
  );
244
- } catch (err) {
258
+ } catch (error) {
245
259
  // GET_SECURITY_INFO not supported, fall back to magic value detection
246
260
  this.logger.debug(
247
- `GET_SECURITY_INFO failed, using magic value detection: ${err}`,
261
+ `GET_SECURITY_INFO failed, using magic value detection: ${error}`,
248
262
  );
249
263
 
250
264
  // Clear input buffer and re-sync to recover from failed command
@@ -261,9 +275,9 @@ export class ESPLoader extends EventTarget {
261
275
  }
262
276
  }
263
277
 
264
- // Fallback: Use magic value detection for ESP8266, ESP32, ESP32-S2, and ESP32-P4 RC versions
265
- let chipMagicValue = await this.readRegister(CHIP_DETECT_MAGIC_REG_ADDR);
266
- let chip = CHIP_DETECT_MAGIC_VALUES[chipMagicValue >>> 0];
278
+ // Fallback: Use magic value detection for ESP8266, ESP32, ESP32-S2
279
+ const chipMagicValue = await this.readRegister(CHIP_DETECT_MAGIC_REG_ADDR);
280
+ const chip = CHIP_DETECT_MAGIC_VALUES[chipMagicValue >>> 0];
267
281
  if (chip === undefined) {
268
282
  throw new Error(
269
283
  `Unknown Chip: Hex: ${toHex(
@@ -275,7 +289,6 @@ export class ESPLoader extends EventTarget {
275
289
  this.chipName = chip.name;
276
290
  this.chipFamily = chip.family;
277
291
 
278
- // For ESP32-P4 detected via magic value (old revisions), set variant
279
292
  if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
280
293
  this.chipRevision = await this.getChipRevision();
281
294
  this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
@@ -325,7 +338,7 @@ export class ESPLoader extends EventTarget {
325
338
  chipId: number;
326
339
  apiVersion: number;
327
340
  }> {
328
- const [_, responseData] = await this.checkCommand(
341
+ const [, responseData] = await this.checkCommand(
329
342
  ESP_GET_SECURITY_INFO,
330
343
  [],
331
344
  0,
@@ -377,10 +390,12 @@ export class ESPLoader extends EventTarget {
377
390
  this._reader = this.port.readable!.getReader();
378
391
 
379
392
  try {
380
- while (true) {
393
+ let keepReading = true;
394
+ while (keepReading) {
381
395
  const { value, done } = await this._reader.read();
382
396
  if (done) {
383
397
  this._reader.releaseLock();
398
+ keepReading = false;
384
399
  break;
385
400
  }
386
401
  if (!value || value.length === 0) {
@@ -395,11 +410,25 @@ export class ESPLoader extends EventTarget {
395
410
  // Track total bytes read from serial port
396
411
  this._totalBytesRead += value.length;
397
412
  }
398
- } catch (err) {
399
- console.error("Read loop got disconnected");
413
+ } catch {
414
+ this.logger.error("Read loop got disconnected");
400
415
  }
401
416
  // Disconnected!
402
417
  this.connected = false;
418
+
419
+ // Check if this is ESP32-S2 Native USB that needs port reselection
420
+ // Only trigger reconnect if initialization did NOT succeed (wrong port)
421
+ if (this._isESP32S2NativeUSB && !this._initializationSucceeded) {
422
+ this.logger.log(
423
+ "ESP32-S2 Native USB detected - requesting port reselection",
424
+ );
425
+ this.dispatchEvent(
426
+ new CustomEvent("esp32s2-usb-reconnect", {
427
+ detail: { message: "ESP32-S2 Native USB requires port reselection" },
428
+ }),
429
+ );
430
+ }
431
+
403
432
  this.dispatchEvent(new Event("disconnect"));
404
433
  this.logger.debug("Finished read loop");
405
434
  }
@@ -474,11 +503,11 @@ export class ESPLoader extends EventTarget {
474
503
  * The MAC address burned into the OTP memory of the ESP chip
475
504
  */
476
505
  macAddr() {
477
- let macAddr = new Array(6).fill(0);
478
- let mac0 = this._efuses[0];
479
- let mac1 = this._efuses[1];
480
- let mac2 = this._efuses[2];
481
- let mac3 = this._efuses[3];
506
+ const macAddr = new Array(6).fill(0);
507
+ const mac0 = this._efuses[0];
508
+ const mac1 = this._efuses[1];
509
+ const mac2 = this._efuses[2];
510
+ const mac3 = this._efuses[3];
482
511
  let oui;
483
512
  if (this.chipFamily == CHIP_FAMILY_ESP8266) {
484
513
  if (mac3 != 0) {
@@ -534,9 +563,9 @@ export class ESPLoader extends EventTarget {
534
563
  if (this.debug) {
535
564
  this.logger.debug("Reading from Register " + toHex(reg, 8));
536
565
  }
537
- let packet = pack("<I", reg);
566
+ const packet = pack("<I", reg);
538
567
  await this.sendCommand(ESP_READ_REG, packet);
539
- let [val, _data] = await this.getResponse(ESP_READ_REG);
568
+ const [val] = await this.getResponse(ESP_READ_REG);
540
569
  return val;
541
570
  }
542
571
 
@@ -554,12 +583,13 @@ export class ESPLoader extends EventTarget {
554
583
  ): Promise<[number, number[]]> {
555
584
  timeout = Math.min(timeout, MAX_TIMEOUT);
556
585
  await this.sendCommand(opcode, buffer, checksum);
557
- let [value, data] = await this.getResponse(opcode, timeout);
586
+ const [value, responseData] = await this.getResponse(opcode, timeout);
558
587
 
559
- if (data === null) {
588
+ if (responseData === null) {
560
589
  throw new Error("Didn't get enough status bytes");
561
590
  }
562
591
 
592
+ let data = responseData;
563
593
  let statusLen = 0;
564
594
 
565
595
  if (this.IS_STUB || this.chipFamily == CHIP_FAMILY_ESP8266) {
@@ -595,7 +625,7 @@ export class ESPLoader extends EventTarget {
595
625
  if (data.length < statusLen) {
596
626
  throw new Error("Didn't get enough status bytes");
597
627
  }
598
- let status = data.slice(-statusLen, data.length);
628
+ const status = data.slice(-statusLen, data.length);
599
629
  data = data.slice(0, -statusLen);
600
630
  if (this.debug) {
601
631
  this.logger.debug("status", status);
@@ -619,7 +649,7 @@ export class ESPLoader extends EventTarget {
619
649
  * does not check response
620
650
  */
621
651
  async sendCommand(opcode: number, buffer: number[], checksum = 0) {
622
- let packet = slipEncode([
652
+ const packet = slipEncode([
623
653
  ...pack("<BBHI", 0x00, opcode, buffer.length, checksum),
624
654
  ...buffer,
625
655
  ]);
@@ -643,7 +673,7 @@ export class ESPLoader extends EventTarget {
643
673
  let inEscape = false;
644
674
  let readBytes: number[] = [];
645
675
  while (true) {
646
- let stamp = Date.now();
676
+ const stamp = Date.now();
647
677
  readBytes = [];
648
678
  while (Date.now() - stamp < timeout) {
649
679
  if (this._inputBuffer.length > 0) {
@@ -655,14 +685,14 @@ export class ESPLoader extends EventTarget {
655
685
  }
656
686
  }
657
687
  if (readBytes.length == 0) {
658
- let waitingFor = partialPacket === null ? "header" : "content";
688
+ const waitingFor = partialPacket === null ? "header" : "content";
659
689
  throw new SlipReadError("Timed out waiting for packet " + waitingFor);
660
690
  }
661
691
  if (this.debug)
662
692
  this.logger.debug(
663
693
  "Read " + readBytes.length + " bytes: " + hexFormatter(readBytes),
664
694
  );
665
- for (let b of readBytes) {
695
+ for (const b of readBytes) {
666
696
  if (partialPacket === null) {
667
697
  // waiting for packet header
668
698
  if (b == 0xc0) {
@@ -738,7 +768,7 @@ export class ESPLoader extends EventTarget {
738
768
  continue;
739
769
  }
740
770
 
741
- const [resp, opRet, _lenRet, val] = unpack("<BBHI", packet.slice(0, 8));
771
+ const [resp, opRet, , val] = unpack("<BBHI", packet.slice(0, 8));
742
772
  if (resp != 1) {
743
773
  continue;
744
774
  }
@@ -759,7 +789,7 @@ export class ESPLoader extends EventTarget {
759
789
  * Calculate checksum of a blob, as it is defined by the ROM
760
790
  */
761
791
  checksum(data: number[], state = ESP_CHECKSUM_MAGIC) {
762
- for (let b of data) {
792
+ for (const b of data) {
763
793
  state ^= b;
764
794
  }
765
795
  return state;
@@ -772,10 +802,10 @@ export class ESPLoader extends EventTarget {
772
802
 
773
803
  try {
774
804
  // Send ESP_ROM_BAUD(115200) as the old one if running STUB otherwise 0
775
- let buffer = pack("<II", baud, this.IS_STUB ? ESP_ROM_BAUD : 0);
805
+ const buffer = pack("<II", baud, this.IS_STUB ? ESP_ROM_BAUD : 0);
776
806
  await this.checkCommand(ESP_CHANGE_BAUDRATE, buffer);
777
807
  } catch (e) {
778
- console.error(e);
808
+ this.logger.error(`Baudrate change error: ${e}`);
779
809
  throw new Error(
780
810
  `Unable to change the baud rate to ${baud}: No response from set baud rate command.`,
781
811
  );
@@ -828,7 +858,7 @@ export class ESPLoader extends EventTarget {
828
858
  // Restart Readloop
829
859
  this.readLoop();
830
860
  } catch (e) {
831
- console.error(e);
861
+ this.logger.error(`Reconfigure port error: ${e}`);
832
862
  throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
833
863
  }
834
864
  }
@@ -841,7 +871,7 @@ export class ESPLoader extends EventTarget {
841
871
  async sync() {
842
872
  for (let i = 0; i < 5; i++) {
843
873
  this._inputBuffer.length = 0;
844
- let response = await this._sync();
874
+ const response = await this._sync();
845
875
  if (response) {
846
876
  await sleep(SYNC_TIMEOUT);
847
877
  return true;
@@ -861,11 +891,11 @@ export class ESPLoader extends EventTarget {
861
891
  await this.sendCommand(ESP_SYNC, SYNC_PACKET);
862
892
  for (let i = 0; i < 8; i++) {
863
893
  try {
864
- let [_reply, data] = await this.getResponse(ESP_SYNC, SYNC_TIMEOUT);
894
+ const [, data] = await this.getResponse(ESP_SYNC, SYNC_TIMEOUT);
865
895
  if (data.length > 1 && data[0] == 0 && data[1] == 0) {
866
896
  return true;
867
897
  }
868
- } catch (err) {
898
+ } catch {
869
899
  // If read packet fails.
870
900
  }
871
901
  }
@@ -898,10 +928,10 @@ export class ESPLoader extends EventTarget {
898
928
  ) {
899
929
  if (binaryData.byteLength >= 8) {
900
930
  // unpack the (potential) image header
901
- var header = Array.from(new Uint8Array(binaryData, 0, 4));
902
- let headerMagic = header[0];
903
- let headerFlashMode = header[2];
904
- let headerFlashSizeFreq = header[3];
931
+ const header = Array.from(new Uint8Array(binaryData, 0, 4));
932
+ const headerMagic = header[0];
933
+ const headerFlashMode = header[2];
934
+ const headerFlashSizeFreq = header[3];
905
935
 
906
936
  this.logger.log(
907
937
  `Image header, Magic=${toHex(headerMagic)}, FlashMode=${toHex(
@@ -910,7 +940,7 @@ export class ESPLoader extends EventTarget {
910
940
  );
911
941
  }
912
942
 
913
- let uncompressedFilesize = binaryData.byteLength;
943
+ const uncompressedFilesize = binaryData.byteLength;
914
944
  let compressedFilesize = 0;
915
945
 
916
946
  let dataToFlash;
@@ -939,10 +969,10 @@ export class ESPLoader extends EventTarget {
939
969
  let seq = 0;
940
970
  let written = 0;
941
971
  let position = 0;
942
- let stamp = Date.now();
943
- let flashWriteSize = this.getFlashWriteSize();
972
+ const stamp = Date.now();
973
+ const flashWriteSize = this.getFlashWriteSize();
944
974
 
945
- let filesize = compress ? compressedFilesize : uncompressedFilesize;
975
+ const filesize = compress ? compressedFilesize : uncompressedFilesize;
946
976
 
947
977
  while (filesize - position > 0) {
948
978
  if (this.debug) {
@@ -1028,8 +1058,7 @@ export class ESPLoader extends EventTarget {
1028
1058
  await this.flushSerialBuffers();
1029
1059
 
1030
1060
  let eraseSize;
1031
- let buffer;
1032
- let flashWriteSize = this.getFlashWriteSize();
1061
+ const flashWriteSize = this.getFlashWriteSize();
1033
1062
  if (
1034
1063
  !this.IS_STUB &&
1035
1064
  [
@@ -1050,22 +1079,19 @@ export class ESPLoader extends EventTarget {
1050
1079
  ) {
1051
1080
  await this.checkCommand(ESP_SPI_ATTACH, new Array(8).fill(0));
1052
1081
  }
1053
- let numBlocks = Math.floor((size + flashWriteSize - 1) / flashWriteSize);
1082
+ const numBlocks = Math.floor((size + flashWriteSize - 1) / flashWriteSize);
1054
1083
  if (this.chipFamily == CHIP_FAMILY_ESP8266) {
1055
1084
  eraseSize = this.getEraseSize(offset, size);
1056
1085
  } else {
1057
1086
  eraseSize = size;
1058
1087
  }
1059
1088
 
1060
- let timeout;
1061
- if (this.IS_STUB) {
1062
- timeout = DEFAULT_TIMEOUT;
1063
- } else {
1064
- timeout = timeoutPerMb(ERASE_REGION_TIMEOUT_PER_MB, size);
1065
- }
1089
+ const timeout = this.IS_STUB
1090
+ ? DEFAULT_TIMEOUT
1091
+ : timeoutPerMb(ERASE_REGION_TIMEOUT_PER_MB, size);
1066
1092
 
1067
- let stamp = Date.now();
1068
- buffer = pack("<IIII", eraseSize, numBlocks, flashWriteSize, offset);
1093
+ const stamp = Date.now();
1094
+ let buffer = pack("<IIII", eraseSize, numBlocks, flashWriteSize, offset);
1069
1095
  if (
1070
1096
  this.chipFamily == CHIP_FAMILY_ESP32 ||
1071
1097
  this.chipFamily == CHIP_FAMILY_ESP32S2 ||
@@ -1109,22 +1135,18 @@ export class ESPLoader extends EventTarget {
1109
1135
  *
1110
1136
  */
1111
1137
 
1112
- async flashDeflBegin(
1113
- size = 0,
1114
- compressedSize = 0,
1115
- offset = 0,
1116
- encrypted = false,
1117
- ) {
1138
+ async flashDeflBegin(size = 0, compressedSize = 0, offset = 0) {
1118
1139
  // Start downloading compressed data to Flash (performs an erase)
1119
1140
  // Returns number of blocks to write.
1120
- let flashWriteSize = this.getFlashWriteSize();
1121
- let numBlocks = Math.floor(
1141
+ const flashWriteSize = this.getFlashWriteSize();
1142
+ const numBlocks = Math.floor(
1122
1143
  (compressedSize + flashWriteSize - 1) / flashWriteSize,
1123
1144
  );
1124
- let eraseBlocks = Math.floor((size + flashWriteSize - 1) / flashWriteSize);
1145
+ const eraseBlocks = Math.floor(
1146
+ (size + flashWriteSize - 1) / flashWriteSize,
1147
+ );
1125
1148
  let writeSize = 0;
1126
1149
  let timeout = 0;
1127
- let buffer;
1128
1150
 
1129
1151
  if (this.IS_STUB) {
1130
1152
  writeSize = size; // stub expects number of bytes here, manages erasing internally
@@ -1133,7 +1155,7 @@ export class ESPLoader extends EventTarget {
1133
1155
  writeSize = eraseBlocks * flashWriteSize; // ROM expects rounded up to erase block size
1134
1156
  timeout = DEFAULT_TIMEOUT;
1135
1157
  }
1136
- buffer = pack("<IIII", writeSize, numBlocks, flashWriteSize, offset);
1158
+ const buffer = pack("<IIII", writeSize, numBlocks, flashWriteSize, offset);
1137
1159
 
1138
1160
  await this.checkCommand(ESP_FLASH_DEFL_BEGIN, buffer, 0, timeout);
1139
1161
 
@@ -1141,24 +1163,24 @@ export class ESPLoader extends EventTarget {
1141
1163
  }
1142
1164
 
1143
1165
  async flashFinish() {
1144
- let buffer = pack("<I", 1);
1166
+ const buffer = pack("<I", 1);
1145
1167
  await this.checkCommand(ESP_FLASH_END, buffer);
1146
1168
  }
1147
1169
 
1148
1170
  async flashDeflFinish() {
1149
- let buffer = pack("<I", 1);
1171
+ const buffer = pack("<I", 1);
1150
1172
  await this.checkCommand(ESP_FLASH_DEFL_END, buffer);
1151
1173
  }
1152
1174
 
1153
1175
  getBootloaderOffset() {
1154
- let bootFlashOffs = getSpiFlashAddresses(this.getChipFamily());
1155
- let BootldrFlashOffs = bootFlashOffs.flashOffs;
1176
+ const bootFlashOffs = getSpiFlashAddresses(this.getChipFamily());
1177
+ const BootldrFlashOffs = bootFlashOffs.flashOffs;
1156
1178
  return BootldrFlashOffs;
1157
1179
  }
1158
1180
 
1159
1181
  async flashId() {
1160
- let SPIFLASH_RDID = 0x9f;
1161
- let result = await this.runSpiFlashCommand(SPIFLASH_RDID, [], 24);
1182
+ const SPIFLASH_RDID = 0x9f;
1183
+ const result = await this.runSpiFlashCommand(SPIFLASH_RDID, [], 24);
1162
1184
  return result;
1163
1185
  }
1164
1186
 
@@ -1176,7 +1198,7 @@ export class ESPLoader extends EventTarget {
1176
1198
  let buffer = pack("<IIII", address, value, mask, delayUs);
1177
1199
  if (delayAfterUs > 0) {
1178
1200
  // add a dummy write to a date register as an excuse to have a delay
1179
- buffer.concat(
1201
+ buffer = buffer.concat(
1180
1202
  pack(
1181
1203
  "<IIII",
1182
1204
  getSpiFlashAddresses(this.getChipFamily()).uartDateReg,
@@ -1195,9 +1217,11 @@ export class ESPLoader extends EventTarget {
1195
1217
  misoBits: number,
1196
1218
  ) {
1197
1219
  if (spiAddresses.mosiDlenOffs != -1) {
1198
- // ESP32/32S2/32S3/32C3 has a more sophisticated way to set up "user" commands
1199
- let SPI_MOSI_DLEN_REG = spiAddresses.regBase + spiAddresses.mosiDlenOffs;
1200
- let SPI_MISO_DLEN_REG = spiAddresses.regBase + spiAddresses.misoDlenOffs;
1220
+ // Actual MCUs have a more sophisticated way to set up "user" commands
1221
+ const SPI_MOSI_DLEN_REG =
1222
+ spiAddresses.regBase + spiAddresses.mosiDlenOffs;
1223
+ const SPI_MISO_DLEN_REG =
1224
+ spiAddresses.regBase + spiAddresses.misoDlenOffs;
1201
1225
  if (mosiBits > 0) {
1202
1226
  await this.writeRegister(SPI_MOSI_DLEN_REG, mosiBits - 1);
1203
1227
  }
@@ -1205,19 +1229,19 @@ export class ESPLoader extends EventTarget {
1205
1229
  await this.writeRegister(SPI_MISO_DLEN_REG, misoBits - 1);
1206
1230
  }
1207
1231
  } else {
1208
- let SPI_DATA_LEN_REG = spiAddresses.regBase + spiAddresses.usr1Offs;
1209
- let SPI_MOSI_BITLEN_S = 17;
1210
- let SPI_MISO_BITLEN_S = 8;
1211
- let mosiMask = mosiBits == 0 ? 0 : mosiBits - 1;
1212
- let misoMask = misoBits == 0 ? 0 : misoBits - 1;
1213
- let value =
1232
+ const SPI_DATA_LEN_REG = spiAddresses.regBase + spiAddresses.usr1Offs;
1233
+ const SPI_MOSI_BITLEN_S = 17;
1234
+ const SPI_MISO_BITLEN_S = 8;
1235
+ const mosiMask = mosiBits == 0 ? 0 : mosiBits - 1;
1236
+ const misoMask = misoBits == 0 ? 0 : misoBits - 1;
1237
+ const value =
1214
1238
  (misoMask << SPI_MISO_BITLEN_S) | (mosiMask << SPI_MOSI_BITLEN_S);
1215
1239
  await this.writeRegister(SPI_DATA_LEN_REG, value);
1216
1240
  }
1217
1241
  }
1218
1242
  async waitDone(spiCmdReg: number, spiCmdUsr: number) {
1219
1243
  for (let i = 0; i < 10; i++) {
1220
- let cmdValue = await this.readRegister(spiCmdReg);
1244
+ const cmdValue = await this.readRegister(spiCmdReg);
1221
1245
  if ((cmdValue & spiCmdUsr) == 0) {
1222
1246
  return;
1223
1247
  }
@@ -1241,23 +1265,23 @@ export class ESPLoader extends EventTarget {
1241
1265
  // reads back 'read_bits' of reply on MISO. Result is a number.
1242
1266
 
1243
1267
  // SPI_USR register flags
1244
- let SPI_USR_COMMAND = 1 << 31;
1245
- let SPI_USR_MISO = 1 << 28;
1246
- let SPI_USR_MOSI = 1 << 27;
1247
-
1248
- // SPI registers, base address differs ESP32* vs 8266
1249
- let spiAddresses = getSpiFlashAddresses(this.getChipFamily());
1250
- let base = spiAddresses.regBase;
1251
- let SPI_CMD_REG = base;
1252
- let SPI_USR_REG = base + spiAddresses.usrOffs;
1253
- let SPI_USR2_REG = base + spiAddresses.usr2Offs;
1254
- let SPI_W0_REG = base + spiAddresses.w0Offs;
1268
+ const SPI_USR_COMMAND = 1 << 31;
1269
+ const SPI_USR_MISO = 1 << 28;
1270
+ const SPI_USR_MOSI = 1 << 27;
1271
+
1272
+ // SPI registers, base address differs
1273
+ const spiAddresses = getSpiFlashAddresses(this.getChipFamily());
1274
+ const base = spiAddresses.regBase;
1275
+ const SPI_CMD_REG = base;
1276
+ const SPI_USR_REG = base + spiAddresses.usrOffs;
1277
+ const SPI_USR2_REG = base + spiAddresses.usr2Offs;
1278
+ const SPI_W0_REG = base + spiAddresses.w0Offs;
1255
1279
 
1256
1280
  // SPI peripheral "command" bitmasks for SPI_CMD_REG
1257
- let SPI_CMD_USR = 1 << 18;
1281
+ const SPI_CMD_USR = 1 << 18;
1258
1282
 
1259
1283
  // shift values
1260
- let SPI_USR2_COMMAND_LEN_SHIFT = 28;
1284
+ const SPI_USR2_COMMAND_LEN_SHIFT = 28;
1261
1285
 
1262
1286
  if (readBits > 32) {
1263
1287
  throw new Error(
@@ -1270,9 +1294,9 @@ export class ESPLoader extends EventTarget {
1270
1294
  );
1271
1295
  }
1272
1296
 
1273
- let dataBits = data.length * 8;
1274
- let oldSpiUsr = await this.readRegister(SPI_USR_REG);
1275
- let oldSpiUsr2 = await this.readRegister(SPI_USR2_REG);
1297
+ const dataBits = data.length * 8;
1298
+ const oldSpiUsr = await this.readRegister(SPI_USR_REG);
1299
+ const oldSpiUsr2 = await this.readRegister(SPI_USR2_REG);
1276
1300
 
1277
1301
  let flags = SPI_USR_COMMAND;
1278
1302
 
@@ -1293,9 +1317,10 @@ export class ESPLoader extends EventTarget {
1293
1317
  if (dataBits == 0) {
1294
1318
  await this.writeRegister(SPI_W0_REG, 0); // clear data register before we read it
1295
1319
  } else {
1296
- data.concat(new Array(data.length % 4).fill(0x00)); // pad to 32-bit multiple
1320
+ const padLen = (4 - (data.length % 4)) % 4;
1321
+ data = data.concat(new Array(padLen).fill(0x00)); // pad to 32-bit multiple
1297
1322
 
1298
- let words = unpack("I".repeat(Math.floor(data.length / 4)), data);
1323
+ const words = unpack("I".repeat(Math.floor(data.length / 4)), data);
1299
1324
  let nextReg = SPI_W0_REG;
1300
1325
 
1301
1326
  this.logger.debug(`Words Length: ${words.length}`);
@@ -1311,7 +1336,7 @@ export class ESPLoader extends EventTarget {
1311
1336
  await this.writeRegister(SPI_CMD_REG, SPI_CMD_USR);
1312
1337
  await this.waitDone(SPI_CMD_REG, SPI_CMD_USR);
1313
1338
 
1314
- let status = await this.readRegister(SPI_W0_REG);
1339
+ const status = await this.readRegister(SPI_W0_REG);
1315
1340
  // restore some SPI controller registers
1316
1341
  await this.writeRegister(SPI_USR_REG, oldSpiUsr);
1317
1342
  await this.writeRegister(SPI_USR2_REG, oldSpiUsr2);
@@ -1320,9 +1345,9 @@ export class ESPLoader extends EventTarget {
1320
1345
  async detectFlashSize() {
1321
1346
  this.logger.log("Detecting Flash Size");
1322
1347
 
1323
- let flashId = await this.flashId();
1324
- let manufacturer = flashId & 0xff;
1325
- let flashIdLowbyte = (flashId >> 16) & 0xff;
1348
+ const flashId = await this.flashId();
1349
+ const manufacturer = flashId & 0xff;
1350
+ const flashIdLowbyte = (flashId >> 16) & 0xff;
1326
1351
 
1327
1352
  this.logger.log(`FlashId: ${toHex(flashId)}`);
1328
1353
  this.logger.log(`Flash Manufacturer: ${manufacturer.toString(16)}`);
@@ -1342,10 +1367,10 @@ export class ESPLoader extends EventTarget {
1342
1367
  * Provides a workaround for the bootloader erase bug on ESP8266.
1343
1368
  */
1344
1369
  getEraseSize(offset: number, size: number) {
1345
- let sectorsPerBlock = 16;
1346
- let sectorSize = FLASH_SECTOR_SIZE;
1347
- let numSectors = Math.floor((size + sectorSize - 1) / sectorSize);
1348
- let startSector = Math.floor(offset / sectorSize);
1370
+ const sectorsPerBlock = 16;
1371
+ const sectorSize = FLASH_SECTOR_SIZE;
1372
+ const numSectors = Math.floor((size + sectorSize - 1) / sectorSize);
1373
+ const startSector = Math.floor(offset / sectorSize);
1349
1374
 
1350
1375
  let headSectors = sectorsPerBlock - (startSector % sectorsPerBlock);
1351
1376
  if (numSectors < headSectors) {
@@ -1397,16 +1422,13 @@ export class ESPLoader extends EventTarget {
1397
1422
  * ignore errors.
1398
1423
  */
1399
1424
  async memFinish(entrypoint = 0) {
1400
- let timeout = this.IS_STUB ? DEFAULT_TIMEOUT : MEM_END_ROM_TIMEOUT;
1401
- let data = pack("<II", entrypoint == 0 ? 1 : 0, entrypoint);
1425
+ const timeout = this.IS_STUB ? DEFAULT_TIMEOUT : MEM_END_ROM_TIMEOUT;
1426
+ const data = pack("<II", entrypoint == 0 ? 1 : 0, entrypoint);
1402
1427
  return await this.checkCommand(ESP_MEM_END, data, 0, timeout);
1403
1428
  }
1404
1429
 
1405
1430
  async runStub(skipFlashDetection = false): Promise<EspStubLoader> {
1406
- const stub: Record<string, any> | null = await getStubCode(
1407
- this.chipFamily,
1408
- this.chipRevision,
1409
- );
1431
+ const stub = await getStubCode(this.chipFamily, this.chipRevision);
1410
1432
 
1411
1433
  // No stub available for this chip, return ROM loader
1412
1434
  if (stub === null) {
@@ -1417,32 +1439,29 @@ export class ESPLoader extends EventTarget {
1417
1439
  }
1418
1440
 
1419
1441
  // We're transferring over USB, right?
1420
- let ramBlock = USB_RAM_BLOCK;
1442
+ const ramBlock = USB_RAM_BLOCK;
1421
1443
 
1422
1444
  // Upload
1423
1445
  this.logger.log("Uploading stub...");
1424
- for (let field of ["text", "data"]) {
1425
- if (Object.keys(stub).includes(field)) {
1426
- let offset = stub[field + "_start"];
1427
- let length = stub[field].length;
1428
- let blocks = Math.floor((length + ramBlock - 1) / ramBlock);
1429
- await this.memBegin(length, blocks, ramBlock, offset);
1430
- for (let seq of Array(blocks).keys()) {
1431
- let fromOffs = seq * ramBlock;
1432
- let toOffs = fromOffs + ramBlock;
1433
- if (toOffs > length) {
1434
- toOffs = length;
1435
- }
1436
- await this.memBlock(stub[field].slice(fromOffs, toOffs), seq);
1446
+ for (const field of ["text", "data"] as const) {
1447
+ const fieldData = stub[field];
1448
+ const offset = stub[`${field}_start` as "text_start" | "data_start"];
1449
+ const length = fieldData.length;
1450
+ const blocks = Math.floor((length + ramBlock - 1) / ramBlock);
1451
+ await this.memBegin(length, blocks, ramBlock, offset);
1452
+ for (const seq of Array(blocks).keys()) {
1453
+ const fromOffs = seq * ramBlock;
1454
+ let toOffs = fromOffs + ramBlock;
1455
+ if (toOffs > length) {
1456
+ toOffs = length;
1437
1457
  }
1458
+ await this.memBlock(fieldData.slice(fromOffs, toOffs), seq);
1438
1459
  }
1439
1460
  }
1440
- await this.memFinish(stub["entry"]);
1441
-
1442
- let pChar: string;
1461
+ await this.memFinish(stub.entry);
1443
1462
 
1444
1463
  const p = await this.readPacket(500);
1445
- pChar = String.fromCharCode(...p);
1464
+ const pChar = String.fromCharCode(...p);
1446
1465
 
1447
1466
  if (pChar != "OHAI") {
1448
1467
  throw new Error("Failed to start stub. Unexpected response: " + pChar);
@@ -1459,12 +1478,16 @@ export class ESPLoader extends EventTarget {
1459
1478
  }
1460
1479
 
1461
1480
  async writeToStream(data: number[]) {
1462
- const writer = this.port.writable!.getWriter();
1481
+ if (!this.port.writable) {
1482
+ this.logger.debug("Port writable stream not available, skipping write");
1483
+ return;
1484
+ }
1485
+ const writer = this.port.writable.getWriter();
1463
1486
  await writer.write(new Uint8Array(data));
1464
1487
  try {
1465
1488
  writer.releaseLock();
1466
1489
  } catch (err) {
1467
- console.error("Ignoring release lock error", err);
1490
+ this.logger.error(`Ignoring release lock error: ${err}`);
1468
1491
  }
1469
1492
  }
1470
1493
 
@@ -1473,7 +1496,11 @@ export class ESPLoader extends EventTarget {
1473
1496
  await this._parent.disconnect();
1474
1497
  return;
1475
1498
  }
1476
- await this.port.writable!.getWriter().close();
1499
+ if (!this.port.writable) {
1500
+ this.logger.debug("Port already closed, skipping disconnect");
1501
+ return;
1502
+ }
1503
+ await this.port.writable.getWriter().close();
1477
1504
  await new Promise((resolve) => {
1478
1505
  if (!this._reader) {
1479
1506
  resolve(undefined);
@@ -1509,8 +1536,6 @@ export class ESPLoader extends EventTarget {
1509
1536
  this._reader = undefined;
1510
1537
  }
1511
1538
 
1512
- await sleep(SYNC_TIMEOUT);
1513
-
1514
1539
  // Close port
1515
1540
  try {
1516
1541
  await this.port.close();
@@ -1519,9 +1544,6 @@ export class ESPLoader extends EventTarget {
1519
1544
  this.logger.debug(`Port close error: ${err}`);
1520
1545
  }
1521
1546
 
1522
- // Wait for port to fully close
1523
- await sleep(SYNC_TIMEOUT);
1524
-
1525
1547
  // Open the port
1526
1548
  this.logger.debug("Opening port...");
1527
1549
  try {
@@ -1531,9 +1553,6 @@ export class ESPLoader extends EventTarget {
1531
1553
  throw new Error(`Failed to open port: ${err}`);
1532
1554
  }
1533
1555
 
1534
- // Wait for port to be fully ready
1535
- await sleep(SYNC_TIMEOUT);
1536
-
1537
1556
  // Verify port streams are available
1538
1557
  if (!this.port.readable || !this.port.writable) {
1539
1558
  throw new Error(
@@ -1548,7 +1567,7 @@ export class ESPLoader extends EventTarget {
1548
1567
  const savedChipVariant = this.chipVariant;
1549
1568
  const savedFlashSize = this.flashSize;
1550
1569
 
1551
- // Reinitialize without chip detection
1570
+ // Reinitialize
1552
1571
  await this.hardReset(true);
1553
1572
 
1554
1573
  if (!this._parent) {
@@ -1560,7 +1579,7 @@ export class ESPLoader extends EventTarget {
1560
1579
  await this.flushSerialBuffers();
1561
1580
  await this.sync();
1562
1581
 
1563
- // Restore chip info (skip detection)
1582
+ // Restore chip info
1564
1583
  this.chipFamily = savedChipFamily;
1565
1584
  this.chipName = savedChipName;
1566
1585
  this.chipRevision = savedChipRevision;
@@ -1574,7 +1593,7 @@ export class ESPLoader extends EventTarget {
1574
1593
  throw new Error("Port not ready after reconnect");
1575
1594
  }
1576
1595
 
1577
- // Load stub (skip flash detection)
1596
+ // Load stub
1578
1597
  const stubLoader = await this.runStub(true);
1579
1598
  this.logger.debug("Stub loaded");
1580
1599
 
@@ -1582,9 +1601,6 @@ export class ESPLoader extends EventTarget {
1582
1601
  if (this._currentBaudRate !== ESP_ROM_BAUD) {
1583
1602
  await stubLoader.setBaudrate(this._currentBaudRate);
1584
1603
 
1585
- // Wait for port to be ready after baudrate change
1586
- await sleep(SYNC_TIMEOUT);
1587
-
1588
1604
  // Verify port is still ready after baudrate change
1589
1605
  if (!this.port.writable || !this.port.readable) {
1590
1606
  throw new Error(
@@ -1606,22 +1622,14 @@ export class ESPLoader extends EventTarget {
1606
1622
  * This clears both the application RX buffer and waits for hardware buffers to drain
1607
1623
  */
1608
1624
  private async flushSerialBuffers(): Promise<void> {
1609
- // Clear application RX buffer
1625
+ // Clear application buffer
1610
1626
  if (!this._parent) {
1611
1627
  this.__inputBuffer = [];
1612
1628
  }
1613
1629
 
1614
- // Wait for any pending TX operations and in-flight RX data
1630
+ // Wait for any pending data
1615
1631
  await sleep(SYNC_TIMEOUT);
1616
1632
 
1617
- // Clear RX buffer again
1618
- if (!this._parent) {
1619
- this.__inputBuffer = [];
1620
- }
1621
-
1622
- // Wait longer to ensure all stale data has been received and discarded
1623
- await sleep(SYNC_TIMEOUT * 2);
1624
-
1625
1633
  // Final clear
1626
1634
  if (!this._parent) {
1627
1635
  this.__inputBuffer = [];
@@ -1653,22 +1661,6 @@ export class ESPLoader extends EventTarget {
1653
1661
  );
1654
1662
  }
1655
1663
 
1656
- // Check if we should reconnect BEFORE starting the read
1657
- // Reconnect if total bytes read >= 4MB to ensure clean state
1658
- if (this._totalBytesRead >= 4 * 1024 * 1024) {
1659
- this.logger.log(
1660
- // `Total bytes read: ${this._totalBytesRead}. Reconnecting before new read...`,
1661
- `Reconnecting before new read...`,
1662
- );
1663
-
1664
- try {
1665
- await this.reconnect();
1666
- } catch (err) {
1667
- // If reconnect fails, throw error - don't continue with potentially broken state
1668
- throw new Error(`Reconnect failed: ${err}`);
1669
- }
1670
- }
1671
-
1672
1664
  // Flush serial buffers before flash read operation
1673
1665
  await this.flushSerialBuffers();
1674
1666
 
@@ -1683,18 +1675,6 @@ export class ESPLoader extends EventTarget {
1683
1675
  let remainingSize = size;
1684
1676
 
1685
1677
  while (remainingSize > 0) {
1686
- // Reconnect every 4MB to prevent browser buffer issues
1687
- if (allData.length > 0 && allData.length % (4 * 1024 * 1024) === 0) {
1688
- this.logger.debug(
1689
- `Read ${allData.length} bytes. Reconnecting to clear buffers...`,
1690
- );
1691
- try {
1692
- await this.reconnect();
1693
- } catch (err) {
1694
- throw new Error(`Reconnect failed during read: ${err}`);
1695
- }
1696
- }
1697
-
1698
1678
  const chunkSize = Math.min(CHUNK_SIZE, remainingSize);
1699
1679
  let chunkSuccess = false;
1700
1680
  let retryCount = 0;
@@ -1708,8 +1688,8 @@ export class ESPLoader extends EventTarget {
1708
1688
  );
1709
1689
 
1710
1690
  // Send read flash command for this chunk
1711
- let pkt = pack("<IIII", currentAddr, chunkSize, 0x1000, 1024);
1712
- const [res, _] = await this.checkCommand(ESP_READ_FLASH, pkt);
1691
+ const pkt = pack("<IIII", currentAddr, chunkSize, 0x1000, 1024);
1692
+ const [res] = await this.checkCommand(ESP_READ_FLASH, pkt);
1713
1693
 
1714
1694
  if (res != 0) {
1715
1695
  throw new Error("Failed to read memory: " + res);
@@ -1798,7 +1778,7 @@ export class ESPLoader extends EventTarget {
1798
1778
  remainingSize -= chunkSize;
1799
1779
 
1800
1780
  this.logger.debug(
1801
- `Total progress: 0x${allData.length.toString(16)}/0x${size.toString(16)} bytes`,
1781
+ `Total progress: 0x${allData.length.toString(16)} from 0x${size.toString(16)} bytes`,
1802
1782
  );
1803
1783
  }
1804
1784
 
@@ -1820,27 +1800,26 @@ class EspStubLoader extends ESPLoader {
1820
1800
  */
1821
1801
  async memBegin(
1822
1802
  size: number,
1823
- blocks: number,
1824
- blocksize: number,
1803
+ _blocks: number,
1804
+ _blocksize: number,
1825
1805
  offset: number,
1826
- ): Promise<any> {
1827
- let stub = await getStubCode(this.chipFamily, this.chipRevision);
1806
+ ): Promise<[number, number[]]> {
1807
+ const stub = await getStubCode(this.chipFamily, this.chipRevision);
1828
1808
 
1829
1809
  // Stub may be null for chips without stub support
1830
1810
  if (stub === null) {
1831
- return;
1811
+ return [0, []];
1832
1812
  }
1833
1813
 
1834
- let load_start = offset;
1835
- let load_end = offset + size;
1836
- console.log(load_start, load_end);
1837
- console.log(
1838
- stub.data_start,
1839
- stub.data.length,
1840
- stub.text_start,
1841
- stub.text.length,
1814
+ const load_start = offset;
1815
+ const load_end = offset + size;
1816
+ this.logger.debug(
1817
+ `Load range: ${toHex(load_start, 8)}-${toHex(load_end, 8)}`,
1818
+ );
1819
+ this.logger.debug(
1820
+ `Stub data: ${toHex(stub.data_start, 8)}, len: ${stub.data.length}, text: ${toHex(stub.text_start, 8)}, len: ${stub.text.length}`,
1842
1821
  );
1843
- for (let [start, end] of [
1822
+ for (const [start, end] of [
1844
1823
  [stub.data_start, stub.data_start + stub.data.length],
1845
1824
  [stub.text_start, stub.text_start + stub.text.length],
1846
1825
  ]) {
@@ -1860,6 +1839,7 @@ class EspStubLoader extends ESPLoader {
1860
1839
  );
1861
1840
  }
1862
1841
  }
1842
+ return [0, []];
1863
1843
  }
1864
1844
 
1865
1845
  /**