@robotical/raftjs 1.4.7 → 2.0.4

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.
Files changed (137) hide show
  1. package/dist/react-native/RaftAttributeHandler.d.ts +2 -0
  2. package/dist/react-native/RaftAttributeHandler.js +136 -10
  3. package/dist/react-native/RaftAttributeHandler.js.map +1 -1
  4. package/dist/react-native/RaftChannel.d.ts +2 -0
  5. package/dist/react-native/RaftChannelBLE.native.d.ts +2 -0
  6. package/dist/react-native/RaftChannelBLE.native.js +14 -7
  7. package/dist/react-native/RaftChannelBLE.native.js.map +1 -1
  8. package/dist/react-native/RaftChannelBLE.web.d.ts +2 -0
  9. package/dist/react-native/RaftChannelBLE.web.js +24 -15
  10. package/dist/react-native/RaftChannelBLE.web.js.map +1 -1
  11. package/dist/react-native/RaftChannelSimulated.d.ts +32 -0
  12. package/dist/react-native/RaftChannelSimulated.js +418 -0
  13. package/dist/react-native/RaftChannelSimulated.js.map +1 -0
  14. package/dist/react-native/RaftChannelWebSerial.d.ts +2 -0
  15. package/dist/react-native/RaftChannelWebSerial.js +18 -9
  16. package/dist/react-native/RaftChannelWebSerial.js.map +1 -1
  17. package/dist/react-native/RaftChannelWebSocket.d.ts +2 -0
  18. package/dist/react-native/RaftChannelWebSocket.js +10 -0
  19. package/dist/react-native/RaftChannelWebSocket.js.map +1 -1
  20. package/dist/react-native/RaftConnector.js +13 -6
  21. package/dist/react-native/RaftConnector.js.map +1 -1
  22. package/dist/react-native/RaftCustomAttrHandler.js +15 -0
  23. package/dist/react-native/RaftCustomAttrHandler.js.map +1 -1
  24. package/dist/react-native/RaftDeviceInfo.d.ts +8 -1
  25. package/dist/react-native/RaftDeviceInfo.js +17 -3
  26. package/dist/react-native/RaftDeviceInfo.js.map +1 -1
  27. package/dist/react-native/RaftDeviceManager.d.ts +3 -0
  28. package/dist/react-native/RaftDeviceManager.js +123 -43
  29. package/dist/react-native/RaftDeviceManager.js.map +1 -1
  30. package/dist/react-native/RaftFileHandler.js +8 -8
  31. package/dist/react-native/RaftFileHandler.js.map +1 -1
  32. package/dist/react-native/RaftLog.js +1 -1
  33. package/dist/react-native/RaftLog.js.map +1 -1
  34. package/dist/react-native/RaftMsgHandler.d.ts +5 -0
  35. package/dist/react-native/RaftMsgHandler.js +32 -0
  36. package/dist/react-native/RaftMsgHandler.js.map +1 -1
  37. package/dist/react-native/RaftStreamHandler.js +6 -5
  38. package/dist/react-native/RaftStreamHandler.js.map +1 -1
  39. package/dist/react-native/RaftStruct.js +197 -147
  40. package/dist/react-native/RaftStruct.js.map +1 -1
  41. package/dist/react-native/RaftSysTypeManager.d.ts +2 -0
  42. package/dist/react-native/RaftSysTypeManager.js +25 -0
  43. package/dist/react-native/RaftSysTypeManager.js.map +1 -1
  44. package/dist/react-native/RaftSystemType.d.ts +9 -7
  45. package/dist/react-native/RaftSystemUtils.js +3 -1
  46. package/dist/react-native/RaftSystemUtils.js.map +1 -1
  47. package/dist/react-native/RaftUpdateManager.js +2 -2
  48. package/dist/react-native/RaftUpdateManager.js.map +1 -1
  49. package/dist/react-native/RaftUtils.d.ts +2 -0
  50. package/dist/react-native/RaftUtils.js +22 -2
  51. package/dist/react-native/RaftUtils.js.map +1 -1
  52. package/dist/react-native/main.d.ts +2 -0
  53. package/dist/react-native/main.js +4 -1
  54. package/dist/react-native/main.js.map +1 -1
  55. package/dist/web/RaftAttributeHandler.d.ts +2 -0
  56. package/dist/web/RaftAttributeHandler.js +136 -10
  57. package/dist/web/RaftAttributeHandler.js.map +1 -1
  58. package/dist/web/RaftChannel.d.ts +2 -0
  59. package/dist/web/RaftChannelBLE.web.d.ts +2 -0
  60. package/dist/web/RaftChannelBLE.web.js +24 -15
  61. package/dist/web/RaftChannelBLE.web.js.map +1 -1
  62. package/dist/web/RaftChannelSimulated.d.ts +32 -0
  63. package/dist/web/RaftChannelSimulated.js +418 -0
  64. package/dist/web/RaftChannelSimulated.js.map +1 -0
  65. package/dist/web/RaftChannelWebSerial.d.ts +2 -0
  66. package/dist/web/RaftChannelWebSerial.js +18 -9
  67. package/dist/web/RaftChannelWebSerial.js.map +1 -1
  68. package/dist/web/RaftChannelWebSocket.d.ts +2 -0
  69. package/dist/web/RaftChannelWebSocket.js +10 -0
  70. package/dist/web/RaftChannelWebSocket.js.map +1 -1
  71. package/dist/web/RaftConnector.js +13 -6
  72. package/dist/web/RaftConnector.js.map +1 -1
  73. package/dist/web/RaftCustomAttrHandler.js +15 -0
  74. package/dist/web/RaftCustomAttrHandler.js.map +1 -1
  75. package/dist/web/RaftDeviceInfo.d.ts +8 -1
  76. package/dist/web/RaftDeviceInfo.js +17 -3
  77. package/dist/web/RaftDeviceInfo.js.map +1 -1
  78. package/dist/web/RaftDeviceManager.d.ts +3 -0
  79. package/dist/web/RaftDeviceManager.js +123 -43
  80. package/dist/web/RaftDeviceManager.js.map +1 -1
  81. package/dist/web/RaftFileHandler.js +8 -8
  82. package/dist/web/RaftFileHandler.js.map +1 -1
  83. package/dist/web/RaftLog.js +1 -1
  84. package/dist/web/RaftLog.js.map +1 -1
  85. package/dist/web/RaftMsgHandler.d.ts +5 -0
  86. package/dist/web/RaftMsgHandler.js +32 -0
  87. package/dist/web/RaftMsgHandler.js.map +1 -1
  88. package/dist/web/RaftStreamHandler.js +6 -5
  89. package/dist/web/RaftStreamHandler.js.map +1 -1
  90. package/dist/web/RaftStruct.js +197 -147
  91. package/dist/web/RaftStruct.js.map +1 -1
  92. package/dist/web/RaftSysTypeManager.d.ts +2 -0
  93. package/dist/web/RaftSysTypeManager.js +25 -0
  94. package/dist/web/RaftSysTypeManager.js.map +1 -1
  95. package/dist/web/RaftSystemType.d.ts +9 -7
  96. package/dist/web/RaftSystemUtils.js +3 -1
  97. package/dist/web/RaftSystemUtils.js.map +1 -1
  98. package/dist/web/RaftUpdateManager.js +2 -2
  99. package/dist/web/RaftUpdateManager.js.map +1 -1
  100. package/dist/web/RaftUtils.d.ts +2 -0
  101. package/dist/web/RaftUtils.js +22 -2
  102. package/dist/web/RaftUtils.js.map +1 -1
  103. package/dist/web/main.d.ts +2 -0
  104. package/dist/web/main.js +4 -1
  105. package/dist/web/main.js.map +1 -1
  106. package/examples/dashboard/package.json +1 -1
  107. package/examples/dashboard/src/CommandPanel.tsx +3 -3
  108. package/examples/dashboard/src/ConnManager.ts +83 -6
  109. package/examples/dashboard/src/DeviceActionsForm.tsx +2 -2
  110. package/examples/dashboard/src/DevicePanel.tsx +2 -2
  111. package/examples/dashboard/src/Main.tsx +14 -4
  112. package/examples/dashboard/src/SystemTypeMarty/RICSystemUtils.ts +4 -4
  113. package/examples/dashboard/src/styles.css +8 -0
  114. package/examples/dashboard/tsconfig.json +1 -1
  115. package/package.json +10 -11
  116. package/src/RaftAttributeHandler.ts +163 -11
  117. package/src/RaftChannel.ts +2 -0
  118. package/src/RaftChannelBLE.native.ts +17 -8
  119. package/src/RaftChannelBLE.web.ts +28 -16
  120. package/src/RaftChannelSimulated.ts +482 -0
  121. package/src/RaftChannelWebSerial.ts +18 -7
  122. package/src/RaftChannelWebSocket.ts +13 -0
  123. package/src/RaftConnector.ts +13 -7
  124. package/src/RaftCustomAttrHandler.ts +17 -0
  125. package/src/RaftDeviceInfo.ts +27 -5
  126. package/src/RaftDeviceManager.ts +155 -47
  127. package/src/RaftFileHandler.ts +8 -8
  128. package/src/RaftLog.ts +1 -1
  129. package/src/RaftMsgHandler.ts +36 -0
  130. package/src/RaftStreamHandler.ts +48 -47
  131. package/src/RaftStruct.ts +220 -147
  132. package/src/RaftSysTypeManager.ts +27 -0
  133. package/src/RaftSystemType.ts +9 -7
  134. package/src/RaftSystemUtils.ts +3 -1
  135. package/src/RaftUpdateManager.ts +2 -2
  136. package/src/RaftUtils.ts +25 -5
  137. package/src/main.ts +2 -0
