UNPKG

6.16 kBPlain TextView Raw
1// @ts-ignore-next-line
2import { Transport } from "esptool-js/webserial.js";
3// @ts-ignore-next-line
4import { ESPLoader } from "esptool-js/ESPLoader.js";
5import {
6 Build,
7 FlashError,
8 FlashState,
9 Manifest,
10 FlashStateType,
11} from "./const";
12import { sleep } from "./util/sleep";
13
14const resetTransport = async (transport: Transport) => {
15 await transport.device.setSignals({
16 dataTerminalReady: false,
17 requestToSend: true,
18 });
19 await transport.device.setSignals({
20 dataTerminalReady: false,
21 requestToSend: false,
22 });
23};
24
25export const flash = async (
26 onEvent: (state: FlashState) => void,
27 port: SerialPort,
28 manifestPath: string,
29 manifest: Manifest,
30 eraseFirst: boolean
31) => {
32 let build: Build | undefined;
33 let chipFamily: Build["chipFamily"];
34
35 const fireStateEvent = (stateUpdate: FlashState) =>
36 onEvent({
37 ...stateUpdate,
38 manifest,
39 build,
40 chipFamily,
41 });
42
43 const transport = new Transport(port);
44 const esploader = new ESPLoader(transport, 115200);
45
46 // For debugging
47 (window as any).esploader = esploader;
48
49 fireStateEvent({
50 state: FlashStateType.INITIALIZING,
51 message: "Initializing...",
52 details: { done: false },
53 });
54
55 try {
56 await esploader.main_fn();
57 await esploader.flash_id();
58 } catch (err: any) {
59 console.error(err);
60 fireStateEvent({
61 state: FlashStateType.ERROR,
62 message:
63 "Failed to initialize. Try resetting your device or holding the BOOT button while clicking INSTALL.",
64 details: { error: FlashError.FAILED_INITIALIZING, details: err },
65 });
66 await resetTransport(transport);
67 await transport.disconnect();
68 return;
69 }
70
71 chipFamily = await esploader.chip.CHIP_NAME;
72
73 if (!esploader.chip.ROM_TEXT) {
74 fireStateEvent({
75 state: FlashStateType.ERROR,
76 message: `Chip ${chipFamily} is not supported`,
77 details: {
78 error: FlashError.NOT_SUPPORTED,
79 details: `Chip ${chipFamily} is not supported`,
80 },
81 });
82 await resetTransport(transport);
83 await transport.disconnect();
84 return;
85 }
86
87 fireStateEvent({
88 state: FlashStateType.INITIALIZING,
89 message: `Initialized. Found ${chipFamily}`,
90 details: { done: true },
91 });
92
93 build = manifest.builds.find((b) => b.chipFamily === chipFamily);
94
95 if (!build) {
96 fireStateEvent({
97 state: FlashStateType.ERROR,
98 message: `Your ${chipFamily} board is not supported.`,
99 details: { error: FlashError.NOT_SUPPORTED, details: chipFamily },
100 });
101 await resetTransport(transport);
102 await transport.disconnect();
103 return;
104 }
105
106 fireStateEvent({
107 state: FlashStateType.PREPARING,
108 message: "Preparing installation...",
109 details: { done: false },
110 });
111
112 const manifestURL = new URL(manifestPath, location.toString()).toString();
113 const filePromises = build.parts.map(async (part) => {
114 const url = new URL(part.path, manifestURL).toString();
115 const resp = await fetch(url);
116 if (!resp.ok) {
117 throw new Error(
118 `Downlading firmware ${part.path} failed: ${resp.status}`
119 );
120 }
121
122 const reader = new FileReader();
123 const blob = await resp.blob();
124
125 return new Promise<string>((resolve) => {
126 reader.addEventListener("load", () => resolve(reader.result as string));
127 reader.readAsBinaryString(blob);
128 });
129 });
130
131 const fileArray: Array<{ data: string; address: number }> = [];
132 let totalSize = 0;
133
134 for (let part = 0; part < filePromises.length; part++) {
135 try {
136 const data = await filePromises[part];
137 fileArray.push({ data, address: build.parts[part].offset });
138 totalSize += data.length;
139 } catch (err: any) {
140 fireStateEvent({
141 state: FlashStateType.ERROR,
142 message: err.message,
143 details: {
144 error: FlashError.FAILED_FIRMWARE_DOWNLOAD,
145 details: err.message,
146 },
147 });
148 await resetTransport(transport);
149 await transport.disconnect();
150 return;
151 }
152 }
153
154 fireStateEvent({
155 state: FlashStateType.PREPARING,
156 message: "Installation prepared",
157 details: { done: true },
158 });
159
160 if (eraseFirst) {
161 fireStateEvent({
162 state: FlashStateType.ERASING,
163 message: "Erasing device...",
164 details: { done: false },
165 });
166 await esploader.erase_flash();
167 fireStateEvent({
168 state: FlashStateType.ERASING,
169 message: "Device erased",
170 details: { done: true },
171 });
172 }
173
174 fireStateEvent({
175 state: FlashStateType.WRITING,
176 message: `Writing progress: 0%`,
177 details: {
178 bytesTotal: totalSize,
179 bytesWritten: 0,
180 percentage: 0,
181 },
182 });
183
184 let totalWritten = 0;
185
186 try {
187 await esploader.write_flash({
188 fileArray,
189 reportProgress(fileIndex: number, written: number, total: number) {
190 const uncompressedWritten =
191 (written / total) * fileArray[fileIndex].data.length;
192
193 const newPct = Math.floor(
194 ((totalWritten + uncompressedWritten) / totalSize) * 100
195 );
196
197 // we're done with this file
198 if (written === total) {
199 totalWritten += uncompressedWritten;
200 return;
201 }
202
203 fireStateEvent({
204 state: FlashStateType.WRITING,
205 message: `Writing progress: ${newPct}%`,
206 details: {
207 bytesTotal: totalSize,
208 bytesWritten: totalWritten + written,
209 percentage: newPct,
210 },
211 });
212 },
213 });
214 } catch (err: any) {
215 fireStateEvent({
216 state: FlashStateType.ERROR,
217 message: err.message,
218 details: { error: FlashError.WRITE_FAILED, details: err },
219 });
220 await resetTransport(transport);
221 await transport.disconnect();
222 return;
223 }
224
225 fireStateEvent({
226 state: FlashStateType.WRITING,
227 message: "Writing complete",
228 details: {
229 bytesTotal: totalSize,
230 bytesWritten: totalWritten,
231 percentage: 100,
232 },
233 });
234
235 await sleep(100);
236 console.log("HARD RESET");
237 await resetTransport(transport);
238 console.log("DISCONNECT");
239 await transport.disconnect();
240
241 fireStateEvent({
242 state: FlashStateType.FINISHED,
243 message: "All done!",
244 });
245};
246
\No newline at end of file