tasmota-webserial-esptool 6.0.1

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 (65) hide show
  1. package/.github/dependabot.yml +10 -0
  2. package/.github/workflows/build_upload.yml +31 -0
  3. package/.github/workflows/ci.yml +22 -0
  4. package/.prettierignore +1 -0
  5. package/README.md +18 -0
  6. package/css/dark.css +91 -0
  7. package/css/light.css +79 -0
  8. package/css/style.css +383 -0
  9. package/dist/const.d.ts +159 -0
  10. package/dist/const.js +252 -0
  11. package/dist/esp_loader.d.ts +166 -0
  12. package/dist/esp_loader.js +931 -0
  13. package/dist/index.d.ts +6 -0
  14. package/dist/index.js +12 -0
  15. package/dist/struct.d.ts +2 -0
  16. package/dist/struct.js +105 -0
  17. package/dist/stubs/esp32.json +1 -0
  18. package/dist/stubs/esp32c3.json +1 -0
  19. package/dist/stubs/esp32s2.json +1 -0
  20. package/dist/stubs/esp32s3.json +1 -0
  21. package/dist/stubs/esp8266.json +1 -0
  22. package/dist/stubs/index.d.ts +10 -0
  23. package/dist/stubs/index.js +26 -0
  24. package/dist/util.d.ts +14 -0
  25. package/dist/util.js +46 -0
  26. package/dist/web/esp32-a2dcbc2e.js +1 -0
  27. package/dist/web/esp32c3-18e9678b.js +1 -0
  28. package/dist/web/esp32s2-3109ccc6.js +1 -0
  29. package/dist/web/esp32s3-c1dbd867.js +1 -0
  30. package/dist/web/esp8266-144419c0.js +1 -0
  31. package/dist/web/index.js +1 -0
  32. package/index.html +196 -0
  33. package/js/esptool.js +1292 -0
  34. package/js/modules/esp32-a2dcbc2e.js +1 -0
  35. package/js/modules/esp32c3-18e9678b.js +1 -0
  36. package/js/modules/esp32s2-3109ccc6.js +1 -0
  37. package/js/modules/esp32s3-c1dbd867.js +1 -0
  38. package/js/modules/esp8266-144419c0.js +1 -0
  39. package/js/modules/esptool.js +1 -0
  40. package/js/script.js +447 -0
  41. package/js/utilities.js +148 -0
  42. package/license.md +12 -0
  43. package/package.json +36 -0
  44. package/rollup.config.js +27 -0
  45. package/script/build +8 -0
  46. package/script/develop +17 -0
  47. package/script/stubgen.py +47 -0
  48. package/src/const.ts +315 -0
  49. package/src/esp_loader.ts +1204 -0
  50. package/src/index.ts +27 -0
  51. package/src/struct.ts +115 -0
  52. package/src/stubs/esp32.json +1 -0
  53. package/src/stubs/esp32c2.json +1 -0
  54. package/src/stubs/esp32c3.json +1 -0
  55. package/src/stubs/esp32h2.json +1 -0
  56. package/src/stubs/esp32s2.json +1 -0
  57. package/src/stubs/esp32s3.json +1 -0
  58. package/src/stubs/esp8266.json +1 -0
  59. package/src/stubs/index.ts +48 -0
  60. package/src/util.ts +49 -0
  61. package/stubs/esp32c2.json +1 -0
  62. package/stubs/esp32c3.json +1 -0
  63. package/stubs/esp32h2.json +1 -0
  64. package/stubs/esp32s3.json +1 -0
  65. package/tsconfig.json +19 -0
