ota-hub-reactjs 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ export type ESP32ConnectionState = DeviceConnectionState & {
5
5
  baudrate?: number;
6
6
  transport?: Transport;
7
7
  esp?: ESPLoader;
8
- reader?: AsyncGenerator<Uint8Array>;
8
+ debugLogging?: false;
9
9
  slipReadWrite?: boolean;
10
10
  isFlashing: boolean;
11
11
  flashProgress: number;
@@ -36,7 +36,7 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
36
36
  let inSlipFrame = false; // are we inside a SLIP frame?
37
37
  let escapeNext = false;
38
38
  const reader = transport.rawRead();
39
- base.updateConnection(uuid, (c) => ({ ...c, reader }));
39
+ base.updateConnection(uuid, (c) => ({ ...c }));
40
40
  try {
41
41
  while (true) {
42
42
  const conn = base.getConnection(uuid);
@@ -44,6 +44,8 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
44
44
  console.log("Kack!");
45
45
  return;
46
46
  }
47
+ if (conn.isFlashing || !conn.isConnected)
48
+ break;
47
49
  const { value, done } = await reader.next();
48
50
  if (done || !value)
49
51
  break;
@@ -114,6 +116,7 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
114
116
  }
115
117
  }
116
118
  }
119
+ await new Promise((resolve) => setTimeout(resolve, 100)); // Little breather before attempt any disconnects
117
120
  }
118
121
  catch (e) {
119
122
  base.appendLog(uuid, {
@@ -122,13 +125,11 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
122
125
  });
123
126
  }
124
127
  finally {
125
- // Clear reader state so it's fresh for next connect
126
- base.updateConnection(uuid, (c) => ({ ...c, reader: undefined }));
127
- base.appendLog(uuid, {
128
- level: 0,
129
- message: "[!] Serial disconnected",
130
- });
131
- await disconnect(uuid);
128
+ base.updateConnection(uuid, (c) => ({ ...c }));
129
+ const conn = base.getConnection(uuid);
130
+ if (conn?.isConnected) {
131
+ await disconnect(uuid);
132
+ }
132
133
  }
133
134
  };