@@ -0,0 +1,482 @@
1
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2
+ //
3
+ // RaftChannelSimulated.ts
4
+ // Part of RaftJS
5
+ //
6
+ // Rob Dobson 2020-2025
7
+ // (C) 2020-2025 All rights reserved
8
+ //
9
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
10
+
11
+ import RaftChannel from "./RaftChannel";
12
+ import RaftMsgHandler from "./RaftMsgHandler";
13
+ import RaftLog from "./RaftLog";
14
+ import { RaftConnEvent, RaftConnEventFn } from "./RaftConnEvents";
15
+ import { RaftCommsMsgTypeCode } from './RaftProtocolDefs';
16
+ import { ConnectorOptions } from "./RaftSystemType";
17
+ import { DeviceTypeInfoRecs, DeviceTypeInfo } from "./RaftDeviceInfo";
18
+
19
+ interface SimulatedDeviceInfo {
20
+ name: string;
21
+ publishRatePerSecond: number;
22
+ }
23
+
24
+ export default class RaftChannelSimulated implements RaftChannel {
25
+
26
+ // Message handler
27
+ private _raftMsgHandler: RaftMsgHandler | null = null;
28
+
29
+ // Is connected
30
+ private _isConnected = false;
31
+
32
+ // Simulated device name and rate
33
+ private _simulatedDeviceInfo: Array<SimulatedDeviceInfo> | null = null;
34
+
35
+ // Simulated device information timer
36
+ private _simulatedDeviceInfoTimers: Array<NodeJS.Timeout> | null = null;
37
+ private _simulatedDeviceInfoTimeMs: Array<number> = [];
38
+
39
+ // Conn event fn
40
+ private _onConnEvent: RaftConnEventFn | null = null;
41
+
42
+ // File Handler parameters
43
+ private _requestedBatchAckSize = 10;
44
+ private _requestedFileBlockSize = 500;
45
+
46
+ fhBatchAckSize(): number { return this._requestedBatchAckSize; }
47
+ fhFileBlockSize(): number { return this._requestedFileBlockSize; }
48
+
49
+ // isConnected
50
+ isConnected(): boolean {
51
+ return this._isConnected;
52
+ }
53
+
54
+ // Set message handler
55
+ setMsgHandler(raftMsgHandler: RaftMsgHandler): void {
56
+ this._raftMsgHandler = raftMsgHandler;
57
+ this._raftMsgHandler.setRawMsgMode(true);
58
+ }
59
+
60
+ // WebSocket interfaces require subscription to published messages
61
+ requiresSubscription(): boolean {
62
+ return true;
63
+ }
64
+
65
+ // RICREST command before disconnect
66
+ ricRestCmdBeforeDisconnect(): string | null {
67
+ return null;
68
+ }
69
+
70
+ // Set onConnEvent handler
71
+ setOnConnEvent(connEventFn: RaftConnEventFn): void {
72
+ this._onConnEvent = connEventFn;
73
+ }
74
+
75
+ // Get connected locator
76
+ getConnectedLocator(): string | object {
77
+ return "simulated";
78
+ }
79
+
80
+ // Connect to a device
81
+ async connect(locator: string | object, connectorOptions: ConnectorOptions): Promise<boolean> {
82
+
83
+ // Debug
84
+ RaftLog.debug(`RaftChannelSimulated.connect connected ${locator.toString()} options ${JSON.stringify(connectorOptions)}`);
85
+
86
+ // Extract SimulatedDeviceInfo from JSON locator
87
+ if (typeof locator === 'string') {
88
+ try {
89
+ const parsedLocator = JSON.parse(locator);
90
+ if (parsedLocator && Array.isArray(parsedLocator)) {
91
+ this._simulatedDeviceInfo = parsedLocator;
92
+ }
93
+ } catch (e) {
94
+ RaftLog.warn(`RaftChannelSimulated.connect - error parsing locator ${locator}`);
95
+ return false;
96
+ }
97
+ }
98
+
99
+ // Handle simulated devices
100
+ if (this._simulatedDeviceInfo) {
101
+ // Create timers for simulated devices
102
+ this._simulatedDeviceInfoTimers = [];
103
+ for (let i = 0; i < this._simulatedDeviceInfo.length; i++) {
104
+ const deviceInfo = this._simulatedDeviceInfo[i];
105
+ const deviceName = deviceInfo.name ? deviceInfo.name : `SimulatedDevice${i}`;
106
+ this._simulatedDeviceInfoTimeMs.push(0);
107
+ const deviceRate = deviceInfo.publishRatePerSecond ? deviceInfo.publishRatePerSecond : 1;
108
+ let deviceIntervalMs = 911;
109
+ if ((deviceRate > 0.01) && (deviceRate < 1000)) {
110
+ deviceIntervalMs = Math.floor(1000 / deviceRate);
111
+ }
112
+ const deviceTypeInfo = this._deviceTypeInfo[deviceName];
113
+ if (deviceTypeInfo) {
114
+ const timer = setInterval(() => {
115
+ const msg = this._createSimulatedDeviceInfoMsg(
116
+ deviceIntervalMs,
117
+ deviceName,
118
+ deviceTypeInfo,
119
+ this._simulatedDeviceInfoTimeMs[i]
120
+ );
121
+ this._raftMsgHandler?.handleNewRxMsgRaw(msg, RaftCommsMsgTypeCode.MSG_TYPE_PUBLISH, 0, this._simulatedDeviceInfoTimeMs[i]);
122
+ this._simulatedDeviceInfoTimeMs[i] += deviceIntervalMs;
123
+ }, deviceIntervalMs);
124
+ this._simulatedDeviceInfoTimers.push(timer);
125
+ } else {
126
+ RaftLog.warn(`RaftChannelSimulated.connect - device type info not found for ${deviceName}`);
127
+ }
128
+ }
129
+ } else {
130
+ RaftLog.warn(`RaftChannelSimulated.connect - no simulated devices found`);
131
+ }
132
+
133
+ // Connected
134
+ this._isConnected = true;
135
+ return true;
136
+ }
137
+
138
+ // Disconnect
139
+ async disconnect(): Promise<void> {
140
+
141
+ // Not connected
142
+ this._isConnected = false;
143
+
144
+ // Clear timers
145
+ if (this._simulatedDeviceInfoTimers) {
146
+ for (const timer of this._simulatedDeviceInfoTimers) {
147
+ clearInterval(timer);
148
+ }
149
+ this._simulatedDeviceInfoTimers = null;
150
+ }
151
+
152
+ // Notify connection event
153
+ if (this._onConnEvent) {
154
+ this._onConnEvent(RaftConnEvent.CONN_DISCONNECTED);
155
+ }
156
+
157
+ // Debug
158
+ RaftLog.debug(`RaftChannelSimulated.disconnect closed`);
159
+ }
160
+
161
+ pauseConnection(pause: boolean): void {
162
+ RaftLog.debug(`pauseConnection ${pause} - no effect for this channel type`);
163
+ return;
164
+ }
165
+
166
+ // Send a message
167
+ async sendTxMsg(
168
+ msg: Uint8Array,
169
+ sendWithResponse: boolean
170
+ ): Promise<boolean> {
171
+
172
+ // Check connected
173
+ if (!this._isConnected)
174
+ return false;
175
+
176
+ // Debug
177
+ RaftLog.debug(`RaftChannelSimulated.sendTxMsg ${msg.toString()} sendWithResp ${sendWithResponse.toString()}`);
178
+ return true;
179
+ }
180
+
181
+ async sendTxMsgNoAwait(
182
+ msg: Uint8Array,
183
+ sendWithResponse: boolean
184
+ ): Promise<boolean> {
185
+
186
+ // Check connected
187
+ if (!this._isConnected)
188
+ return false;
189
+
190
+ // Debug
191
+ RaftLog.debug(`RaftChannelSimulated.sendTxMsgNoAwait ${msg.toString()} sendWithResp ${sendWithResponse.toString()}`);
192
+ return true;
193
+ }
194
+
195
+ // Method used for testing and simulation should never be called
196
+ sendTxMsgRaw(msg: string): boolean {
197
+ RaftLog.debug(`sendTxMsgRaw - not implemented ${msg}`);
198
+ return false;
199
+ }
200
+
201
+ // Method used for testing and simulation should never be called
202
+ sendTxMsgRawAndWaitForReply<T>(msgPayload: Uint8Array): T {
203
+ RaftLog.debug(`sendTxMsgRawAndWaitForReply ${msgPayload}`);
204
+
205
+ // Decode the message from Uint8Array to string
206
+ const textDecoder = new TextDecoder('utf-8');
207
+ const decodedString = textDecoder.decode(msgPayload.slice(1)).replace("\0", "").trim()
208
+
209
+ RaftLog.debug(`sendTxMsgRawAndWaitForReply ${decodedString}`);
210
+
211
+ // Check for version request
212
+ if (decodedString === "v") {
213
+ // R"({"req":"%s","rslt":"ok","SystemName":"%s","SystemVersion":"%s","Friendly":"%s","SerialNo":"%s","MAC":"%s",%s})",
214
+ const response = {
215
+ req: "v",
216
+ rslt: "ok",
217
+ SystemName: "Simulated",
218
+ SystemVersion: "1.0.0",
219
+ Friendly: "Simulated",
220
+ SerialNo: "123456",
221
+ MAC: "00:00:00:00:00:00"
222
+ }
223
+ return response as T;
224
+ }
225
+
226
+ else if (decodedString.startsWith("sub")) {
227
+ const response = {
228
+ req: decodedString,
229
+ rslt: "ok"
230
+ }
231
+ return response as T;
232
+ }
233
+
234
+ // Check if this is a device type info request
235
+ else if (decodedString.startsWith("devman/typeinfo?")) {
236
+ // Extract the type parameter from the request
237
+ const match = decodedString.match(/type=([^&]+)/);
238
+ if (match && match[1]) {
239
+ const deviceType = match[1];
240
+
241
+ // Look up the device type in the _deviceTypeInfo
242
+ if (deviceType in this._deviceTypeInfo) {
243
+ // Prepare response with the device type info
244
+ const response = {
245
+ req: decodedString,
246
+ rslt: "ok",
247
+ devinfo: this._deviceTypeInfo[deviceType]
248
+ };
249
+
250
+ RaftLog.debug(`Device type info for ${deviceType} found, returning response`);
251
+ return response as T;
252
+ } else {
253
+ // Device type not found
254
+ const response = {
255
+ req: decodedString,
256
+ rslt: "err",
257
+ msg: `Device type ${deviceType} not found`
258
+ };
259
+
260
+ RaftLog.warn(`Device type info for ${deviceType} not found`);
261
+ return response as T;
262
+ }
263
+ }
264
+ }
265
+
266
+ // Unknown message
267
+ const response = {
268
+ req: decodedString,
269
+ rslt: "err",
270
+ msg: `Unknown request`
271
+ };
272
+ return response as T;
273
+ }
274
+
275
+ // Create simulated device info message
276
+ private _createSimulatedDeviceInfoMsg(
277
+ deviceIntervalMs: number,
278
+ deviceName: string,
279
+ deviceTypeInfo: DeviceTypeInfo,
280
+ deviceTimeMs: number
281
+ ): Uint8Array {
282
+ // Make sure we have response metadata
283
+ if (!deviceTypeInfo?.resp?.a) {
284
+ return new Uint8Array(0);
285
+ }
286
+
287
+ const attributes = deviceTypeInfo.resp.a;
288
+ const dataBlockSizeBytes = deviceTypeInfo.resp.b;
289
+
290
+ // Create a buffer for the data
291
+ const dataBuffer = new ArrayBuffer(dataBlockSizeBytes + 2);
292
+ const dataView = new DataView(dataBuffer);
293
+ let bytePos = 0;
294
+
295
+ // Add 16 bit big endian deviceTimeMs mod 65536 to the buffer
296
+ dataView.setUint16(bytePos, deviceTimeMs % 65536, false);
297
+ bytePos += 2;
298
+
299
+ // Calculate sine wave with phase offsets for each attribute
300
+ const numAttributes = attributes.length;
301
+ // Adjust frequency based on the device interval with N samples per cycle
302
+ const numSamplesPerCycle = 10;
303
+ let frequencyHz = 0.1; // Default frequency in Hz
304
+ if (deviceIntervalMs > 0) {
305
+ frequencyHz = (1000 / deviceIntervalMs) / numSamplesPerCycle;
306
+ }
307
+
308
+ // Amplitude of the sine wave (0 to 1)
309
+ const amplitude = 0.8;
310
+
311
+ // Iterate through attributes and set values
312
+ for (let i = 0; i < numAttributes; i++) {
313
+ const attr = attributes[i];
314
+ // Calculate phase offset for this attribute
315
+ const phaseOffset = (2 * Math.PI * i) / numAttributes;
316
+
317
+ // Generate sine wave value
318
+ const timeRadians = deviceTimeMs * frequencyHz * (2 * Math.PI) / 1000;
319
+ const sinValue = Math.sin(timeRadians + phaseOffset);
320
+
321
+ // Scale the value to fit within the attribute's range
322
+ let scaledValue: number;
323
+ if (attr.r && attr.r.length >= 2) {
324
+ const minValue = attr.r[0];
325
+ const maxValue = attr.r[1];
326
+ const midPoint = (maxValue + minValue) / 2;
327
+ const range = (maxValue - minValue) / 2;
328
+ scaledValue = midPoint + sinValue * range * amplitude;
329
+ } else {
330
+ // Default range if not specified
331
+ scaledValue = sinValue * 1000 * amplitude;
332
+ }
333
+
334
+ // Convert to raw integer value if needed
335
+ let rawValue = scaledValue;
336
+ if (attr.d) {
337
+ // Multiply by the divisor to get the raw value (reverse of what happens when decoding)
338
+ rawValue = scaledValue * attr.d;
339
+ }
340
+
341
+ // Write the value to the buffer based on its type
342
+ if (attr.t === "b") {
343
+ dataView.setUint8(bytePos, Math.round(rawValue));
344
+ bytePos += 1;
345
+ } else if (attr.t === "B") {
346
+ dataView.setUint8(bytePos, Math.round(rawValue));
347
+ bytePos += 1;
348
+ } else if (attr.t === "c") {
349
+ dataView.setInt8(bytePos, Math.round(rawValue));
350
+ bytePos += 1;
351
+ } else if (attr.t === "C") {
352
+ dataView.setUint8(bytePos, Math.round(rawValue));
353
+ bytePos += 1;
354
+ } else if (attr.t === "<h") {
355
+ dataView.setInt16(bytePos, Math.round(rawValue), true); // Little endian
356
+ bytePos += 2;
357
+ } else if (attr.t === ">h") {
358
+ dataView.setInt16(bytePos, Math.round(rawValue), false); // Big endian
359
+ bytePos += 2;
360
+ } else if (attr.t === "<H") {
361
+ dataView.setUint16(bytePos, Math.round(rawValue), true); // Little endian
362
+ bytePos += 2;
363
+ } else if (attr.t === ">H") {
364
+ dataView.setUint16(bytePos, Math.round(rawValue), false); // Big endian
365
+ bytePos += 2;
366
+ } else if (attr.t === "f" || attr.t === "<f") {
367
+ dataView.setFloat32(bytePos, rawValue, true); // Little endian
368
+ bytePos += 4;
369
+ } else if (attr.t === ">f") {
370
+ dataView.setFloat32(bytePos, rawValue, false); // Big endian
371
+ bytePos += 4;
372
+ } else {
373
+ RaftLog.warn(`RaftChannelSimulated._createSimulatedDeviceInfoMsg - unsupported attribute type ${attr.t}`);
374
+ }
375
+
376
+ }
377
+
378
+ // Convert the buffer to a byte array
379
+ const dataBytes = new Uint8Array(dataBuffer);
380
+
381
+ // Create the JSON message structure
382
+ const message = {
383
+ "BUS1": {
384
+ [deviceName]: {
385
+ "_t": deviceTypeInfo.type,
386
+ "_o": 1, // Device is online
387
+ "pub": this._bytesToHexStr(dataBytes)
388
+ }
389
+ }
390
+ };
391
+
392
+ // Convert the JSON to a string and then to Uint8Array with prepended timestamp
393
+ const jsonString = JSON.stringify(message);
394
+ const encodedMsg = new TextEncoder().encode(jsonString);
395
+ const msgWithPrefix = new ArrayBuffer(2 + encodedMsg.byteLength);
396
+ const msgWithPrefixView = new DataView(msgWithPrefix);
397
+ const msgPrefixBytes = new Uint8Array(msgWithPrefix);
398
+ msgWithPrefixView.setUint16(0, 0, false);
399
+ msgPrefixBytes.set(encodedMsg, 2);
400
+ return msgPrefixBytes;
401
+
402
+ }
403
+
404
+ // Helper function to convert bytes to hex string
405
+ private _bytesToHexStr(bytes: Uint8Array): string {
406
+ return Array.from(bytes)
407
+ .map(byte => byte.toString(16).padStart(2, '0'))
408
+ .join('');
409
+ }
410
+
411
+ // Simulated device type information - this is a copy of part of DeviceTypeInfo in RaftCore
412
+ private _deviceTypeInfo: DeviceTypeInfoRecs =
413
+ {
414
+ "LSM6DS": {
415
+ "name": "LSM6DS",
416
+ "desc": "6-Axis IMU",
417
+ "manu": "ST",
418
+ "type": "LSM6DS",
419
+ "clas": ["ACC","GYRO"],
420
+ "resp": {
421
+ "b": 12,
422
+ "a": [
423
+ {
424
+ "n": "gx",
425
+ "t": "<h",
426
+ "u": "&deg;/s",
427
+ "r": [-2000, 2000],
428
+ "d": 16.384,
429
+ "f": ".2f",
430
+ "o": "float"
431
+ },
432
+ {
433
+ "n": "gy",
434
+ "t": "<h",
435
+ "u": "&deg;/s",
436
+ "r": [-2000, 2000],
437
+ "d": 16.384,
438
+ "f": ".2f",
439
+ "o": "float"
440
+ },
441
+ {
442
+ "n": "gz",
443
+ "t": "<h",
444
+ "u": "&deg;/s",
445
+ "r": [-2000, 2000],
446
+ "d": 16.384,
447
+ "f": ".2f",
448
+ "o": "float"
449
+ },
450
+ {
451
+ "n": "ax",
452
+ "t": "<h",
453
+ "u": "g",
454
+ "r": [-4.0, 4.0],
455
+ "d": 8192,
456
+ "f": ".2f",
457
+ "o": "float"
458
+ },
459
+ {
460
+ "n": "ay",
461
+ "t": "<h",
462
+ "u": "g",
463
+ "r": [-4.0,4.0],
464
+ "d": 8192,
465
+ "f": ".2f",
466
+ "o": "float"
467
+ },
468
+ {
469
+ "n": "az",
470
+ "t": "<h",
471
+ "u": "g",
472
+ "r": [-4.0,4.0],
473
+ "d": 8192,
474
+ "f": ".2f",
475
+ "o": "float"
476
+ }
477
+ ]
478
+ }
479
+ }
480
+ };
481
+
482
+ }
@@ -13,7 +13,6 @@ import RaftMsgHandler from "./RaftMsgHandler";
13
13
  import RaftLog from "./RaftLog";
