esp32tool 1.0.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.
Files changed (115) hide show
  1. package/README.md +31 -0
  2. package/css/dark.css +156 -0
  3. package/css/light.css +156 -0
  4. package/css/style.css +870 -0
  5. package/dist/const.d.ts +277 -0
  6. package/dist/const.js +511 -0
  7. package/dist/esp_loader.d.ts +222 -0
  8. package/dist/esp_loader.js +1466 -0
  9. package/dist/index.d.ts +10 -0
  10. package/dist/index.js +15 -0
  11. package/dist/lib/spiffs/index.d.ts +15 -0
  12. package/dist/lib/spiffs/index.js +16 -0
  13. package/dist/lib/spiffs/spiffs.d.ts +26 -0
  14. package/dist/lib/spiffs/spiffs.js +132 -0
  15. package/dist/lib/spiffs/spiffsBlock.d.ts +36 -0
  16. package/dist/lib/spiffs/spiffsBlock.js +140 -0
  17. package/dist/lib/spiffs/spiffsConfig.d.ts +63 -0
  18. package/dist/lib/spiffs/spiffsConfig.js +79 -0
  19. package/dist/lib/spiffs/spiffsPage.d.ts +45 -0
  20. package/dist/lib/spiffs/spiffsPage.js +260 -0
  21. package/dist/lib/spiffs/spiffsReader.d.ts +19 -0
  22. package/dist/lib/spiffs/spiffsReader.js +192 -0
  23. package/dist/partition.d.ts +26 -0
  24. package/dist/partition.js +129 -0
  25. package/dist/struct.d.ts +2 -0
  26. package/dist/struct.js +91 -0
  27. package/dist/stubs/esp32.json +8 -0
  28. package/dist/stubs/esp32c2.json +8 -0
  29. package/dist/stubs/esp32c3.json +8 -0
  30. package/dist/stubs/esp32c5.json +8 -0
  31. package/dist/stubs/esp32c6.json +8 -0
  32. package/dist/stubs/esp32c61.json +8 -0
  33. package/dist/stubs/esp32h2.json +8 -0
  34. package/dist/stubs/esp32p4.json +8 -0
  35. package/dist/stubs/esp32p4r3.json +8 -0
  36. package/dist/stubs/esp32s2.json +8 -0
  37. package/dist/stubs/esp32s3.json +8 -0
  38. package/dist/stubs/esp8266.json +8 -0
  39. package/dist/stubs/index.d.ts +10 -0
  40. package/dist/stubs/index.js +56 -0
  41. package/dist/util.d.ts +14 -0
  42. package/dist/util.js +46 -0
  43. package/dist/wasm/filesystems.d.ts +33 -0
  44. package/dist/wasm/filesystems.js +114 -0
  45. package/dist/web/esp32-D955RjN9.js +16 -0
  46. package/dist/web/esp32c2-CJkxHDQi.js +16 -0
  47. package/dist/web/esp32c3-BhUHzH0o.js +16 -0
  48. package/dist/web/esp32c5-Chs0HtmA.js +16 -0
  49. package/dist/web/esp32c6-D6mPN6ut.js +16 -0
  50. package/dist/web/esp32c61-CQiYCWAs.js +16 -0
  51. package/dist/web/esp32h2-LsKJE9AS.js +16 -0
  52. package/dist/web/esp32p4-7nWC-HiD.js +16 -0
  53. package/dist/web/esp32p4r3-CwiPecZW.js +16 -0
  54. package/dist/web/esp32s2-CtqVheSJ.js +16 -0
  55. package/dist/web/esp32s3-CRbtB0QR.js +16 -0
  56. package/dist/web/esp8266-nEkNAo8K.js +16 -0
  57. package/dist/web/index.js +7265 -0
  58. package/electron/main.js +333 -0
  59. package/electron/preload.js +37 -0
  60. package/eslint.config.js +22 -0
  61. package/index.html +408 -0
  62. package/js/modules/esp32-D955RjN9.js +16 -0
  63. package/js/modules/esp32c2-CJkxHDQi.js +16 -0
  64. package/js/modules/esp32c3-BhUHzH0o.js +16 -0
  65. package/js/modules/esp32c5-Chs0HtmA.js +16 -0
  66. package/js/modules/esp32c6-D6mPN6ut.js +16 -0
  67. package/js/modules/esp32c61-CQiYCWAs.js +16 -0
  68. package/js/modules/esp32h2-LsKJE9AS.js +16 -0
  69. package/js/modules/esp32p4-7nWC-HiD.js +16 -0
  70. package/js/modules/esp32p4r3-CwiPecZW.js +16 -0
  71. package/js/modules/esp32s2-CtqVheSJ.js +16 -0
  72. package/js/modules/esp32s3-CRbtB0QR.js +16 -0
  73. package/js/modules/esp8266-nEkNAo8K.js +16 -0
  74. package/js/modules/esptool.js +7265 -0
  75. package/js/script.js +2237 -0
  76. package/js/utilities.js +182 -0
  77. package/license.md +11 -0
  78. package/package.json +61 -0
  79. package/script/build +12 -0
  80. package/script/develop +17 -0
  81. package/src/const.ts +599 -0
  82. package/src/esp_loader.ts +1907 -0
  83. package/src/index.ts +63 -0
  84. package/src/lib/spiffs/index.ts +22 -0
  85. package/src/lib/spiffs/spiffs.ts +175 -0
  86. package/src/lib/spiffs/spiffsBlock.ts +204 -0
  87. package/src/lib/spiffs/spiffsConfig.ts +140 -0
  88. package/src/lib/spiffs/spiffsPage.ts +357 -0
  89. package/src/lib/spiffs/spiffsReader.ts +280 -0
  90. package/src/partition.ts +155 -0
  91. package/src/struct.ts +108 -0
  92. package/src/stubs/README.md +3 -0
  93. package/src/stubs/esp32.json +8 -0
  94. package/src/stubs/esp32c2.json +8 -0
  95. package/src/stubs/esp32c3.json +8 -0
  96. package/src/stubs/esp32c5.json +8 -0
  97. package/src/stubs/esp32c6.json +8 -0
  98. package/src/stubs/esp32c61.json +8 -0
  99. package/src/stubs/esp32h2.json +8 -0
  100. package/src/stubs/esp32p4.json +8 -0
  101. package/src/stubs/esp32p4r3.json +8 -0
  102. package/src/stubs/esp32s2.json +8 -0
  103. package/src/stubs/esp32s3.json +8 -0
  104. package/src/stubs/esp8266.json +8 -0
  105. package/src/stubs/index.ts +86 -0
  106. package/src/util.ts +49 -0
  107. package/src/wasm/fatfs/fatfs.wasm +0 -0
  108. package/src/wasm/fatfs/index.d.ts +26 -0
  109. package/src/wasm/fatfs/index.js +343 -0
  110. package/src/wasm/filesystems.ts +156 -0
  111. package/src/wasm/littlefs/index.d.ts +83 -0
  112. package/src/wasm/littlefs/index.js +529 -0
  113. package/src/wasm/littlefs/littlefs.js +2 -0
  114. package/src/wasm/littlefs/littlefs.wasm +0 -0
  115. package/src/wasm/shared/types.ts +13 -0
