@robotical/raftjs 2.1.0 → 2.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.
- package/devdocs/devbin-backwards-compatibility.md +105 -0
- package/devdocs/pseudocode-to-js-transpiler.md +563 -0
- package/dist/react-native/PseudocodeTranspiler.d.ts +6 -0
- package/dist/react-native/PseudocodeTranspiler.js +115 -0
- package/dist/react-native/PseudocodeTranspiler.js.map +1 -0
- package/dist/react-native/RaftAttributeHandler.d.ts +1 -1
- package/dist/react-native/RaftAttributeHandler.js +108 -32
- package/dist/react-native/RaftAttributeHandler.js.map +1 -1
- package/dist/react-native/RaftChannelBLE.web.d.ts +4 -0
- package/dist/react-native/RaftChannelBLE.web.js +59 -21
- package/dist/react-native/RaftChannelBLE.web.js.map +1 -1
- package/dist/react-native/RaftChannelSimulated.d.ts +1 -0
- package/dist/react-native/RaftChannelSimulated.js +9 -5
- package/dist/react-native/RaftChannelSimulated.js.map +1 -1
- package/dist/react-native/RaftChannelWebSocket.js +16 -1
- package/dist/react-native/RaftChannelWebSocket.js.map +1 -1
- package/dist/react-native/RaftConnector.d.ts +29 -1
- package/dist/react-native/RaftConnector.js +177 -11
- package/dist/react-native/RaftConnector.js.map +1 -1
- package/dist/react-native/RaftCustomAttrHandler.d.ts +2 -2
- package/dist/react-native/RaftCustomAttrHandler.js +32 -44
- package/dist/react-native/RaftCustomAttrHandler.js.map +1 -1
- package/dist/react-native/RaftDeviceInfo.d.ts +18 -0
- package/dist/react-native/RaftDeviceInfo.js +8 -0
- package/dist/react-native/RaftDeviceInfo.js.map +1 -1
- package/dist/react-native/RaftDeviceManager.d.ts +30 -3
- package/dist/react-native/RaftDeviceManager.js +618 -107
- package/dist/react-native/RaftDeviceManager.js.map +1 -1
- package/dist/react-native/RaftDeviceMgrIF.d.ts +11 -2
- package/dist/react-native/RaftDeviceStates.d.ts +27 -3
- package/dist/react-native/RaftDeviceStates.js +31 -6
- package/dist/react-native/RaftDeviceStates.js.map +1 -1
- package/dist/react-native/RaftFileHandler.d.ts +1 -1
- package/dist/react-native/RaftFileHandler.js +101 -34
- package/dist/react-native/RaftFileHandler.js.map +1 -1
- package/dist/react-native/RaftMicroPythonConsoleClient.d.ts +38 -0
- package/dist/react-native/RaftMicroPythonConsoleClient.js +45 -0
- package/dist/react-native/RaftMicroPythonConsoleClient.js.map +1 -0
- package/dist/react-native/RaftMsgHandler.d.ts +1 -1
- package/dist/react-native/RaftMsgHandler.js +6 -3
- package/dist/react-native/RaftMsgHandler.js.map +1 -1
- package/dist/react-native/RaftPublish.d.ts +2 -0
- package/dist/react-native/RaftPublish.js +81 -0
- package/dist/react-native/RaftPublish.js.map +1 -0
- package/dist/react-native/RaftStreamHandler.d.ts +11 -0
- package/dist/react-native/RaftStreamHandler.js +66 -0
- package/dist/react-native/RaftStreamHandler.js.map +1 -1
- package/dist/react-native/RaftStruct.d.ts +2 -2
- package/dist/react-native/RaftStruct.js +97 -26
- package/dist/react-native/RaftStruct.js.map +1 -1
- package/dist/react-native/RaftSystemType.d.ts +1 -0
- package/dist/react-native/RaftSystemUtils.d.ts +17 -1
- package/dist/react-native/RaftSystemUtils.js +51 -0
- package/dist/react-native/RaftSystemUtils.js.map +1 -1
- package/dist/react-native/RaftTimezone.d.ts +16 -0
- package/dist/react-native/RaftTimezone.js +153 -0
- package/dist/react-native/RaftTimezone.js.map +1 -0
- package/dist/react-native/RaftTypes.d.ts +46 -1
- package/dist/react-native/RaftTypes.js.map +1 -1
- package/dist/react-native/RaftUpdateManager.js +1 -1
- package/dist/react-native/RaftUpdateManager.js.map +1 -1
- package/dist/react-native/main.d.ts +3 -0
- package/dist/react-native/main.js +8 -1
- package/dist/react-native/main.js.map +1 -1
- package/dist/web/PseudocodeTranspiler.d.ts +6 -0
- package/dist/web/PseudocodeTranspiler.js +115 -0
- package/dist/web/PseudocodeTranspiler.js.map +1 -0
- package/dist/web/RaftAttributeHandler.d.ts +1 -1
- package/dist/web/RaftAttributeHandler.js +108 -32
- package/dist/web/RaftAttributeHandler.js.map +1 -1
- package/dist/web/RaftChannelBLE.web.d.ts +4 -0
- package/dist/web/RaftChannelBLE.web.js +59 -21
- package/dist/web/RaftChannelBLE.web.js.map +1 -1
- package/dist/web/RaftChannelSimulated.d.ts +1 -0
- package/dist/web/RaftChannelSimulated.js +9 -5
- package/dist/web/RaftChannelSimulated.js.map +1 -1
- package/dist/web/RaftChannelWebSocket.js +16 -1
- package/dist/web/RaftChannelWebSocket.js.map +1 -1
- package/dist/web/RaftConnector.d.ts +29 -1
- package/dist/web/RaftConnector.js +177 -11
- package/dist/web/RaftConnector.js.map +1 -1
- package/dist/web/RaftCustomAttrHandler.d.ts +2 -2
- package/dist/web/RaftCustomAttrHandler.js +32 -44
- package/dist/web/RaftCustomAttrHandler.js.map +1 -1
- package/dist/web/RaftDeviceInfo.d.ts +18 -0
- package/dist/web/RaftDeviceInfo.js +8 -0
- package/dist/web/RaftDeviceInfo.js.map +1 -1
- package/dist/web/RaftDeviceManager.d.ts +30 -3
- package/dist/web/RaftDeviceManager.js +618 -107
- package/dist/web/RaftDeviceManager.js.map +1 -1
- package/dist/web/RaftDeviceMgrIF.d.ts +11 -2
- package/dist/web/RaftDeviceStates.d.ts +27 -3
- package/dist/web/RaftDeviceStates.js +31 -6
- package/dist/web/RaftDeviceStates.js.map +1 -1
- package/dist/web/RaftFileHandler.d.ts +1 -1
- package/dist/web/RaftFileHandler.js +101 -34
- package/dist/web/RaftFileHandler.js.map +1 -1
- package/dist/web/RaftMicroPythonConsoleClient.d.ts +38 -0
- package/dist/web/RaftMicroPythonConsoleClient.js +45 -0
- package/dist/web/RaftMicroPythonConsoleClient.js.map +1 -0
- package/dist/web/RaftMsgHandler.d.ts +1 -1
- package/dist/web/RaftMsgHandler.js +6 -3
- package/dist/web/RaftMsgHandler.js.map +1 -1
- package/dist/web/RaftPublish.d.ts +2 -0
- package/dist/web/RaftPublish.js +81 -0
- package/dist/web/RaftPublish.js.map +1 -0
- package/dist/web/RaftStreamHandler.d.ts +11 -0
- package/dist/web/RaftStreamHandler.js +66 -0
- package/dist/web/RaftStreamHandler.js.map +1 -1
- package/dist/web/RaftStruct.d.ts +2 -2
- package/dist/web/RaftStruct.js +97 -26
- package/dist/web/RaftStruct.js.map +1 -1
- package/dist/web/RaftSystemType.d.ts +1 -0
- package/dist/web/RaftSystemUtils.d.ts +17 -1
- package/dist/web/RaftSystemUtils.js +51 -0
- package/dist/web/RaftSystemUtils.js.map +1 -1
- package/dist/web/RaftTimezone.d.ts +16 -0
- package/dist/web/RaftTimezone.js +153 -0
- package/dist/web/RaftTimezone.js.map +1 -0
- package/dist/web/RaftTypes.d.ts +46 -1
- package/dist/web/RaftTypes.js.map +1 -1
- package/dist/web/RaftUpdateManager.js +1 -1
- package/dist/web/RaftUpdateManager.js.map +1 -1
- package/dist/web/main.d.ts +3 -0
- package/dist/web/main.js +8 -1
- package/dist/web/main.js.map +1 -1
- package/examples/dashboard/package.json +2 -2
- package/examples/dashboard/src/DeviceActionsForm.tsx +177 -17
- package/examples/dashboard/src/DeviceLineChart.tsx +16 -3
- package/examples/dashboard/src/DevicePanel.tsx +92 -11
- package/examples/dashboard/src/DeviceSelectDialog.tsx +224 -0
- package/examples/dashboard/src/DeviceStatsPanel.tsx +76 -0
- package/examples/dashboard/src/DevicesPanel.tsx +11 -0
- package/examples/dashboard/src/LogConfigPanel.tsx +357 -0
- package/examples/dashboard/src/LogFilesPanel.tsx +200 -0
- package/examples/dashboard/src/LoggingPanel.tsx +264 -0
- package/examples/dashboard/src/Main.tsx +12 -2
- package/examples/dashboard/src/SettingsScreen.tsx +9 -4
- package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +10 -3
- package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +37 -3
- package/examples/dashboard/src/SystemTypeGeneric/StateInfoGeneric.ts +10 -2
- package/examples/dashboard/src/SystemTypeGeneric/SystemTypeGeneric.ts +41 -7
- package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +34 -3
- package/examples/dashboard/src/styles.css +766 -1
- package/notes/web-ble-reconnect-retry.md +69 -0
- package/package.json +10 -7
- package/src/PseudocodeTranspiler.test.ts +372 -0
- package/src/PseudocodeTranspiler.ts +127 -0
- package/src/RaftAttributeHandler.ts +152 -76
- package/src/RaftChannelBLE.web.ts +62 -20
- package/src/RaftChannelSimulated.ts +10 -5
- package/src/RaftChannelWebSocket.ts +16 -2
- package/src/RaftConnector.ts +204 -17
- package/src/RaftCustomAttrHandler.ts +35 -45
- package/src/RaftDeviceInfo.ts +27 -0
- package/src/RaftDeviceManager.test.ts +164 -0
- package/src/RaftDeviceManager.ts +705 -127
- package/src/RaftDeviceMgrIF.ts +13 -2
- package/src/RaftDeviceStates.ts +49 -8
- package/src/RaftFileHandler.ts +112 -39
- package/src/RaftMicroPythonConsoleClient.ts +78 -0
- package/src/RaftMsgHandler.ts +8 -4
- package/src/RaftPublish.ts +92 -0
- package/src/RaftStreamHandler.ts +84 -1
- package/src/RaftStruct.test.ts +229 -0
- package/src/RaftStruct.ts +101 -37
- package/src/RaftSystemType.ts +1 -0
- package/src/RaftSystemUtils.ts +59 -0
- package/src/RaftTimezone.ts +151 -0
- package/src/RaftTypes.ts +57 -1
- package/src/RaftUpdateManager.ts +1 -1
- package/src/main.ts +3 -0
|
@@ -175,6 +175,16 @@ export default class RaftChannelWebSocket implements RaftChannel {
|
|
|
175
175
|
// return false;
|
|
176
176
|
// }
|
|
177
177
|
this._webSocket = null;
|
|
178
|
+
// Close any existing WebSocket before creating new one
|
|
179
|
+
RaftLog.verbose(`[RaftChannelWebSocket._wsConnect] START existing WebSocket?, ${!!this._webSocket}`);
|
|
180
|
+
if (this._webSocket) {
|
|
181
|
+
RaftLog.verbose(`[RaftChannelWebSocket._wsConnect] Closing existing WebSocket...`);
|
|
182
|
+
try {
|
|
183
|
+
this._webSocket.close(1000);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
RaftLog.warn(`[RaftChannelWebSocket._wsConnect] Error closing existing WebSocket: ${e}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
178
188
|
return new Promise((resolve: (value: boolean | PromiseLike<boolean>) => void,
|
|
179
189
|
reject: (reason?: unknown) => void) => {
|
|
180
190
|
this._webSocketOpen(wsURL).then((ws) => {
|
|
@@ -222,6 +232,7 @@ export default class RaftChannelWebSocket implements RaftChannel {
|
|
|
222
232
|
|
|
223
233
|
// Open the socket
|
|
224
234
|
try {
|
|
235
|
+
RaftLog.verbose(`[RaftChannelWebSocket._webSocketOpen] Creating WebSocket: ${url}`);
|
|
225
236
|
const webSocket = new WebSocket(url);
|
|
226
237
|
|
|
227
238
|
// Open socket
|
|
@@ -232,10 +243,13 @@ export default class RaftChannelWebSocket implements RaftChannel {
|
|
|
232
243
|
this._isConnected = true;
|
|
233
244
|
resolve(webSocket);
|
|
234
245
|
};
|
|
235
|
-
webSocket.onerror =
|
|
246
|
+
webSocket.onerror = (evt: WebSocket.ErrorEvent) => {
|
|
236
247
|
RaftLog.warn(`RaftChannelWebSocket._webSocketOpen - onerror: ${evt.message}`);
|
|
237
248
|
reject(evt);
|
|
238
|
-
}
|
|
249
|
+
};
|
|
250
|
+
webSocket.onclose = (evt: WebSocket.CloseEvent) => {
|
|
251
|
+
RaftLog.info(`[RaftChannelWebSocket._webSocketOpen] onclose fired! code: ${evt.code} reason: ${evt.reason} wasClean: ${evt.wasClean}`);
|
|
252
|
+
};
|
|
239
253
|
} catch (error: unknown) {
|
|
240
254
|
RaftLog.warn(`RaftChannelWebSocket._webSocketOpen - open failed ${error}`);
|
|
241
255
|
reject(error);
|
package/src/RaftConnector.ts
CHANGED
|
@@ -14,7 +14,7 @@ import RaftChannelWebSocket from "./RaftChannelWebSocket";
|
|
|
14
14
|
import RaftChannelWebSerial from "./RaftChannelWebSerial";
|
|
15
15
|
import RaftChannelSimulated from "./RaftChannelSimulated";
|
|
16
16
|
import RaftCommsStats from "./RaftCommsStats";
|
|
17
|
-
import { RaftEventFn, RaftOKFail, RaftFileSendType, RaftFileDownloadResult, RaftProgressCBType, RaftBridgeSetupResp, RaftFileDownloadFn, RaftReportMsg } from "./RaftTypes";
|
|
17
|
+
import { RaftEventFn, RaftOKFail, RaftFileSendType, RaftFileDownloadResult, RaftProgressCBType, RaftStreamDataProgressCBType, RaftBridgeSetupResp, RaftFileDownloadFn, RaftReportMsg, RaftRtStreamDataCBType, RaftRtStreamHandle, RaftRtStreamOptions, RaftRtStreamStartResp } from "./RaftTypes";
|
|
18
18
|
import RaftSystemUtils from "./RaftSystemUtils";
|
|
19
19
|
import RaftFileHandler from "./RaftFileHandler";
|
|
20
20
|
import RaftStreamHandler from "./RaftStreamHandler";
|
|
@@ -23,8 +23,7 @@ import { RaftConnEvent, RaftConnEventNames } from "./RaftConnEvents";
|
|
|
23
23
|
import { RaftGetSystemTypeCBType, RaftSystemType } from "./RaftSystemType";
|
|
24
24
|
import { RaftUpdateEvent, RaftUpdateEventNames } from "./RaftUpdateEvents";
|
|
25
25
|
import RaftUpdateManager from "./RaftUpdateManager";
|
|
26
|
-
import { createBLEChannel } from "./RaftChannelBLEFactory";
|
|
27
|
-
|
|
26
|
+
import { createBLEChannel } from "./RaftChannelBLEFactory";import { getHostPosixTZ } from './RaftTimezone';
|
|
28
27
|
|
|
29
28
|
export default class RaftConnector {
|
|
30
29
|
|
|
@@ -88,6 +87,10 @@ export default class RaftConnector {
|
|
|
88
87
|
// Update manager
|
|
89
88
|
private _raftUpdateManager: RaftUpdateManager | null = null;
|
|
90
89
|
|
|
90
|
+
// Open-ended RT stream callbacks keyed by streamID
|
|
91
|
+
private _rtStreamCallbacks = new Map<number, RaftRtStreamDataCBType>();
|
|
92
|
+
private _fallbackRtStreamCallback: { streamID: number, callback: RaftRtStreamDataCBType } | null = null;
|
|
93
|
+
|
|
91
94
|
/**
|
|
92
95
|
* RaftConnector constructor
|
|
93
96
|
* @param getSystemTypeCB - callback to get system type
|
|
@@ -194,6 +197,22 @@ export default class RaftConnector {
|
|
|
194
197
|
return this._commsStats;
|
|
195
198
|
}
|
|
196
199
|
|
|
200
|
+
/**
|
|
201
|
+
* getOperationQueueDepth
|
|
202
|
+
* @returns number of high-level device operations queued or running
|
|
203
|
+
*/
|
|
204
|
+
getOperationQueueDepth(): number {
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* isOperationBusy
|
|
210
|
+
* @returns true when a high-level device operation is queued or running
|
|
211
|
+
*/
|
|
212
|
+
isOperationBusy(): boolean {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
197
216
|
/**
|
|
198
217
|
* Get Raft message handler (to allow message sending and receiving)
|
|
199
218
|
* @returns RaftMsgHandler - Raft message handler
|
|
@@ -214,7 +233,22 @@ export default class RaftConnector {
|
|
|
214
233
|
* Initialize the Raft channel
|
|
215
234
|
*/
|
|
216
235
|
async initializeChannel(method: string): Promise<boolean> {
|
|
236
|
+
// Disconnect any existing channel first to avoid stale connections
|
|
237
|
+
RaftLog.verbose(`[RaftConnector.initializeChannel] START method=${method} hasExistingChannel=${!!this._raftChannel}`);
|
|
238
|
+
if (this._raftChannel) {
|
|
239
|
+
RaftLog.verbose(`[RaftConnector.initializeChannel] Disconnecting existing channel...`);
|
|
240
|
+
try {
|
|
241
|
+
await this._raftChannel.disconnect();
|
|
242
|
+
RaftLog.verbose(`[RaftConnector.initializeChannel] Existing channel disconnected successfully`);
|
|
243
|
+
} catch (e) {
|
|
244
|
+
RaftLog.warn(`[RaftConnector.initializeChannel] Error disconnecting existing channel: ${e}`);
|
|
245
|
+
}
|
|
246
|
+
this._raftChannel = null;
|
|
247
|
+
RaftLog.verbose(`[RaftConnector.initializeChannel] Existing channel nulled`);
|
|
248
|
+
}
|
|
249
|
+
|
|
217
250
|
// Initialize raft channel
|
|
251
|
+
RaftLog.verbose(`[RaftConnector.initializeChannel] Creating new channel...`);
|
|
218
252
|
if (method === 'WebBLE' || method === 'PhoneBLE') {
|
|
219
253
|
const RaftChannelBLE = createBLEChannel();
|
|
220
254
|
this._raftChannel = new RaftChannelBLE();
|
|
@@ -226,7 +260,7 @@ export default class RaftConnector {
|
|
|
226
260
|
this._raftChannel = new RaftChannelWebSerial();
|
|
227
261
|
this._channelConnMethod = 'WebSerial';
|
|
228
262
|
} else if (method === 'Simulated') {
|
|
229
|
-
this._raftChannel = new RaftChannelSimulated();
|
|
263
|
+
this._raftChannel = new RaftChannelSimulated();
|
|
230
264
|
this._channelConnMethod = 'Simulated';
|
|
231
265
|
} else {
|
|
232
266
|
RaftLog.warn('Unknown method: ' + method);
|
|
@@ -268,7 +302,7 @@ export default class RaftConnector {
|
|
|
268
302
|
// Store locator
|
|
269
303
|
this._channelConnLocator = locator;
|
|
270
304
|
|
|
271
|
-
// Connect
|
|
305
|
+
// Connect channel first (system type resolution needs a live connection)
|
|
272
306
|
let connOk = false;
|
|
273
307
|
try {
|
|
274
308
|
// Event
|
|
@@ -281,9 +315,9 @@ export default class RaftConnector {
|
|
|
281
315
|
}
|
|
282
316
|
|
|
283
317
|
if (connOk) {
|
|
284
|
-
|
|
318
|
+
|
|
319
|
+
// Resolve system type now that the channel is connected
|
|
285
320
|
if (this._getSystemTypeCB) {
|
|
286
|
-
// Get system type
|
|
287
321
|
this._systemType = await this._getSystemTypeCB(this._raftSystemUtils);
|
|
288
322
|
|
|
289
323
|
// Set defaults
|
|
@@ -310,14 +344,36 @@ export default class RaftConnector {
|
|
|
310
344
|
}
|
|
311
345
|
}
|
|
312
346
|
|
|
347
|
+
// configure file handler
|
|
348
|
+
this.configureFileHandler(this._raftChannel.fhFileBlockSize(), this._raftChannel.fhBatchAckSize());
|
|
349
|
+
|
|
350
|
+
// Sync time to device if enabled (default: true)
|
|
351
|
+
const syncTime = this._systemType?.connectorOptions?.syncTimeOnConnect ?? true;
|
|
352
|
+
if (syncTime) {
|
|
353
|
+
try {
|
|
354
|
+
const now = new Date();
|
|
355
|
+
const utc = now.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
356
|
+
const params: Record<string, string> = { UTC: utc };
|
|
357
|
+
const posixTZ = getHostPosixTZ();
|
|
358
|
+
if (posixTZ) {
|
|
359
|
+
params.tz = posixTZ;
|
|
360
|
+
}
|
|
361
|
+
await this.sendRICRESTMsg('datetime', params);
|
|
362
|
+
RaftLog.info(`connect synced time to device: ${utc}${posixTZ ? ` tz=${posixTZ}` : ''}`);
|
|
363
|
+
} catch (error) {
|
|
364
|
+
RaftLog.warn(`connect time sync failed: ${error}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
313
368
|
// Send connected event
|
|
314
369
|
this.onConnEvent(RaftConnEvent.CONN_CONNECTED);
|
|
315
370
|
|
|
316
371
|
} else {
|
|
317
372
|
// Failed Event
|
|
318
373
|
this.onConnEvent(RaftConnEvent.CONN_CONNECTION_FAILED);
|
|
319
|
-
|
|
320
|
-
|
|
374
|
+
|
|
375
|
+
// Clear system type
|
|
376
|
+
this._systemType = null;
|
|
321
377
|
}
|
|
322
378
|
|
|
323
379
|
return connOk;
|
|
@@ -327,8 +383,12 @@ export default class RaftConnector {
|
|
|
327
383
|
// Disconnect
|
|
328
384
|
this._retryIfLostIsConnected = false;
|
|
329
385
|
if (this._raftChannel) {
|
|
386
|
+
// Store reference to channel before async operations to avoid race condition
|
|
387
|
+
const channelToDisconnect = this._raftChannel;
|
|
388
|
+
this._raftChannel = null;
|
|
389
|
+
|
|
330
390
|
// Check if there is a RICREST command to send before disconnecting
|
|
331
|
-
const ricRestCommand =
|
|
391
|
+
const ricRestCommand = channelToDisconnect.ricRestCmdBeforeDisconnect();
|
|
332
392
|
if (ricRestCommand) {
|
|
333
393
|
console.log(`sending RICREST command before disconnect: ${ricRestCommand}`);
|
|
334
394
|
await this.sendRICRESTMsg(ricRestCommand, {});
|
|
@@ -336,8 +396,24 @@ export default class RaftConnector {
|
|
|
336
396
|
// Pause a little before disconnecting
|
|
337
397
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
338
398
|
// await this.sendRICRESTMsg("bledisc", {});
|
|
339
|
-
await
|
|
340
|
-
|
|
399
|
+
await channelToDisconnect.disconnect();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
disconnectForPageUnload(): void {
|
|
404
|
+
this._retryIfLostIsConnected = false;
|
|
405
|
+
|
|
406
|
+
if (!this._raftChannel) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const channelToDisconnect = this._raftChannel;
|
|
411
|
+
this._raftChannel = null;
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
void channelToDisconnect.disconnect();
|
|
415
|
+
} catch (error) {
|
|
416
|
+
RaftLog.warn(`RaftConnector.disconnectForPageUnload failed ${error}`);
|
|
341
417
|
}
|
|
342
418
|
}
|
|
343
419
|
|
|
@@ -352,6 +428,11 @@ export default class RaftConnector {
|
|
|
352
428
|
*
|
|
353
429
|
*/
|
|
354
430
|
async sendRICRESTMsg(commandName: string, params: object,
|
|
431
|
+
bridgeID: number | undefined = undefined): Promise<RaftOKFail> {
|
|
432
|
+
return this._sendRICRESTMsg(commandName, params, bridgeID);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private async _sendRICRESTMsg(commandName: string, params: object,
|
|
355
436
|
bridgeID: number | undefined = undefined): Promise<RaftOKFail> {
|
|
356
437
|
try {
|
|
357
438
|
// Format the paramList as query string
|
|
@@ -359,11 +440,12 @@ export default class RaftConnector {
|
|
|
359
440
|
let paramQueryStr = '';
|
|
360
441
|
for (const param of paramEntries) {
|
|
361
442
|
if (paramQueryStr.length > 0) paramQueryStr += '&';
|
|
362
|
-
paramQueryStr += param[0]
|
|
443
|
+
paramQueryStr += `${encodeURIComponent(param[0])}=${encodeURIComponent(String(param[1]))}`;
|
|
363
444
|
}
|
|
364
445
|
// Format the url to send
|
|
365
446
|
if (paramQueryStr.length > 0) commandName += '?' + paramQueryStr;
|
|
366
|
-
|
|
447
|
+
const response = await this._raftMsgHandler.sendRICRESTURL<RaftOKFail | null>(commandName, bridgeID);
|
|
448
|
+
return response ?? { rslt: 'fail' };
|
|
367
449
|
} catch (error) {
|
|
368
450
|
RaftLog.warn(`sendRICRESTMsg failed ${error}`);
|
|
369
451
|
return { rslt: 'fail' };
|
|
@@ -417,6 +499,13 @@ export default class RaftConnector {
|
|
|
417
499
|
fileBlockData: Uint8Array
|
|
418
500
|
): void {
|
|
419
501
|
// RaftLog.info(`onRxFileBlock filePos ${filePos} fileBlockData ${RaftUtils.bufferToHex(fileBlockData)}`);
|
|
502
|
+
const streamID = (filePos >>> 24) & 0xff;
|
|
503
|
+
const streamFilePos = filePos & 0x00ffffff;
|
|
504
|
+
const streamCallback = this._rtStreamCallbacks.get(streamID);
|
|
505
|
+
if (streamID !== 0 && streamCallback) {
|
|
506
|
+
streamCallback(fileBlockData, streamFilePos, streamID);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
420
509
|
this._raftFileHandler.onFileBlock(filePos, fileBlockData);
|
|
421
510
|
}
|
|
422
511
|
|
|
@@ -456,6 +545,104 @@ export default class RaftConnector {
|
|
|
456
545
|
|
|
457
546
|
// Mark: Streaming --------------------------------------------------------------------------------
|
|
458
547
|
|
|
548
|
+
/**
|
|
549
|
+
* streamData - stream arbitrary data to a named firmware endpoint using the RT_STREAM protocol.
|
|
550
|
+
* @param streamContents data to stream
|
|
551
|
+
* @param fileName logical filename sent in ufStart (e.g. "pattern.thr")
|
|
552
|
+
* @param targetEndpoint REST API endpoint name on the firmware (e.g. "streampattern")
|
|
553
|
+
* @param progressCallback optional (sent, total, progress) callback
|
|
554
|
+
* @returns Promise<boolean> true on success
|
|
555
|
+
*/
|
|
556
|
+
async streamData(
|
|
557
|
+
streamContents: Uint8Array,
|
|
558
|
+
fileName: string,
|
|
559
|
+
targetEndpoint: string,
|
|
560
|
+
progressCallback?: RaftStreamDataProgressCBType,
|
|
561
|
+
): Promise<boolean> {
|
|
562
|
+
if (this._raftStreamHandler && this.isConnected()) {
|
|
563
|
+
return this._raftStreamHandler.streamData(
|
|
564
|
+
streamContents, fileName, targetEndpoint, progressCallback,
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* openRtStream - open an indefinite bidirectional RT stream.
|
|
572
|
+
* The returned handle can send byte blocks and closes with ufEnd.
|
|
573
|
+
*/
|
|
574
|
+
async openRtStream(options: RaftRtStreamOptions): Promise<RaftRtStreamHandle> {
|
|
575
|
+
const cmdMsg = JSON.stringify({
|
|
576
|
+
cmdName: "ufStart",
|
|
577
|
+
reqStr: "ufStart",
|
|
578
|
+
fileType: "rtstream",
|
|
579
|
+
fileName: options.fileName,
|
|
580
|
+
endpoint: options.endpoint,
|
|
581
|
+
fileLen: 0,
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
const startResp = await this._raftMsgHandler.sendRICRESTCmdFrame<RaftRtStreamStartResp>(cmdMsg);
|
|
585
|
+
if (!startResp || startResp.rslt !== "ok" || startResp.streamID === undefined) {
|
|
586
|
+
throw new Error(`openRtStream failed ${startResp?.rslt ?? "no response"}`);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const streamID = startResp.streamID;
|
|
590
|
+
const maxBlockSize = startResp.maxBlockSize || this._raftStreamHandler.maxBlockSize;
|
|
591
|
+
let txFilePos = 0;
|
|
592
|
+
let sendQueue = Promise.resolve();
|
|
593
|
+
this._rtStreamCallbacks.set(streamID, options.onData);
|
|
594
|
+
this._fallbackRtStreamCallback = { streamID, callback: options.onData };
|
|
595
|
+
|
|
596
|
+
const sendBytes = async (bytes: Uint8Array): Promise<boolean> => {
|
|
597
|
+
let sentOk = false;
|
|
598
|
+
sendQueue = sendQueue
|
|
599
|
+
.catch(() => {
|
|
600
|
+
// Keep later terminal input flowing even if an earlier block failed.
|
|
601
|
+
})
|
|
602
|
+
.then(async () => {
|
|
603
|
+
sentOk = await this._raftMsgHandler.sendStreamBlock(bytes, txFilePos, streamID);
|
|
604
|
+
if (sentOk) {
|
|
605
|
+
txFilePos = (txFilePos + bytes.length) & 0x00ffffff;
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
await sendQueue;
|
|
609
|
+
return sentOk;
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
const close = async (): Promise<boolean> => {
|
|
613
|
+
this._rtStreamCallbacks.delete(streamID);
|
|
614
|
+
if (this._fallbackRtStreamCallback?.streamID === streamID) {
|
|
615
|
+
this._fallbackRtStreamCallback = null;
|
|
616
|
+
}
|
|
617
|
+
const endMsg = JSON.stringify({
|
|
618
|
+
cmdName: "ufEnd",
|
|
619
|
+
reqStr: "ufEnd",
|
|
620
|
+
streamID,
|
|
621
|
+
});
|
|
622
|
+
try {
|
|
623
|
+
const endResp = await this._raftMsgHandler.sendRICRESTCmdFrame<RaftOKFail>(endMsg);
|
|
624
|
+
return endResp?.rslt === "ok";
|
|
625
|
+
} catch (error) {
|
|
626
|
+
RaftLog.warn(`closeRtStream failed ${streamID}: ${error}`);
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
// Some endpoints use an initial empty block as an attach signal.
|
|
632
|
+
// Keep this opt-in because older endpoints reject zero-length ufBlock frames.
|
|
633
|
+
if (options.sendInitialEmptyBlock) {
|
|
634
|
+
await sendBytes(new Uint8Array());
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return {
|
|
638
|
+
streamID,
|
|
639
|
+
maxBlockSize,
|
|
640
|
+
sendBytes,
|
|
641
|
+
sendText: (text: string) => sendBytes(new TextEncoder().encode(text)),
|
|
642
|
+
close,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
|
|
459
646
|
/**
|
|
460
647
|
* streamAudio - stream audio
|
|
461
648
|
* @param streamContents audio data
|
|
@@ -534,7 +721,7 @@ export default class RaftConnector {
|
|
|
534
721
|
*/
|
|
535
722
|
async checkConnPerformance(): Promise<number | undefined> {
|
|
536
723
|
|
|
537
|
-
// Sends a magic sequence of bytes followed by blocks of random data
|
|
724
|
+
// Sends a magic sequence of bytes followed by blocks of random data
|
|
538
725
|
// these will be ignored by the Raft library (as it recognises magic sequence)
|
|
539
726
|
// and is used performance evaluation
|
|
540
727
|
let prbsState = 1;
|
|
@@ -681,8 +868,8 @@ export default class RaftConnector {
|
|
|
681
868
|
|
|
682
869
|
/**
|
|
683
870
|
* onUpdateEvent - handle update event
|
|
684
|
-
* @param eventEnum
|
|
685
|
-
* @param data
|
|
871
|
+
* @param eventEnum
|
|
872
|
+
* @param data
|
|
686
873
|
*/
|
|
687
874
|
_onUpdateEvent(eventEnum: RaftUpdateEvent, data: object | string | null | undefined = undefined): void {
|
|
688
875
|
// Notify
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
9
9
|
|
|
10
10
|
import { CustomFunctionDefinition, DeviceTypePollRespMetadata } from "./RaftDeviceInfo";
|
|
11
|
+
import { transpilePseudocodeToJs } from "./PseudocodeTranspiler";
|
|
11
12
|
|
|
12
13
|
type CustomAttrJsFn = (
|
|
13
14
|
buf: Uint8Array,
|
|
@@ -22,8 +23,8 @@ type CustomAttrJsFn = (
|
|
|
22
23
|
export default class CustomAttrHandler {
|
|
23
24
|
|
|
24
25
|
private _jsFunctionCache = new Map<string, CustomAttrJsFn>();
|
|
25
|
-
|
|
26
|
-
public handleAttr(pollRespMetadata: DeviceTypePollRespMetadata, msgBuffer: Uint8Array, msgBufIdx: number): number[][] {
|
|
26
|
+
|
|
27
|
+
public handleAttr(pollRespMetadata: DeviceTypePollRespMetadata, msgBuffer: Uint8Array, msgBufIdx: number, msgEndIdx = msgBuffer.length): number[][] {
|
|
27
28
|
|
|
28
29
|
// Number of bytes in each message
|
|
29
30
|
const numMsgBytes = pollRespMetadata.b;
|
|
@@ -45,59 +46,48 @@ export default class CustomAttrHandler {
|
|
|
45
46
|
return attrValueVecs;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
// Provide
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
// Provide this poll block to the custom handler. Use the smaller of
|
|
50
|
+
// pollRespMetadata.b and the bytes actually available — variable-length
|
|
51
|
+
// samples may be shorter than b, and the last sample in a frame may not
|
|
52
|
+
// have b bytes remaining in the buffer.
|
|
53
|
+
const boundedMsgEndIdx = Math.min(Math.max(msgEndIdx, msgBufIdx), msgBuffer.length);
|
|
54
|
+
const availableBytes = Math.min(numMsgBytes, boundedMsgEndIdx - msgBufIdx);
|
|
55
|
+
if (availableBytes <= 0) {
|
|
51
56
|
return [];
|
|
52
57
|
}
|
|
58
|
+
const buf = msgBuffer.slice(msgBufIdx, msgBufIdx + availableBytes);
|
|
53
59
|
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
const jsFn = this.getOrCompileJsFunction(customFnDef);
|
|
57
|
-
if (!jsFn) {
|
|
58
|
-
return attrValueVecs;
|
|
59
|
-
}
|
|
60
|
-
try {
|
|
61
|
-
jsFn(buf, attrValues, attrValueVecs, pollRespMetadata, msgBuffer, msgBufIdx, numMsgBytes);
|
|
62
|
-
} catch (err) {
|
|
63
|
-
console.error(`CustomAttrHandler JS function ${customFnDef.n} execution failed`, err);
|
|
64
|
-
}
|
|
60
|
+
const fn = this.getOrCompileFunction(customFnDef);
|
|
61
|
+
if (!fn) {
|
|
65
62
|
return attrValueVecs;
|
|
66
63
|
}
|
|
67
64
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
let k = 3;
|
|
73
|
-
let i = 0;
|
|
74
|
-
while (i < N) {
|
|
75
|
-
attrValues["Red"].push(0);
|
|
76
|
-
attrValues["Red"][attrValues["Red"].length - 1] = (buf[k] << 16) | (buf[k + 1] << 8) | buf[k + 2];
|
|
77
|
-
attrValues["IR"].push(0);
|
|
78
|
-
attrValues["IR"][attrValues["IR"].length - 1] = (buf[k + 3] << 16) | (buf[k + 4] << 8) | buf[k + 5];
|
|
79
|
-
k += 6;
|
|
80
|
-
i++;
|
|
81
|
-
}
|
|
82
|
-
} else if (customFnDef.n === "gravity_o2_calc") {
|
|
83
|
-
const key = 20.9 / 120.0;
|
|
84
|
-
const val = key * (buf[0] + buf[1] / 10.0 + buf[2] / 100.0);
|
|
85
|
-
attrValues["oxygen"].push(val);
|
|
65
|
+
try {
|
|
66
|
+
fn(buf, attrValues, attrValueVecs, pollRespMetadata, msgBuffer, msgBufIdx, numMsgBytes);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error(`CustomAttrHandler function ${customFnDef.n} execution failed`, err);
|
|
86
69
|
}
|
|
87
70
|
return attrValueVecs;
|
|
88
71
|
}
|
|
89
72
|
|
|
90
|
-
private
|
|
91
|
-
if
|
|
73
|
+
private getOrCompileFunction(customFnDef: CustomFunctionDefinition): CustomAttrJsFn | null {
|
|
74
|
+
// Prefer explicit JS if provided, otherwise transpile from pseudocode
|
|
75
|
+
let jsSource = customFnDef.j?.trim();
|
|
76
|
+
if (!jsSource && customFnDef.c) {
|
|
77
|
+
jsSource = transpilePseudocodeToJs(customFnDef.c);
|
|
78
|
+
}
|
|
79
|
+
if (!jsSource) {
|
|
92
80
|
return null;
|
|
93
81
|
}
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
82
|
+
|
|
83
|
+
const cacheKey = `${customFnDef.n}::${jsSource}`;
|
|
84
|
+
const cached = this._jsFunctionCache.get(cacheKey);
|
|
85
|
+
if (cached) {
|
|
86
|
+
return cached;
|
|
98
87
|
}
|
|
88
|
+
|
|
99
89
|
try {
|
|
100
|
-
const
|
|
90
|
+
const fn = new Function(
|
|
101
91
|
"buf",
|
|
102
92
|
"attrValues",
|
|
103
93
|
"attrValueVecs",
|
|
@@ -105,12 +95,12 @@ export default class CustomAttrHandler {
|
|
|
105
95
|
"msgBuffer",
|
|
106
96
|
"msgBufIdx",
|
|
107
97
|
"numMsgBytes",
|
|
108
|
-
|
|
98
|
+
jsSource
|
|
109
99
|
) as CustomAttrJsFn;
|
|
110
|
-
this._jsFunctionCache.set(cacheKey,
|
|
111
|
-
return
|
|
100
|
+
this._jsFunctionCache.set(cacheKey, fn);
|
|
101
|
+
return fn;
|
|
112
102
|
} catch (err) {
|
|
113
|
-
console.error(`CustomAttrHandler failed to compile
|
|
103
|
+
console.error(`CustomAttrHandler failed to compile function ${customFnDef.n}`, err);
|
|
114
104
|
return null;
|
|
115
105
|
}
|
|
116
106
|
}
|
package/src/RaftDeviceInfo.ts
CHANGED
|
@@ -89,6 +89,31 @@ export interface DeviceTypePollRespMetadata {
|
|
|
89
89
|
us?: number; // Time between consecutive samples in microseconds
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
export interface SampleRateResult {
|
|
93
|
+
ok: boolean;
|
|
94
|
+
requestedRateHz: number;
|
|
95
|
+
actualRateHz: number; // Closest supported rate from _conf.rate map
|
|
96
|
+
intervalUs: number; // Polling interval set
|
|
97
|
+
numSamples: number; // Buffer depth set
|
|
98
|
+
error?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface ActionMapEntry {
|
|
102
|
+
w: string; // Hex bytes to write (may contain &-separated multi-writes)
|
|
103
|
+
i?: number; // Recommended polling interval in microseconds
|
|
104
|
+
s?: number; // Recommended number of poll result samples (buffer depth)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type ActionMapValue = string | ActionMapEntry;
|
|
108
|
+
|
|
109
|
+
export function getActionMapHex(entry: ActionMapValue): string {
|
|
110
|
+
return typeof entry === 'string' ? entry : entry.w;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function getActionMapEntry(entry: ActionMapValue): ActionMapEntry | null {
|
|
114
|
+
return typeof entry === 'object' ? entry : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
92
117
|
export interface DeviceTypeAction {
|
|
93
118
|
n: string; // Action name
|
|
94
119
|
t?: string; // Action type using python struct module format (e.g. 'H' for unsigned short, 'h' for signed short, 'f' for float etc.)
|
|
@@ -102,6 +127,8 @@ export interface DeviceTypeAction {
|
|
|
102
127
|
mul?: number; // Multiplier to apply
|
|
103
128
|
sub?: number; // Value to subtract before multiplying
|
|
104
129
|
d?: number; // Default value
|
|
130
|
+
map?: Record<string, ActionMapValue>; // Discrete value mapping (display value -> hex string or {w, i, s} object)
|
|
131
|
+
desc?: string; // Human-readable description
|
|
105
132
|
}
|
|
106
133
|
|
|
107
134
|
export interface DeviceTypeInfo {
|