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 | if (!esploader.chip.ROM_TEXT) {
|
55 | fireStateEvent({
|
56 | state: "error" ,
|
57 | message: `Chip ${chipFamily} is not supported`,
|
58 | details: {
|
59 | error: "not_supported" ,
|
60 | details: `Chip ${chipFamily} is not supported`,
|
61 | },
|
62 | });
|
63 | await resetTransport(transport);
|
64 | await transport.disconnect();
|
65 | return;
|
66 | }
|
67 | fireStateEvent({
|
68 | state: "initializing" ,
|
69 | message: `Initialized. Found ${chipFamily}`,
|
70 | details: { done: true },
|
71 | });
|
72 | build = manifest.builds.find((b) => b.chipFamily === chipFamily);
|
73 | if (!build) {
|
74 | fireStateEvent({
|
75 | state: "error" ,
|
76 | message: `Your ${chipFamily} board is not supported.`,
|
77 | details: { error: "not_supported" , details: chipFamily },
|
78 | });
|
79 | await resetTransport(transport);
|
80 | await transport.disconnect();
|
81 | return;
|
82 | }
|
83 | fireStateEvent({
|
84 | state: "preparing" ,
|
85 | message: "Preparing installation...",
|
86 | details: { done: false },
|
87 | });
|
88 | const manifestURL = new URL(manifestPath, location.toString()).toString();
|
89 | const filePromises = build.parts.map(async (part) => {
|
90 | const url = new URL(part.path, manifestURL).toString();
|
91 | const resp = await fetch(url);
|
92 | if (!resp.ok) {
|
93 | throw new Error(`Downlading firmware ${part.path} failed: ${resp.status}`);
|
94 | }
|
95 | const reader = new FileReader();
|
96 | const blob = await resp.blob();
|
97 | return new Promise((resolve) => {
|
98 | reader.addEventListener("load", () => resolve(reader.result));
|
99 | reader.readAsBinaryString(blob);
|
100 | });
|
101 | });
|
102 | const fileArray = [];
|
103 | let totalSize = 0;
|
104 | for (let part = 0; part < filePromises.length; part++) {
|
105 | try {
|
106 | const data = await filePromises[part];
|
107 | fileArray.push({ data, address: build.parts[part].offset });
|
108 | totalSize += data.length;
|
109 | }
|
110 | catch (err) {
|
111 | fireStateEvent({
|
112 | state: "error" ,
|
113 | message: err.message,
|
114 | details: {
|
115 | error: "failed_firmware_download" ,
|
116 | details: err.message,
|
117 | },
|
118 | });
|
119 | await resetTransport(transport);
|
120 | await transport.disconnect();
|
121 | return;
|
122 | }
|
123 | }
|
124 | fireStateEvent({
|
125 | state: "preparing" ,
|
126 | message: "Installation prepared",
|
127 | details: { done: true },
|
128 | });
|
129 | if (eraseFirst) {
|
130 | fireStateEvent({
|
131 | state: "erasing" ,
|
132 | message: "Erasing device...",
|
133 | details: { done: false },
|
134 | });
|
135 | await esploader.eraseFlash();
|
136 | fireStateEvent({
|
137 | state: "erasing" ,
|
138 | message: "Device erased",
|
139 | details: { done: true },
|
140 | });
|
141 | }
|
142 | fireStateEvent({
|
143 | state: "writing" ,
|
144 | message: `Writing progress: 0%`,
|
145 | details: {
|
146 | bytesTotal: totalSize,
|
147 | bytesWritten: 0,
|
148 | percentage: 0,
|
149 | },
|
150 | });
|
151 | let totalWritten = 0;
|
152 | try {
|
153 | await esploader.writeFlash({
|
154 | fileArray,
|
155 | flashSize: "keep",
|
156 | flashMode: "keep",
|
157 | flashFreq: "keep",
|
158 | eraseAll: false,
|
159 | compress: true,
|
160 |
|
161 | reportProgress: (fileIndex, written, total) => {
|
162 | const uncompressedWritten = (written / total) * fileArray[fileIndex].data.length;
|
163 | const newPct = Math.floor(((totalWritten + uncompressedWritten) / totalSize) * 100);
|
164 |
|
165 | if (written === total) {
|
166 | totalWritten += uncompressedWritten;
|
167 | return;
|
168 | }
|
169 | fireStateEvent({
|
170 | state: "writing" ,
|
171 | message: `Writing progress: ${newPct}%`,
|
172 | details: {
|
173 | bytesTotal: totalSize,
|
174 | bytesWritten: totalWritten + written,
|
175 | percentage: newPct,
|
176 | },
|
177 | });
|
178 | },
|
179 | });
|
180 | }
|
181 | catch (err) {
|
182 | fireStateEvent({
|
183 | state: "error" ,
|
184 | message: err.message,
|
185 | details: { error: "write_failed" , details: err },
|
186 | });
|
187 | await resetTransport(transport);
|
188 | await transport.disconnect();
|
189 | return;
|
190 | }
|
191 | fireStateEvent({
|
192 | state: "writing" ,
|
193 | message: "Writing complete",
|
194 | details: {
|
195 | bytesTotal: totalSize,
|
196 | bytesWritten: totalWritten,
|
197 | percentage: 100,
|
198 | },
|
199 | });
|
200 | await sleep(100);
|
201 | console.log("HARD RESET");
|
202 | await resetTransport(transport);
|
203 | console.log("DISCONNECT");
|
204 | await transport.disconnect();
|
205 | fireStateEvent({
|
206 | state: "finished" ,
|
207 | message: "All done!",
|
208 | });
|
209 | };
|