@@ -0,0 +1,1466 @@
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";
3
+ import { getStubCode } from "./stubs";
4
+ import { hexFormatter, sleep, slipEncode, toHex } from "./util";
5
+ // @ts-expect-error pako ESM module doesn't have proper type definitions
6
+ import { deflate } from "pako/dist/pako.esm.mjs";
7
+ import { pack, unpack } from "./struct";
8
+ export class ESPLoader extends EventTarget {
9
+ constructor(port, logger, _parent) {
10
+ super();
11
+ this.port = port;
12
+ this.logger = logger;
13
+ this._parent = _parent;
14
+ this.chipName = null;
15
+ this.chipRevision = null;
16
+ this.chipVariant = null;
17
+ this._efuses = new Array(4).fill(0);
18
+ this._flashsize = 4 * 1024 * 1024;
19
+ this.debug = false;
20
+ this.IS_STUB = false;
21
+ this.connected = true;
22
+ this.flashSize = null;
23
+ this._currentBaudRate = ESP_ROM_BAUD;
24
+ this._isESP32S2NativeUSB = false;
25
+ this._initializationSucceeded = false;
26
+ this.state_DTR = false;
27
+ }
28
+ get _inputBuffer() {
29
+ return this._parent ? this._parent._inputBuffer : this.__inputBuffer;
30
+ }
31
+ get _totalBytesRead() {
32
+ return this._parent
33
+ ? this._parent._totalBytesRead
34
+ : this.__totalBytesRead || 0;
35
+ }
36
+ set _totalBytesRead(value) {
37
+ if (this._parent) {
38
+ this._parent._totalBytesRead = value;
39
+ }
40
+ else {
41
+ this.__totalBytesRead = value;
42
+ }
43
+ }
44
+ detectUSBSerialChip(vendorId, productId) {
45
+ // Common USB-Serial chip vendors and their products
46
+ const chips = {
47
+ 0x1a86: {
48
+ // QinHeng Electronics
49
+ 0x7522: { name: "CH340", maxBaudrate: 460800 },
50
+ 0x7523: { name: "CH340", maxBaudrate: 460800 },
51
+ 0x7584: { name: "CH340", maxBaudrate: 460800 },
52
+ 0x5523: { name: "CH341", maxBaudrate: 2000000 },
53
+ 0x55d3: { name: "CH343", maxBaudrate: 6000000 },
54
+ 0x55d4: { name: "CH9102", maxBaudrate: 6000000 },
55
+ 0x55d8: { name: "CH9101", maxBaudrate: 3000000 },
56
+ },
57
+ 0x10c4: {
58
+ // Silicon Labs
59
+ 0xea60: { name: "CP2102(n)", maxBaudrate: 3000000 },
60
+ 0xea70: { name: "CP2105", maxBaudrate: 2000000 },
61
+ 0xea71: { name: "CP2108", maxBaudrate: 2000000 },
62
+ },
63
+ 0x0403: {
64
+ // FTDI
65
+ 0x6001: { name: "FT232R", maxBaudrate: 3000000 },
66
+ 0x6010: { name: "FT2232", maxBaudrate: 3000000 },
67
+ 0x6011: { name: "FT4232", maxBaudrate: 3000000 },
68
+ 0x6014: { name: "FT232H", maxBaudrate: 12000000 },
69
+ 0x6015: { name: "FT230X", maxBaudrate: 3000000 },
70
+ },
71
+ 0x303a: {
72
+ // Espressif (native USB)
73
+ 0x2: { name: "ESP32-S2 Native USB", maxBaudrate: 2000000 },
74
+ 0x1001: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
75
+ 0x1002: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
76
+ 0x4002: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
77
+ 0x1000: { name: "ESP32 Native USB", maxBaudrate: 2000000 },
78
+ },
79
+ };
80
+ const vendor = chips[vendorId];
81
+ if (vendor && vendor[productId]) {
82
+ return vendor[productId];
83
+ }
84
+ return {
85
+ name: `Unknown (VID: 0x${vendorId.toString(16)}, PID: 0x${productId.toString(16)})`,
86
+ };
87
+ }
88
+ async initialize() {
89
+ await this.hardReset(true);
90
+ if (!this._parent) {
91
+ this.__inputBuffer = [];
92
+ this.__totalBytesRead = 0;
93
+ // Detect and log USB-Serial chip info
94
+ const portInfo = this.port.getInfo();
95
+ if (portInfo.usbVendorId && portInfo.usbProductId) {
96
+ const chipInfo = this.detectUSBSerialChip(portInfo.usbVendorId, portInfo.usbProductId);
97
+ this.logger.log(`USB-Serial: ${chipInfo.name} (VID: 0x${portInfo.usbVendorId.toString(16)}, PID: 0x${portInfo.usbProductId.toString(16)})`);
98
+ if (chipInfo.maxBaudrate) {
99
+ this._maxUSBSerialBaudrate = chipInfo.maxBaudrate;
100
+ this.logger.log(`Max baudrate: ${chipInfo.maxBaudrate}`);
101
+ }
102
+ // Detect ESP32-S2 Native USB
103
+ if (portInfo.usbVendorId === 0x303a && portInfo.usbProductId === 0x2) {
104
+ this._isESP32S2NativeUSB = true;
105
+ }
106
+ }
107
+ // Don't await this promise so it doesn't block rest of method.
108
+ this.readLoop();
109
+ }
110
+ // Clear buffer again after starting read loop
111
+ await this.flushSerialBuffers();
112
+ await this.sync();
113
+ // Detect chip type
114
+ await this.detectChip();
115
+ // Read the OTP data for this chip and store into this.efuses array
116
+ const FlAddr = getSpiFlashAddresses(this.getChipFamily());
117
+ const AddrMAC = FlAddr.macFuse;
118
+ for (let i = 0; i < 4; i++) {
119
+ this._efuses[i] = await this.readRegister(AddrMAC + 4 * i);
120
+ }
121
+ this.logger.log(`Chip type ${this.chipName}`);
122
+ this.logger.debug(`Bootloader flash offset: 0x${FlAddr.flashOffs.toString(16)}`);
123
+ // Mark initialization as successful
124
+ this._initializationSucceeded = true;
125
+ }
126
+ /**
127
+ * Detect chip type using GET_SECURITY_INFO (for newer chips) or magic value (for older chips)
128
+ */
129
+ async detectChip() {
130
+ try {
131
+ // Try GET_SECURITY_INFO command first (ESP32-C3 and later)
132
+ const securityInfo = await this.getSecurityInfo();
133
+ const chipId = securityInfo.chipId;
134
+ const chipInfo = CHIP_ID_TO_INFO[chipId];
135
+ if (chipInfo) {
136
+ this.chipName = chipInfo.name;
137
+ this.chipFamily = chipInfo.family;
138
+ // Get chip revision for ESP32-P4
139
+ if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
140
+ this.chipRevision = await this.getChipRevision();
141
+ this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
142
+ // Set chip variant based on revision
143
+ if (this.chipRevision >= 300) {
144
+ this.chipVariant = "rev300";
145
+ }
146
+ else {
147
+ this.chipVariant = "rev0";
148
+ }
149
+ this.logger.debug(`ESP32-P4 variant: ${this.chipVariant}`);
150
+ }
151
+ this.logger.debug(`Detected chip via IMAGE_CHIP_ID: ${chipId} (${this.chipName})`);
152
+ return;
153
+ }
154
+ this.logger.debug(`Unknown IMAGE_CHIP_ID: ${chipId}, falling back to magic value detection`);
155
+ }
156
+ catch (error) {
157
+ // GET_SECURITY_INFO not supported, fall back to magic value detection
158
+ this.logger.debug(`GET_SECURITY_INFO failed, using magic value detection: ${error}`);
159
+ // Clear input buffer and re-sync to recover from failed command
160
+ this._inputBuffer.length = 0;
161
+ await sleep(SYNC_TIMEOUT);
162
+ // Re-sync with the chip to ensure clean communication
163
+ try {
164
+ await this.sync();
165
+ }
166
+ catch (syncErr) {
167
+ this.logger.debug(`Re-sync after GET_SECURITY_INFO failure: ${syncErr}`);
168
+ }
169
+ }
170
+ // Fallback: Use magic value detection for ESP8266, ESP32, ESP32-S2
171
+ const chipMagicValue = await this.readRegister(CHIP_DETECT_MAGIC_REG_ADDR);
172
+ const chip = CHIP_DETECT_MAGIC_VALUES[chipMagicValue >>> 0];
173
+ if (chip === undefined) {
174
+ throw new Error(`Unknown Chip: Hex: ${toHex(chipMagicValue >>> 0, 8).toLowerCase()} Number: ${chipMagicValue}`);
175
+ }
176
+ this.chipName = chip.name;
177
+ this.chipFamily = chip.family;
178
+ if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
179
+ this.chipRevision = await this.getChipRevision();
180
+ this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
181
+ if (this.chipRevision >= 300) {
182
+ this.chipVariant = "rev300";
183
+ }
184
+ else {
185
+ this.chipVariant = "rev0";
186
+ }
187
+ this.logger.debug(`ESP32-P4 variant: ${this.chipVariant}`);
188
+ }
189
+ this.logger.debug(`Detected chip via magic value: ${toHex(chipMagicValue >>> 0, 8)} (${this.chipName})`);
190
+ }
191
+ /**
192
+ * Get chip revision for ESP32-P4
193
+ */
194
+ async getChipRevision() {
195
+ if (this.chipFamily !== CHIP_FAMILY_ESP32P4) {
196
+ return 0;
197
+ }
198
+ // Read from EFUSE_BLOCK1 to get chip revision
199
+ // Word 2 contains revision info for ESP32-P4
200
+ const word2 = await this.readRegister(ESP32P4_EFUSE_BLOCK1_ADDR + 8);
201
+ // Minor revision: bits [3:0]
202
+ const minorRev = word2 & 0x0f;
203
+ // Major revision: bits [23] << 2 | bits [5:4]
204
+ const majorRev = (((word2 >> 23) & 1) << 2) | ((word2 >> 4) & 0x03);
205
+ // Revision is major * 100 + minor
206
+ return majorRev * 100 + minorRev;
207
+ }
208
+ /**
209
+ * Get security info including chip ID (ESP32-C3 and later)
210
+ */
211
+ async getSecurityInfo() {
212
+ const [, responseData] = await this.checkCommand(ESP_GET_SECURITY_INFO, [], 0);
213
+ // Some chips/ROM versions return empty response or don't support this command
214
+ if (responseData.length === 0) {
215
+ throw new Error(`GET_SECURITY_INFO not supported or returned empty response`);
216
+ }
217
+ if (responseData.length < 12) {
218
+ throw new Error(`Invalid security info response length: ${responseData.length} (expected at least 12 bytes)`);
219
+ }
220
+ const flags = unpack("<I", responseData.slice(0, 4))[0];
221
+ const flashCryptCnt = responseData[4];
222
+ const keyPurposes = Array.from(responseData.slice(5, 12));
223
+ const chipId = responseData.length >= 16
224
+ ? unpack("<I", responseData.slice(12, 16))[0]
225
+ : 0;
226
+ const apiVersion = responseData.length >= 20
227
+ ? unpack("<I", responseData.slice(16, 20))[0]
228
+ : 0;
229
+ return {
230
+ flags,
231
+ flashCryptCnt,
232
+ keyPurposes,
233
+ chipId,
234
+ apiVersion,
235
+ };
236
+ }
237
+ /**
238
+ * @name readLoop
239
+ * Reads data from the input stream and places it in the inputBuffer
240
+ */
241
+ async readLoop() {
242
+ if (this.debug) {
243
+ this.logger.debug("Starting read loop");
244
+ }
245
+ this._reader = this.port.readable.getReader();
246
+ try {
247
+ let keepReading = true;
248
+ while (keepReading) {
249
+ const { value, done } = await this._reader.read();
250
+ if (done) {
251
+ this._reader.releaseLock();
252
+ keepReading = false;
253
+ break;
254
+ }
255
+ if (!value || value.length === 0) {
256
+ continue;
257
+ }
258
+ // Always read from browser's serial buffer immediately
259
+ // to prevent browser buffer overflow. Don't apply back-pressure here.
260
+ const chunk = Array.from(value);
261
+ Array.prototype.push.apply(this._inputBuffer, chunk);
262
+ // Track total bytes read from serial port
263
+ this._totalBytesRead += value.length;
264
+ }
265
+ }
266
+ catch {
267
+ this.logger.error("Read loop got disconnected");
268
+ }
269
+ // Disconnected!
270
+ this.connected = false;
271
+ // Check if this is ESP32-S2 Native USB that needs port reselection
272
+ // Only trigger reconnect if initialization did NOT succeed (wrong port)
273
+ if (this._isESP32S2NativeUSB && !this._initializationSucceeded) {
274
+ this.logger.log("ESP32-S2 Native USB detected - requesting port reselection");
275
+ this.dispatchEvent(new CustomEvent("esp32s2-usb-reconnect", {
276
+ detail: { message: "ESP32-S2 Native USB requires port reselection" },
277
+ }));
278
+ }
279
+ this.dispatchEvent(new Event("disconnect"));
280
+ this.logger.debug("Finished read loop");
281
+ }
282
+ sleep(ms = 100) {
283
+ return new Promise((resolve) => setTimeout(resolve, ms));
284
+ }
285
+ async setRTS(state) {
286
+ await this.port.setSignals({ requestToSend: state });
287
+ // Work-around for adapters on Windows using the usbser.sys driver:
288
+ // generate a dummy change to DTR so that the set-control-line-state
289
+ // request is sent with the updated RTS state and the same DTR state
290
+ // Referenced to esptool.py
291
+ await this.setDTR(this.state_DTR);
292
+ }
293
+ async setDTR(state) {
294
+ this.state_DTR = state;
295
+ await this.port.setSignals({ dataTerminalReady: state });
296
+ }
297
+ async hardReset(bootloader = false) {
298
+ if (bootloader) {
299
+ // enter flash mode
300
+ if (this.port.getInfo().usbProductId === USB_JTAG_SERIAL_PID) {
301
+ // esp32c3 esp32s3 etc. build-in USB serial.
302
+ // when connect to computer direct via usb, using following signals
303
+ // to enter flash mode automatically.
304
+ await this.setDTR(false);
305
+ await this.setRTS(false);
306
+ await this.sleep(100);
307
+ await this.setDTR(true);
308
+ await this.setRTS(false);
309
+ await this.sleep(100);
310
+ await this.setRTS(true);
311
+ await this.setDTR(false);
312
+ await this.setRTS(true);
313
+ await this.sleep(100);
314
+ await this.setDTR(false);
315
+ await this.setRTS(false);
316
+ this.logger.log("USB MCU reset.");
317
+ }
318
+ else {
319
+ // otherwise, esp chip should be connected to computer via usb-serial
320
+ // bridge chip like ch340,CP2102 etc.
321
+ // use normal way to enter flash mode.
322
+ await this.setDTR(false);
323
+ await this.setRTS(true);
324
+ await this.sleep(100);
325
+ await this.setDTR(true);
326
+ await this.setRTS(false);
327
+ await this.sleep(50);
328
+ await this.setDTR(false);
329
+ this.logger.log("DTR/RTS USB serial chip reset.");
330
+ }
331
+ }
332
+ else {
333
+ // just reset
334
+ await this.setRTS(true); // EN->LOW
335
+ await this.sleep(100);
336
+ await this.setRTS(false);
337
+ this.logger.log("Hard reset.");
338
+ }
339
+ await new Promise((resolve) => setTimeout(resolve, 1000));
340
+ }
341
+ /**
342
+ * @name macAddr
343
+ * The MAC address burned into the OTP memory of the ESP chip
344
+ */
345
+ macAddr() {
346
+ const macAddr = new Array(6).fill(0);
347
+ const mac0 = this._efuses[0];
348
+ const mac1 = this._efuses[1];
349
+ const mac2 = this._efuses[2];
350
+ const mac3 = this._efuses[3];
351
+ let oui;
352
+ if (this.chipFamily == CHIP_FAMILY_ESP8266) {
353
+ if (mac3 != 0) {
354
+ oui = [(mac3 >> 16) & 0xff, (mac3 >> 8) & 0xff, mac3 & 0xff];
355
+ }
356
+ else if (((mac1 >> 16) & 0xff) == 0) {
357
+ oui = [0x18, 0xfe, 0x34];
358
+ }
359
+ else if (((mac1 >> 16) & 0xff) == 1) {
360
+ oui = [0xac, 0xd0, 0x74];
361
+ }
362
+ else {
363
+ throw new Error("Couldnt determine OUI");
364
+ }
365
+ macAddr[0] = oui[0];
366
+ macAddr[1] = oui[1];
367
+ macAddr[2] = oui[2];
368
+ macAddr[3] = (mac1 >> 8) & 0xff;
369
+ macAddr[4] = mac1 & 0xff;
370
+ macAddr[5] = (mac0 >> 24) & 0xff;
371
+ }
372
+ else if (this.chipFamily == CHIP_FAMILY_ESP32) {
373
+ macAddr[0] = (mac2 >> 8) & 0xff;
374
+ macAddr[1] = mac2 & 0xff;
375
+ macAddr[2] = (mac1 >> 24) & 0xff;
376
+ macAddr[3] = (mac1 >> 16) & 0xff;
377
+ macAddr[4] = (mac1 >> 8) & 0xff;
378
+ macAddr[5] = mac1 & 0xff;
379
+ }
380
+ else if (this.chipFamily == CHIP_FAMILY_ESP32S2 ||
381
+ this.chipFamily == CHIP_FAMILY_ESP32S3 ||
382
+ this.chipFamily == CHIP_FAMILY_ESP32C2 ||
383
+ this.chipFamily == CHIP_FAMILY_ESP32C3 ||
384
+ this.chipFamily == CHIP_FAMILY_ESP32C5 ||
385
+ this.chipFamily == CHIP_FAMILY_ESP32C6 ||
386
+ this.chipFamily == CHIP_FAMILY_ESP32C61 ||
387
+ this.chipFamily == CHIP_FAMILY_ESP32H2 ||
388
+ this.chipFamily == CHIP_FAMILY_ESP32H4 ||
389
+ this.chipFamily == CHIP_FAMILY_ESP32H21 ||
390
+ this.chipFamily == CHIP_FAMILY_ESP32P4 ||
391
+ this.chipFamily == CHIP_FAMILY_ESP32S31) {
392
+ macAddr[0] = (mac1 >> 8) & 0xff;
393
+ macAddr[1] = mac1 & 0xff;
394
+ macAddr[2] = (mac0 >> 24) & 0xff;
395
+ macAddr[3] = (mac0 >> 16) & 0xff;
396
+ macAddr[4] = (mac0 >> 8) & 0xff;
397
+ macAddr[5] = mac0 & 0xff;
398
+ }
399
+ else {
400
+ throw new Error("Unknown chip family");
401
+ }
402
+ return macAddr;
403
+ }
404
+ async readRegister(reg) {
405
+ if (this.debug) {
406
+ this.logger.debug("Reading from Register " + toHex(reg, 8));
407
+ }
408
+ const packet = pack("<I", reg);
409
+ await this.sendCommand(ESP_READ_REG, packet);
410
+ const [val] = await this.getResponse(ESP_READ_REG);
411
+ return val;
412
+ }
413
+ /**
414
+ * @name checkCommand
415
+ * Send a command packet, check that the command succeeded and
416
+ * return a tuple with the value and data.
417
+ * See the ESP Serial Protocol for more details on what value/data are
418
+ */
419
+ async checkCommand(opcode, buffer, checksum = 0, timeout = DEFAULT_TIMEOUT) {
420
+ timeout = Math.min(timeout, MAX_TIMEOUT);
421
+ await this.sendCommand(opcode, buffer, checksum);
422
+ const [value, responseData] = await this.getResponse(opcode, timeout);
423
+ if (responseData === null) {
424
+ throw new Error("Didn't get enough status bytes");
425
+ }
426
+ let data = responseData;
427
+ let statusLen = 0;
428
+ if (this.IS_STUB || this.chipFamily == CHIP_FAMILY_ESP8266) {
429
+ statusLen = 2;
430
+ }
431
+ else if ([
432
+ CHIP_FAMILY_ESP32,
433
+ CHIP_FAMILY_ESP32S2,
434
+ CHIP_FAMILY_ESP32S3,
435
+ CHIP_FAMILY_ESP32C2,
436
+ CHIP_FAMILY_ESP32C3,
437
+ CHIP_FAMILY_ESP32C5,
438
+ CHIP_FAMILY_ESP32C6,
439
+ CHIP_FAMILY_ESP32C61,
440
+ CHIP_FAMILY_ESP32H2,
441
+ CHIP_FAMILY_ESP32H4,
442
+ CHIP_FAMILY_ESP32H21,
443
+ CHIP_FAMILY_ESP32P4,
444
+ CHIP_FAMILY_ESP32S31,
445
+ ].includes(this.chipFamily)) {
446
+ statusLen = 4;
447
+ }
448
+ else {
449
+ // When chipFamily is not yet set (e.g., during GET_SECURITY_INFO in detectChip),
450
+ // assume modern chips use 4-byte status
451
+ if (opcode === ESP_GET_SECURITY_INFO) {
452
+ statusLen = 4;
453
+ }
454
+ else if ([2, 4].includes(data.length)) {
455
+ statusLen = data.length;
456
+ }
457
+ }
458
+ if (data.length < statusLen) {
459
+ throw new Error("Didn't get enough status bytes");
460
+ }
461
+ const status = data.slice(-statusLen, data.length);
462
+ data = data.slice(0, -statusLen);
463
+ if (this.debug) {
464
+ this.logger.debug("status", status);
465
+ this.logger.debug("value", value);
466
+ this.logger.debug("data", data);
467
+ }
468
+ if (status[0] == 1) {
469
+ if (status[1] == ROM_INVALID_RECV_MSG) {
470
+ // Unsupported command can result in more than one error response
471
+ // Use drainInputBuffer for CP210x compatibility on Windows
472
+ await this.drainInputBuffer(200);
473
+ throw new Error("Invalid (unsupported) command " + toHex(opcode));
474
+ }
475
+ else {
476
+ throw new Error("Command failure error code " + toHex(status[1]));
477
+ }
478
+ }
479
+ return [value, data];
480
+ }
481
+ /**
482
+ * @name sendCommand
483
+ * Send a slip-encoded, checksummed command over the UART,
484
+ * does not check response
485
+ */
486
+ async sendCommand(opcode, buffer, checksum = 0) {
487
+ const packet = slipEncode([
488
+ ...pack("<BBHI", 0x00, opcode, buffer.length, checksum),
489
+ ...buffer,
490
+ ]);
491
+ if (this.debug) {
492
+ this.logger.debug(`Writing ${packet.length} byte${packet.length == 1 ? "" : "s"}:`, packet);
493
+ }
494
+ await this.writeToStream(packet);
495
+ }
496
+ /**
497
+ * @name readPacket
498
+ * Generator to read SLIP packets from a serial port.
499
+ * Yields one full SLIP packet at a time, raises exception on timeout or invalid data.
500
+ */
501
+ async readPacket(timeout) {
502
+ let partialPacket = null;
503
+ let inEscape = false;
504
+ let readBytes = [];
505
+ while (true) {
506
+ const stamp = Date.now();
507
+ readBytes = [];
508
+ while (Date.now() - stamp < timeout) {
509
+ if (this._inputBuffer.length > 0) {
510
+ readBytes.push(this._inputBuffer.shift());
511
+ break;
512
+ }
513
+ else {
514
+ // Reduced sleep time for faster response during high-speed transfers
515
+ await sleep(1);
516
+ }
517
+ }
518
+ if (readBytes.length == 0) {
519
+ const waitingFor = partialPacket === null ? "header" : "content";
520
+ throw new SlipReadError("Timed out waiting for packet " + waitingFor);
521
+ }
522
+ if (this.debug)
523
+ this.logger.debug("Read " + readBytes.length + " bytes: " + hexFormatter(readBytes));
524
+ for (const b of readBytes) {
525
+ if (partialPacket === null) {
526
+ // waiting for packet header
527
+ if (b == 0xc0) {
528
+ partialPacket = [];
529
+ }
530
+ else {
531
+ if (this.debug) {
532
+ this.logger.debug("Read invalid data: " + hexFormatter(readBytes));
533
+ this.logger.debug("Remaining data in serial buffer: " +
534
+ hexFormatter(this._inputBuffer));
535
+ }
536
+ throw new SlipReadError("Invalid head of packet (" + toHex(b) + ")");
537
+ }
538
+ }
539
+ else if (inEscape) {
540
+ // part-way through escape sequence
541
+ inEscape = false;
542
+ if (b == 0xdc) {
543
+ partialPacket.push(0xc0);
544
+ }
545
+ else if (b == 0xdd) {
546
+ partialPacket.push(0xdb);
547
+ }
548
+ else {
549
+ if (this.debug) {
550
+ this.logger.debug("Read invalid data: " + hexFormatter(readBytes));
551
+ this.logger.debug("Remaining data in serial buffer: " +
552
+ hexFormatter(this._inputBuffer));
553
+ }
554
+ throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(b) + ")");
555
+ }
556
+ }
557
+ else if (b == 0xdb) {
558
+ // start of escape sequence
559
+ inEscape = true;
560
+ }
561
+ else if (b == 0xc0) {
562
+ // end of packet
563
+ if (this.debug)
564
+ this.logger.debug("Received full packet: " + hexFormatter(partialPacket));
565
+ return partialPacket;
566
+ }
567
+ else {
568
+ // normal byte in packet
569
+ partialPacket.push(b);
570
+ }
571
+ }
572
+ }
573
+ throw new SlipReadError("Invalid state");
574
+ }
575
+ /**
576
+ * @name getResponse
577
+ * Read response data and decodes the slip packet, then parses
578
+ * out the value/data and returns as a tuple of (value, data) where
579
+ * each is a list of bytes
580
+ */
581
+ async getResponse(opcode, timeout = DEFAULT_TIMEOUT) {
582
+ for (let i = 0; i < 100; i++) {
583
+ const packet = await this.readPacket(timeout);
584
+ if (packet.length < 8) {
585
+ continue;
586
+ }
587
+ const [resp, opRet, , val] = unpack("<BBHI", packet.slice(0, 8));
588
+ if (resp != 1) {
589
+ continue;
590
+ }
591
+ const data = packet.slice(8);
592
+ if (opcode == null || opRet == opcode) {
593
+ return [val, data];
594
+ }
595
+ if (data[0] != 0 && data[1] == ROM_INVALID_RECV_MSG) {
596
+ // Unsupported command can result in more than one error response
597
+ // Use drainInputBuffer for CP210x compatibility on Windows
598
+ await this.drainInputBuffer(200);
599
+ throw new Error(`Invalid (unsupported) command ${toHex(opcode)}`);
600
+ }
601
+ }
602
+ throw "Response doesn't match request";
603
+ }
604
+ /**
605
+ * @name checksum
606
+ * Calculate checksum of a blob, as it is defined by the ROM
607
+ */
608
+ checksum(data, state = ESP_CHECKSUM_MAGIC) {
609
+ for (const b of data) {
610
+ state ^= b;
611
+ }
612
+ return state;
613
+ }
614
+ async setBaudrate(baud) {
615
+ if (this.chipFamily == CHIP_FAMILY_ESP8266) {
616
+ throw new Error("Changing baud rate is not supported on the ESP8266");
617
+ }
618
+ try {
619
+ // Send ESP_ROM_BAUD(115200) as the old one if running STUB otherwise 0
620
+ const buffer = pack("<II", baud, this.IS_STUB ? ESP_ROM_BAUD : 0);
621
+ await this.checkCommand(ESP_CHANGE_BAUDRATE, buffer);
622
+ }
623
+ catch (e) {
624
+ this.logger.error(`Baudrate change error: ${e}`);
625
+ throw new Error(`Unable to change the baud rate to ${baud}: No response from set baud rate command.`);
626
+ }
627
+ if (this._parent) {
628
+ await this._parent.reconfigurePort(baud);
629
+ }
630
+ else {
631
+ await this.reconfigurePort(baud);
632
+ }
633
+ // Wait for port to be ready after baudrate change
634
+ await sleep(SYNC_TIMEOUT);
635
+ // Track current baudrate for reconnect
636
+ if (this._parent) {
637
+ this._parent._currentBaudRate = baud;
638
+ }
639
+ else {
640
+ this._currentBaudRate = baud;
641
+ }
642
+ // Warn if baudrate exceeds USB-Serial chip capability
643
+ const maxBaud = this._parent
644
+ ? this._parent._maxUSBSerialBaudrate
645
+ : this._maxUSBSerialBaudrate;
646
+ if (maxBaud && baud > maxBaud) {
647
+ this.logger.log(`⚠️ WARNING: Baudrate ${baud} exceeds USB-Serial chip limit (${maxBaud})!`);
648
+ this.logger.log(`⚠️ This may cause data corruption or connection failures!`);
649
+ }
650
+ this.logger.log(`Changed baud rate to ${baud}`);
651
+ }
652
+ async reconfigurePort(baud) {
653
+ var _a;
654
+ try {
655
+ // SerialPort does not allow to be reconfigured while open so we close and re-open
656
+ // reader.cancel() causes the Promise returned by the read() operation running on
657
+ // the readLoop to return immediately with { value: undefined, done: true } and thus
658
+ // breaking the loop and exiting readLoop();
659
+ await ((_a = this._reader) === null || _a === void 0 ? void 0 : _a.cancel());
660
+ await this.port.close();
661
+ // Reopen Port
662
+ await this.port.open({ baudRate: baud });
663
+ // Clear buffer again
664
+ await this.flushSerialBuffers();
665
+ // Restart Readloop
666
+ this.readLoop();
667
+ }
668
+ catch (e) {
669
+ this.logger.error(`Reconfigure port error: ${e}`);
670
+ throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
671
+ }
672
+ }
673
+ /**
674
+ * @name sync
675
+ * Put into ROM bootload mode & attempt to synchronize with the
676
+ * ESP ROM bootloader, we will retry a few times
677
+ */
678
+ async sync() {
679
+ for (let i = 0; i < 5; i++) {
680
+ this._inputBuffer.length = 0;
681
+ const response = await this._sync();
682
+ if (response) {
683
+ await sleep(SYNC_TIMEOUT);
684
+ return true;
685
+ }
686
+ await sleep(SYNC_TIMEOUT);
687
+ }
688
+ throw new Error("Couldn't sync to ESP. Try resetting.");
689
+ }
690
+ /**
691
+ * @name _sync
692
+ * Perform a soft-sync using AT sync packets, does not perform
693
+ * any hardware resetting
694
+ */
695
+ async _sync() {
696
+ await this.sendCommand(ESP_SYNC, SYNC_PACKET);
697
+ for (let i = 0; i < 8; i++) {
698
+ try {
699
+ const [, data] = await this.getResponse(ESP_SYNC, SYNC_TIMEOUT);
700
+ if (data.length > 1 && data[0] == 0 && data[1] == 0) {
701
+ return true;
702
+ }
703
+ }
704
+ catch {
705
+ // If read packet fails.
706
+ }
707
+ }
708
+ return false;
709
+ }
710
+ /**
711
+ * @name getFlashWriteSize
712
+ * Get the Flash write size based on the chip
713
+ */
714
+ getFlashWriteSize() {
715
+ if (this.IS_STUB) {
716
+ return STUB_FLASH_WRITE_SIZE;
717
+ }
718
+ return FLASH_WRITE_SIZE;
719
+ }
720
+ /**
721
+ * @name flashData
722
+ * Program a full, uncompressed binary file into SPI Flash at
723
+ * a given offset. If an ESP32 and md5 string is passed in, will also
724
+ * verify memory. ESP8266 does not have checksum memory verification in
725
+ * ROM
726
+ */
727
+ async flashData(binaryData, updateProgress, offset = 0, compress = false) {
728
+ if (binaryData.byteLength >= 8) {
729
+ // unpack the (potential) image header
730
+ const header = Array.from(new Uint8Array(binaryData, 0, 4));
731
+ const headerMagic = header[0];
732
+ const headerFlashMode = header[2];
733
+ const headerFlashSizeFreq = header[3];
734
+ this.logger.log(`Image header, Magic=${toHex(headerMagic)}, FlashMode=${toHex(headerFlashMode)}, FlashSizeFreq=${toHex(headerFlashSizeFreq)}`);
735
+ }
736
+ const uncompressedFilesize = binaryData.byteLength;
737
+ let compressedFilesize = 0;
738
+ let dataToFlash;
739
+ let timeout = DEFAULT_TIMEOUT;
740
+ if (compress) {
741
+ dataToFlash = deflate(new Uint8Array(binaryData), {
742
+ level: 9,
743
+ }).buffer;
744
+ compressedFilesize = dataToFlash.byteLength;
745
+ this.logger.log(`Writing data with filesize: ${uncompressedFilesize}. Compressed Size: ${compressedFilesize}`);
746
+ timeout = await this.flashDeflBegin(uncompressedFilesize, compressedFilesize, offset);
747
+ }
748
+ else {
749
+ this.logger.log(`Writing data with filesize: ${uncompressedFilesize}`);
750
+ dataToFlash = binaryData;
751
+ await this.flashBegin(uncompressedFilesize, offset);
752
+ }
753
+ let block = [];
754
+ let seq = 0;
755
+ let written = 0;
756
+ let position = 0;
757
+ const stamp = Date.now();
758
+ const flashWriteSize = this.getFlashWriteSize();
759
+ const filesize = compress ? compressedFilesize : uncompressedFilesize;
760
+ while (filesize - position > 0) {
761
+ if (this.debug) {
762
+ this.logger.log(`Writing at ${toHex(offset + seq * flashWriteSize, 8)} `);
763
+ }
764
+ if (filesize - position >= flashWriteSize) {
765
+ block = Array.from(new Uint8Array(dataToFlash, position, flashWriteSize));
766
+ }
767
+ else {
768
+ // Pad the last block only if we are sending uncompressed data.
769
+ block = Array.from(new Uint8Array(dataToFlash, position, filesize - position));
770
+ if (!compress) {
771
+ block = block.concat(new Array(flashWriteSize - block.length).fill(0xff));
772
+ }
773
+ }
774
+ if (compress) {
775
+ await this.flashDeflBlock(block, seq, timeout);
776
+ }
777
+ else {
778
+ await this.flashBlock(block, seq);
779
+ }
780
+ seq += 1;
781
+ // If using compression we update the progress with the proportional size of the block taking into account the compression ratio.
782
+ // This way we report progress on the uncompressed size
783
+ written += compress
784
+ ? Math.round((block.length * uncompressedFilesize) / compressedFilesize)
785
+ : block.length;
786
+ position += flashWriteSize;
787
+ updateProgress(Math.min(written, uncompressedFilesize), uncompressedFilesize);
788
+ }
789
+ this.logger.log("Took " + (Date.now() - stamp) + "ms to write " + filesize + " bytes");
790
+ // Only send flashF finish if running the stub because ir causes the ROM to exit and run user code
791
+ if (this.IS_STUB) {
792
+ await this.flashBegin(0, 0);
793
+ if (compress) {
794
+ await this.flashDeflFinish();
795
+ }
796
+ else {
797
+ await this.flashFinish();
798
+ }
799
+ }
800
+ }
801
+ /**
802
+ * @name flashBlock
803
+ * Send one block of data to program into SPI Flash memory
804
+ */
805
+ async flashBlock(data, seq, timeout = DEFAULT_TIMEOUT) {
806
+ await this.checkCommand(ESP_FLASH_DATA, pack("<IIII", data.length, seq, 0, 0).concat(data), this.checksum(data), timeout);
807
+ }
808
+ async flashDeflBlock(data, seq, timeout = DEFAULT_TIMEOUT) {
809
+ await this.checkCommand(ESP_FLASH_DEFL_DATA, pack("<IIII", data.length, seq, 0, 0).concat(data), this.checksum(data), timeout);
810
+ }
811
+ /**
812
+ * @name flashBegin
813
+ * Prepare for flashing by attaching SPI chip and erasing the
814
+ * number of blocks requred.
815
+ */
816
+ async flashBegin(size = 0, offset = 0, encrypted = false) {
817
+ // Flush serial buffers before flash write operation
818
+ await this.flushSerialBuffers();
819
+ let eraseSize;
820
+ const flashWriteSize = this.getFlashWriteSize();
821
+ if (!this.IS_STUB &&
822
+ [
823
+ CHIP_FAMILY_ESP32,
824
+ CHIP_FAMILY_ESP32S2,
825
+ CHIP_FAMILY_ESP32S3,
826
+ CHIP_FAMILY_ESP32C2,
827
+ CHIP_FAMILY_ESP32C3,
828
+ CHIP_FAMILY_ESP32C5,
829
+ CHIP_FAMILY_ESP32C6,
830
+ CHIP_FAMILY_ESP32C61,
831
+ CHIP_FAMILY_ESP32H2,
832
+ CHIP_FAMILY_ESP32H4,
833
+ CHIP_FAMILY_ESP32H21,
834
+ CHIP_FAMILY_ESP32P4,
835
+ CHIP_FAMILY_ESP32S31,
836
+ ].includes(this.chipFamily)) {
837
+ await this.checkCommand(ESP_SPI_ATTACH, new Array(8).fill(0));
838
+ }
839
+ const numBlocks = Math.floor((size + flashWriteSize - 1) / flashWriteSize);
840
+ if (this.chipFamily == CHIP_FAMILY_ESP8266) {
841
+ eraseSize = this.getEraseSize(offset, size);
842
+ }
843
+ else {
844
+ eraseSize = size;
845
+ }
846
+ const timeout = this.IS_STUB
847
+ ? DEFAULT_TIMEOUT
848
+ : timeoutPerMb(ERASE_REGION_TIMEOUT_PER_MB, size);
849
+ const stamp = Date.now();
850
+ let buffer = pack("<IIII", eraseSize, numBlocks, flashWriteSize, offset);
851
+ if (this.chipFamily == CHIP_FAMILY_ESP32 ||
852
+ this.chipFamily == CHIP_FAMILY_ESP32S2 ||
853
+ this.chipFamily == CHIP_FAMILY_ESP32S3 ||
854
+ this.chipFamily == CHIP_FAMILY_ESP32C2 ||
855
+ this.chipFamily == CHIP_FAMILY_ESP32C3 ||
856
+ this.chipFamily == CHIP_FAMILY_ESP32C5 ||
857
+ this.chipFamily == CHIP_FAMILY_ESP32C6 ||
858
+ this.chipFamily == CHIP_FAMILY_ESP32C61 ||
859
+ this.chipFamily == CHIP_FAMILY_ESP32H2 ||
860
+ this.chipFamily == CHIP_FAMILY_ESP32H4 ||
861
+ this.chipFamily == CHIP_FAMILY_ESP32H21 ||
862
+ this.chipFamily == CHIP_FAMILY_ESP32P4 ||
863
+ this.chipFamily == CHIP_FAMILY_ESP32S31) {
864
+ buffer = buffer.concat(pack("<I", encrypted ? 1 : 0));
865
+ }
866
+ this.logger.log("Erase size " +
867
+ eraseSize +
868
+ ", blocks " +
869
+ numBlocks +
870
+ ", block size " +
871
+ toHex(flashWriteSize, 4) +
872
+ ", offset " +
873
+ toHex(offset, 4) +
874
+ ", encrypted " +
875
+ (encrypted ? "yes" : "no"));
876
+ await this.checkCommand(ESP_FLASH_BEGIN, buffer, 0, timeout);
877
+ if (size != 0 && !this.IS_STUB) {
878
+ this.logger.log("Took " + (Date.now() - stamp) + "ms to erase " + numBlocks + " bytes");
879
+ }
880
+ return numBlocks;
881
+ }
882
+ /**
883
+ * @name flashDeflBegin
884
+ *
885
+ */
886
+ async flashDeflBegin(size = 0, compressedSize = 0, offset = 0) {
887
+ // Start downloading compressed data to Flash (performs an erase)
888
+ // Returns number of blocks to write.
889
+ const flashWriteSize = this.getFlashWriteSize();
890
+ const numBlocks = Math.floor((compressedSize + flashWriteSize - 1) / flashWriteSize);
891
+ const eraseBlocks = Math.floor((size + flashWriteSize - 1) / flashWriteSize);
892
+ let writeSize = 0;
893
+ let timeout = 0;
894
+ if (this.IS_STUB) {
895
+ writeSize = size; // stub expects number of bytes here, manages erasing internally
896
+ timeout = timeoutPerMb(ERASE_REGION_TIMEOUT_PER_MB, writeSize); // ROM performs the erase up front
897
+ }
898
+ else {
899
+ writeSize = eraseBlocks * flashWriteSize; // ROM expects rounded up to erase block size
900
+ timeout = DEFAULT_TIMEOUT;
901
+ }
902
+ const buffer = pack("<IIII", writeSize, numBlocks, flashWriteSize, offset);
903
+ await this.checkCommand(ESP_FLASH_DEFL_BEGIN, buffer, 0, timeout);
904
+ return timeout;
905
+ }
906
+ async flashFinish() {
907
+ const buffer = pack("<I", 1);
908
+ await this.checkCommand(ESP_FLASH_END, buffer);
909
+ }
910
+ async flashDeflFinish() {
911
+ const buffer = pack("<I", 1);
912
+ await this.checkCommand(ESP_FLASH_DEFL_END, buffer);
913
+ }
914
+ getBootloaderOffset() {
915
+ const bootFlashOffs = getSpiFlashAddresses(this.getChipFamily());
916
+ const BootldrFlashOffs = bootFlashOffs.flashOffs;
917
+ return BootldrFlashOffs;
918
+ }
919
+ async flashId() {
920
+ const SPIFLASH_RDID = 0x9f;
921
+ const result = await this.runSpiFlashCommand(SPIFLASH_RDID, [], 24);
922
+ return result;
923
+ }
924
+ getChipFamily() {
925
+ return this._parent ? this._parent.chipFamily : this.chipFamily;
926
+ }
927
+ async writeRegister(address, value, mask = 0xffffffff, delayUs = 0, delayAfterUs = 0) {
928
+ let buffer = pack("<IIII", address, value, mask, delayUs);
929
+ if (delayAfterUs > 0) {
930
+ // add a dummy write to a date register as an excuse to have a delay
931
+ buffer = buffer.concat(pack("<IIII", getSpiFlashAddresses(this.getChipFamily()).uartDateReg, 0, 0, delayAfterUs));
932
+ }
933
+ await this.checkCommand(ESP_WRITE_REG, buffer);
934
+ }
935
+ async setDataLengths(spiAddresses, mosiBits, misoBits) {
936
+ if (spiAddresses.mosiDlenOffs != -1) {
937
+ // Actual MCUs have a more sophisticated way to set up "user" commands
938
+ const SPI_MOSI_DLEN_REG = spiAddresses.regBase + spiAddresses.mosiDlenOffs;
939
+ const SPI_MISO_DLEN_REG = spiAddresses.regBase + spiAddresses.misoDlenOffs;
940
+ if (mosiBits > 0) {
941
+ await this.writeRegister(SPI_MOSI_DLEN_REG, mosiBits - 1);
942
+ }
943
+ if (misoBits > 0) {
944
+ await this.writeRegister(SPI_MISO_DLEN_REG, misoBits - 1);
945
+ }
946
+ }
947
+ else {
948
+ const SPI_DATA_LEN_REG = spiAddresses.regBase + spiAddresses.usr1Offs;
949
+ const SPI_MOSI_BITLEN_S = 17;
950
+ const SPI_MISO_BITLEN_S = 8;
951
+ const mosiMask = mosiBits == 0 ? 0 : mosiBits - 1;
952
+ const misoMask = misoBits == 0 ? 0 : misoBits - 1;
953
+ const value = (misoMask << SPI_MISO_BITLEN_S) | (mosiMask << SPI_MOSI_BITLEN_S);
954
+ await this.writeRegister(SPI_DATA_LEN_REG, value);
955
+ }
956
+ }
957
+ async waitDone(spiCmdReg, spiCmdUsr) {
958
+ for (let i = 0; i < 10; i++) {
959
+ const cmdValue = await this.readRegister(spiCmdReg);
960
+ if ((cmdValue & spiCmdUsr) == 0) {
961
+ return;
962
+ }
963
+ }
964
+ throw Error("SPI command did not complete in time");
965
+ }
966
+ async runSpiFlashCommand(spiflashCommand, data, readBits = 0) {
967
+ // Run an arbitrary SPI flash command.
968
+ // This function uses the "USR_COMMAND" functionality in the ESP
969
+ // SPI hardware, rather than the precanned commands supported by
970
+ // hardware. So the value of spiflash_command is an actual command
971
+ // byte, sent over the wire.
972
+ // After writing command byte, writes 'data' to MOSI and then
973
+ // reads back 'read_bits' of reply on MISO. Result is a number.
974
+ // SPI_USR register flags
975
+ const SPI_USR_COMMAND = 1 << 31;
976
+ const SPI_USR_MISO = 1 << 28;
977
+ const SPI_USR_MOSI = 1 << 27;
978
+ // SPI registers, base address differs
979
+ const spiAddresses = getSpiFlashAddresses(this.getChipFamily());
980
+ const base = spiAddresses.regBase;
981
+ const SPI_CMD_REG = base;
982
+ const SPI_USR_REG = base + spiAddresses.usrOffs;
983
+ const SPI_USR2_REG = base + spiAddresses.usr2Offs;
984
+ const SPI_W0_REG = base + spiAddresses.w0Offs;
985
+ // SPI peripheral "command" bitmasks for SPI_CMD_REG
986
+ const SPI_CMD_USR = 1 << 18;
987
+ // shift values
988
+ const SPI_USR2_COMMAND_LEN_SHIFT = 28;
989
+ if (readBits > 32) {
990
+ throw new Error("Reading more than 32 bits back from a SPI flash operation is unsupported");
991
+ }
992
+ if (data.length > 64) {
993
+ throw new Error("Writing more than 64 bytes of data with one SPI command is unsupported");
994
+ }
995
+ const dataBits = data.length * 8;
996
+ const oldSpiUsr = await this.readRegister(SPI_USR_REG);
997
+ const oldSpiUsr2 = await this.readRegister(SPI_USR2_REG);
998
+ let flags = SPI_USR_COMMAND;
999
+ if (readBits > 0) {
1000
+ flags |= SPI_USR_MISO;
1001
+ }
1002
+ if (dataBits > 0) {
1003
+ flags |= SPI_USR_MOSI;
1004
+ }
1005
+ await this.setDataLengths(spiAddresses, dataBits, readBits);
1006
+ await this.writeRegister(SPI_USR_REG, flags);
1007
+ await this.writeRegister(SPI_USR2_REG, (7 << SPI_USR2_COMMAND_LEN_SHIFT) | spiflashCommand);
1008
+ if (dataBits == 0) {
1009
+ await this.writeRegister(SPI_W0_REG, 0); // clear data register before we read it
1010
+ }
1011
+ else {
1012
+ const padLen = (4 - (data.length % 4)) % 4;
1013
+ data = data.concat(new Array(padLen).fill(0x00)); // pad to 32-bit multiple
1014
+ const words = unpack("I".repeat(Math.floor(data.length / 4)), data);
1015
+ let nextReg = SPI_W0_REG;
1016
+ this.logger.debug(`Words Length: ${words.length}`);
1017
+ for (const word of words) {
1018
+ this.logger.debug(`Writing word ${toHex(word)} to register offset ${toHex(nextReg)}`);
1019
+ await this.writeRegister(nextReg, word);
1020
+ nextReg += 4;
1021
+ }
1022
+ }
1023
+ await this.writeRegister(SPI_CMD_REG, SPI_CMD_USR);
1024
+ await this.waitDone(SPI_CMD_REG, SPI_CMD_USR);
1025
+ const status = await this.readRegister(SPI_W0_REG);
1026
+ // restore some SPI controller registers
1027
+ await this.writeRegister(SPI_USR_REG, oldSpiUsr);
1028
+ await this.writeRegister(SPI_USR2_REG, oldSpiUsr2);
1029
+ return status;
1030
+ }
1031
+ async detectFlashSize() {
1032
+ this.logger.log("Detecting Flash Size");
1033
+ const flashId = await this.flashId();
1034
+ const manufacturer = flashId & 0xff;
1035
+ const flashIdLowbyte = (flashId >> 16) & 0xff;
1036
+ this.logger.log(`FlashId: ${toHex(flashId)}`);
1037
+ this.logger.log(`Flash Manufacturer: ${manufacturer.toString(16)}`);
1038
+ this.logger.log(`Flash Device: ${((flashId >> 8) & 0xff).toString(16)}${flashIdLowbyte.toString(16)}`);
1039
+ this.flashSize = DETECTED_FLASH_SIZES[flashIdLowbyte];
1040
+ this.logger.log(`Auto-detected Flash size: ${this.flashSize}`);
1041
+ }
1042
+ /**
1043
+ * @name getEraseSize
1044
+ * Calculate an erase size given a specific size in bytes.
1045
+ * Provides a workaround for the bootloader erase bug on ESP8266.
1046
+ */
1047
+ getEraseSize(offset, size) {
1048
+ const sectorsPerBlock = 16;
1049
+ const sectorSize = FLASH_SECTOR_SIZE;
1050
+ const numSectors = Math.floor((size + sectorSize - 1) / sectorSize);
1051
+ const startSector = Math.floor(offset / sectorSize);
1052
+ let headSectors = sectorsPerBlock - (startSector % sectorsPerBlock);
1053
+ if (numSectors < headSectors) {
1054
+ headSectors = numSectors;
1055
+ }
1056
+ if (numSectors < 2 * headSectors) {
1057
+ return Math.floor(((numSectors + 1) / 2) * sectorSize);
1058
+ }
1059
+ return (numSectors - headSectors) * sectorSize;
1060
+ }
1061
+ /**
1062
+ * @name memBegin (592)
1063
+ * Start downloading an application image to RAM
1064
+ */
1065
+ async memBegin(size, blocks, blocksize, offset) {
1066
+ return await this.checkCommand(ESP_MEM_BEGIN, pack("<IIII", size, blocks, blocksize, offset));
1067
+ }
1068
+ /**
1069
+ * @name memBlock (609)
1070
+ * Send a block of an image to RAM
1071
+ */
1072
+ async memBlock(data, seq) {
1073
+ return await this.checkCommand(ESP_MEM_DATA, pack("<IIII", data.length, seq, 0, 0).concat(data), this.checksum(data));
1074
+ }
1075
+ /**
1076
+ * @name memFinish (615)
1077
+ * Leave download mode and run the application
1078
+ *
1079
+ * Sending ESP_MEM_END usually sends a correct response back, however sometimes
1080
+ * (with ROM loader) the executed code may reset the UART or change the baud rate
1081
+ * before the transmit FIFO is empty. So in these cases we set a short timeout and
1082
+ * ignore errors.
1083
+ */
1084
+ async memFinish(entrypoint = 0) {
1085
+ const timeout = this.IS_STUB ? DEFAULT_TIMEOUT : MEM_END_ROM_TIMEOUT;
1086
+ const data = pack("<II", entrypoint == 0 ? 1 : 0, entrypoint);
1087
+ return await this.checkCommand(ESP_MEM_END, data, 0, timeout);
1088
+ }
1089
+ async runStub(skipFlashDetection = false) {
1090
+ const stub = await getStubCode(this.chipFamily, this.chipRevision);
1091
+ // No stub available for this chip, return ROM loader
1092
+ if (stub === null) {
1093
+ this.logger.log(`Stub flasher is not yet supported on ${this.chipName}, using ROM loader`);
1094
+ return this;
1095
+ }
1096
+ // We're transferring over USB, right?
1097
+ const ramBlock = USB_RAM_BLOCK;
1098
+ // Upload
1099
+ this.logger.log("Uploading stub...");
1100
+ for (const field of ["text", "data"]) {
1101
+ const fieldData = stub[field];
1102
+ const offset = stub[`${field}_start`];
1103
+ const length = fieldData.length;
1104
+ const blocks = Math.floor((length + ramBlock - 1) / ramBlock);
1105
+ await this.memBegin(length, blocks, ramBlock, offset);
1106
+ for (const seq of Array(blocks).keys()) {
1107
+ const fromOffs = seq * ramBlock;
1108
+ let toOffs = fromOffs + ramBlock;
1109
+ if (toOffs > length) {
1110
+ toOffs = length;
1111
+ }
1112
+ await this.memBlock(fieldData.slice(fromOffs, toOffs), seq);
1113
+ }
1114
+ }
1115
+ await this.memFinish(stub.entry);
1116
+ const p = await this.readPacket(500);
1117
+ const pChar = String.fromCharCode(...p);
1118
+ if (pChar != "OHAI") {
1119
+ throw new Error("Failed to start stub. Unexpected response: " + pChar);
1120
+ }
1121
+ this.logger.log("Stub is now running...");
1122
+ const espStubLoader = new EspStubLoader(this.port, this.logger, this);
1123
+ // Try to autodetect the flash size.
1124
+ if (!skipFlashDetection) {
1125
+ await espStubLoader.detectFlashSize();
1126
+ }
1127
+ return espStubLoader;
1128
+ }
1129
+ async writeToStream(data) {
1130
+ if (!this.port.writable) {
1131
+ this.logger.debug("Port writable stream not available, skipping write");
1132
+ return;
1133
+ }
1134
+ const writer = this.port.writable.getWriter();
1135
+ await writer.write(new Uint8Array(data));
1136
+ try {
1137
+ writer.releaseLock();
1138
+ }
1139
+ catch (err) {
1140
+ this.logger.error(`Ignoring release lock error: ${err}`);
1141
+ }
1142
+ }
1143
+ async disconnect() {
1144
+ if (this._parent) {
1145
+ await this._parent.disconnect();
1146
+ return;
1147
+ }
1148
+ if (!this.port.writable) {
1149
+ this.logger.debug("Port already closed, skipping disconnect");
1150
+ return;
1151
+ }
1152
+ await this.port.writable.getWriter().close();
1153
+ await new Promise((resolve) => {
1154
+ if (!this._reader) {
1155
+ resolve(undefined);
1156
+ }
1157
+ this.addEventListener("disconnect", resolve, { once: true });
1158
+ this._reader.cancel();
1159
+ });
1160
+ this.connected = false;
1161
+ }
1162
+ /**
1163
+ * @name reconnectAndResume
1164
+ * Reconnect the serial port to flush browser buffers and reload stub
1165
+ */
1166
+ async reconnect() {
1167
+ if (this._parent) {
1168
+ await this._parent.reconnect();
1169
+ return;
1170
+ }
1171
+ this.logger.log("Reconnecting serial port...");
1172
+ this.connected = false;
1173
+ this.__inputBuffer = [];
1174
+ // Cancel reader
1175
+ if (this._reader) {
1176
+ try {
1177
+ await this._reader.cancel();
1178
+ }
1179
+ catch (err) {
1180
+ this.logger.debug(`Reader cancel error: ${err}`);
1181
+ }
1182
+ this._reader = undefined;
1183
+ }
1184
+ // Close port
1185
+ try {
1186
+ await this.port.close();
1187
+ this.logger.log("Port closed");
1188
+ }
1189
+ catch (err) {
1190
+ this.logger.debug(`Port close error: ${err}`);
1191
+ }
1192
+ // Open the port
1193
+ this.logger.debug("Opening port...");
1194
+ try {
1195
+ await this.port.open({ baudRate: ESP_ROM_BAUD });
1196
+ this.connected = true;
1197
+ }
1198
+ catch (err) {
1199
+ throw new Error(`Failed to open port: ${err}`);
1200
+ }
1201
+ // Verify port streams are available
1202
+ if (!this.port.readable || !this.port.writable) {
1203
+ throw new Error(`Port streams not available after open (readable: ${!!this.port.readable}, writable: ${!!this.port.writable})`);
1204
+ }
1205
+ // Save chip info and flash size (no need to detect again)
1206
+ const savedChipFamily = this.chipFamily;
1207
+ const savedChipName = this.chipName;
1208
+ const savedChipRevision = this.chipRevision;
1209
+ const savedChipVariant = this.chipVariant;
1210
+ const savedFlashSize = this.flashSize;
1211
+ // Reinitialize
1212
+ await this.hardReset(true);
1213
+ if (!this._parent) {
1214
+ this.__inputBuffer = [];
1215
+ this.__totalBytesRead = 0;
1216
+ this.readLoop();
1217
+ }
1218
+ await this.flushSerialBuffers();
1219
+ await this.sync();
1220
+ // Restore chip info
1221
+ this.chipFamily = savedChipFamily;
1222
+ this.chipName = savedChipName;
1223
+ this.chipRevision = savedChipRevision;
1224
+ this.chipVariant = savedChipVariant;
1225
+ this.flashSize = savedFlashSize;
1226
+ this.logger.debug(`Reconnect complete (chip: ${this.chipName})`);
1227
+ // Verify port is ready
1228
+ if (!this.port.writable || !this.port.readable) {
1229
+ throw new Error("Port not ready after reconnect");
1230
+ }
1231
+ // Load stub
1232
+ const stubLoader = await this.runStub(true);
1233
+ this.logger.debug("Stub loaded");
1234
+ // Restore baudrate if it was changed
1235
+ if (this._currentBaudRate !== ESP_ROM_BAUD) {
1236
+ await stubLoader.setBaudrate(this._currentBaudRate);
1237
+ // Verify port is still ready after baudrate change
1238
+ if (!this.port.writable || !this.port.readable) {
1239
+ throw new Error(`Port not ready after baudrate change (readable: ${!!this.port.readable}, writable: ${!!this.port.writable})`);
1240
+ }
1241
+ }
1242
+ // Copy stub state to this instance if we're a stub loader
1243
+ if (this.IS_STUB) {
1244
+ Object.assign(this, stubLoader);
1245
+ }
1246
+ this.logger.debug("Reconnection successful");
1247
+ }
1248
+ /**
1249
+ * @name drainInputBuffer
1250
+ * Actively drain the input buffer by reading data for a specified time.
1251
+ * Simple approach for some drivers (especially CP210x on Windows) that have
1252
+ * issues with buffer flushing.
1253
+ *
1254
+ * Based on esptool.py fix: https://github.com/espressif/esptool/commit/5338ea054e5099ac7be235c54034802ac8a43162
1255
+ *
1256
+ * @param bufferingTime - Time in milliseconds to wait for the buffer to fill
1257
+ */
1258
+ async drainInputBuffer(bufferingTime = 200) {
1259
+ // Wait for the buffer to fill
1260
+ await sleep(bufferingTime);
1261
+ // Unsupported command response is sent 8 times and has
1262
+ // 14 bytes length including delimiter 0xC0 bytes.
1263
+ // At least part of it is read as a command response,
1264
+ // but to be safe, read it all.
1265
+ const bytesToDrain = 14 * 8;
1266
+ let drained = 0;
1267
+ // Drain the buffer by reading available data
1268
+ const drainStart = Date.now();
1269
+ const drainTimeout = 100; // Short timeout for draining
1270
+ while (drained < bytesToDrain && Date.now() - drainStart < drainTimeout) {
1271
+ if (this._inputBuffer.length > 0) {
1272
+ const byte = this._inputBuffer.shift();
1273
+ if (byte !== undefined) {
1274
+ drained++;
1275
+ }
1276
+ }
1277
+ else {
1278
+ // Small sleep to avoid busy waiting
1279
+ await sleep(1);
1280
+ }
1281
+ }
1282
+ if (drained > 0) {
1283
+ this.logger.debug(`Drained ${drained} bytes from input buffer`);
1284
+ }
1285
+ // Final clear of application buffer
1286
+ if (!this._parent) {
1287
+ this.__inputBuffer = [];
1288
+ }
1289
+ }
1290
+ /**
1291
+ * @name flushSerialBuffers
1292
+ * Flush any pending data in the TX and RX serial port buffers
1293
+ * This clears both the application RX buffer and waits for hardware buffers to drain
1294
+ */
1295
+ async flushSerialBuffers() {
1296
+ // Clear application buffer
1297
+ if (!this._parent) {
1298
+ this.__inputBuffer = [];
1299
+ }
1300
+ // Wait for any pending data
1301
+ await sleep(SYNC_TIMEOUT);
1302
+ // Final clear
1303
+ if (!this._parent) {
1304
+ this.__inputBuffer = [];
1305
+ }
1306
+ this.logger.debug("Serial buffers flushed");
1307
+ }
1308
+ /**
1309
+ * @name readFlash
1310
+ * Read flash memory from the chip (only works with stub loader)
1311
+ * @param addr - Address to read from
1312
+ * @param size - Number of bytes to read
1313
+ * @param onPacketReceived - Optional callback function called when packet is received
1314
+ * @returns Uint8Array containing the flash data
1315
+ */
1316
+ async readFlash(addr, size, onPacketReceived) {
1317
+ if (!this.IS_STUB) {
1318
+ throw new Error("Reading flash is only supported in stub mode. Please run runStub() first.");
1319
+ }
1320
+ // Flush serial buffers before flash read operation
1321
+ await this.flushSerialBuffers();
1322
+ this.logger.log(`Reading ${size} bytes from flash at address 0x${addr.toString(16)}...`);
1323
+ const CHUNK_SIZE = 0x10000; // 64KB chunks
1324
+ let allData = new Uint8Array(0);
1325
+ let currentAddr = addr;
1326
+ let remainingSize = size;
1327
+ while (remainingSize > 0) {
1328
+ const chunkSize = Math.min(CHUNK_SIZE, remainingSize);
1329
+ let chunkSuccess = false;
1330
+ let retryCount = 0;
1331
+ const MAX_RETRIES = 3;
1332
+ // Retry loop for this chunk
1333
+ while (!chunkSuccess && retryCount <= MAX_RETRIES) {
1334
+ try {
1335
+ this.logger.debug(`Reading chunk at 0x${currentAddr.toString(16)}, size: 0x${chunkSize.toString(16)}`);
1336
+ // Send read flash command for this chunk
1337
+ const pkt = pack("<IIII", currentAddr, chunkSize, 0x1000, 1024);
1338
+ const [res] = await this.checkCommand(ESP_READ_FLASH, pkt);
1339
+ if (res != 0) {
1340
+ throw new Error("Failed to read memory: " + res);
1341
+ }
1342
+ let resp = new Uint8Array(0);
1343
+ while (resp.length < chunkSize) {
1344
+ // Read a SLIP packet
1345
+ let packet;
1346
+ try {
1347
+ packet = await this.readPacket(FLASH_READ_TIMEOUT);
1348
+ }
1349
+ catch (err) {
1350
+ if (err instanceof SlipReadError) {
1351
+ this.logger.debug(`SLIP read error at ${resp.length} bytes: ${err.message}`);
1352
+ // If we've read all the data we need, break
1353
+ if (resp.length >= chunkSize) {
1354
+ break;
1355
+ }
1356
+ }
1357
+ throw err;
1358
+ }
1359
+ if (packet && packet.length > 0) {
1360
+ const packetData = new Uint8Array(packet);
1361
+ // Append to response
1362
+ const newResp = new Uint8Array(resp.length + packetData.length);
1363
+ newResp.set(resp);
1364
+ newResp.set(packetData, resp.length);
1365
+ resp = newResp;
1366
+ // Send acknowledgment
1367
+ const ackData = pack("<I", resp.length);
1368
+ const slipEncodedAck = slipEncode(ackData);
1369
+ await this.writeToStream(slipEncodedAck);
1370
+ }
1371
+ }
1372
+ // Chunk read successfully - append to all data
1373
+ const newAllData = new Uint8Array(allData.length + resp.length);
1374
+ newAllData.set(allData);
1375
+ newAllData.set(resp, allData.length);
1376
+ allData = newAllData;
1377
+ chunkSuccess = true;
1378
+ }
1379
+ catch (err) {
1380
+ retryCount++;
1381
+ // Check if it's a timeout error
1382
+ if (err instanceof SlipReadError &&
1383
+ err.message.includes("Timed out")) {
1384
+ if (retryCount <= MAX_RETRIES) {
1385
+ this.logger.log(`⚠️ Timeout error at 0x${currentAddr.toString(16)}. Reconnecting and retrying (attempt ${retryCount}/${MAX_RETRIES})...`);
1386
+ try {
1387
+ await this.reconnect();
1388
+ // Continue to retry the same chunk
1389
+ }
1390
+ catch (reconnectErr) {
1391
+ throw new Error(`Reconnect failed: ${reconnectErr}`);
1392
+ }
1393
+ }
1394
+ else {
1395
+ throw new Error(`Failed to read chunk at 0x${currentAddr.toString(16)} after ${MAX_RETRIES} retries: ${err}`);
1396
+ }
1397
+ }
1398
+ else {
1399
+ // Non-timeout error, don't retry
1400
+ throw err;
1401
+ }
1402
+ }
1403
+ }
1404
+ // Update progress (use empty array since we already appended to allData)
1405
+ if (onPacketReceived) {
1406
+ onPacketReceived(new Uint8Array(chunkSize), allData.length, size);
1407
+ }
1408
+ currentAddr += chunkSize;
1409
+ remainingSize -= chunkSize;
1410
+ this.logger.debug(`Total progress: 0x${allData.length.toString(16)} from 0x${size.toString(16)} bytes`);
1411
+ }
1412
+ this.logger.debug(`Successfully read ${allData.length} bytes from flash`);
1413
+ return allData;
1414
+ }
1415
+ }
1416
+ class EspStubLoader extends ESPLoader {
1417
+ constructor() {
1418
+ super(...arguments);
1419
+ /*
1420
+ The Stubloader has commands that run on the uploaded Stub Code in RAM
1421
+ rather than built in commands.
1422
+ */
1423
+ this.IS_STUB = true;
1424
+ }
1425
+ /**
1426
+ * @name memBegin (592)
1427
+ * Start downloading an application image to RAM
1428
+ */
1429
+ async memBegin(size, _blocks, _blocksize, offset) {
1430
+ const stub = await getStubCode(this.chipFamily, this.chipRevision);
1431
+ // Stub may be null for chips without stub support
1432
+ if (stub === null) {
1433
+ return [0, []];
1434
+ }
1435
+ const load_start = offset;
1436
+ const load_end = offset + size;
1437
+ this.logger.debug(`Load range: ${toHex(load_start, 8)}-${toHex(load_end, 8)}`);
1438
+ this.logger.debug(`Stub data: ${toHex(stub.data_start, 8)}, len: ${stub.data.length}, text: ${toHex(stub.text_start, 8)}, len: ${stub.text.length}`);
1439
+ for (const [start, end] of [
1440
+ [stub.data_start, stub.data_start + stub.data.length],
1441
+ [stub.text_start, stub.text_start + stub.text.length],
1442
+ ]) {
1443
+ if (load_start < end && load_end > start) {
1444
+ throw new Error("Software loader is resident at " +
1445
+ toHex(start, 8) +
1446
+ "-" +
1447
+ toHex(end, 8) +
1448
+ ". " +
1449
+ "Can't load binary at overlapping address range " +
1450
+ toHex(load_start, 8) +
1451
+ "-" +
1452
+ toHex(load_end, 8) +
1453
+ ". " +
1454
+ "Try changing the binary loading address.");
1455
+ }
1456
+ }
1457
+ return [0, []];
1458
+ }
1459
+ /**
1460
+ * @name getEraseSize
1461
+ * depending on flash chip model the erase may take this long (maybe longer!)
1462
+ */
1463
+ async eraseFlash() {
1464
+ await this.checkCommand(ESP_ERASE_FLASH, [], 0, CHIP_ERASE_TIMEOUT);
1465
+ }
1466
+ }