134
135
  const restartDevice = async (uuid, default_transport) => {
@@ -177,7 +178,8 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
177
178
  const esploader = new ESPLoader({
178
179
  transport,
179
180
  baudrate: use_baudrate,
180
- enableTracing: false,
181
+ enableTracing: !!conn.debugLogging,
182
+ debugLogging: !!conn.debugLogging,
181
183
  });
182
184
  try {
183
185
  await esploader.main();
@@ -229,18 +231,6 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
229
231
  };
230
232
  const disconnect = async (uuid, timeout = 2000) => {
231
233
  const conn = base.getConnection(uuid);
232
- // 1. POLITELY KILL THE READER
233
- // If we have an active reader generator in state, call return() to
234
- // gracefully resolve the pending reader.next() in the background loop
235
- if (conn?.reader && typeof conn.reader.return === "function") {
236
- try {
237
- // @ts-ignore - The underlying AsyncGenerator handles return gracefully
238
- await conn.reader.return();
239
- }
240
- catch (e) {
241
- console.warn(`[${uuid}] Error returning generator lock:`, e);
242
- }
243
- }
244
234
  if (conn?.transport) {
245
235
  try {
246
236
  // Attempt disconnect, but don’t hang if the port is crashed
@@ -256,13 +246,17 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
256
246
  // Always clear the transport and reset connection state
257
247
  base.updateConnection(uuid, (c) => ({
258
248
  ...c,
259
- port: releasePortByDefault ? undefined : c.port,
260
- transport: releasePortByDefault ? undefined : c.transport,
261
- reader: undefined, // Guarantee reader is cleared out
249
+ port: releasePortByDefault ? null : c.port,
250
+ transport: releasePortByDefault ? null : c.transport,
251
+ esp: releasePortByDefault ? null : c.transport,
262
252
  isConnected: false,
263
253
  isConnecting: false,
264
254
  autoConnect: false,
265
255
  }));
256
+ base.appendLog(uuid, {
257
+ level: 0,
258
+ message: "[!] Serial disconnected",
259
+ });
266
260
  await conn?.onDisconnect?.();
267
261
  };
268
262
  const addConnection = async ({ uuid, propCreator, }) => {
@@ -332,37 +326,34 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
332
326
  };
333
327
  // This function now handles an entire device flashing session
334
328
  const handleFlashFirmware = async (uuid, assetsToFlash) => {
329
+ // Capture the original connection and port reference before disconnect
335
330
  const conn = base.getConnection(uuid);
336
331
  // 1. Bail if we don't have the required state
337
- if (!conn ||
338
- !conn.transport ||
339
- !conn.port ||
340
- !conn.esp ||
341
- assetsToFlash.length === 0)
332
+ if (!conn || !conn.port || assetsToFlash.length === 0)
342
333
  return;
343
- // Gracefully cancel reader and completely drop the transport natively
344
- await disconnect(uuid);
334
+ const prevAutoConnect = conn.autoConnect;
345
335
  base.updateConnection(uuid, (c) => ({
346
336
  ...c,
337
+ port: conn.port, // Restore the port into state in case disconnect wiped it
347
338
  isFlashing: true,
348
339
  flashProgress: 0,
349
340
  flashError: undefined,
341
+ autoConnect: false,
350
342
  }));
343
+ if (conn.isConnected)
344
+ await disconnect(uuid);
345
+ await new Promise((resolve) => setTimeout(resolve, 400));
351
346
  try {
352
347
  // --- Connect ONCE ---
353
348
  const transport = new Transport(conn.port, true);
354
349
  const esploader = new ESPLoader({
355
350
  transport,
356
351
  baudrate: 921600,
357
- enableTracing: false,
352
+ enableTracing: !!conn.debugLogging,
353
+ debugLogging: !!conn.debugLogging,
358
354
  });
359
- try {
360
- await esploader.main();
361
- }
362
- catch (e) {
363
- console.log("failed to esploader.main()", e);
364
- return;
365
- }
355
+ // This may fail, that'll bubble the failure up to the upper try/catch
356
+ await esploader.main();
366
357
  // --- Prepare an ARRAY of files for the library ---
367
358
  const fileArray = await Promise.all(assetsToFlash.map(async ({ blob, address }) => {
368
359
  const arrayBuffer = await blob.arrayBuffer();
@@ -371,32 +362,34 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
371
362
  .join("");
372
363
  return { data: binaryString, address };
373
364
  }));
365
+ let lastRenderTime = 0;
374
366
  const flashOptions = {
375
- fileArray, // Pass the whole array here
367
+ fileArray,
376
368
  flashSize: "keep",
377
369
  flashMode: "qio",
378
370
  flashFreq: "80m",
379
371
  eraseAll: fileArray.length > 1, // Writing more than 1 thing, so likely writing partitions.
380
372
  compress: true,
381
373
  reportProgress: (fileIndex, written, total) => {
382
- // You can enhance progress reporting to show which file is being flashed
383
374
  const progress = (written / total) * 100;
384
- console.log(`Flashing file ${fileIndex + 1}/${fileArray.length}: ${progress.toFixed(1)}%`);
385
- base.updateConnection(uuid, (c) => ({
386
- ...c,
387
- flashProgress: progress,
388
- }));
375
+ const now = performance.now();
376
+ // Throttle UI updates to at most once every 100ms (~10 FPS),
377
+ // but ALWAYS ensure the final 100% tick renders.
378
+ if (now - lastRenderTime > 100 || written === total) {
379
+ lastRenderTime = now;
380
+ // Optional: Only log every 100ms as well to prevent console spam
381
+ console.log(`Flashing file ${fileIndex + 1}/${fileArray.length}: ${progress.toFixed(1)}%`);
382
+ base.updateConnection(uuid, (c) => ({
383
+ ...c,
384
+ flashProgress: progress,
385
+ }));
386
+ }
389
387
  },
390
388
  };
391
389
  // --- Call writeFlash ONCE with all files ---
392
- try {
393
- base.updateConnection(uuid, (c) => ({ ...c, flashProgress: -1 }));
394
- await esploader.writeFlash(flashOptions);
395
- }
396
- catch (e) {
397
- console.log("failed to esploader.writeFlash", e);
398
- }
399
- // --- Disconnect ---
390
+ base.updateConnection(uuid, (c) => ({ ...c, flashProgress: -1 }));
391
+ await esploader.writeFlash(flashOptions);
392
+ // --- Disconnect cleanly ---
400
393
  await esploader.after();
401
394
  try {
402
395
  await transport.disconnect();
@@ -411,9 +404,8 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
411
404
  ...c,
412
405
  isFlashing: false,
413
406
  flashProgress: 100,
407
+ autoConnect: prevAutoConnect,
414
408
  }));
415
- // --- Reconnect the standard monitor ---
416
- await connect(uuid);
417
409
  }
418
410
  catch (e) {
419
411
  console.error(`[${uuid}] Flashing failed:`, e);
@@ -423,6 +415,8 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
423
415
  flashError: e?.message ?? "Unknown error",
424
416
  }));
425
417
  // Attempt recovery
418
+ }
419
+ finally {
426
420
  await connect(uuid);
427
421
  }
428
422
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ota-hub-reactjs",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "ReactJS tools for building web apps to flash MCU devices such as esp32, brought to you by OTA Hub.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",