14
14
  import { RaftConnEvent, RaftConnEventFn } from "./RaftConnEvents";
15
15
  import { ConnectorOptions } from "./RaftSystemType";
16
- import { TextDecoder } from 'text-encoding';
17
16
 
18
17
  type TWebParityType = 'none' | 'even' | 'odd';
19
18
  type TWebFlowControlType = 'none' | 'hardware';
@@ -161,7 +160,7 @@ export default class RaftChannelWebSerial implements RaftChannel {
161
160
  if (err.name == "InvalidStateError") {
162
161
  RaftLog.debug(`Opening port failed - already open ${err}`);
163
162
  } else {
164
- RaftLog.error(`Opening port failed: ${err}`);
163
+ RaftLog.warn(`Opening port failed: ${err}`);
165
164
  throw err;
166
165
  }
167
166
  }
@@ -178,7 +177,7 @@ export default class RaftChannelWebSerial implements RaftChannel {
178
177
  });
179
178
  // TODO: handle errors
180
179
  } catch (err) {
181
- RaftLog.error("RaftChannelWebSerial.connect fail. Error: " + JSON.stringify(err));
180
+ RaftLog.warn("RaftChannelWebSerial.connect fail. Error: " + JSON.stringify(err));
182
181
  return false;
183
182
  }