@@ -0,0 +1,931 @@
1
+ import { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, CHIP_FAMILY_ESP32C3, 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, 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, CHIP_ERASE_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, SlipReadError, } from "./const";
2
+ import { getStubCode } from "./stubs";
3
+ import { hexFormatter, sleep, slipEncode, toHex } from "./util";
4
+ // @ts-ignore
5
+ import { deflate } from "pako/dist/pako.esm.mjs";
6
+ import { pack, unpack } from "./struct";
7
+ export class ESPLoader extends EventTarget {
8
+ constructor(port, logger, _parent) {
9
+ super();
10
+ this.port = port;
11
+ this.logger = logger;
12
+ this._parent = _parent;
13
+ this.chipName = null;
14
+ this._efuses = new Array(4).fill(0);
15
+ this._flashsize = 4 * 1024 * 1024;
16
+ this.debug = false;
17
+ this.IS_STUB = false;
18
+ this.connected = true;
19
+ this.flashSize = null;
20
+ this.state_DTR = false;
21
+ }
22
+ get _inputBuffer() {
23
+ return this._parent ? this._parent._inputBuffer : this.__inputBuffer;
24
+ }
25
+ async initialize() {
26
+ await this.hardReset(true);
27
+ if (!this._parent) {
28
+ this.__inputBuffer = [];
29
+ // Don't await this promise so it doesn't block rest of method.
30
+ this.readLoop();
31
+ }
32
+ await this.sync();
33
+ // Determine chip family and name
34
+ let chipMagicValue = await this.readRegister(CHIP_DETECT_MAGIC_REG_ADDR);
35
+ let chip = CHIP_DETECT_MAGIC_VALUES[chipMagicValue >>> 0];
36
+ if (chip === undefined) {
37
+ throw new Error(`Unknown Chip: Hex: ${toHex(chipMagicValue >>> 0, 8).toLowerCase()} Number: ${chipMagicValue}`);
38
+ }
39
+ this.chipName = chip.name;
40
+ this.chipFamily = chip.family;
41
+ // Read the OTP data for this chip and store into this.efuses array
42
+ let FlAddr = getSpiFlashAddresses(this.getChipFamily());
43
+ let AddrMAC = FlAddr.macFuse;
44
+ for (let i = 0; i < 4; i++) {
45
+ this._efuses[i] = await this.readRegister(AddrMAC + 4 * i);
46
+ }
47
+ this.logger.log(`Chip type ${this.chipName}`);
48
+ //this.logger.log("FLASHID");
49
+ }
50
+ /**
51
+ * @name readLoop
52
+ * Reads data from the input stream and places it in the inputBuffer
53
+ */
54
+ async readLoop() {
55
+ if (this.debug) {
56
+ this.logger.debug("Starting read loop");
57
+ }
58
+ this._reader = this.port.readable.getReader();
59
+ try {
60
+ while (true) {
61
+ const { value, done } = await this._reader.read();
62
+ if (done) {
63
+ this._reader.releaseLock();
64
+ break;
65
+ }
66
+ if (!value || value.length === 0) {
67
+ continue;
68
+ }
69
+ this._inputBuffer.push(...Array.from(value));
70
+ }
71
+ }
72
+ catch (err) {
73
+ console.error("Read loop got disconnected");
74
+ }
75
+ // Disconnected!
76
+ this.connected = false;
77
+ this.dispatchEvent(new Event("disconnect"));
78
+ this.logger.debug("Finished read loop");
79
+ }
80
+ sleep(ms = 100) {
81
+ return new Promise((resolve) => setTimeout(resolve, ms));
82
+ }
83
+ async setRTS(state) {
84
+ await this.port.setSignals({ requestToSend: state });
85
+ // # Work-around for adapters on Windows using the usbser.sys driver:
86
+ // # generate a dummy change to DTR so that the set-control-line-state
87
+ // # request is sent with the updated RTS state and the same DTR state
88
+ // Referenced to esptool.py
89
+ await this.setDTR(this.state_DTR);
90
+ }
91
+ async setDTR(state) {
92
+ this.state_DTR = state;
93
+ await this.port.setSignals({ dataTerminalReady: state });
94
+ }
95
+ async hardReset(bootloader = false) {
96
+ this.logger.log("Try hard reset.");
97
+ if (bootloader) {
98
+ // enter flash mode
99
+ if (this.port.getInfo().usbProductId === USB_JTAG_SERIAL_PID) {
100
+ // esp32c3 esp32s3 etc. build-in USB serial.
101
+ // when connect to computer direct via usb, using following signals
102
+ // to enter flash mode automatically.
103
+ await this.setRTS(false);
104
+ await this.setDTR(false);
105
+ await this.sleep(100);
106
+ await this.setDTR(true);
107
+ await this.setRTS(false);
108
+ await this.sleep(100);
109
+ await this.setRTS(true);
110
+ await this.setDTR(false);
111
+ await this.setRTS(true);
112
+ await this.sleep(100);
113
+ await this.setRTS(false);
114
+ await this.setDTR(false);
115
+ }
116
+ else {
117
+ // otherwise, esp chip should be connected to computer via usb-serial
118
+ // bridge chip like ch340,CP2102 etc.
119
+ // use normal way to enter flash mode.
120
+ await this.setDTR(false);
121
+ await this.setRTS(true);
122
+ await this.sleep(100);
123
+ await this.setDTR(true);
124
+ await this.setRTS(false);
125
+ await this.sleep(50);
126
+ await this.setDTR(false);
127
+ }
128
+ }
129
+ else {
130
+ // just reset
131
+ await this.setRTS(true); // EN->LOW
132
+ await this.sleep(100);
133
+ await this.setRTS(false);
134
+ }
135
+ await new Promise((resolve) => setTimeout(resolve, 1000));
136
+ }
137
+ /**
138
+ * @name macAddr
139
+ * The MAC address burned into the OTP memory of the ESP chip
140
+ */
141
+ macAddr() {
142
+ let macAddr = new Array(6).fill(0);
143
+ let mac0 = this._efuses[0];
144
+ let mac1 = this._efuses[1];
145
+ let mac2 = this._efuses[2];
146
+ let mac3 = this._efuses[3];
147
+ let oui;
148
+ if (this.chipFamily == CHIP_FAMILY_ESP8266) {
149
+ if (mac3 != 0) {
150
+ oui = [(mac3 >> 16) & 0xff, (mac3 >> 8) & 0xff, mac3 & 0xff];
151
+ }
152
+ else if (((mac1 >> 16) & 0xff) == 0) {
153
+ oui = [0x18, 0xfe, 0x34];
154
+ }
155
+ else if (((mac1 >> 16) & 0xff) == 1) {
156
+ oui = [0xac, 0xd0, 0x74];
157
+ }
158
+ else {
159
+ throw new Error("Couldnt determine OUI");
160
+ }
161
+ macAddr[0] = oui[0];
162
+ macAddr[1] = oui[1];
163
+ macAddr[2] = oui[2];
164
+ macAddr[3] = (mac1 >> 8) & 0xff;
165
+ macAddr[4] = mac1 & 0xff;
166
+ macAddr[5] = (mac0 >> 24) & 0xff;
167
+ }
168
+ else if (this.chipFamily == CHIP_FAMILY_ESP32) {
169
+ macAddr[0] = (mac2 >> 8) & 0xff;
170
+ macAddr[1] = mac2 & 0xff;
171
+ macAddr[2] = (mac1 >> 24) & 0xff;
172
+ macAddr[3] = (mac1 >> 16) & 0xff;
173
+ macAddr[4] = (mac1 >> 8) & 0xff;
174
+ macAddr[5] = mac1 & 0xff;
175
+ }
176
+ else if (this.chipFamily == CHIP_FAMILY_ESP32S2 ||
177
+ this.chipFamily == CHIP_FAMILY_ESP32S3 ||
178
+ this.chipFamily == CHIP_FAMILY_ESP32C3) {
179
+ macAddr[0] = (mac1 >> 8) & 0xff;
180
+ macAddr[1] = mac1 & 0xff;
181
+ macAddr[2] = (mac0 >> 24) & 0xff;
182
+ macAddr[3] = (mac0 >> 16) & 0xff;
183
+ macAddr[4] = (mac0 >> 8) & 0xff;
184
+ macAddr[5] = mac0 & 0xff;
185
+ }
186
+ else {
187
+ throw new Error("Unknown chip family");
188
+ }
189
+ return macAddr;
190
+ }
191
+ async readRegister(reg) {
192
+ if (this.debug) {
193
+ this.logger.debug("Reading from Register " + toHex(reg, 8));
194
+ }
195
+ let packet = pack("<I", reg);
196
+ await this.sendCommand(ESP_READ_REG, packet);
197
+ let [val, _data] = await this.getResponse(ESP_READ_REG);
198
+ return val;
199
+ }
200
+ /**
201
+ * @name checkCommand
202
+ * Send a command packet, check that the command succeeded and
203
+ * return a tuple with the value and data.
204
+ * See the ESP Serial Protocol for more details on what value/data are
205
+ */
206
+ async checkCommand(opcode, buffer, checksum = 0, timeout = DEFAULT_TIMEOUT) {
207
+ timeout = Math.min(timeout, MAX_TIMEOUT);
208
+ await this.sendCommand(opcode, buffer, checksum);
209
+ let [value, data] = await this.getResponse(opcode, timeout);
210
+ if (data === null) {
211
+ throw new Error("Didn't get enough status bytes");
212
+ }
213
+ let statusLen = 0;
214
+ if (this.IS_STUB || this.chipFamily == CHIP_FAMILY_ESP8266) {
215
+ statusLen = 2;
216
+ }
217
+ else if ([
218
+ CHIP_FAMILY_ESP32,
219
+ CHIP_FAMILY_ESP32S2,
220
+ CHIP_FAMILY_ESP32S3,
221
+ CHIP_FAMILY_ESP32C3,
222
+ ].includes(this.chipFamily)) {
223
+ statusLen = 4;
224
+ }
225
+ else {
226
+ if ([2, 4].includes(data.length)) {
227
+ statusLen = data.length;
228
+ }
229
+ }
230
+ if (data.length < statusLen) {
231
+ throw new Error("Didn't get enough status bytes");
232
+ }
233
+ let status = data.slice(-statusLen, data.length);
234
+ data = data.slice(0, -statusLen);
235
+ if (this.debug) {
236
+ this.logger.debug("status", status);
237
+ this.logger.debug("value", value);
238
+ this.logger.debug("data", data);
239
+ }
240
+ if (status[0] == 1) {
241
+ if (status[1] == ROM_INVALID_RECV_MSG) {
242
+ throw new Error("Invalid (unsupported) command " + toHex(opcode));
243
+ }
244
+ else {
245
+ throw new Error("Command failure error code " + toHex(status[1]));
246
+ }
247
+ }
248
+ return [value, data];
249
+ }
250
+ /**
251
+ * @name sendCommand
252
+ * Send a slip-encoded, checksummed command over the UART,
253
+ * does not check response
254
+ */
255
+ async sendCommand(opcode, buffer, checksum = 0) {
256
+ let packet = slipEncode([
257
+ ...pack("<BBHI", 0x00, opcode, buffer.length, checksum),
258
+ ...buffer,
259
+ ]);
260
+ if (this.debug) {
261
+ this.logger.debug(`Writing ${packet.length} byte${packet.length == 1 ? "" : "s"}:`, packet);
262
+ }
263
+ await this.writeToStream(packet);
264
+ }
265
+ /**
266
+ * @name readPacket
267
+ * Generator to read SLIP packets from a serial port.
268
+ * Yields one full SLIP packet at a time, raises exception on timeout or invalid data.
269
+ * Designed to avoid too many calls to serial.read(1), which can bog
270
+ * down on slow systems.
271
+ */
272
+ async readPacket(timeout) {
273
+ let partialPacket = null;
274
+ let inEscape = false;
275
+ let readBytes = [];
276
+ while (true) {
277
+ let stamp = Date.now();
278
+ readBytes = [];
279
+ while (Date.now() - stamp < timeout) {
280
+ if (this._inputBuffer.length > 0) {
281
+ readBytes.push(this._inputBuffer.shift());
282
+ break;
283
+ }
284
+ else {
285
+ await sleep(10);
286
+ }
287
+ }
288
+ if (readBytes.length == 0) {
289
+ let waitingFor = partialPacket === null ? "header" : "content";
290
+ throw new SlipReadError("Timed out waiting for packet " + waitingFor);
291
+ }
292
+ if (this.debug)
293
+ this.logger.debug("Read " + readBytes.length + " bytes: " + hexFormatter(readBytes));
294
+ for (let b of readBytes) {
295
+ if (partialPacket === null) {
296
+ // waiting for packet header
297
+ if (b == 0xc0) {
298
+ partialPacket = [];
299
+ }
300
+ else {
301
+ if (this.debug) {
302
+ this.logger.debug("Read invalid data: " + hexFormatter(readBytes));
303
+ this.logger.debug("Remaining data in serial buffer: " +
304
+ hexFormatter(this._inputBuffer));
305
+ }
306
+ throw new SlipReadError("Invalid head of packet (" + toHex(b) + ")");
307
+ }
308
+ }
309
+ else if (inEscape) {
310
+ // part-way through escape sequence
311
+ inEscape = false;
312
+ if (b == 0xdc) {
313
+ partialPacket.push(0xc0);
314
+ }
315
+ else if (b == 0xdd) {
316
+ partialPacket.push(0xdb);
317
+ }
318
+ else {
319
+ if (this.debug) {
320
+ this.logger.debug("Read invalid data: " + hexFormatter(readBytes));
321
+ this.logger.debug("Remaining data in serial buffer: " +
322
+ hexFormatter(this._inputBuffer));
323
+ }
324
+ throw new SlipReadError("Invalid SLIP escape (0xdb, " + toHex(b) + ")");
325
+ }
326
+ }
327
+ else if (b == 0xdb) {
328
+ // start of escape sequence
329
+ inEscape = true;
330
+ }
331
+ else if (b == 0xc0) {
332
+ // end of packet
333
+ if (this.debug)
334
+ this.logger.debug("Received full packet: " + hexFormatter(partialPacket));
335
+ return partialPacket;
336
+ }
337
+ else {
338
+ // normal byte in packet
339
+ partialPacket.push(b);
340
+ }
341
+ }
342
+ }
343
+ throw new SlipReadError("Invalid state");
344
+ }
345
+ /**
346
+ * @name getResponse
347
+ * Read response data and decodes the slip packet, then parses
348
+ * out the value/data and returns as a tuple of (value, data) where
349
+ * each is a list of bytes
350
+ */
351
+ async getResponse(opcode, timeout = DEFAULT_TIMEOUT) {
352
+ for (let i = 0; i < 100; i++) {
353
+ const packet = await this.readPacket(timeout);
354
+ if (packet.length < 8) {
355
+ continue;
356
+ }
357
+ const [resp, opRet, _lenRet, val] = unpack("<BBHI", packet.slice(0, 8));
358
+ if (resp != 1) {
359
+ continue;
360
+ }
361
+ const data = packet.slice(8);
362
+ if (opcode == null || opRet == opcode) {
363
+ return [val, data];
364
+ }
365
+ if (data[0] != 0 && data[1] == ROM_INVALID_RECV_MSG) {
366
+ this._inputBuffer.length = 0;
367
+ throw new Error(`Invalid (unsupported) command ${toHex(opcode)}`);
368
+ }
369
+ }
370
+ throw "Response doesn't match request";
371
+ }
372
+ /**
373
+ * @name checksum
374
+ * Calculate checksum of a blob, as it is defined by the ROM
375
+ */
376
+ checksum(data, state = ESP_CHECKSUM_MAGIC) {
377
+ for (let b of data) {
378
+ state ^= b;
379
+ }
380
+ return state;
381
+ }
382
+ async setBaudrate(baud) {
383
+ if (this.chipFamily == CHIP_FAMILY_ESP8266) {
384
+ throw new Error("Changing baud rate is not supported on the ESP8266");
385
+ }
386
+ this.logger.log("Attempting to change baud rate to " + baud + "...");
387
+ try {
388
+ // Send ESP_ROM_BAUD(115200) as the old one if running STUB otherwise 0
389
+ let buffer = pack("<II", baud, this.IS_STUB ? ESP_ROM_BAUD : 0);
390
+ await this.checkCommand(ESP_CHANGE_BAUDRATE, buffer);
391
+ }
392
+ catch (e) {
393
+ console.error(e);
394
+ throw new Error(`Unable to change the baud rate to ${baud}: No response from set baud rate command.`);
395
+ }
396
+ if (this._parent) {
397
+ await this._parent.reconfigurePort(baud);
398
+ }
399
+ else {
400
+ await this.reconfigurePort(baud);
401
+ }
402
+ }
403
+ async reconfigurePort(baud) {
404
+ var _a;
405
+ try {
406
+ // SerialPort does not allow to be reconfigured while open so we close and re-open
407
+ // reader.cancel() causes the Promise returned by the read() operation running on
408
+ // the readLoop to return immediately with { value: undefined, done: true } and thus
409
+ // breaking the loop and exiting readLoop();
410
+ await ((_a = this._reader) === null || _a === void 0 ? void 0 : _a.cancel());
411
+ await this.port.close();
412
+ // Reopen Port
413
+ await this.port.open({ baudRate: baud });
414
+ // Restart Readloop
415
+ this.readLoop();
416
+ this.logger.log(`Changed baud rate to ${baud}`);
417
+ }
418
+ catch (e) {
419
+ console.error(e);
420
+ throw new Error(`Unable to change the baud rate to ${baud}: ${e}`);
421
+ }
422
+ }
423
+ /**
424
+ * @name sync
425
+ * Put into ROM bootload mode & attempt to synchronize with the
426
+ * ESP ROM bootloader, we will retry a few times
427
+ */
428
+ async sync() {
429
+ for (let i = 0; i < 5; i++) {
430
+ this._inputBuffer.length = 0;
431
+ let response = await this._sync();
432
+ if (response) {
433
+ await sleep(100);
434
+ return true;
435
+ }
436
+ await sleep(100);
437
+ }
438
+ throw new Error("Couldn't sync to ESP. Try resetting.");
439
+ }
440
+ /**
441
+ * @name _sync
442
+ * Perform a soft-sync using AT sync packets, does not perform
443
+ * any hardware resetting
444
+ */
445
+ async _sync() {
446
+ await this.sendCommand(ESP_SYNC, SYNC_PACKET);
447
+ for (let i = 0; i < 8; i++) {
448
+ try {
449
+ let [_reply, data] = await this.getResponse(ESP_SYNC, SYNC_TIMEOUT);
450
+ if (data.length > 1 && data[0] == 0 && data[1] == 0) {
451
+ return true;
452
+ }
453
+ }
454
+ catch (err) {
455
+ // If read packet fails.
456
+ }
457
+ }
458
+ return false;
459
+ }
460
+ /**
461
+ * @name getFlashWriteSize
462
+ * Get the Flash write size based on the chip
463
+ */
464
+ getFlashWriteSize() {
465
+ if (this.IS_STUB) {
466
+ return STUB_FLASH_WRITE_SIZE;
467
+ }
468
+ return FLASH_WRITE_SIZE;
469
+ }
470
+ /**
471
+ * @name flashData
472
+ * Program a full, uncompressed binary file into SPI Flash at
473
+ * a given offset. If an ESP32 and md5 string is passed in, will also
474
+ * verify memory. ESP8266 does not have checksum memory verification in
475
+ * ROM
476
+ */
477
+ async flashData(binaryData, updateProgress, offset = 0, compress = false) {
478
+ if (binaryData.byteLength >= 8) {
479
+ // unpack the (potential) image header
480
+ var header = Array.from(new Uint8Array(binaryData, 0, 4));
481
+ let headerMagic = header[0];
482
+ let headerFlashMode = header[2];
483
+ let headerFlashSizeFreq = header[3];
484
+ this.logger.log(`Image header, Magic=${toHex(headerMagic)}, FlashMode=${toHex(headerFlashMode)}, FlashSizeFreq=${toHex(headerFlashSizeFreq)}`);
485
+ }
486
+ let uncompressedFilesize = binaryData.byteLength;
487
+ let compressedFilesize = 0;
488
+ let dataToFlash;
489
+ let timeout = DEFAULT_TIMEOUT;
490
+ if (compress) {
491
+ dataToFlash = deflate(new Uint8Array(binaryData), {
492
+ level: 9,
493
+ }).buffer;
494
+ compressedFilesize = dataToFlash.byteLength;
495
+ this.logger.log(`Writing data with filesize: ${uncompressedFilesize}. Compressed Size: ${compressedFilesize}`);
496
+ timeout = await this.flashDeflBegin(uncompressedFilesize, compressedFilesize, offset);
497
+ }
498
+ else {
499
+ this.logger.log(`Writing data with filesize: ${uncompressedFilesize}`);
500
+ dataToFlash = binaryData;
501
+ await this.flashBegin(uncompressedFilesize, offset);
502
+ }
503
+ let block = [];
504
+ let seq = 0;
505
+ let written = 0;
506
+ let position = 0;
507
+ let stamp = Date.now();
508
+ let flashWriteSize = this.getFlashWriteSize();
509
+ let filesize = compress ? compressedFilesize : uncompressedFilesize;
510
+ while (filesize - position > 0) {
511
+ if (this.debug) {
512
+ this.logger.log(`Writing at ${toHex(offset + seq * flashWriteSize, 8)} `);
513
+ }
514
+ if (filesize - position >= flashWriteSize) {
515
+ block = Array.from(new Uint8Array(dataToFlash, position, flashWriteSize));
516
+ }
517
+ else {
518
+ // Pad the last block only if we are sending uncompressed data.
519
+ block = Array.from(new Uint8Array(dataToFlash, position, filesize - position));
520
+ if (!compress) {
521
+ block = block.concat(new Array(flashWriteSize - block.length).fill(0xff));
522
+ }
523
+ }
524
+ if (compress) {
525
+ await this.flashDeflBlock(block, seq, timeout);
526
+ }
527
+ else {
528
+ await this.flashBlock(block, seq);
529
+ }
530
+ seq += 1;
531
+ // If using compression we update the progress with the proportional size of the block taking into account the compression ratio.
532
+ // This way we report progress on the uncompressed size
533
+ written += compress
534
+ ? Math.round((block.length * uncompressedFilesize) / compressedFilesize)
535
+ : block.length;
536
+ position += flashWriteSize;
537
+ updateProgress(Math.min(written, uncompressedFilesize), uncompressedFilesize);
538
+ }
539
+ this.logger.log("Took " + (Date.now() - stamp) + "ms to write " + filesize + " bytes");
540
+ // Only send flashF finish if running the stub because ir causes the ROM to exit and run user code
541
+ if (this.IS_STUB) {
542
+ await this.flashBegin(0, 0);
543
+ if (compress) {
544
+ await this.flashDeflFinish();
545
+ }
546
+ else {
547
+ await this.flashFinish();
548
+ }
549
+ }
550
+ }
551
+ /**
552
+ * @name flashBlock
553
+ * Send one block of data to program into SPI Flash memory
554
+ */
555
+ async flashBlock(data, seq, timeout = DEFAULT_TIMEOUT) {
556
+ await this.checkCommand(ESP_FLASH_DATA, pack("<IIII", data.length, seq, 0, 0).concat(data), this.checksum(data), timeout);
557
+ }
558
+ async flashDeflBlock(data, seq, timeout = DEFAULT_TIMEOUT) {
559
+ await this.checkCommand(ESP_FLASH_DEFL_DATA, pack("<IIII", data.length, seq, 0, 0).concat(data), this.checksum(data), timeout);
560
+ }
561
+ /**
562
+ * @name flashBegin
563
+ * Prepare for flashing by attaching SPI chip and erasing the
564
+ * number of blocks requred.
565
+ */
566
+ async flashBegin(size = 0, offset = 0, encrypted = false) {
567
+ let eraseSize;
568
+ let buffer;
569
+ let flashWriteSize = this.getFlashWriteSize();
570
+ if (!this.IS_STUB &&
571
+ [
572
+ CHIP_FAMILY_ESP32,
573
+ CHIP_FAMILY_ESP32S2,
574
+ CHIP_FAMILY_ESP32S3,
575
+ CHIP_FAMILY_ESP32C3,
576
+ ].includes(this.chipFamily)) {
577
+ await this.checkCommand(ESP_SPI_ATTACH, new Array(8).fill(0));
578
+ }
579
+ let numBlocks = Math.floor((size + flashWriteSize - 1) / flashWriteSize);
580
+ if (this.chipFamily == CHIP_FAMILY_ESP8266) {
581
+ eraseSize = this.getEraseSize(offset, size);
582
+ }
583
+ else {
584
+ eraseSize = size;
585
+ }
586
+ let timeout;
587
+ if (this.IS_STUB) {
588
+ timeout = DEFAULT_TIMEOUT;
589
+ }
590
+ else {
591
+ timeout = timeoutPerMb(ERASE_REGION_TIMEOUT_PER_MB, size);
592
+ }
593
+ let stamp = Date.now();
594
+ buffer = pack("<IIII", eraseSize, numBlocks, flashWriteSize, offset);
595
+ if (this.chipFamily == CHIP_FAMILY_ESP32 ||
596
+ this.chipFamily == CHIP_FAMILY_ESP32S2 ||
597
+ this.chipFamily == CHIP_FAMILY_ESP32S3 ||
598
+ this.chipFamily == CHIP_FAMILY_ESP32C3) {
599
+ buffer = buffer.concat(pack("<I", encrypted ? 1 : 0));
600
+ }
601
+ this.logger.log("Erase size " +
602
+ eraseSize +
603
+ ", blocks " +
604
+ numBlocks +
605
+ ", block size " +
606
+ toHex(flashWriteSize, 4) +
607
+ ", offset " +
608
+ toHex(offset, 4) +
609
+ ", encrypted " +
610
+ (encrypted ? "yes" : "no"));
611
+ await this.checkCommand(ESP_FLASH_BEGIN, buffer, 0, timeout);
612
+ if (size != 0 && !this.IS_STUB) {
613
+ this.logger.log("Took " + (Date.now() - stamp) + "ms to erase " + numBlocks + " bytes");
614
+ }
615
+ return numBlocks;
616
+ }
617
+ /**
618
+ * @name flashDeflBegin
619
+ *
620
+ */
621
+ async flashDeflBegin(size = 0, compressedSize = 0, offset = 0, encrypted = false) {
622
+ // Start downloading compressed data to Flash (performs an erase)
623
+ // Returns number of blocks to write.
624
+ let flashWriteSize = this.getFlashWriteSize();
625
+ let numBlocks = Math.floor((compressedSize + flashWriteSize - 1) / flashWriteSize);
626
+ let eraseBlocks = Math.floor((size + flashWriteSize - 1) / flashWriteSize);
627
+ let writeSize = 0;
628
+ let timeout = 0;
629
+ let buffer;
630
+ if (this.IS_STUB) {
631
+ writeSize = size; // stub expects number of bytes here, manages erasing internally
632
+ timeout = timeoutPerMb(ERASE_REGION_TIMEOUT_PER_MB, writeSize); // ROM performs the erase up front
633
+ }
634
+ else {
635
+ writeSize = eraseBlocks * flashWriteSize; // ROM expects rounded up to erase block size
636
+ timeout = DEFAULT_TIMEOUT;
637
+ }
638
+ buffer = pack("<IIII", writeSize, numBlocks, flashWriteSize, offset);
639
+ await this.checkCommand(ESP_FLASH_DEFL_BEGIN, buffer, 0, timeout);
640
+ return timeout;
641
+ }
642
+ async flashFinish() {
643
+ let buffer = pack("<I", 1);
644
+ await this.checkCommand(ESP_FLASH_END, buffer);
645
+ }
646
+ async flashDeflFinish() {
647
+ let buffer = pack("<I", 1);
648
+ await this.checkCommand(ESP_FLASH_DEFL_END, buffer);
649
+ }
650
+ getBootloaderOffset() {
651
+ let bootFlashOffs = getSpiFlashAddresses(this.getChipFamily());
652
+ let BootldrFlashOffs = bootFlashOffs.flashOffs;
653
+ return BootldrFlashOffs;
654
+ }
655
+ async flashId() {
656
+ let SPIFLASH_RDID = 0x9f;
657
+ let result = await this.runSpiFlashCommand(SPIFLASH_RDID, [], 24);
658
+ return result;
659
+ }
660
+ getChipFamily() {
661
+ return this._parent ? this._parent.chipFamily : this.chipFamily;
662
+ }
663
+ async writeRegister(address, value, mask = 0xffffffff, delayUs = 0, delayAfterUs = 0) {
664
+ let buffer = pack("<IIII", address, value, mask, delayUs);
665
+ if (delayAfterUs > 0) {
666
+ // add a dummy write to a date register as an excuse to have a delay
667
+ buffer.concat(pack("<IIII", getSpiFlashAddresses(this.getChipFamily()).uartDateReg, 0, 0, delayAfterUs));
668
+ }
669
+ await this.checkCommand(ESP_WRITE_REG, buffer);
670
+ }
671
+ async setDataLengths(spiAddresses, mosiBits, misoBits) {
672
+ if (spiAddresses.mosiDlenOffs != -1) {
673
+ // ESP32/32S2/32S3/32C3 has a more sophisticated way to set up "user" commands
674
+ let SPI_MOSI_DLEN_REG = spiAddresses.regBase + spiAddresses.mosiDlenOffs;
675
+ let SPI_MISO_DLEN_REG = spiAddresses.regBase + spiAddresses.misoDlenOffs;
676
+ if (mosiBits > 0) {
677
+ await this.writeRegister(SPI_MOSI_DLEN_REG, mosiBits - 1);
678
+ }
679
+ if (misoBits > 0) {
680
+ await this.writeRegister(SPI_MISO_DLEN_REG, misoBits - 1);
681
+ }
682
+ }
683
+ else {
684
+ let SPI_DATA_LEN_REG = spiAddresses.regBase + spiAddresses.usr1Offs;
685
+ let SPI_MOSI_BITLEN_S = 17;
686
+ let SPI_MISO_BITLEN_S = 8;
687
+ let mosiMask = mosiBits == 0 ? 0 : mosiBits - 1;
688
+ let misoMask = misoBits == 0 ? 0 : misoBits - 1;
689
+ let value = (misoMask << SPI_MISO_BITLEN_S) | (mosiMask << SPI_MOSI_BITLEN_S);
690
+ await this.writeRegister(SPI_DATA_LEN_REG, value);
691
+ }
692
+ }
693
+ async waitDone(spiCmdReg, spiCmdUsr) {
694
+ for (let i = 0; i < 10; i++) {
695
+ let cmdValue = await this.readRegister(spiCmdReg);
696
+ if ((cmdValue & spiCmdUsr) == 0) {
697
+ return;
698
+ }
699
+ }
700
+ throw Error("SPI command did not complete in time");
701
+ }
702
+ async runSpiFlashCommand(spiflashCommand, data, readBits = 0) {
703
+ // Run an arbitrary SPI flash command.
704
+ // This function uses the "USR_COMMAND" functionality in the ESP
705
+ // SPI hardware, rather than the precanned commands supported by
706
+ // hardware. So the value of spiflash_command is an actual command
707
+ // byte, sent over the wire.
708
+ // After writing command byte, writes 'data' to MOSI and then
709
+ // reads back 'read_bits' of reply on MISO. Result is a number.
710
+ // SPI_USR register flags
711
+ let SPI_USR_COMMAND = 1 << 31;
712
+ let SPI_USR_MISO = 1 << 28;
713
+ let SPI_USR_MOSI = 1 << 27;
714
+ // SPI registers, base address differs ESP32* vs 8266
715
+ let spiAddresses = getSpiFlashAddresses(this.getChipFamily());
716
+ let base = spiAddresses.regBase;
717
+ let SPI_CMD_REG = base;
718
+ let SPI_USR_REG = base + spiAddresses.usrOffs;
719
+ let SPI_USR2_REG = base + spiAddresses.usr2Offs;
720
+ let SPI_W0_REG = base + spiAddresses.w0Offs;
721
+ // SPI peripheral "command" bitmasks for SPI_CMD_REG
722
+ let SPI_CMD_USR = 1 << 18;
723
+ // shift values
724
+ let SPI_USR2_COMMAND_LEN_SHIFT = 28;
725
+ if (readBits > 32) {
726
+ throw new Error("Reading more than 32 bits back from a SPI flash operation is unsupported");
727
+ }
728
+ if (data.length > 64) {
729
+ throw new Error("Writing more than 64 bytes of data with one SPI command is unsupported");
730
+ }
731
+ let dataBits = data.length * 8;
732
+ let oldSpiUsr = await this.readRegister(SPI_USR_REG);
733
+ let oldSpiUsr2 = await this.readRegister(SPI_USR2_REG);
734
+ let flags = SPI_USR_COMMAND;
735
+ if (readBits > 0) {
736
+ flags |= SPI_USR_MISO;
737
+ }
738
+ if (dataBits > 0) {
739
+ flags |= SPI_USR_MOSI;
740
+ }
741
+ await this.setDataLengths(spiAddresses, dataBits, readBits);
742
+ await this.writeRegister(SPI_USR_REG, flags);
743
+ await this.writeRegister(SPI_USR2_REG, (7 << SPI_USR2_COMMAND_LEN_SHIFT) | spiflashCommand);
744
+ if (dataBits == 0) {
745
+ await this.writeRegister(SPI_W0_REG, 0); // clear data register before we read it
746
+ }
747
+ else {
748
+ data.concat(new Array(data.length % 4).fill(0x00)); // pad to 32-bit multiple
749
+ let words = unpack("I".repeat(Math.floor(data.length / 4)), data);
750
+ let nextReg = SPI_W0_REG;
751
+ this.logger.debug(`Words Length: ${words.length}`);
752
+ for (const word of words) {
753
+ this.logger.debug(`Writing word ${toHex(word)} to register offset ${toHex(nextReg)}`);
754
+ await this.writeRegister(nextReg, word);
755
+ nextReg += 4;
756
+ }
757
+ }
758
+ await this.writeRegister(SPI_CMD_REG, SPI_CMD_USR);
759
+ await this.waitDone(SPI_CMD_REG, SPI_CMD_USR);
760
+ let status = await this.readRegister(SPI_W0_REG);
761
+ // restore some SPI controller registers
762
+ await this.writeRegister(SPI_USR_REG, oldSpiUsr);
763
+ await this.writeRegister(SPI_USR2_REG, oldSpiUsr2);
764
+ return status;
765
+ }
766
+ async detectFlashSize() {
767
+ this.logger.log("Detecting Flash Size");
768
+ let flashId = await this.flashId();
769
+ let manufacturer = flashId & 0xff;
770
+ let flashIdLowbyte = (flashId >> 16) & 0xff;
771
+ this.logger.log(`FlashId: ${toHex(flashId)}`);
772
+ this.logger.log(`Flash Manufacturer: ${manufacturer.toString(16)}`);
773
+ this.logger.log(`Flash Device: ${((flashId >> 8) & 0xff).toString(16)}${flashIdLowbyte.toString(16)}`);
774
+ this.flashSize = DETECTED_FLASH_SIZES[flashIdLowbyte];
775
+ this.logger.log(`Auto-detected Flash size: ${this.flashSize}`);
776
+ }
777
+ /**
778
+ * @name getEraseSize
779
+ * Calculate an erase size given a specific size in bytes.
780
+ * Provides a workaround for the bootloader erase bug on ESP8266.
781
+ */
782
+ getEraseSize(offset, size) {
783
+ let sectorsPerBlock = 16;
784
+ let sectorSize = FLASH_SECTOR_SIZE;
785
+ let numSectors = Math.floor((size + sectorSize - 1) / sectorSize);
786
+ let startSector = Math.floor(offset / sectorSize);
787
+ let headSectors = sectorsPerBlock - (startSector % sectorsPerBlock);
788
+ if (numSectors < headSectors) {
789
+ headSectors = numSectors;
790
+ }
791
+ if (numSectors < 2 * headSectors) {
792
+ return Math.floor(((numSectors + 1) / 2) * sectorSize);
793
+ }
794
+ return (numSectors - headSectors) * sectorSize;
795
+ }
796
+ /**
797
+ * @name memBegin (592)
798
+ * Start downloading an application image to RAM
799
+ */
800
+ async memBegin(size, blocks, blocksize, offset) {
801
+ return await this.checkCommand(ESP_MEM_BEGIN, pack("<IIII", size, blocks, blocksize, offset));
802
+ }
803
+ /**
804
+ * @name memBlock (609)
805
+ * Send a block of an image to RAM
806
+ */
807
+ async memBlock(data, seq) {
808
+ return await this.checkCommand(ESP_MEM_DATA, pack("<IIII", data.length, seq, 0, 0).concat(data), this.checksum(data));
809
+ }
810
+ /**
811
+ * @name memFinish (615)
812
+ * Leave download mode and run the application
813
+ *
814
+ * Sending ESP_MEM_END usually sends a correct response back, however sometimes
815
+ * (with ROM loader) the executed code may reset the UART or change the baud rate
816
+ * before the transmit FIFO is empty. So in these cases we set a short timeout and
817
+ * ignore errors.
818
+ */
819
+ async memFinish(entrypoint = 0) {
820
+ let timeout = this.IS_STUB ? DEFAULT_TIMEOUT : MEM_END_ROM_TIMEOUT;
821
+ let data = pack("<II", entrypoint == 0 ? 1 : 0, entrypoint);
822
+ return await this.checkCommand(ESP_MEM_END, data, 0, timeout);
823
+ }
824
+ async runStub() {
825
+ const stub = await getStubCode(this.chipFamily);
826
+ // We're transferring over USB, right?
827
+ let ramBlock = USB_RAM_BLOCK;
828
+ // Upload
829
+ this.logger.log("Uploading stub...");
830
+ for (let field of ["text", "data"]) {
831
+ if (Object.keys(stub).includes(field)) {
832
+ let offset = stub[field + "_start"];
833
+ let length = stub[field].length;
834
+ let blocks = Math.floor((length + ramBlock - 1) / ramBlock);
835
+ await this.memBegin(length, blocks, ramBlock, offset);
836
+ for (let seq of Array(blocks).keys()) {
837
+ let fromOffs = seq * ramBlock;
838
+ let toOffs = fromOffs + ramBlock;
839
+ if (toOffs > length) {
840
+ toOffs = length;
841
+ }
842
+ await this.memBlock(stub[field].slice(fromOffs, toOffs), seq);
843
+ }
844
+ }
845
+ }
846
+ this.logger.log("Running stub...");
847
+ await this.memFinish(stub["entry"]);
848
+ let pChar;
849
+ const p = await this.readPacket(500);
850
+ pChar = String.fromCharCode(...p);
851
+ if (pChar != "OHAI") {
852
+ throw new Error("Failed to start stub. Unexpected response: " + pChar);
853
+ }
854
+ this.logger.log("Stub is now running...");
855
+ const espStubLoader = new EspStubLoader(this.port, this.logger, this);
856
+ // Try to autodetect the flash size as soon as the stub is running.
857
+ await espStubLoader.detectFlashSize();
858
+ return espStubLoader;
859
+ }
860
+ async writeToStream(data) {
861
+ const writer = this.port.writable.getWriter();
862
+ await writer.write(new Uint8Array(data));
863
+ try {
864
+ writer.releaseLock();
865
+ }
866
+ catch (err) {
867
+ console.error("Ignoring release lock error", err);
868
+ }
869
+ }
870
+ async disconnect() {
871
+ if (this._parent) {
872
+ await this._parent.disconnect();
873
+ return;
874
+ }
875
+ await this.port.writable.getWriter().close();
876
+ await new Promise((resolve) => {
877
+ if (!this._reader) {
878
+ resolve(undefined);
879
+ }
880
+ this.addEventListener("disconnect", resolve, { once: true });
881
+ this._reader.cancel();
882
+ });
883
+ this.connected = false;
884
+ }
885
+ }
886
+ class EspStubLoader extends ESPLoader {
887
+ constructor() {
888
+ super(...arguments);
889
+ /*
890
+ The Stubloader has commands that run on the uploaded Stub Code in RAM
891
+ rather than built in commands.
892
+ */
893
+ this.IS_STUB = true;
894
+ }
895
+ /**
896
+ * @name memBegin (592)
897
+ * Start downloading an application image to RAM
898
+ */
899
+ async memBegin(size, blocks, blocksize, offset) {
900
+ let stub = await getStubCode(this.chipFamily);
901
+ let load_start = offset;
902
+ let load_end = offset + size;
903
+ console.log(load_start, load_end);
904
+ console.log(stub.data_start, stub.data.length, stub.text_start, stub.text.length);
905
+ for (let [start, end] of [
906
+ [stub.data_start, stub.data_start + stub.data.length],
907
+ [stub.text_start, stub.text_start + stub.text.length],
908
+ ]) {
909
+ if (load_start < end && load_end > start) {
910
+ throw new Error("Software loader is resident at " +
911
+ toHex(start, 8) +
912
+ "-" +
913
+ toHex(end, 8) +
914
+ ". " +
915
+ "Can't load binary at overlapping address range " +
916
+ toHex(load_start, 8) +
917
+ "-" +
918
+ toHex(load_end, 8) +
919
+ ". " +
920
+ "Try changing the binary loading address.");
921
+ }
922
+ }
923
+ }
924
+ /**
925
+ * @name getEraseSize
926
+ * depending on flash chip model the erase may take this long (maybe longer!)
927
+ */
928
+ async eraseFlash() {
929
+ await this.checkCommand(ESP_ERASE_FLASH, [], 0, CHIP_ERASE_TIMEOUT);
930
+ }
931
+ }