ota-hub-reactjs 0.0.13 → 0.0.15

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.
@@ -27,7 +27,6 @@ export type DeviceConnectionState = {
27
27
  export declare function createDefaultInitialDeviceState<T extends DeviceConnectionState>(uuid: string, props?: any): T;
28
28
  export type DeviceWhispererProps<T extends DeviceConnectionState> = {
29
29
  createInitialConnectionState?: (uuid: string) => Partial<T>;
30
- connectOn?: boolean;
31
30
  };
32
31
  export declare function MultiDeviceWhisperer<T extends DeviceConnectionState>({ createInitialConnectionState, }?: DeviceWhispererProps<T>): {
33
32
  connections: T[];
@@ -256,7 +256,7 @@ export function ESP32MultiDeviceWhisperer({ ...props } = {}) {
256
256
  const port = await navigator.serial.requestPort({
257
257
  filters: [{ usbVendorId: 0x303a }]
258
258
  });
259
- return await base.addConnection({
259
+ const return_uuid = await base.addConnection({
260
260
  uuid,
261
261
  propCreator: (id) => {
262
262
  const props = propCreator?.(id);
@@ -271,6 +271,10 @@ export function ESP32MultiDeviceWhisperer({ ...props } = {}) {
271
271
  };
272
272
  }
273
273
  });
274
+ const conn = base.getConnection(return_uuid);
275
+ if (conn?.autoConnect)
276
+ await connect(return_uuid);
277
+ return return_uuid;
274
278
  };
275
279
  const removeConnection = async (uuid) => {
276
280
  try {
@@ -3,7 +3,7 @@ export type MQTTConnectionState = DeviceConnectionState & {
3
3
  pingFunction?: (props?: any) => void;
4
4
  touchHeartbeat?: () => void;
5
5
  };
6
- export declare function MQTTMultiDeviceWhisperer<AppOrMessageLayer extends MQTTConnectionState>({ serverUrl, uuidFromMessage, subTopicFromUuid, pubTopicFromUuid, serverPort, clientId, username, password, autoConnect, ...props }: {
6
+ export declare function MQTTMultiDeviceWhisperer<AppOrMessageLayer extends MQTTConnectionState>({ serverUrl, uuidFromMessage, subTopicFromUuid, pubTopicFromUuid, serverPort, clientId, username, password, serverAutoConnect, serverConnectOn, ...props }: {
7
7
  serverUrl: string;
8
8
  uuidFromMessage: (topic: string, payload: Buffer<ArrayBufferLike>) => string;
9
9
  subTopicFromUuid?: (uuid: string) => string;
@@ -12,7 +12,8 @@ export declare function MQTTMultiDeviceWhisperer<AppOrMessageLayer extends MQTTC
12
12
  clientId?: string;
13
13
  username?: string;
14
14
  password?: string;
15
- autoConnect?: boolean;
15
+ serverAutoConnect?: boolean;
16
+ serverConnectOn?: boolean;
16
17
  } & DeviceWhispererProps<AppOrMessageLayer>): {
17
18
  addConnection: ({ uuid, propCreator }: AddConnectionProps<AppOrMessageLayer>) => Promise<string | undefined>;
18
19
  removeConnection: (uuid: string) => Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useRef } from "react";
2
2
  import { MultiDeviceWhisperer } from "../base/device-whisperer.js";
3
3
  import mqtt from "mqtt";
4
- export function MQTTMultiDeviceWhisperer({ serverUrl, uuidFromMessage, subTopicFromUuid = undefined, pubTopicFromUuid = undefined, serverPort = 8883, clientId = undefined, username = undefined, password = undefined, autoConnect = true, ...props }) {
4
+ export function MQTTMultiDeviceWhisperer({ serverUrl, uuidFromMessage, subTopicFromUuid = undefined, pubTopicFromUuid = undefined, serverPort = 8883, clientId = undefined, username = undefined, password = undefined, serverAutoConnect = true, serverConnectOn = false, ...props }) {
5
5
  const base = MultiDeviceWhisperer(props);
6
6
  const clientRef = useRef(null);
7
7
  const isUnmountedRef = useRef(false);
@@ -211,7 +211,9 @@ export function MQTTMultiDeviceWhisperer({ serverUrl, uuidFromMessage, subTopicF
211
211
  // Delete this adding connections item
212
212
  addingConnections.current.delete(uuid);
213
213
  // Connect immediately
214
- connect(uuid);
214
+ const conn = base.getConnection(uuid);
215
+ if (conn?.autoConnect)
216
+ await connect(uuid);
215
217
  return uuid;
216
218
  };
217
219
  const removeConnection = async (uuid) => {
@@ -228,11 +230,11 @@ export function MQTTMultiDeviceWhisperer({ serverUrl, uuidFromMessage, subTopicF
228
230
  }
229
231
  };
230
232
  useEffect(() => {
231
- if (!(autoConnect || props.connectOn))
233
+ if (!(serverAutoConnect || serverConnectOn))
232
234
  return;
233
235
  const cleanup = connectToMQTTServer();
234
236
  return cleanup;
235
- }, [serverUrl, props.connectOn]);
237
+ }, [serverUrl, serverConnectOn]);
236
238
  return {
237
239
  ...base,
238
240
  addConnection,
@@ -1,7 +1,8 @@
1
1
  import { DeviceConnectionState, AddConnectionProps } from "../base/device-whisperer.js";
2
2
  export declare class UsbTransport {
3
3
  device: USBDevice;
4
- interfaceNumber: number;
4
+ controlInterface: number;
5
+ dataInterface: number;
5
6
  endpointIn: number;
6
7
  endpointOut: number;
7
8
  private static readonly SET_LINE_CODING;
@@ -14,7 +14,8 @@ import { useEffect } from "react";
14
14
  // usb-transport.ts
15
15
  export class UsbTransport {
16
16
  constructor(device) {
17
- this.interfaceNumber = 0;
17
+ this.controlInterface = 0;
18
+ this.dataInterface = 1;
18
19
  this.endpointIn = 0;
19
20
  this.endpointOut = 0;
20
21
  this.device = device;
@@ -27,25 +28,40 @@ export class UsbTransport {
27
28
  if (this.device.configuration === null) {
28
29
  await this.device.selectConfiguration(1);
29
30
  }
30
- // Find the CDC Data interface (usually class 10) or just the first one with Bulk endpoints
31
31
  const interfaces = this.device.configuration?.interfaces || [];
32
- let targetInterface;
33
- for (const iface of interfaces) {
32
+ let ctrlIface;
33
+ let dataIface;
34
+ dataIface = interfaces.find(iface => {
34
35
  const endpoints = iface.alternate.endpoints;
35
- const hasIn = endpoints.some(e => e.direction === 'in' && e.type === 'bulk');
36
- const hasOut = endpoints.some(e => e.direction === 'out' && e.type === 'bulk');
37
- if (hasIn && hasOut) {
38
- targetInterface = iface;
39
- break;
36
+ return endpoints.some(e => e.direction === 'in' && e.type === 'bulk') &&
37
+ endpoints.some(e => e.direction === 'out' && e.type === 'bulk');
38
+ });
39
+ if (dataIface) {
40
+ this.dataInterface = dataIface.interfaceNumber;
41
+ ctrlIface = interfaces.find(i => i.interfaceNumber === this.dataInterface - 1)
42
+ || interfaces.find(i => i.alternate.interfaceClass === 2);
43
+ this.controlInterface = ctrlIface ? ctrlIface.interfaceNumber : this.dataInterface;
44
+ }
45
+ if (!dataIface) {
46
+ throw new Error("No serial-compatible Bulk interface found.");
47
+ }
48
+ // Note: On Android, if the OS has claimed Interface 0, this first call will fail.
49
+ try {
50
+ if (this.controlInterface !== this.dataInterface) {
51
+ await this.device.claimInterface(this.controlInterface);
40
52
  }
41
53
  }
42
- if (!targetInterface) {
43
- throw new Error("No serial-compatible interface found on device.");
54
+ catch (e) {
55
+ console.warn("Could not claim Control Interface (OS locked?). Proceeding to Data...", e);
56
+ // We continue, but setSignals might fail later.
57
+ }
58
+ try {
59
+ await this.device.claimInterface(this.dataInterface);
60
+ }
61
+ catch (e) {
62
+ throw new Error(`Failed to claim Data Interface. Android OS has locked the device driver. Try using a 'Vendor Specific' USB Class device or a native Serial App workaround.`);
44
63
  }
45
- this.interfaceNumber = targetInterface.interfaceNumber;
46
- await this.device.claimInterface(this.interfaceNumber);
47
- // Map endpoints
48
- const endpoints = targetInterface.alternate.endpoints;
64
+ const endpoints = dataIface.alternate.endpoints;
49
65
  this.endpointIn = endpoints.find(e => e.direction === 'in' && e.type === 'bulk').endpointNumber;
50
66
  this.endpointOut = endpoints.find(e => e.direction === 'out' && e.type === 'bulk').endpointNumber;
51
67
  await this.setBaudRate(baudRate);
@@ -57,7 +73,6 @@ export class UsbTransport {
57
73
  async write(data) {
58
74
  if (!this.device.opened)
59
75
  return;
60
- // Cast to 'unknown' then 'BufferSource' to satisfy the strict type definition
61
76
  await this.device.transferOut(this.endpointOut, data);
62
77
  }
63
78
  /**
@@ -85,14 +100,13 @@ export class UsbTransport {
85
100
  async setSignals({ dtr, rts }) {
86
101
  if (!this.device.opened)
87
102
  return;
88
- // CDC-ACM: DTR is bit 0, RTS is bit 1
89
103
  const value = (Number(dtr) | (Number(rts) << 1));
90
104
  await this.device.controlTransferOut({
91
105
  requestType: 'class',
92
106
  recipient: 'interface',
93
107
  request: UsbTransport.SET_CONTROL_LINE_STATE,
94
108
  value: value,
95
- index: this.interfaceNumber
109
+ index: this.controlInterface
96
110
  });
97
111
  }
98
112
  async setBaudRate(baud) {
@@ -102,20 +116,31 @@ export class UsbTransport {
102
116
  // 4 bytes: baud (LE), 1 byte: stop bits, 1 byte: parity, 1 byte: data bits
103
117
  const buffer = new ArrayBuffer(7);
104
118
  const view = new DataView(buffer);
105
- view.setUint32(0, baud, true); // Little Endian
106
- view.setUint8(4, 0); // 1 stop bit
107
- view.setUint8(5, 0); // No parity
108
- view.setUint8(6, 8); // 8 data bits
119
+ view.setUint32(0, baud, true);
120
+ view.setUint8(4, 0);
121
+ view.setUint8(5, 0);
122
+ view.setUint8(6, 8);
109
123
  await this.device.controlTransferOut({
110
124
  requestType: 'class',
111
125
  recipient: 'interface',
112
126
  request: UsbTransport.SET_LINE_CODING,
113
127
  value: 0,
114
- index: this.interfaceNumber
128
+ index: this.controlInterface
115
129
  }, buffer);
116
130
  }
117
131
  async disconnect() {
118
132
  if (this.device.opened) {
133
+ // Release both
134
+ try {
135
+ await this.device.releaseInterface(this.dataInterface);
136
+ }
137
+ catch (e) { }
138
+ if (this.controlInterface !== this.dataInterface) {
139
+ try {
140
+ await this.device.releaseInterface(this.controlInterface);
141
+ }
142
+ catch (e) { }
143
+ }
119
144
  await this.device.close();
120
145
  }
121
146
  }
@@ -292,7 +317,7 @@ export function SerialMultiDeviceWhisperer({ ...props } = {}) {
292
317
  const device = await navigator.usb.requestDevice({
293
318
  filters: []
294
319
  });
295
- return await base.addConnection({
320
+ const return_uuid = await base.addConnection({
296
321
  uuid,
297
322
  propCreator: (id) => {
298
323
  const props = propCreator?.(id);
@@ -308,6 +333,10 @@ export function SerialMultiDeviceWhisperer({ ...props } = {}) {
308
333
  };
309
334
  }
310
335
  });
336
+ const conn = base.getConnection(return_uuid);
337
+ if (conn?.autoConnect)
338
+ await connect(return_uuid);
339
+ return return_uuid;
311
340
  }
312
341
  catch (e) {
313
342
  console.log("User cancelled or no device selected");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ota-hub-reactjs",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
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",