184
183
 
@@ -349,7 +348,7 @@ export default class RaftChannelWebSerial implements RaftChannel {
349
348
  writer.write(msg).then(() => { writer.releaseLock(); });
350
349
  }
351
350
  } catch (err) {
352
- RaftLog.error("sendMsg error: " + JSON.stringify(err));
351
+ RaftLog.warn("sendMsg error: " + JSON.stringify(err));
353
352
  }
354
353
 
355
354
  return true;
@@ -364,7 +363,7 @@ export default class RaftChannelWebSerial implements RaftChannel {
364
363
  let retries = 10;
365
364
  try {
366
365
  if (!this._port.readable) {
367
- RaftLog.error("RaftChannelWebSerial _readLoop port is not readble");
366
+ RaftLog.warn("RaftChannelWebSerial _readLoop port is not readble");
368
367
  return;
369
368
  }
370
369
  this._reader = this._port.readable.getReader();
@@ -390,7 +389,7 @@ export default class RaftChannelWebSerial implements RaftChannel {
390
389
 
391
390
  this._onMsgRx(new Uint8Array(value));
392
391
  } catch (err) {
393
- RaftLog.error("read loop issue: " + JSON.stringify(err));
392
+ RaftLog.warn("read loop issue: " + JSON.stringify(err));
394
393
  retries -= 1;
395
394
  if (!retries) break;
396
395
  await new Promise(resolve => setTimeout(resolve, 100));
@@ -400,10 +399,22 @@ export default class RaftChannelWebSerial implements RaftChannel {
400
399
  if (this._reader) this._reader.releaseLock();
401
400
  this._reader = undefined;
402
401
  } catch (err) {
403
- RaftLog.error("Read loop got disconnected. err: " + JSON.stringify(err));
402
+ RaftLog.warn("Read loop got disconnected. err: " + JSON.stringify(err));
404
403
  }
405
404
  // Disconnected!
406
405
  this._isConnected = false;
407
406
  RaftLog.debug("Finished read loop");
408
407
  }
408
+
409
+ // Method used for testing and simulation should never be called
410
+ sendTxMsgRaw(): boolean {
411
+ RaftLog.debug(`sendTxMsgRaw - not implemented`);
412
+ return false;
413
+ }
414
+
415
+ // Method used for testing and simulation should never be called
416
+ sendTxMsgRawAndWaitForReply<T>(): T {
417
+ RaftLog.debug(`sendTxMsgRawAndWaitForReply - not implemented`);
418
+ return null as T;
419
+ }
409
420
  }
@@ -242,4 +242,17 @@ export default class RaftChannelWebSocket implements RaftChannel {
242
242
  }
243
243
  });
