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
package/js/script.js
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
let espStub;
|
|
2
|
+
|
|
3
|
+
const baudRates = [921600, 115200, 230400, 460800];
|
|
4
|
+
|
|
5
|
+
const bufferSize = 512;
|
|
6
|
+
const colors = ["#00a7e9", "#f89521", "#be1e2d"];
|
|
7
|
+
const measurementPeriodId = "0001";
|
|
8
|
+
|
|
9
|
+
const maxLogLength = 100;
|
|
10
|
+
const log = document.getElementById("log");
|
|
11
|
+
const butConnect = document.getElementById("butConnect");
|
|
12
|
+
const baudRate = document.getElementById("baudRate");
|
|
13
|
+
const butClear = document.getElementById("butClear");
|
|
14
|
+
const butErase = document.getElementById("butErase");
|
|
15
|
+
const butProgram = document.getElementById("butProgram");
|
|
16
|
+
const autoscroll = document.getElementById("autoscroll");
|
|
17
|
+
const lightSS = document.getElementById("light");
|
|
18
|
+
const darkSS = document.getElementById("dark");
|
|
19
|
+
const darkMode = document.getElementById("darkmode");
|
|
20
|
+
const firmware = document.querySelectorAll(".upload .firmware input");
|
|
21
|
+
const progress = document.querySelectorAll(".upload .progress-bar");
|
|
22
|
+
const offsets = document.querySelectorAll(".upload .offset");
|
|
23
|
+
const appDiv = document.getElementById("app");
|
|
24
|
+
|
|
25
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
26
|
+
butConnect.addEventListener("click", () => {
|
|
27
|
+
clickConnect().catch(async (e) => {
|
|
28
|
+
console.error(e);
|
|
29
|
+
errorMsg(e.message || e);
|
|
30
|
+
if (espStub) {
|
|
31
|
+
await espStub.disconnect();
|
|
32
|
+
}
|
|
33
|
+
toggleUIConnected(false);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
butClear.addEventListener("click", clickClear);
|
|
37
|
+
butErase.addEventListener("click", clickErase);
|
|
38
|
+
butProgram.addEventListener("click", clickProgram);
|
|
39
|
+
for (let i = 0; i < firmware.length; i++) {
|
|
40
|
+
firmware[i].addEventListener("change", checkFirmware);
|
|
41
|
+
}
|
|
42
|
+
for (let i = 0; i < offsets.length; i++) {
|
|
43
|
+
offsets[i].addEventListener("change", checkProgrammable);
|
|
44
|
+
}
|
|
45
|
+
autoscroll.addEventListener("click", clickAutoscroll);
|
|
46
|
+
baudRate.addEventListener("change", changeBaudRate);
|
|
47
|
+
darkMode.addEventListener("click", clickDarkMode);
|
|
48
|
+
window.addEventListener("error", function (event) {
|
|
49
|
+
console.log("Got an uncaught error: ", event.error);
|
|
50
|
+
});
|
|
51
|
+
if ("serial" in navigator) {
|
|
52
|
+
const notSupported = document.getElementById("notSupported");
|
|
53
|
+
notSupported.classList.add("hidden");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
initBaudRate();
|
|
57
|
+
loadAllSettings();
|
|
58
|
+
updateTheme();
|
|
59
|
+
logMsg("ESP Web Flasher loaded.");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
function initBaudRate() {
|
|
63
|
+
for (let rate of baudRates) {
|
|
64
|
+
var option = document.createElement("option");
|
|
65
|
+
option.text = rate + " Baud";
|
|
66
|
+
option.value = rate;
|
|
67
|
+
baudRate.add(option);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function logMsg(text) {
|
|
72
|
+
log.innerHTML += text + "<br>";
|
|
73
|
+
|
|
74
|
+
// Remove old log content
|
|
75
|
+
if (log.textContent.split("\n").length > maxLogLength + 1) {
|
|
76
|
+
let logLines = log.innerHTML.replace(/(\n)/gm, "").split("<br>");
|
|
77
|
+
log.innerHTML = logLines.splice(-maxLogLength).join("<br>\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (autoscroll.checked) {
|
|
81
|
+
log.scrollTop = log.scrollHeight;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function debugMsg(...args) {
|
|
86
|
+
function getStackTrace() {
|
|
87
|
+
let stack = new Error().stack;
|
|
88
|
+
//console.log(stack);
|
|
89
|
+
stack = stack.split("\n").map((v) => v.trim());
|
|
90
|
+
stack.shift();
|
|
91
|
+
stack.shift();
|
|
92
|
+
|
|
93
|
+
let trace = [];
|
|
94
|
+
for (let line of stack) {
|
|
95
|
+
line = line.replace("at ", "");
|
|
96
|
+
trace.push({
|
|
97
|
+
func: line.substr(0, line.indexOf("(") - 1),
|
|
98
|
+
pos: line.substring(line.indexOf(".js:") + 4, line.lastIndexOf(":")),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return trace;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let stack = getStackTrace();
|
|
106
|
+
stack.shift();
|
|
107
|
+
let top = stack.shift();
|
|
108
|
+
let prefix =
|
|
109
|
+
'<span class="debug-function">[' + top.func + ":" + top.pos + "]</span> ";
|
|
110
|
+
for (let arg of args) {
|
|
111
|
+
if (arg === undefined) {
|
|
112
|
+
logMsg(prefix + "undefined");
|
|
113
|
+
} else if (arg === null) {
|
|
114
|
+
logMsg(prefix + "null");
|
|
115
|
+
} else if (typeof arg == "string") {
|
|
116
|
+
logMsg(prefix + arg);
|
|
117
|
+
} else if (typeof arg == "number") {
|
|
118
|
+
logMsg(prefix + arg);
|
|
119
|
+
} else if (typeof arg == "boolean") {
|
|
120
|
+
logMsg(prefix + (arg ? "true" : "false"));
|
|
121
|
+
} else if (Array.isArray(arg)) {
|
|
122
|
+
logMsg(prefix + "[" + arg.map((value) => toHex(value)).join(", ") + "]");
|
|
123
|
+
} else if (typeof arg == "object" && arg instanceof Uint8Array) {
|
|
124
|
+
logMsg(
|
|
125
|
+
prefix +
|
|
126
|
+
"[" +
|
|
127
|
+
Array.from(arg)
|
|
128
|
+
.map((value) => toHex(value))
|
|
129
|
+
.join(", ") +
|
|
130
|
+
"]"
|
|
131
|
+
);
|
|
132
|
+
} else {
|
|
133
|
+
logMsg(prefix + "Unhandled type of argument:" + typeof arg);
|
|
134
|
+
console.log(arg);
|
|
135
|
+
}
|
|
136
|
+
prefix = ""; // Only show for first argument
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function errorMsg(text) {
|
|
141
|
+
logMsg('<span class="error-message">Error:</span> ' + text);
|
|
142
|
+
console.error(text);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @name updateTheme
|
|
147
|
+
* Sets the theme to dark mode. Can be refactored later for more themes
|
|
148
|
+
*/
|
|
149
|
+
function updateTheme() {
|
|
150
|
+
// Disable all themes
|
|
151
|
+
document
|
|
152
|
+
.querySelectorAll("link[rel=stylesheet].alternate")
|
|
153
|
+
.forEach((styleSheet) => {
|
|
154
|
+
enableStyleSheet(styleSheet, false);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (darkMode.checked) {
|
|
158
|
+
enableStyleSheet(darkSS, true);
|
|
159
|
+
} else {
|
|
160
|
+
enableStyleSheet(lightSS, true);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function enableStyleSheet(node, enabled) {
|
|
165
|
+
node.disabled = !enabled;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function formatMacAddr(macAddr) {
|
|
169
|
+
return macAddr
|
|
170
|
+
.map((value) => value.toString(16).toUpperCase().padStart(2, "0"))
|
|
171
|
+
.join(":");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @name clickConnect
|
|
176
|
+
* Click handler for the connect/disconnect button.
|
|
177
|
+
*/
|
|
178
|
+
async function clickConnect() {
|
|
179
|
+
if (espStub) {
|
|
180
|
+
await espStub.disconnect();
|
|
181
|
+
await espStub.port.close();
|
|
182
|
+
toggleUIConnected(false);
|
|
183
|
+
espStub = undefined;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const esploaderMod = await window.esptoolPackage;
|
|
188
|
+
|
|
189
|
+
const esploader = await esploaderMod.connect({
|
|
190
|
+
log: (...args) => logMsg(...args),
|
|
191
|
+
debug: (...args) => debugMsg(...args),
|
|
192
|
+
error: (...args) => errorMsg(...args),
|
|
193
|
+
});
|
|
194
|
+
try {
|
|
195
|
+
await esploader.initialize();
|
|
196
|
+
|
|
197
|
+
logMsg("Connected to " + esploader.chipName);
|
|
198
|
+
logMsg("MAC Address: " + formatMacAddr(esploader.macAddr()));
|
|
199
|
+
|
|
200
|
+
espStub = await esploader.runStub();
|
|
201
|
+
toggleUIConnected(true);
|
|
202
|
+
toggleUIToolbar(true);
|
|
203
|
+
espStub.addEventListener("disconnect", () => {
|
|
204
|
+
toggleUIConnected(false);
|
|
205
|
+
espStub = false;
|
|
206
|
+
});
|
|
207
|
+
} catch (err) {
|
|
208
|
+
await esploader.disconnect();
|
|
209
|
+
throw err;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @name changeBaudRate
|
|
215
|
+
* Change handler for the Baud Rate selector.
|
|
216
|
+
*/
|
|
217
|
+
async function changeBaudRate() {
|
|
218
|
+
saveSetting("baudrate", baudRate.value);
|
|
219
|
+
if (espStub) {
|
|
220
|
+
let baud = parseInt(baudRate.value);
|
|
221
|
+
if (baudRates.includes(baud)) {
|
|
222
|
+
await espStub.setBaudrate(baud);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* @name clickAutoscroll
|
|
229
|
+
* Change handler for the Autoscroll checkbox.
|
|
230
|
+
*/
|
|
231
|
+
async function clickAutoscroll() {
|
|
232
|
+
saveSetting("autoscroll", autoscroll.checked);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* @name clickDarkMode
|
|
237
|
+
* Change handler for the Dark Mode checkbox.
|
|
238
|
+
*/
|
|
239
|
+
async function clickDarkMode() {
|
|
240
|
+
updateTheme();
|
|
241
|
+
saveSetting("darkmode", darkMode.checked);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* @name clickErase
|
|
246
|
+
* Click handler for the erase button.
|
|
247
|
+
*/
|
|
248
|
+
async function clickErase() {
|
|
249
|
+
if (
|
|
250
|
+
window.confirm("This will erase the entire flash. Click OK to continue.")
|
|
251
|
+
) {
|
|
252
|
+
baudRate.disabled = true;
|
|
253
|
+
butErase.disabled = true;
|
|
254
|
+
butProgram.disabled = true;
|
|
255
|
+
try {
|
|
256
|
+
logMsg("Erasing flash memory. Please wait...");
|
|
257
|
+
let stamp = Date.now();
|
|
258
|
+
await espStub.eraseFlash();
|
|
259
|
+
logMsg("Finished. Took " + (Date.now() - stamp) + "ms to erase.");
|
|
260
|
+
} catch (e) {
|
|
261
|
+
errorMsg(e);
|
|
262
|
+
} finally {
|
|
263
|
+
butErase.disabled = false;
|
|
264
|
+
baudRate.disabled = false;
|
|
265
|
+
butProgram.disabled = getValidFiles().length == 0;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* @name clickProgram
|
|
272
|
+
* Click handler for the program button.
|
|
273
|
+
*/
|
|
274
|
+
async function clickProgram() {
|
|
275
|
+
const readUploadedFileAsArrayBuffer = (inputFile) => {
|
|
276
|
+
const reader = new FileReader();
|
|
277
|
+
|
|
278
|
+
return new Promise((resolve, reject) => {
|
|
279
|
+
reader.onerror = () => {
|
|
280
|
+
reader.abort();
|
|
281
|
+
reject(new DOMException("Problem parsing input file."));
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
reader.onload = () => {
|
|
285
|
+
resolve(reader.result);
|
|
286
|
+
};
|
|
287
|
+
reader.readAsArrayBuffer(inputFile);
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
baudRate.disabled = true;
|
|
292
|
+
butErase.disabled = true;
|
|
293
|
+
butProgram.disabled = true;
|
|
294
|
+
for (let i = 0; i < 4; i++) {
|
|
295
|
+
firmware[i].disabled = true;
|
|
296
|
+
offsets[i].disabled = true;
|
|
297
|
+
}
|
|
298
|
+
for (let file of getValidFiles()) {
|
|
299
|
+
progress[file].classList.remove("hidden");
|
|
300
|
+
let binfile = firmware[file].files[0];
|
|
301
|
+
let contents = await readUploadedFileAsArrayBuffer(binfile);
|
|
302
|
+
try {
|
|
303
|
+
let offset = parseInt(offsets[file].value, 16);
|
|
304
|
+
const progressBar = progress[file].querySelector("div");
|
|
305
|
+
await espStub.flashData(
|
|
306
|
+
contents,
|
|
307
|
+
(bytesWritten, totalBytes) => {
|
|
308
|
+
progressBar.style.width =
|
|
309
|
+
Math.floor((bytesWritten / totalBytes) * 100) + "%";
|
|
310
|
+
},
|
|
311
|
+
offset
|
|
312
|
+
);
|
|
313
|
+
await sleep(100);
|
|
314
|
+
} catch (e) {
|
|
315
|
+
errorMsg(e);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
for (let i = 0; i < 4; i++) {
|
|
319
|
+
firmware[i].disabled = false;
|
|
320
|
+
offsets[i].disabled = false;
|
|
321
|
+
progress[i].classList.add("hidden");
|
|
322
|
+
progress[i].querySelector("div").style.width = "0";
|
|
323
|
+
}
|
|
324
|
+
butErase.disabled = false;
|
|
325
|
+
baudRate.disabled = false;
|
|
326
|
+
butProgram.disabled = getValidFiles().length == 0;
|
|
327
|
+
logMsg("To run the new firmware, please reset your device.");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function getValidFiles() {
|
|
331
|
+
// Get a list of file and offsets
|
|
332
|
+
// This will be used to check if we have valid stuff
|
|
333
|
+
// and will also return a list of files to program
|
|
334
|
+
let validFiles = [];
|
|
335
|
+
let offsetVals = [];
|
|
336
|
+
for (let i = 0; i < 4; i++) {
|
|
337
|
+
let offs = parseInt(offsets[i].value, 16);
|
|
338
|
+
if (firmware[i].files.length > 0 && !offsetVals.includes(offs)) {
|
|
339
|
+
validFiles.push(i);
|
|
340
|
+
offsetVals.push(offs);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return validFiles;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* @name checkProgrammable
|
|
348
|
+
* Check if the conditions to program the device are sufficient
|
|
349
|
+
*/
|
|
350
|
+
async function checkProgrammable() {
|
|
351
|
+
butProgram.disabled = getValidFiles().length == 0;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* @name checkFirmware
|
|
356
|
+
* Handler for firmware upload changes
|
|
357
|
+
*/
|
|
358
|
+
async function checkFirmware(event) {
|
|
359
|
+
let filename = event.target.value.split("\\").pop();
|
|
360
|
+
let label = event.target.parentNode.querySelector("span");
|
|
361
|
+
let icon = event.target.parentNode.querySelector("svg");
|
|
362
|
+
if (filename != "") {
|
|
363
|
+
if (filename.length > 17) {
|
|
364
|
+
label.innerHTML = filename.substring(0, 14) + "…";
|
|
365
|
+
} else {
|
|
366
|
+
label.innerHTML = filename;
|
|
367
|
+
}
|
|
368
|
+
icon.classList.add("hidden");
|
|
369
|
+
} else {
|
|
370
|
+
label.innerHTML = "Choose a file…";
|
|
371
|
+
icon.classList.remove("hidden");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
await checkProgrammable();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* @name clickClear
|
|
379
|
+
* Click handler for the clear button.
|
|
380
|
+
*/
|
|
381
|
+
async function clickClear() {
|
|
382
|
+
reset();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function convertJSON(chunk) {
|
|
386
|
+
try {
|
|
387
|
+
let jsonObj = JSON.parse(chunk);
|
|
388
|
+
return jsonObj;
|
|
389
|
+
} catch (e) {
|
|
390
|
+
return chunk;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function toggleUIToolbar(show) {
|
|
395
|
+
isConnected = show;
|
|
396
|
+
for (let i = 0; i < 4; i++) {
|
|
397
|
+
progress[i].classList.add("hidden");
|
|
398
|
+
progress[i].querySelector("div").style.width = "0";
|
|
399
|
+
}
|
|
400
|
+
if (show) {
|
|
401
|
+
appDiv.classList.add("connected");
|
|
402
|
+
} else {
|
|
403
|
+
appDiv.classList.remove("connected");
|
|
404
|
+
}
|
|
405
|
+
butErase.disabled = !show;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function toggleUIConnected(connected) {
|
|
409
|
+
let lbl = "Connect";
|
|
410
|
+
if (connected) {
|
|
411
|
+
lbl = "Disconnect";
|
|
412
|
+
} else {
|
|
413
|
+
toggleUIToolbar(false);
|
|
414
|
+
}
|
|
415
|
+
butConnect.textContent = lbl;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function loadAllSettings() {
|
|
419
|
+
// Load all saved settings or defaults
|
|
420
|
+
autoscroll.checked = loadSetting("autoscroll", true);
|
|
421
|
+
baudRate.value = loadSetting("baudrate", 115200);
|
|
422
|
+
darkMode.checked = loadSetting("darkmode", false);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function loadSetting(setting, defaultValue) {
|
|
426
|
+
let value = JSON.parse(window.localStorage.getItem(setting));
|
|
427
|
+
if (value == null) {
|
|
428
|
+
return defaultValue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return value;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function saveSetting(setting, value) {
|
|
435
|
+
window.localStorage.setItem(setting, JSON.stringify(value));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function ucWords(text) {
|
|
439
|
+
return text
|
|
440
|
+
.replace("_", " ")
|
|
441
|
+
.toLowerCase()
|
|
442
|
+
.replace(/(?<= )[^\s]|^./g, (a) => a.toUpperCase());
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function sleep(ms) {
|
|
446
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
447
|
+
}
|
package/js/utilities.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @name toByteArray
|
|
3
|
+
* Convert a string to a byte array
|
|
4
|
+
*/
|
|
5
|
+
function toByteArray(str) {
|
|
6
|
+
let byteArray = [];
|
|
7
|
+
for (let i = 0; i < str.length; i++) {
|
|
8
|
+
let charcode = str.charCodeAt(i);
|
|
9
|
+
if (charcode <= 0xFF) {
|
|
10
|
+
byteArray.push(charcode);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return byteArray;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function fromByteArray(byteArray) {
|
|
17
|
+
return String.fromCharCode.apply(String, byteArray);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function crc32(data, value=0) {
|
|
21
|
+
if (data instanceof Array) {
|
|
22
|
+
data = fromByteArray(data);
|
|
23
|
+
}
|
|
24
|
+
let table = [];
|
|
25
|
+
for(let entry, c = 0; c < 256; c++) {
|
|
26
|
+
entry = c;
|
|
27
|
+
for(let k = 0; k < 8; k++) {
|
|
28
|
+
entry = 1 & entry ? 3988292384^entry >>> 1 : entry >>> 1;
|
|
29
|
+
}
|
|
30
|
+
table[c] = entry;
|
|
31
|
+
}
|
|
32
|
+
let n = -1 - value;
|
|
33
|
+
for(let t = 0; t < data.length; t++) {
|
|
34
|
+
n = n >>> 8^table[255 & (n^data.charCodeAt(t))];
|
|
35
|
+
}
|
|
36
|
+
return (-1 ^ n) >>> 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function zipLongest() {
|
|
40
|
+
var args = [].slice.call(arguments);
|
|
41
|
+
var longest = args.reduce(function(a,b){
|
|
42
|
+
return a.length > b.length ? a : b
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
return longest.map(function(_,i){
|
|
46
|
+
return args.map(function(array){return array[i]})
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class struct {
|
|
51
|
+
static lut = {
|
|
52
|
+
"b": {u: DataView.prototype.getInt8, p: DataView.prototype.setInt8, bytes: 1},
|
|
53
|
+
"B": {u: DataView.prototype.getUint8, p: DataView.prototype.setUint8, bytes: 1},
|
|
54
|
+
"h": {u: DataView.prototype.getInt16, p: DataView.prototype.setInt16, bytes: 2},
|
|
55
|
+
"H": {u: DataView.prototype.getUint16, p: DataView.prototype.setUint16, bytes: 2},
|
|
56
|
+
"i": {u: DataView.prototype.getInt32, p: DataView.prototype.setInt32, bytes: 4},
|
|
57
|
+
"I": {u: DataView.prototype.getUint32, p: DataView.prototype.setUint32, bytes: 4},
|
|
58
|
+
"q": {u: DataView.prototype.getInt64, p: DataView.prototype.setInt64, bytes: 8},
|
|
59
|
+
"Q": {u: DataView.prototype.getUint64, p: DataView.prototype.setUint64, bytes: 8},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static pack(...args) {
|
|
63
|
+
let format = args[0];
|
|
64
|
+
let pointer = 0;
|
|
65
|
+
let data = args.slice(1);
|
|
66
|
+
if (format.replace(/[<>]/, '').length != data.length) {
|
|
67
|
+
throw("Pack format to Argument count mismatch");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
let bytes = [];
|
|
71
|
+
let littleEndian = true;
|
|
72
|
+
for (let i = 0; i < format.length; i++) {
|
|
73
|
+
if (format[i] == "<") {
|
|
74
|
+
littleEndian = true;
|
|
75
|
+
} else if (format[i] == ">") {
|
|
76
|
+
littleEndian = false;
|
|
77
|
+
} else {
|
|
78
|
+
pushBytes(format[i], data[pointer]);
|
|
79
|
+
pointer++;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function pushBytes(formatChar, value) {
|
|
84
|
+
if (!(formatChar in struct.lut)) {
|
|
85
|
+
throw("Unhandled character '" + formatChar + "' in pack format");
|
|
86
|
+
}
|
|
87
|
+
let dataSize = struct.lut[formatChar].bytes;
|
|
88
|
+
let view = new DataView(new ArrayBuffer(dataSize));
|
|
89
|
+
let dataViewFn = struct.lut[formatChar].p.bind(view);
|
|
90
|
+
dataViewFn(0, value, littleEndian);
|
|
91
|
+
for (let i = 0; i < dataSize; i++) {
|
|
92
|
+
bytes.push(view.getUint8(i));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return bytes;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
static unpack(format, bytes) {
|
|
100
|
+
let pointer = 0;
|
|
101
|
+
let data = [];
|
|
102
|
+
let littleEndian = true;
|
|
103
|
+
|
|
104
|
+
for (let c of format) {
|
|
105
|
+
if (c == "<") {
|
|
106
|
+
littleEndian = true;
|
|
107
|
+
} else if (c == ">") {
|
|
108
|
+
littleEndian = false;
|
|
109
|
+
} else {
|
|
110
|
+
pushData(c);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function pushData(formatChar) {
|
|
115
|
+
if (!(formatChar in struct.lut)) {
|
|
116
|
+
throw("Unhandled character '" + formatChar + "' in unpack format");
|
|
117
|
+
}
|
|
118
|
+
let dataSize = struct.lut[formatChar].bytes;
|
|
119
|
+
let view = new DataView(new ArrayBuffer(dataSize));
|
|
120
|
+
for (let i = 0; i < dataSize; i++) {
|
|
121
|
+
view.setUint8(i, bytes[pointer + i] & 0xFF);
|
|
122
|
+
}
|
|
123
|
+
let dataViewFn = struct.lut[formatChar].u.bind(view);
|
|
124
|
+
data.push(dataViewFn(0, littleEndian));
|
|
125
|
+
pointer += dataSize;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return data;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
static calcsize(format) {
|
|
132
|
+
let size = 0;
|
|
133
|
+
for (let i = 0; i < format.length; i++) {
|
|
134
|
+
if (format[i] != "<" && format[i] != ">") {
|
|
135
|
+
size += struct.lut[format[i]].bytes;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return size;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function* makeFileIterator(content) {
|
|
144
|
+
for (let line of content.split(/\r?\n/)) {
|
|
145
|
+
yield line.trim();
|
|
146
|
+
}
|
|
147
|
+
return '';
|
|
148
|
+
}
|
package/license.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Johann Obermeier
|
|
4
|
+
Copyright (c) 2021 Nabu Casa
|
|
5
|
+
Copyright (c) 2020 Melissa LeBlanc-Williams for Adafruit Industries
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
8
|
+
|
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
10
|
+
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
12
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tasmota-webserial-esptool",
|
|
3
|
+
"version": "6.0.1",
|
|
4
|
+
"description": "Flash ESP devices using WebSerial",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git://github.com/Jason2866/WebSerial_ESPTool.git"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"registry": "https://registry.npmjs.org/"
|
|
12
|
+
},
|
|
13
|
+
"author": "Johann Obermeier",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"prepublishOnly": "script/build"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@rollup/plugin-json": "^6.0.0",
|
|
21
|
+
"@rollup/plugin-node-resolve": "^15.0.2",
|
|
22
|
+
"@rollup/plugin-terser": "^0.4.3",
|
|
23
|
+
"@rollup/plugin-typescript": "^11.1.1",
|
|
24
|
+
"@types/pako": "^2.0.0",
|
|
25
|
+
"@types/w3c-web-serial": "^1.0.3",
|
|
26
|
+
"prettier": "^2.8.8",
|
|
27
|
+
"rollup": "^3.23.0",
|
|
28
|
+
"serve": "^14.2.0",
|
|
29
|
+
"typescript": "^5.0.4"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@types/node": "^20.2.3",
|
|
33
|
+
"pako": "^2.1.0",
|
|
34
|
+
"tslib": "^2.5.2"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
|
2
|
+
import json from "@rollup/plugin-json";
|
|
3
|
+
import terser from "@rollup/plugin-terser";
|
|
4
|
+
|
|
5
|
+
const config = {
|
|
6
|
+
input: "dist/index.js",
|
|
7
|
+
output: {
|
|
8
|
+
dir: "dist/web",
|
|
9
|
+
format: "module",
|
|
10
|
+
},
|
|
11
|
+
// preserveEntrySignatures: false,
|
|
12
|
+
plugins: [nodeResolve(), json()],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
if (process.env.NODE_ENV === "production") {
|
|
16
|
+
config.plugins.push(
|
|
17
|
+
terser({
|
|
18
|
+
ecma: 2019,
|
|
19
|
+
toplevel: true,
|
|
20
|
+
output: {
|
|
21
|
+
comments: false,
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default config;
|
package/script/build
ADDED
package/script/develop
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Stop on errors
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
cd "$(dirname "$0")/.."
|
|
5
|
+
|
|
6
|
+
rm -rf dist
|
|
7
|
+
|
|
8
|
+
# Quit all background tasks when script exits
|
|
9
|
+
trap "kill 0" EXIT
|
|
10
|
+
|
|
11
|
+
# Run tsc once as rollup expects those files
|
|
12
|
+
npm exec -- tsc || true
|
|
13
|
+
|
|
14
|
+
npm exec -- serve -p 5004 &
|
|
15
|
+
npm exec -- tsc --watch &
|
|
16
|
+
npm exec -- rollup -c --watch &
|
|
17
|
+
wait
|