@robotical/raftjs 2.0.11 → 2.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/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 +10 -6
- package/dist/react-native/RaftChannelSimulated.js.map +1 -1
- package/dist/react-native/RaftChannelWebSerial.js +1 -1
- package/dist/react-native/RaftChannelWebSerial.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 +11 -1
- package/dist/react-native/RaftConnector.js +75 -9
- 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 +47 -2
- package/dist/react-native/RaftDeviceManager.js +696 -104
- 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 +0 -1
- package/dist/react-native/RaftFileHandler.js +61 -23
- package/dist/react-native/RaftFileHandler.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 +27 -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 +6 -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 +10 -6
- package/dist/web/RaftChannelSimulated.js.map +1 -1
- package/dist/web/RaftChannelWebSerial.js +1 -1
- package/dist/web/RaftChannelWebSerial.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 +11 -1
- package/dist/web/RaftConnector.js +75 -9
- 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 +47 -2
- package/dist/web/RaftDeviceManager.js +696 -104
- 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 +0 -1
- package/dist/web/RaftFileHandler.js +61 -23
- package/dist/web/RaftFileHandler.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 +27 -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 +6 -1
- package/dist/web/main.js.map +1 -1
- package/examples/dashboard/package.json +2 -2
- package/examples/dashboard/src/DeviceActionsForm.tsx +158 -8
- 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 +11 -6
- package/src/RaftChannelWebSerial.ts +1 -1
- package/src/RaftChannelWebSocket.ts +16 -2
- package/src/RaftConnector.ts +93 -15
- package/src/RaftCustomAttrHandler.ts +35 -45
- package/src/RaftDeviceInfo.ts +27 -0
- package/src/RaftDeviceManager.test.ts +164 -0
- package/src/RaftDeviceManager.ts +823 -121
- package/src/RaftDeviceMgrIF.ts +13 -2
- package/src/RaftDeviceStates.ts +49 -8
- package/src/RaftFileHandler.ts +69 -28
- 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 +34 -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 } 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
|
|
|
@@ -214,7 +213,22 @@ export default class RaftConnector {
|
|
|
214
213
|
* Initialize the Raft channel
|
|
215
214
|
*/
|
|
216
215
|
async initializeChannel(method: string): Promise<boolean> {
|
|
216
|
+
// Disconnect any existing channel first to avoid stale connections
|
|
217
|
+
RaftLog.verbose(`[RaftConnector.initializeChannel] START method=${method} hasExistingChannel=${!!this._raftChannel}`);
|
|
218
|
+
if (this._raftChannel) {
|
|
219
|
+
RaftLog.verbose(`[RaftConnector.initializeChannel] Disconnecting existing channel...`);
|
|
220
|
+
try {
|
|
221
|
+
await this._raftChannel.disconnect();
|
|
222
|
+
RaftLog.verbose(`[RaftConnector.initializeChannel] Existing channel disconnected successfully`);
|
|
223
|
+
} catch (e) {
|
|
224
|
+
RaftLog.warn(`[RaftConnector.initializeChannel] Error disconnecting existing channel: ${e}`);
|
|
225
|
+
}
|
|
226
|
+
this._raftChannel = null;
|
|
227
|
+
RaftLog.verbose(`[RaftConnector.initializeChannel] Existing channel nulled`);
|
|
228
|
+
}
|
|
229
|
+
|
|
217
230
|
// Initialize raft channel
|
|
231
|
+
RaftLog.verbose(`[RaftConnector.initializeChannel] Creating new channel...`);
|
|
218
232
|
if (method === 'WebBLE' || method === 'PhoneBLE') {
|
|
219
233
|
const RaftChannelBLE = createBLEChannel();
|
|
220
234
|
this._raftChannel = new RaftChannelBLE();
|
|
@@ -226,7 +240,7 @@ export default class RaftConnector {
|
|
|
226
240
|
this._raftChannel = new RaftChannelWebSerial();
|
|
227
241
|
this._channelConnMethod = 'WebSerial';
|
|
228
242
|
} else if (method === 'Simulated') {
|
|
229
|
-
this._raftChannel = new RaftChannelSimulated();
|
|
243
|
+
this._raftChannel = new RaftChannelSimulated();
|
|
230
244
|
this._channelConnMethod = 'Simulated';
|
|
231
245
|
} else {
|
|
232
246
|
RaftLog.warn('Unknown method: ' + method);
|
|
@@ -268,7 +282,7 @@ export default class RaftConnector {
|
|
|
268
282
|
// Store locator
|
|
269
283
|
this._channelConnLocator = locator;
|
|
270
284
|
|
|
271
|
-
// Connect
|
|
285
|
+
// Connect channel first (system type resolution needs a live connection)
|
|
272
286
|
let connOk = false;
|
|
273
287
|
try {
|
|
274
288
|
// Event
|
|
@@ -281,9 +295,9 @@ export default class RaftConnector {
|
|
|
281
295
|
}
|
|
282
296
|
|
|
283
297
|
if (connOk) {
|
|
284
|
-
|
|
298
|
+
|
|
299
|
+
// Resolve system type now that the channel is connected
|
|
285
300
|
if (this._getSystemTypeCB) {
|
|
286
|
-
// Get system type
|
|
287
301
|
this._systemType = await this._getSystemTypeCB(this._raftSystemUtils);
|
|
288
302
|
|
|
289
303
|
// Set defaults
|
|
@@ -310,14 +324,36 @@ export default class RaftConnector {
|
|
|
310
324
|
}
|
|
311
325
|
}
|
|
312
326
|
|
|
327
|
+
// configure file handler
|
|
328
|
+
this.configureFileHandler(this._raftChannel.fhFileBlockSize(), this._raftChannel.fhBatchAckSize());
|
|
329
|
+
|
|
330
|
+
// Sync time to device if enabled (default: true)
|
|
331
|
+
const syncTime = this._systemType?.connectorOptions?.syncTimeOnConnect ?? true;
|
|
332
|
+
if (syncTime) {
|
|
333
|
+
try {
|
|
334
|
+
const now = new Date();
|
|
335
|
+
const utc = now.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
336
|
+
const params: Record<string, string> = { UTC: utc };
|
|
337
|
+
const posixTZ = getHostPosixTZ();
|
|
338
|
+
if (posixTZ) {
|
|
339
|
+
params.tz = posixTZ;
|
|
340
|
+
}
|
|
341
|
+
await this.sendRICRESTMsg('datetime', params);
|
|
342
|
+
RaftLog.info(`connect synced time to device: ${utc}${posixTZ ? ` tz=${posixTZ}` : ''}`);
|
|
343
|
+
} catch (error) {
|
|
344
|
+
RaftLog.warn(`connect time sync failed: ${error}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
313
348
|
// Send connected event
|
|
314
349
|
this.onConnEvent(RaftConnEvent.CONN_CONNECTED);
|
|
315
350
|
|
|
316
351
|
} else {
|
|
317
352
|
// Failed Event
|
|
318
353
|
this.onConnEvent(RaftConnEvent.CONN_CONNECTION_FAILED);
|
|
319
|
-
|
|
320
|
-
|
|
354
|
+
|
|
355
|
+
// Clear system type
|
|
356
|
+
this._systemType = null;
|
|
321
357
|
}
|
|
322
358
|
|
|
323
359
|
return connOk;
|
|
@@ -327,8 +363,12 @@ export default class RaftConnector {
|
|
|
327
363
|
// Disconnect
|
|
328
364
|
this._retryIfLostIsConnected = false;
|
|
329
365
|
if (this._raftChannel) {
|
|
366
|
+
// Store reference to channel before async operations to avoid race condition
|
|
367
|
+
const channelToDisconnect = this._raftChannel;
|
|
368
|
+
this._raftChannel = null;
|
|
369
|
+
|
|
330
370
|
// Check if there is a RICREST command to send before disconnecting
|
|
331
|
-
const ricRestCommand =
|
|
371
|
+
const ricRestCommand = channelToDisconnect.ricRestCmdBeforeDisconnect();
|
|
332
372
|
if (ricRestCommand) {
|
|
333
373
|
console.log(`sending RICREST command before disconnect: ${ricRestCommand}`);
|
|
334
374
|
await this.sendRICRESTMsg(ricRestCommand, {});
|
|
@@ -336,8 +376,24 @@ export default class RaftConnector {
|
|
|
336
376
|
// Pause a little before disconnecting
|
|
337
377
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
338
378
|
// await this.sendRICRESTMsg("bledisc", {});
|
|
339
|
-
await
|
|
340
|
-
|
|
379
|
+
await channelToDisconnect.disconnect();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
disconnectForPageUnload(): void {
|
|
384
|
+
this._retryIfLostIsConnected = false;
|
|
385
|
+
|
|
386
|
+
if (!this._raftChannel) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const channelToDisconnect = this._raftChannel;
|
|
391
|
+
this._raftChannel = null;
|
|
392
|
+
|
|
393
|
+
try {
|
|
394
|
+
void channelToDisconnect.disconnect();
|
|
395
|
+
} catch (error) {
|
|
396
|
+
RaftLog.warn(`RaftConnector.disconnectForPageUnload failed ${error}`);
|
|
341
397
|
}
|
|
342
398
|
}
|
|
343
399
|
|
|
@@ -456,6 +512,28 @@ export default class RaftConnector {
|
|
|
456
512
|
|
|
457
513
|
// Mark: Streaming --------------------------------------------------------------------------------
|
|
458
514
|
|
|
515
|
+
/**
|
|
516
|
+
* streamData - stream arbitrary data to a named firmware endpoint using the RT_STREAM protocol.
|
|
517
|
+
* @param streamContents data to stream
|
|
518
|
+
* @param fileName logical filename sent in ufStart (e.g. "pattern.thr")
|
|
519
|
+
* @param targetEndpoint REST API endpoint name on the firmware (e.g. "streampattern")
|
|
520
|
+
* @param progressCallback optional (sent, total, progress) callback
|
|
521
|
+
* @returns Promise<boolean> true on success
|
|
522
|
+
*/
|
|
523
|
+
async streamData(
|
|
524
|
+
streamContents: Uint8Array,
|
|
525
|
+
fileName: string,
|
|
526
|
+
targetEndpoint: string,
|
|
527
|
+
progressCallback?: RaftStreamDataProgressCBType,
|
|
528
|
+
): Promise<boolean> {
|
|
529
|
+
if (this._raftStreamHandler && this.isConnected()) {
|
|
530
|
+
return this._raftStreamHandler.streamData(
|
|
531
|
+
streamContents, fileName, targetEndpoint, progressCallback,
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
|
|
459
537
|
/**
|
|
460
538
|
* streamAudio - stream audio
|
|
461
539
|
* @param streamContents audio data
|
|
@@ -534,7 +612,7 @@ export default class RaftConnector {
|
|
|
534
612
|
*/
|
|
535
613
|
async checkConnPerformance(): Promise<number | undefined> {
|
|
536
614
|
|
|
537
|
-
// Sends a magic sequence of bytes followed by blocks of random data
|
|
615
|
+
// Sends a magic sequence of bytes followed by blocks of random data
|
|
538
616
|
// these will be ignored by the Raft library (as it recognises magic sequence)
|
|
539
617
|
// and is used performance evaluation
|
|
540
618
|
let prbsState = 1;
|
|
@@ -681,8 +759,8 @@ export default class RaftConnector {
|
|
|
681
759
|
|
|
682
760
|
/**
|
|
683
761
|
* onUpdateEvent - handle update event
|
|
684
|
-
* @param eventEnum
|
|
685
|
-
* @param data
|
|
762
|
+
* @param eventEnum
|
|
763
|
+
* @param data
|
|
686
764
|
*/
|
|
687
765
|
_onUpdateEvent(eventEnum: RaftUpdateEvent, data: object | string | null | undefined = undefined): void {
|
|
688
766
|
// 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 {
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { DeviceManager } from "./RaftDeviceManager";
|
|
2
|
+
import { DeviceTypeInfo } from "./RaftDeviceInfo";
|
|
3
|
+
import RaftSystemUtils from "./RaftSystemUtils";
|
|
4
|
+
|
|
5
|
+
function makeTypeInfo(name: string, respBytes: number, attrs: Array<{ n: string; t: string }>): DeviceTypeInfo {
|
|
6
|
+
return {
|
|
7
|
+
name,
|
|
8
|
+
desc: name,
|
|
9
|
+
manu: "Robotical",
|
|
10
|
+
type: name,
|
|
11
|
+
resp: {
|
|
12
|
+
b: respBytes,
|
|
13
|
+
a: attrs.map(attr => ({ ...attr, u: "", r: [0, 0] }))
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function makeDeviceManager(typeInfos: Record<string, DeviceTypeInfo>): Promise<DeviceManager> {
|
|
19
|
+
const msgHandler = {
|
|
20
|
+
sendRICRESTURL: jest.fn(async (cmd: string) => {
|
|
21
|
+
const deviceType = new URLSearchParams(cmd.split("?")[1]).get("type");
|
|
22
|
+
const devinfo = deviceType ? typeInfos[deviceType] : undefined;
|
|
23
|
+
return devinfo ? { rslt: "ok", devinfo } : { rslt: "fail" };
|
|
24
|
+
})
|
|
25
|
+
};
|
|
26
|
+
const systemUtils = {
|
|
27
|
+
getMsgHandler: () => msgHandler,
|
|
28
|
+
getPublishTopicName: () => "devbin"
|
|
29
|
+
} as unknown as RaftSystemUtils;
|
|
30
|
+
|
|
31
|
+
const deviceManager = new DeviceManager();
|
|
32
|
+
await deviceManager.setup(systemUtils);
|
|
33
|
+
return deviceManager;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("DeviceManager binary devbin parsing", () => {
|
|
37
|
+
const accelInfo = makeTypeInfo("MXC400xXC", 7, [
|
|
38
|
+
{ n: "x", t: ">h" },
|
|
39
|
+
{ n: "y", t: ">h" },
|
|
40
|
+
{ n: "z", t: ">h" },
|
|
41
|
+
{ n: "status", t: "B" }
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
it("decodes current length-prefixed records", async () => {
|
|
45
|
+
const deviceManager = await makeDeviceManager({ "4": accelInfo });
|
|
46
|
+
const rxMsg = Uint8Array.from([
|
|
47
|
+
0x00, 0x80,
|
|
48
|
+
0xDB, 0xFF, 0x00,
|
|
49
|
+
0x00, 0x12,
|
|
50
|
+
0x81,
|
|
51
|
+
0x00, 0x00, 0x00, 0x15,
|
|
52
|
+
0x00, 0x04,
|
|
53
|
+
0x05,
|
|
54
|
+
0x09,
|
|
55
|
+
0x00, 0x01,
|
|
56
|
+
0x00, 0x01,
|
|
57
|
+
0x00, 0x02,
|
|
58
|
+
0x00, 0x03,
|
|
59
|
+
0x04
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
await deviceManager.handleClientMsgBinary(rxMsg);
|
|
63
|
+
|
|
64
|
+
const deviceState = deviceManager.getDeviceState("1_15");
|
|
65
|
+
expect(deviceState.deviceType).toBe("MXC400xXC");
|
|
66
|
+
expect(deviceState.deviceTimeline.totalSamplesAdded).toBe(1);
|
|
67
|
+
expect(deviceState.deviceAttributes.x.values).toEqual([1]);
|
|
68
|
+
expect(deviceState.deviceAttributes.y.values).toEqual([2]);
|
|
69
|
+
expect(deviceState.deviceAttributes.z.values).toEqual([3]);
|
|
70
|
+
expect(deviceState.deviceAttributes.status.values).toEqual([4]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("decodes Cog v1.9.5 legacy raw accelerometer records", async () => {
|
|
74
|
+
const deviceManager = await makeDeviceManager({ "4": accelInfo });
|
|
75
|
+
const rxMsg = Uint8Array.from([
|
|
76
|
+
0x00, 0x80,
|
|
77
|
+
0x00, 0x10,
|
|
78
|
+
0x81,
|
|
79
|
+
0x00, 0x00, 0x00, 0x15,
|
|
80
|
+
0x00, 0x04,
|
|
81
|
+
0x00, 0x01,
|
|
82
|
+
0x00, 0x01,
|
|
83
|
+
0x00, 0x02,
|
|
84
|
+
0x00, 0x03,
|
|
85
|
+
0x04
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
await deviceManager.handleClientMsgBinary(rxMsg);
|
|
89
|
+
|
|
90
|
+
const deviceState = deviceManager.getDeviceState("1_15");
|
|
91
|
+
expect(deviceState.deviceType).toBe("MXC400xXC");
|
|
92
|
+
expect(deviceState.deviceTimeline.totalSamplesAdded).toBe(1);
|
|
93
|
+
expect(deviceState.deviceAttributes.x.values).toEqual([1]);
|
|
94
|
+
expect(deviceState.deviceAttributes.y.values).toEqual([2]);
|
|
95
|
+
expect(deviceState.deviceAttributes.z.values).toEqual([3]);
|
|
96
|
+
expect(deviceState.deviceAttributes.status.values).toEqual([4]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("decodes Cog v1.9.5 legacy raw records inside a devbin envelope", async () => {
|
|
100
|
+
const deviceManager = await makeDeviceManager({ "4": accelInfo });
|
|
101
|
+
const rxMsg = Uint8Array.from([
|
|
102
|
+
0x00, 0x80,
|
|
103
|
+
0xDB, 0xFF, 0x00,
|
|
104
|
+
0x00, 0x10,
|
|
105
|
+
0x81,
|
|
106
|
+
0x00, 0x00, 0x00, 0x15,
|
|
107
|
+
0x00, 0x04,
|
|
108
|
+
0x00, 0x01,
|
|
109
|
+
0x00, 0x01,
|
|
110
|
+
0x00, 0x02,
|
|
111
|
+
0x00, 0x03,
|
|
112
|
+
0x04
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
await deviceManager.handleClientMsgBinary(rxMsg);
|
|
116
|
+
|
|
117
|
+
const deviceState = deviceManager.getDeviceState("1_15");
|
|
118
|
+
expect(deviceState.deviceType).toBe("MXC400xXC");
|
|
119
|
+
expect(deviceState.deviceTimeline.totalSamplesAdded).toBe(1);
|
|
120
|
+
expect(deviceState.deviceAttributes.x.values).toEqual([1]);
|
|
121
|
+
expect(deviceState.deviceAttributes.y.values).toEqual([2]);
|
|
122
|
+
expect(deviceState.deviceAttributes.z.values).toEqual([3]);
|
|
123
|
+
expect(deviceState.deviceAttributes.status.values).toEqual([4]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("keeps Cog v1.9.5 direct device records distinct when bus and address are both zero", async () => {
|
|
127
|
+
const lightInfo = makeTypeInfo("LightSensors", 16, [
|
|
128
|
+
{ n: "ch0", t: ">H" },
|
|
129
|
+
{ n: "ch1", t: ">H" },
|
|
130
|
+
{ n: "ch2", t: ">H" },
|
|
131
|
+
{ n: "ch3", t: ">H" }
|
|
132
|
+
]);
|
|
133
|
+
const powerInfo = makeTypeInfo("Power", 1, [
|
|
134
|
+
{ n: "battery", t: "B" }
|
|
135
|
+
]);
|
|
136
|
+
const deviceManager = await makeDeviceManager({ "2": lightInfo, "3": powerInfo });
|
|
137
|
+
const rxMsg = Uint8Array.from([
|
|
138
|
+
0x00, 0x80,
|
|
139
|
+
0x00, 0x11,
|
|
140
|
+
0x80,
|
|
141
|
+
0x00, 0x00, 0x00, 0x00,
|
|
142
|
+
0x00, 0x02,
|
|
143
|
+
0x00, 0x01,
|
|
144
|
+
0x00, 0x0a,
|
|
145
|
+
0x00, 0x0b,
|
|
146
|
+
0x00, 0x0c,
|
|
147
|
+
0x00, 0x0d,
|
|
148
|
+
0x00, 0x0a,
|
|
149
|
+
0x80,
|
|
150
|
+
0x00, 0x00, 0x00, 0x00,
|
|
151
|
+
0x00, 0x03,
|
|
152
|
+
0x00, 0x02,
|
|
153
|
+
0x63
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
await deviceManager.handleClientMsgBinary(rxMsg);
|
|
157
|
+
|
|
158
|
+
const devicesState = deviceManager.getDevicesState();
|
|
159
|
+
expect(devicesState["0_0_2"].deviceType).toBe("LightSensors");
|
|
160
|
+
expect(devicesState["0_0_2"].deviceAttributes.ch0.values).toEqual([10]);
|
|
161
|
+
expect(devicesState["0_0_3"].deviceType).toBe("Power");
|
|
162
|
+
expect(devicesState["0_0_3"].deviceAttributes.battery.values).toEqual([99]);
|
|
163
|
+
});
|
|
164
|
+
});
|