244
244
  }
245
+
246
+ // Method used for testing and simulation should never be called
247
+ sendTxMsgRaw(): boolean {
248
+ RaftLog.debug(`sendTxMsgRaw - not implemented`);
249
+ return false;
250
+ }
251
+
252
+ // Method used for testing and simulation should never be called
253
+ sendTxMsgRawAndWaitForReply<T>(): T {
254
+ RaftLog.debug(`sendTxMsgRawAndWaitForReply - not implemented`);
255
+ return null as T;
256
+ }
257
+
245
258
  }
@@ -12,6 +12,7 @@ import RaftChannel from "./RaftChannel";
12
12
  import RaftMsgHandler, { RaftMsgResultCode } from "./RaftMsgHandler";
13
13
  import RaftChannelWebSocket from "./RaftChannelWebSocket";
14
14
  import RaftChannelWebSerial from "./RaftChannelWebSerial";
15
+ import RaftChannelSimulated from "./RaftChannelSimulated";
15
16
  import RaftCommsStats from "./RaftCommsStats";
16
17
  import { RaftEventFn, RaftOKFail, RaftFileSendType, RaftFileDownloadResult, RaftProgressCBType, RaftBridgeSetupResp, RaftFileDownloadFn, RaftReportMsg } from "./RaftTypes";
