1 | import { Transport, ESPLoader } from "esptool-js";
|
2 | import { sleep } from "./util/sleep";
|
3 | const resetTransport = async (transport) => {
|
4 | await transport.device.setSignals({
|
5 | dataTerminalReady: false,
|
6 | requestToSend: true,
|
7 | });
|
8 | await sleep(250);
|
9 | await transport.device.setSignals({
|
10 | dataTerminalReady: false,
|
11 | requestToSend: false,
|
12 | });
|
13 | await sleep(250);
|
14 | };
|
15 | export const flash = async (onEvent, port, manifestPath, manifest, eraseFirst) => {
|
16 | let build;
|
17 | let chipFamily;
|
18 | const fireStateEvent = (stateUpdate) => onEvent({
|
19 | ...stateUpdate,
|
20 | manifest,
|
21 | build,
|
22 | chipFamily,
|
23 | });
|
24 | const transport = new Transport(port);
|
25 | const esploader = new ESPLoader({
|
26 | transport,
|
27 | baudrate: 115200,
|
28 | romBaudrate: 115200,
|
29 | enableTracing: false,
|
30 | });
|
31 |
|
32 | window.esploader = esploader;
|
33 | fireStateEvent({
|
34 | state: "initializing" ,
|
35 | message: "Initializing...",
|
36 | details: { done: false },
|
37 | });
|
38 | try {
|
39 | await esploader.main();
|
40 | await esploader.flashId();
|
41 | }
|
42 | catch (err) {
|
43 | console.error(err);
|
44 | fireStateEvent({
|
45 | state: "error" ,
|
46 | message: "Failed to initialize. Try resetting your device or holding the BOOT button while clicking INSTALL.",
|
47 | details: { error: "failed_initialize" , details: err },
|
48 | });
|
49 | await resetTransport(transport);
|
50 | await transport.disconnect();
|
51 | return;
|
52 | }
|
53 | chipFamily = esploader.chip.CHIP_NAME;
|
54 | fireStateEvent({
|
55 | state: "initializing" ,
|
56 | message: `Initialized. Found ${chipFamily}`,
|
57 | details: { done: true },
|
58 | });
|
59 | build = manifest.builds.find((b) => b.chipFamily === chipFamily);
|
60 | if (!build) {
|
61 | fireStateEvent({
|
62 | state: "error" ,
|
63 | message: `Your ${chipFamily} board is not supported.`,
|
64 | details: { error: "not_supported" , details: chipFamily },
|
65 | });
|
66 | await resetTransport(transport);
|
67 | await transport.disconnect();
|
68 | return;
|
69 | }
|
70 | fireStateEvent({
|
71 | state: "preparing" ,
|
72 | message: "Preparing installation...",
|
73 | details: { done: false },
|
74 | });
|
75 | const manifestURL = new URL(manifestPath, location.toString()).toString();
|
76 | const filePromises = build.parts.map(async (part) => {
|
77 | const url = new URL(part.path, manifestURL).toString();
|
78 | const resp = await fetch(url);
|
79 | if (!resp.ok) {
|
80 | throw new Error(`Downlading firmware ${part.path} failed: ${resp.status}`);
|
81 | }
|
82 | const reader = new FileReader();
|
83 | const blob = await resp.blob();
|
84 | return new Promise((resolve) => {
|
85 | reader.addEventListener("load", () => resolve(reader.result));
|
86 | reader.readAsBinaryString(blob);
|
87 | });
|
88 | });
|
89 | const fileArray = [];
|
90 | let totalSize = 0;
|
91 | for (let part = 0; part < filePromises.length; part++) {
|
92 | try {
|
93 | const data = await filePromises[part];
|
94 | fileArray.push({ data, address: build.parts[part].offset });
|
95 | totalSize += data.length;
|
96 | }
|
97 | catch (err) {
|
98 | fireStateEvent({
|
99 | state: "error" ,
|
100 | message: err.message,
|
101 | details: {
|
102 | error: "failed_firmware_download" ,
|
103 | details: err.message,
|
104 | },
|
105 | });
|
106 | await resetTransport(transport);
|
107 | await transport.disconnect();
|
108 | return;
|
109 | }
|
110 | }
|
111 | fireStateEvent({
|
112 | state: "preparing" ,
|
113 | message: "Installation prepared",
|
114 | details: { done: true },
|
115 | });
|
116 | if (eraseFirst) {
|
117 | fireStateEvent({
|
118 | state: "erasing" ,
|
119 | message: "Erasing device...",
|
120 | details: { done: false },
|
121 | });
|
122 | await esploader.eraseFlash();
|
123 | fireStateEvent({
|
124 | state: "erasing" ,
|
125 | message: "Device erased",
|
126 | details: { done: true },
|
127 | });
|
128 | }
|
129 | fireStateEvent({
|
130 | state: "writing" ,
|
131 | message: `Writing progress: 0%`,
|
132 | details: {
|
133 | bytesTotal: totalSize,
|
134 | bytesWritten: 0,
|
135 | percentage: 0,
|
136 | },
|
137 | });
|
138 | let totalWritten = 0;
|
139 | try {
|
140 | await esploader.writeFlash({
|
141 | fileArray,
|
142 | flashSize: "keep",
|
143 | flashMode: "keep",
|
144 | flashFreq: "keep",
|
145 | eraseAll: false,
|
146 | compress: true,
|
147 |
|
148 | reportProgress: (fileIndex, written, total) => {
|
149 | const uncompressedWritten = (written / total) * fileArray[fileIndex].data.length;
|
150 | const newPct = Math.floor(((totalWritten + uncompressedWritten) / totalSize) * 100);
|
151 |
|
152 | if (written === total) {
|
153 | totalWritten += uncompressedWritten;
|
154 | return;
|
155 | }
|
156 | fireStateEvent({
|
157 | state: "writing" ,
|
158 | message: `Writing progress: ${newPct}%`,
|
159 | details: {
|
160 | bytesTotal: totalSize,
|
161 | bytesWritten: totalWritten + written,
|
162 | percentage: newPct,
|
163 | },
|
164 | });
|
165 | },
|
166 | });
|
167 | }
|
168 | catch (err) {
|
169 | fireStateEvent({
|
170 | state: "error" ,
|
171 | message: err.message,
|
172 | details: { error: "write_failed" , details: err },
|
173 | });
|
174 | await resetTransport(transport);
|
175 | await transport.disconnect();
|
176 | return;
|
177 | }
|
178 | fireStateEvent({
|
179 | state: "writing" ,
|
180 | message: "Writing complete",
|
181 | details: {
|
182 | bytesTotal: totalSize,
|
183 | bytesWritten: totalWritten,
|
184 | percentage: 100,
|
185 | },
|
186 | });
|
187 | await sleep(100);
|
188 | console.log("HARD RESET");
|
189 | await resetTransport(transport);
|
190 | console.log("DISCONNECT");
|
191 | await transport.disconnect();
|
192 | fireStateEvent({
|
193 | state: "finished" ,
|
194 | message: "All done!",
|
195 | });
|
196 | };
|