ota-hub-reactjs 0.0.19 → 0.1.2
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.
- package/dist/base/device-whisperer.d.ts +2 -1
- package/dist/base/device-whisperer.js +4 -1
- package/dist/message_layers/protobuf-wrapper.d.ts +4 -4
- package/dist/message_layers/protobuf-wrapper.js +1 -1
- package/dist/transport_layers/esp32-device-whisperer.d.ts +3 -1
- package/dist/transport_layers/esp32-device-whisperer.js +102 -33
- package/dist/transport_layers/mqtt-device-whisperer.d.ts +1 -1
- package/dist/transport_layers/mqtt-device-whisperer.js +4 -4
- package/dist/transport_layers/serial-device-whisperer.d.ts +1 -1
- package/dist/transport_layers/serial-device-whisperer.js +3 -3
- package/dist/transport_layers/websocket-device-whisperer.d.ts +1 -1
- package/dist/transport_layers/websocket-device-whisperer.js +3 -3
- package/package.json +1 -1
|
@@ -31,8 +31,9 @@ export type DeviceConnectionState = {
|
|
|
31
31
|
export declare function createDefaultInitialDeviceState<T extends DeviceConnectionState>(uuid: string, props?: any): T;
|
|
32
32
|
export type DeviceWhispererProps<T extends DeviceConnectionState> = {
|
|
33
33
|
createInitialConnectionState?: (uuid: string) => Partial<T>;
|
|
34
|
+
setFocussedConnectionOnAddConnection?: boolean;
|
|
34
35
|
};
|
|
35
|
-
export declare function
|
|
36
|
+
export declare function useMultiDeviceWhisperer<T extends DeviceConnectionState>({ createInitialConnectionState, setFocussedConnectionOnAddConnection, }?: DeviceWhispererProps<T>): {
|
|
36
37
|
focussedConnection: string | undefined;
|
|
37
38
|
setFocussedConnection: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
|
|
38
39
|
connections: T[];
|
|
@@ -16,7 +16,7 @@ export function createDefaultInitialDeviceState(uuid, props) {
|
|
|
16
16
|
/* One Device Whisperer is used for all like-devices, such as all Serial with Protobuf.
|
|
17
17
|
Any e.g. Serial without Protobuf, or LoRaWAN etc. devices would be handled via a separate device whisperer
|
|
18
18
|
*/
|
|
19
|
-
export function
|
|
19
|
+
export function useMultiDeviceWhisperer({ createInitialConnectionState = createDefaultInitialDeviceState, setFocussedConnectionOnAddConnection, } = {}) {
|
|
20
20
|
const [connections, setConnections] = useState([]);
|
|
21
21
|
const connectionsRef = useRef(new Map());
|
|
22
22
|
const [isReady, setIsReady] = useState(false);
|
|
@@ -54,6 +54,9 @@ export function MultiDeviceWhisperer({ createInitialConnectionState = createDefa
|
|
|
54
54
|
};
|
|
55
55
|
connectionsRef.current.set(uuid, newConnection);
|
|
56
56
|
setConnections(Array.from(connectionsRef.current.values()));
|
|
57
|
+
// Automatically set to the latest connection
|
|
58
|
+
if (!!setFocussedConnectionOnAddConnection)
|
|
59
|
+
setFocussedConnection(uuid);
|
|
57
60
|
return uuid;
|
|
58
61
|
};
|
|
59
62
|
// ---------- REMOVE ----------
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { AddConnectionProps, DeviceConnectionState,
|
|
1
|
+
import { AddConnectionProps, DeviceConnectionState, useMultiDeviceWhisperer } from "../base/device-whisperer.js";
|
|
2
2
|
export type TopicHandlerContext<AppLayer extends DeviceConnectionState> = {
|
|
3
|
-
base: ReturnType<typeof
|
|
3
|
+
base: ReturnType<typeof useMultiDeviceWhisperer<AppLayer>>;
|
|
4
4
|
uuid: string;
|
|
5
5
|
};
|
|
6
6
|
export type TopicHandlerMap<AppLayer extends DeviceConnectionState, Topic extends string | number, Message = any> = {
|
|
7
7
|
[K in Topic]?: (message: Message, context: TopicHandlerContext<AppLayer>) => void;
|
|
8
8
|
};
|
|
9
9
|
export type ProtobufDeviceWhispererProps<AppLayer extends DeviceConnectionState, Topic extends string | number, MessageRX = any, MessageTX = any> = {
|
|
10
|
-
transportLayer: ReturnType<typeof
|
|
10
|
+
transportLayer: ReturnType<typeof useMultiDeviceWhisperer<AppLayer>>;
|
|
11
11
|
encodeRX: (message: MessageTX) => Uint8Array;
|
|
12
12
|
decodeTX: (bytes: Uint8Array) => MessageRX;
|
|
13
13
|
messageTypeField: keyof MessageRX;
|
|
@@ -15,7 +15,7 @@ export type ProtobufDeviceWhispererProps<AppLayer extends DeviceConnectionState,
|
|
|
15
15
|
HEADER?: Uint8Array;
|
|
16
16
|
expectLength?: boolean;
|
|
17
17
|
};
|
|
18
|
-
export declare function
|
|
18
|
+
export declare function useProtobufMultiDeviceWhisperer<AppLayer extends DeviceConnectionState, Topic extends string | number, MessageRX = any, MessageTX = any>({ transportLayer, encodeRX, decodeTX, messageTypeField, rxTopicHandlerMap, HEADER, }: ProtobufDeviceWhispererProps<AppLayer, Topic, MessageRX, MessageTX>): {
|
|
19
19
|
addConnection: ({ uuid, propCreator, }: AddConnectionProps<AppLayer>) => Promise<string>;
|
|
20
20
|
sendProtobuf: (uuid: string, message: MessageTX) => void;
|
|
21
21
|
protoBufOnReceiveHandler: (uuid: string, data: string | Uint8Array) => void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function
|
|
1
|
+
export function useProtobufMultiDeviceWhisperer({ transportLayer, encodeRX, decodeTX, messageTypeField, rxTopicHandlerMap, HEADER = new Uint8Array([]), }) {
|
|
2
2
|
// --- Utils ---
|
|
3
3
|
const concatUint8Arrays = (a, b) => {
|
|
4
4
|
const result = new Uint8Array(a.length + b.length);
|
|
@@ -5,6 +5,8 @@ export type ESP32ConnectionState = DeviceConnectionState & {
|
|
|
5
5
|
baudrate?: number;
|
|
6
6
|
transport?: Transport;
|
|
7
7
|
esp?: ESPLoader;
|
|
8
|
+
debugLogging?: false;
|
|
9
|
+
reader?: AsyncGenerator<Uint8Array>;
|
|
8
10
|
slipReadWrite?: boolean;
|
|
9
11
|
isFlashing: boolean;
|
|
10
12
|
flashProgress: number;
|
|
@@ -15,7 +17,7 @@ export type FlashFirmwareProps = {
|
|
|
15
17
|
firmwareBlob?: Blob;
|
|
16
18
|
fileArray?: FlashOptions["fileArray"];
|
|
17
19
|
};
|
|
18
|
-
export declare function
|
|
20
|
+
export declare function useESP32MultiDeviceWhisperer<AppOrMessageLayer extends ESP32ConnectionState>({ releasePortByDefault, ...props }?: {
|
|
19
21
|
releasePortByDefault: boolean;
|
|
20
22
|
} & DeviceWhispererProps<AppOrMessageLayer>): {
|
|
21
23
|
addConnection: ({ uuid, propCreator, }: AddConnectionProps<AppOrMessageLayer>) => Promise<string>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useEffect } from "react";
|
|
2
2
|
import { ESPLoader, Transport } from "esptool-js";
|
|
3
|
-
import {
|
|
4
|
-
export function
|
|
5
|
-
const base =
|
|
3
|
+
import { useMultiDeviceWhisperer, } from "../base/device-whisperer.js";
|
|
4
|
+
export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = { releasePortByDefault: true }) {
|
|
5
|
+
const base = useMultiDeviceWhisperer(props);
|
|
6
6
|
const defaultOnReceive = (uuid, data) => {
|
|
7
7
|
const text = typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
8
8
|
const trimmed = text.trim();
|
|
@@ -35,8 +35,9 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
|
|
|
35
35
|
let slipBuffer = []; // accumulate SLIP frames
|
|
36
36
|
let inSlipFrame = false; // are we inside a SLIP frame?
|
|
37
37
|
let escapeNext = false;
|
|
38
|
+
const reader = transport.rawRead();
|
|
39
|
+
base.updateConnection(uuid, (c) => ({ ...c, reader }));
|
|
38
40
|
try {
|
|
39
|
-
const reader = transport.rawRead();
|
|
40
41
|
while (true) {
|
|
41
42
|
const conn = base.getConnection(uuid);
|
|
42
43
|
if (!conn) {
|
|
@@ -121,6 +122,8 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
|
|
|
121
122
|
});
|
|
122
123
|
}
|
|
123
124
|
finally {
|
|
125
|
+
// Clear reader state so it's fresh for next connect
|
|
126
|
+
base.updateConnection(uuid, (c) => ({ ...c, reader: undefined }));
|
|
124
127
|
base.appendLog(uuid, {
|
|
125
128
|
level: 0,
|
|
126
129
|
message: "[!] Serial disconnected",
|
|
@@ -152,6 +155,21 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
|
|
|
152
155
|
});
|
|
153
156
|
base.updateConnection(uuid, (c) => ({ ...c, port }));
|
|
154
157
|
}
|
|
158
|
+
else {
|
|
159
|
+
try {
|
|
160
|
+
const oldInfo = port.getInfo();
|
|
161
|
+
const authorizedPorts = await navigator.serial.getPorts();
|
|
162
|
+
const freshPort = authorizedPorts.find((p) => p.getInfo().usbVendorId === oldInfo.usbVendorId &&
|
|
163
|
+
p.getInfo().usbProductId === oldInfo.usbProductId);
|
|
164
|
+
if (freshPort) {
|
|
165
|
+
port = freshPort; // Swap out the dead pointer
|
|
166
|
+
base.updateConnection(uuid, (c) => ({ ...c, port }));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
console.warn(`[${uuid}] Failed to silently heal port reference:`, e);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
155
173
|
base.updateConnection(uuid, (c) => ({ ...c, isConnecting: true }));
|
|
156
174
|
const use_baudrate = baudrate ?? conn.baudrate ?? 115200;
|
|
157
175
|
const transport = new Transport(port, false, false);
|
|
@@ -160,6 +178,7 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
|
|
|
160
178
|
transport,
|
|
161
179
|
baudrate: use_baudrate,
|
|
162
180
|
enableTracing: false,
|
|
181
|
+
debugLogging: !!conn.debugLogging,
|
|
163
182
|
});
|
|
164
183
|
try {
|
|
165
184
|
await esploader.main();
|
|
@@ -182,6 +201,7 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
|
|
|
182
201
|
base.updateConnection(uuid, (c) => ({
|
|
183
202
|
...c,
|
|
184
203
|
transport,
|
|
204
|
+
esp: esploader,
|
|
185
205
|
baudrate: use_baudrate,
|
|
186
206
|
isConnected: true,
|
|
187
207
|
isConnecting: false,
|
|
@@ -191,7 +211,9 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
|
|
|
191
211
|
message: "[✓] Serial connected",
|
|
192
212
|
});
|
|
193
213
|
await conn.onConnect?.();
|
|
194
|
-
|
|
214
|
+
// FIRE AND FORGET
|
|
215
|
+
// We initiate the read loop but DO NOT await it, returning control immediately.
|
|
216
|
+
readLoop(uuid, transport).catch((e) => console.error(`[${uuid}] Background read loop failed:`, e));
|
|
195
217
|
}
|
|
196
218
|
catch (err) {
|
|
197
219
|
base.updateConnection(uuid, (c) => ({
|
|
@@ -208,9 +230,29 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
|
|
|
208
230
|
};
|
|
209
231
|
const disconnect = async (uuid, timeout = 2000) => {
|
|
210
232
|
const conn = base.getConnection(uuid);
|
|
233
|
+
// 1. Politely kill our high-level generator
|
|
234
|
+
if (conn?.reader && typeof conn.reader.return === "function") {
|
|
235
|
+
try {
|
|
236
|
+
// @ts-ignore
|
|
237
|
+
await conn.reader.return();
|
|
238
|
+
}
|
|
239
|
+
catch (e) {
|
|
240
|
+
console.warn(`[${uuid}] Error returning generator lock:`, e);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
211
243
|
if (conn?.transport) {
|
|
212
244
|
try {
|
|
213
|
-
//
|
|
245
|
+
// 2. AGGRESSIVE LOCK REMOVAL (The Fix)
|
|
246
|
+
// Dig into esptool-js's internal state and force the native reader to release.
|
|
247
|
+
// If we don't do this, transport.disconnect() will hang and fail to close the port.
|
|
248
|
+
const internalReader = conn.transport.reader;
|
|
249
|
+
if (internalReader) {
|
|
250
|
+
await internalReader.cancel().catch(() => { });
|
|
251
|
+
if (typeof internalReader.releaseLock === "function") {
|
|
252
|
+
internalReader.releaseLock();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// 3. Now that locks are cleared, attempt standard disconnect
|
|
214
256
|
await Promise.race([
|
|
215
257
|
conn.transport.disconnect(),
|
|
216
258
|
new Promise((resolve) => setTimeout(resolve, timeout)),
|
|
@@ -220,11 +262,28 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
|
|
|
220
262
|
console.warn(`[${uuid}] Serial Disconnect error:`, e);
|
|
221
263
|
}
|
|
222
264
|
}
|
|
223
|
-
//
|
|
265
|
+
// 4. NATIVE SAFETY NET
|
|
266
|
+
// Guarantee the port is closed natively just in case the transport failed
|
|
267
|
+
if (conn?.port) {
|
|
268
|
+
try {
|
|
269
|
+
if (conn.port.readable?.locked) {
|
|
270
|
+
await conn.port.readable.cancel().catch(() => { });
|
|
271
|
+
}
|
|
272
|
+
if (conn.port.writable?.locked) {
|
|
273
|
+
await conn.port.writable.abort().catch(() => { });
|
|
274
|
+
}
|
|
275
|
+
await conn.port.close().catch(() => { });
|
|
276
|
+
}
|
|
277
|
+
catch (e) {
|
|
278
|
+
// It will throw if already closed, which is perfectly fine.
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// 5. Always clear the transport and reset connection state
|
|
224
282
|
base.updateConnection(uuid, (c) => ({
|
|
225
283
|
...c,
|
|
226
|
-
port: releasePortByDefault ?
|
|
227
|
-
transport: releasePortByDefault ?
|
|
284
|
+
port: releasePortByDefault ? undefined : c.port,
|
|
285
|
+
transport: releasePortByDefault ? undefined : c.transport,
|
|
286
|
+
reader: undefined, // Guarantee reader is cleared out
|
|
228
287
|
isConnected: false,
|
|
229
288
|
isConnecting: false,
|
|
230
289
|
autoConnect: false,
|
|
@@ -298,31 +357,35 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
|
|
|
298
357
|
};
|
|
299
358
|
// This function now handles an entire device flashing session
|
|
300
359
|
const handleFlashFirmware = async (uuid, assetsToFlash) => {
|
|
360
|
+
// Capture the original connection and port reference before disconnect
|
|
301
361
|
const conn = base.getConnection(uuid);
|
|
362
|
+
// 1. Bail if we don't have the required state
|
|
302
363
|
if (!conn || !conn.port || assetsToFlash.length === 0)
|
|
303
364
|
return;
|
|
365
|
+
// Gracefully cancel reader and completely drop the transport natively
|
|
304
366
|
await disconnect(uuid);
|
|
367
|
+
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
305
368
|
base.updateConnection(uuid, (c) => ({
|
|
306
369
|
...c,
|
|
370
|
+
port: conn.port, // Restore the port into state in case disconnect wiped it
|
|
307
371
|
isFlashing: true,
|
|
308
372
|
flashProgress: 0,
|
|
309
373
|
flashError: undefined,
|
|
310
374
|
}));
|
|
375
|
+
// Declare outside the try block (Fixes variable shadowing)
|
|
376
|
+
let transport;
|
|
377
|
+
let esploader;
|
|
311
378
|
try {
|
|
312
379
|
// --- Connect ONCE ---
|
|
313
|
-
|
|
314
|
-
|
|
380
|
+
transport = new Transport(conn.port, true);
|
|
381
|
+
esploader = new ESPLoader({
|
|
315
382
|
transport,
|
|
316
383
|
baudrate: 921600,
|
|
317
384
|
enableTracing: false,
|
|
385
|
+
// debugLogging: !!conn.debugLogging,
|
|
318
386
|
});
|
|
319
|
-
try
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
catch (e) {
|
|
323
|
-
console.log("failed to esploader.main()", e);
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
387
|
+
// This may fail, that'll bubble the failure up to the upper try/catch
|
|
388
|
+
await esploader.main();
|
|
326
389
|
// --- Prepare an ARRAY of files for the library ---
|
|
327
390
|
const fileArray = await Promise.all(assetsToFlash.map(async ({ blob, address }) => {
|
|
328
391
|
const arrayBuffer = await blob.arrayBuffer();
|
|
@@ -331,32 +394,34 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
|
|
|
331
394
|
.join("");
|
|
332
395
|
return { data: binaryString, address };
|
|
333
396
|
}));
|
|
397
|
+
let lastRenderTime = 0;
|
|
334
398
|
const flashOptions = {
|
|
335
|
-
fileArray,
|
|
399
|
+
fileArray,
|
|
336
400
|
flashSize: "keep",
|
|
337
401
|
flashMode: "qio",
|
|
338
402
|
flashFreq: "80m",
|
|
339
403
|
eraseAll: fileArray.length > 1, // Writing more than 1 thing, so likely writing partitions.
|
|
340
404
|
compress: true,
|
|
341
405
|
reportProgress: (fileIndex, written, total) => {
|
|
342
|
-
// You can enhance progress reporting to show which file is being flashed
|
|
343
406
|
const progress = (written / total) * 100;
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
407
|
+
const now = performance.now();
|
|
408
|
+
// Throttle UI updates to at most once every 100ms (~10 FPS),
|
|
409
|
+
// but ALWAYS ensure the final 100% tick renders.
|
|
410
|
+
if (now - lastRenderTime > 100 || written === total) {
|
|
411
|
+
lastRenderTime = now;
|
|
412
|
+
// Optional: Only log every 100ms as well to prevent console spam
|
|
413
|
+
console.log(`Flashing file ${fileIndex + 1}/${fileArray.length}: ${progress.toFixed(1)}%`);
|
|
414
|
+
base.updateConnection(uuid, (c) => ({
|
|
415
|
+
...c,
|
|
416
|
+
flashProgress: progress,
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
349
419
|
},
|
|
350
420
|
};
|
|
351
421
|
// --- Call writeFlash ONCE with all files ---
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
catch (e) {
|
|
357
|
-
console.log("failed to esploader.writeFlash", e);
|
|
358
|
-
}
|
|
359
|
-
// --- Disconnect ---
|
|
422
|
+
base.updateConnection(uuid, (c) => ({ ...c, flashProgress: -1 }));
|
|
423
|
+
await esploader.writeFlash(flashOptions);
|
|
424
|
+
// --- Disconnect cleanly ---
|
|
360
425
|
await esploader.after();
|
|
361
426
|
try {
|
|
362
427
|
await transport.disconnect();
|
|
@@ -372,6 +437,8 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
|
|
|
372
437
|
isFlashing: false,
|
|
373
438
|
flashProgress: 100,
|
|
374
439
|
}));
|
|
440
|
+
// --- Reconnect the standard monitor ---
|
|
441
|
+
await connect(uuid);
|
|
375
442
|
}
|
|
376
443
|
catch (e) {
|
|
377
444
|
console.error(`[${uuid}] Flashing failed:`, e);
|
|
@@ -380,6 +447,8 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
|
|
|
380
447
|
isFlashing: false,
|
|
381
448
|
flashError: e?.message ?? "Unknown error",
|
|
382
449
|
}));
|
|
450
|
+
// Attempt recovery
|
|
451
|
+
await connect(uuid);
|
|
383
452
|
}
|
|
384
453
|
};
|
|
385
454
|
useEffect(() => {
|
|
@@ -13,7 +13,7 @@ export type MQTTConnectionState = DeviceConnectionState & {
|
|
|
13
13
|
pingFunction?: (props?: any) => void;
|
|
14
14
|
touchHeartbeat?: () => void;
|
|
15
15
|
};
|
|
16
|
-
export declare function
|
|
16
|
+
export declare function useMQTTMultiDeviceWhisperer<AppOrMessageLayer extends MQTTConnectionState>({ serverUrl, serverUrlFnc, uuidFromMessage, subTopicFromUuid, pubTopicFromUuid, serverPort, clientId, username, password, serverAutoConnect, serverConnectOn, enableWatchdog, ...props }: {
|
|
17
17
|
serverUrl?: string;
|
|
18
18
|
serverUrlFnc?: () => Promise<string> | string;
|
|
19
19
|
uuidFromMessage: (topic: string, payload: Buffer<ArrayBufferLike>) => string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useEffect, useRef } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { useMultiDeviceWhisperer, } from "../base/device-whisperer.js";
|
|
3
3
|
import mqtt from "mqtt";
|
|
4
|
-
export function
|
|
5
|
-
const base =
|
|
4
|
+
export function useMQTTMultiDeviceWhisperer({ serverUrl, serverUrlFnc, uuidFromMessage, subTopicFromUuid = undefined, pubTopicFromUuid = undefined, serverPort = 443, clientId = undefined, username = undefined, password = undefined, serverAutoConnect = true, serverConnectOn = false, enableWatchdog = true, ...props }) {
|
|
5
|
+
const base = useMultiDeviceWhisperer(props);
|
|
6
6
|
const clientRef = useRef(null);
|
|
7
7
|
const isUnmountedRef = useRef(false);
|
|
8
8
|
// ✨ NEW: Manage our own reconnect timer
|
|
@@ -88,7 +88,7 @@ export function MQTTMultiDeviceWhisperer({ serverUrl, serverUrlFnc, uuidFromMess
|
|
|
88
88
|
};
|
|
89
89
|
const connectToMQTTServer = async () => {
|
|
90
90
|
if (!serverUrl && !serverUrlFnc) {
|
|
91
|
-
console.error("MQTT
|
|
91
|
+
console.error("MQTT useMultiDeviceWhisperer requires either serverUrl or serverUrlFnc");
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
94
|
if (reconnectTimeoutRef.current)
|
|
@@ -38,7 +38,7 @@ export type SerialConnectionState = DeviceConnectionState & {
|
|
|
38
38
|
baudrate?: number;
|
|
39
39
|
slipReadWrite?: boolean;
|
|
40
40
|
};
|
|
41
|
-
export declare function
|
|
41
|
+
export declare function useSerialMultiDeviceWhisperer<AppOrMessageLayer extends SerialConnectionState>({ ...props }?: DeviceWhispererProps<AppOrMessageLayer>): {
|
|
42
42
|
addConnection: ({ uuid, propCreator, }: AddConnectionProps<AppOrMessageLayer>) => Promise<string | undefined>;
|
|
43
43
|
removeConnection: (uuid: string) => Promise<void>;
|
|
44
44
|
connect: (uuid: string, baudrate?: number) => Promise<void>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useMultiDeviceWhisperer, } from "../base/device-whisperer.js";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
/*
|
|
4
4
|
┌────────────────────────────────┐
|
|
@@ -151,8 +151,8 @@ export class UsbTransport {
|
|
|
151
151
|
// Standard CDC-ACM requests
|
|
152
152
|
UsbTransport.SET_LINE_CODING = 0x20;
|
|
153
153
|
UsbTransport.SET_CONTROL_LINE_STATE = 0x22;
|
|
154
|
-
export function
|
|
155
|
-
const base =
|
|
154
|
+
export function useSerialMultiDeviceWhisperer({ ...props } = {}) {
|
|
155
|
+
const base = useMultiDeviceWhisperer(props);
|
|
156
156
|
const releasePortByDefaultRef = useRef(false);
|
|
157
157
|
const [releasePortByDefaultState, setReleasePortByDefaultState] = useState(false);
|
|
158
158
|
useEffect(() => {
|
|
@@ -9,7 +9,7 @@ export type DeviceObjectResponse = {
|
|
|
9
9
|
deviceConnectedTime: Date | null;
|
|
10
10
|
deviceLastCommTime: Date | null;
|
|
11
11
|
};
|
|
12
|
-
export declare function
|
|
12
|
+
export declare function useWebsocketMultiDeviceWhisperer<AppOrMessageLayer extends WebsocketConnectionState>({ server_url, server_port, ...props }: {
|
|
13
13
|
server_url: string;
|
|
14
14
|
server_port: number;
|
|
15
15
|
}): {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect } from "react";
|
|
2
|
-
import {
|
|
3
|
-
export function
|
|
4
|
-
const base =
|
|
2
|
+
import { useMultiDeviceWhisperer, } from "../base/device-whisperer.js";
|
|
3
|
+
export function useWebsocketMultiDeviceWhisperer({ server_url, server_port, ...props }) {
|
|
4
|
+
const base = useMultiDeviceWhisperer(props);
|
|
5
5
|
const defaultOnReceive = (uuid, data) => {
|
|
6
6
|
const conn = base.getConnection(uuid);
|
|
7
7
|
if (!conn)
|
package/package.json
CHANGED