17
18
  import RaftSystemUtils from "./RaftSystemUtils";
@@ -224,8 +225,11 @@ export default class RaftConnector {
224
225
  } else if (method === 'WebSerial') {
225
226
  this._raftChannel = new RaftChannelWebSerial();
226
227
  this._channelConnMethod = 'WebSerial';
228
+ } else if (method === 'Simulated') {
229
+ this._raftChannel = new RaftChannelSimulated();
230
+ this._channelConnMethod = 'Simulated';
227
231
  } else {
228
- RaftLog.error('Unknown method: ' + method);
232
+ RaftLog.warn('Unknown method: ' + method);
229
233
  return false;
230
234
  }
231
235
 
@@ -256,7 +260,7 @@ export default class RaftConnector {
256
260
  */
257
261
  async connect(locator: string | object): Promise<boolean> {
258
262
  if (!this._raftChannel) {
259
- RaftLog.error('Raft channel is not initialized.');
263
+ RaftLog.warn('Raft channel is not initialized.');
260
264
  return false;
261
265
  }
262
266
 
@@ -273,7 +277,7 @@ export default class RaftConnector {
273
277
  // Connect
274
278
  connOk = await this._connectToChannel();
275
279
  } catch (err) {
276
- RaftLog.error('RaftConnector.connect - error: ' + err);
280
+ RaftLog.warn('RaftConnector.connect - error: ' + err);
277
281
  }
278
282
 
279
283
  if (connOk) {
@@ -283,7 +287,7 @@ export default class RaftConnector {
283
287
  this._systemType = await this._getSystemTypeCB(this._raftSystemUtils);
284
288
 
285
289
  // Set defaults
286
- if (this._systemType) {
290
+ if (this._systemType && this._systemType.defaultWiFiHostname) {
287
291
  this._raftSystemUtils.setDefaultWiFiHostname(this._systemType.defaultWiFiHostname);
288
292
  }
289
293
  }
@@ -325,10 +329,12 @@ export default class RaftConnector {
325
329
  if (this._raftChannel) {
326
330
  // Check if there is a RICREST command to send before disconnecting
327
331
  const ricRestCommand = this._raftChannel.ricRestCmdBeforeDisconnect();
328
- console.log(`sending RICREST command before disconnect: ${ricRestCommand}`);
329
332
  if (ricRestCommand) {
333
+ console.log(`sending RICREST command before disconnect: ${ricRestCommand}`);
330
334
  await this.sendRICRESTMsg(ricRestCommand, {});
331
335
  }
336
+ // Pause a little before disconnecting
337
+ await new Promise(resolve => setTimeout(resolve, 1000));
332
338
  // await this.sendRICRESTMsg("bledisc", {});
333
339
  await this._raftChannel.disconnect();
334
340
  this._raftChannel = null;
@@ -659,14 +665,14 @@ export default class RaftConnector {
659
665
  // Connect
660
666
  try {
661
667
  if (this._raftChannel) {
662
- const connected = await this._raftChannel.connect(this._channelConnLocator, this._systemType ? this._systemType.connectorOptions : {});
668
+ const connected = await this._raftChannel.connect(this._channelConnLocator, this._systemType ? this._systemType.connectorOptions : { });
663
669
  if (connected) {
664
670
  this._retryIfLostIsConnected = true;
665
671
  return true;
666
672
  }
667
673
  }
668
674
  } catch (error) {
669
- RaftLog.error(`RaftConnector.connect() error: ${error}`);
675
+ RaftLog.warn(`RaftConnector.connect() error: ${error}`);
670
676
  }
671
677
  return false;
672
678
  }
@@ -13,6 +13,9 @@ export default class CustomAttrHandler {
13
13
 
14
14
  public handleAttr(pollRespMetadata: DeviceTypePollRespMetadata, msgBuffer: Uint8Array, msgBufIdx: number): number[][] {
15
15
 
16
+ // Implement the pseudo-code:
17
+ // int N=(buf[0]+32-buf[2])%32;int k=3;int i=0;while(i<N){out.Red=(buf[k]<<16)|(buf[k+1]<<8)|buf[k+2];out.IR=(buf[k+3]<<16)|(buf[k+4]<<8)|buf[k+5];k+=6;i++;next;}
18
+
16
19
  // Number of bytes in the each message
17
20
  const numMsgBytes = pollRespMetadata.b;
18
21
 
@@ -48,6 +51,20 @@ export default class CustomAttrHandler {
48
51
  i++;
49
52
  ;
50
53
  }
54
+ } else if (pollRespMetadata.c!.n === "gravity_o2_calc") {
55
+ // Get the buffer
56
+ const buf = msgBuffer.slice(msgBufIdx);
57
+ if (buf.length < numMsgBytes) {
58
+ return [];
59
+ }
60
+
61
+ // Implement the pseudo-code:
62
+ // float key = 20.9/120.0; float val = key * (buf[0] + (buf[1]/10.0) + (buf[2]/100.0)); out.oxygen = val;
63
+ const key = 20.9 / 120.0;
64
+ const val = key * (buf[0] + (buf[1] / 10.0) + (buf[2] / 100.0));
65
+
66
+ // Add the value to the oxygen attribute
67
+ attrValues['oxygen'].push(val);
51
68
  }
52
69
  return attrValueVecs;
53
70
  }