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