1
0

flash.ts 5.5 KB

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