flash.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import { connect, ESPLoader, Logger } from "esp-web-flasher";
  2. import { Build, FlashError, FlashState, Manifest, State } from "./const";
  3. import { fireEvent, getChipFamilyName, sleep } from "./util";
  4. export const flash = async (
  5. eventTarget: EventTarget,
  6. logger: Logger,
  7. manifestPath: string,
  8. eraseFirst: boolean
  9. ) => {
  10. let manifest: Manifest;
  11. let build: Build | undefined;
  12. let chipFamily: ReturnType<typeof getChipFamilyName>;
  13. const fireStateEvent = (stateUpdate: FlashState) => {
  14. fireEvent(eventTarget, "state-changed", {
  15. ...stateUpdate,
  16. manifest,
  17. build,
  18. chipFamily,
  19. });
  20. };
  21. const manifestURL = new URL(manifestPath, location.toString()).toString();
  22. const manifestProm = fetch(manifestURL).then(
  23. (resp): Promise<Manifest> => resp.json()
  24. );
  25. let esploader: ESPLoader | undefined;
  26. try {
  27. esploader = await connect(logger);
  28. } catch (err) {
  29. // User pressed cancel on web serial
  30. return;
  31. }
  32. // For debugging
  33. (window as any).esploader = esploader;
  34. fireStateEvent({
  35. state: State.INITIALIZING,
  36. message: "Initializing...",
  37. details: { done: false },
  38. });
  39. try {
  40. await esploader.initialize();
  41. } catch (err) {
  42. logger.error(err);
  43. if (esploader.connected) {
  44. fireStateEvent({
  45. state: State.ERROR,
  46. message:
  47. "Failed to initialize. Try resetting your device or holding the BOOT button while selecting your serial port.",
  48. details: { error: FlashError.FAILED_INITIALIZING, details: err },
  49. });
  50. await esploader.disconnect();
  51. }
  52. return;
  53. }
  54. chipFamily = getChipFamilyName(esploader);
  55. fireStateEvent({
  56. state: State.INITIALIZING,
  57. message: `Initialized. Found ${chipFamily}`,
  58. details: { done: true },
  59. });
  60. fireStateEvent({
  61. state: State.MANIFEST,
  62. message: "Fetching manifest...",
  63. details: { done: false },
  64. });
  65. try {
  66. manifest = await manifestProm;
  67. } catch (err) {
  68. fireStateEvent({
  69. state: State.ERROR,
  70. message: `Unable to fetch manifest: ${err.message}`,
  71. details: { error: FlashError.FAILED_MANIFEST_FETCH, details: err },
  72. });
  73. await esploader.disconnect();
  74. return;
  75. }
  76. build = manifest.builds.find((b) => b.chipFamily === chipFamily);
  77. fireStateEvent({
  78. state: State.MANIFEST,
  79. message: `Found manifest for ${manifest.name}`,
  80. details: { done: true },
  81. });
  82. if (!build) {
  83. fireStateEvent({
  84. state: State.ERROR,
  85. message: `Your ${chipFamily} board is not supported.`,
  86. details: { error: FlashError.NOT_SUPPORTED, details: chipFamily },
  87. });
  88. await esploader.disconnect();
  89. return;
  90. }
  91. fireStateEvent({
  92. state: State.PREPARING,
  93. message: "Preparing installation...",
  94. details: { done: false },
  95. });
  96. const filePromises = build.parts.map(async (part) => {
  97. const url = new URL(part.path, manifestURL).toString();
  98. const resp = await fetch(url);
  99. if (!resp.ok) {
  100. throw new Error(
  101. `Downlading firmware ${part.path} failed: ${resp.status}`
  102. );
  103. }
  104. return resp.arrayBuffer();
  105. });
  106. // Run the stub while we wait for files to download
  107. const espStub = await esploader.runStub();
  108. const files: ArrayBuffer[] = [];
  109. let totalSize = 0;
  110. for (const prom of filePromises) {
  111. try {
  112. const data = await prom;
  113. files.push(data);
  114. totalSize += data.byteLength;
  115. } catch (err) {
  116. fireStateEvent({
  117. state: State.ERROR,
  118. message: err,
  119. details: { error: FlashError.FAILED_FIRMWARE_DOWNLOAD, details: err },
  120. });
  121. await esploader.disconnect();
  122. return;
  123. }
  124. }
  125. fireStateEvent({
  126. state: State.PREPARING,
  127. message: "Installation prepared",
  128. details: { done: true },
  129. });
  130. if (eraseFirst) {
  131. fireStateEvent({
  132. state: State.ERASING,
  133. message: "Erasing device...",
  134. details: { done: false },
  135. });
  136. await espStub.eraseFlash();
  137. fireStateEvent({
  138. state: State.ERASING,
  139. message: "Device erased",
  140. details: { done: true },
  141. });
  142. }
  143. let lastPct = 0;
  144. fireStateEvent({
  145. state: State.WRITING,
  146. message: `Writing progress: ${lastPct}%`,
  147. details: {
  148. bytesTotal: totalSize,
  149. bytesWritten: 0,
  150. percentage: lastPct,
  151. },
  152. });
  153. let totalWritten = 0;
  154. for (const part of build.parts) {
  155. const file = files.shift()!;
  156. try {
  157. await espStub.flashData(
  158. file,
  159. (bytesWritten: number) => {
  160. const newPct = Math.floor(
  161. ((totalWritten + bytesWritten) / totalSize) * 100
  162. );
  163. if (newPct === lastPct) {
  164. return;
  165. }
  166. lastPct = newPct;
  167. fireStateEvent({
  168. state: State.WRITING,
  169. message: `Writing progress: ${newPct}%`,
  170. details: {
  171. bytesTotal: totalSize,
  172. bytesWritten: totalWritten + bytesWritten,
  173. percentage: newPct,
  174. },
  175. });
  176. },
  177. part.offset,
  178. true
  179. );
  180. } catch (err) {
  181. fireStateEvent({
  182. state: State.ERROR,
  183. message: err,
  184. details: { error: FlashError.WRITE_FAILED, details: err },
  185. });
  186. await esploader.disconnect();
  187. return;
  188. }
  189. totalWritten += file.byteLength;
  190. }
  191. fireStateEvent({
  192. state: State.WRITING,
  193. message: "Writing complete",
  194. details: {
  195. bytesTotal: totalSize,
  196. bytesWritten: totalWritten,
  197. percentage: 100,
  198. },
  199. });
  200. await sleep(100);
  201. await esploader.hardReset();
  202. await esploader.disconnect();
  203. fireStateEvent({
  204. state: State.FINISHED,
  205. message: "All done!",
  206. });
  207. };