@robdobsn/raftjs 1.6.5 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react-native/RaftChannel.d.ts +2 -0
- package/dist/react-native/RaftChannelBLE.native.d.ts +2 -0
- package/dist/react-native/RaftChannelBLE.native.js +10 -0
- package/dist/react-native/RaftChannelBLE.native.js.map +1 -1
- package/dist/react-native/RaftChannelBLE.web.d.ts +2 -0
- package/dist/react-native/RaftChannelBLE.web.js +10 -0
- package/dist/react-native/RaftChannelBLE.web.js.map +1 -1
- package/dist/react-native/RaftChannelSimulated.d.ts +32 -0
- package/dist/react-native/RaftChannelSimulated.js +418 -0
- package/dist/react-native/RaftChannelSimulated.js.map +1 -0
- package/dist/react-native/RaftChannelWebSerial.d.ts +2 -0
- package/dist/react-native/RaftChannelWebSerial.js +10 -0
- package/dist/react-native/RaftChannelWebSerial.js.map +1 -1
- package/dist/react-native/RaftChannelWebSocket.d.ts +2 -0
- package/dist/react-native/RaftChannelWebSocket.js +10 -0
- package/dist/react-native/RaftChannelWebSocket.js.map +1 -1
- package/dist/react-native/RaftConnector.js +5 -0
- package/dist/react-native/RaftConnector.js.map +1 -1
- package/dist/react-native/RaftDeviceInfo.d.ts +1 -1
- package/dist/react-native/RaftDeviceManager.js +1 -1
- package/dist/react-native/RaftDeviceManager.js.map +1 -1
- package/dist/react-native/RaftMsgHandler.d.ts +5 -0
- package/dist/react-native/RaftMsgHandler.js +32 -0
- package/dist/react-native/RaftMsgHandler.js.map +1 -1
- package/dist/react-native/RaftStruct.js +1 -1
- package/dist/react-native/RaftStruct.js.map +1 -1
- package/dist/react-native/main.d.ts +2 -0
- package/dist/react-native/main.js +4 -1
- package/dist/react-native/main.js.map +1 -1
- package/dist/web/RaftChannel.d.ts +2 -0
- package/dist/web/RaftChannelBLE.web.d.ts +2 -0
- package/dist/web/RaftChannelBLE.web.js +10 -0
- package/dist/web/RaftChannelBLE.web.js.map +1 -1
- package/dist/web/RaftChannelSimulated.d.ts +32 -0
- package/dist/web/RaftChannelSimulated.js +418 -0
- package/dist/web/RaftChannelSimulated.js.map +1 -0
- package/dist/web/RaftChannelWebSerial.d.ts +2 -0
- package/dist/web/RaftChannelWebSerial.js +10 -0
- package/dist/web/RaftChannelWebSerial.js.map +1 -1
- package/dist/web/RaftChannelWebSocket.d.ts +2 -0
- package/dist/web/RaftChannelWebSocket.js +10 -0
- package/dist/web/RaftChannelWebSocket.js.map +1 -1
- package/dist/web/RaftConnector.js +5 -0
- package/dist/web/RaftConnector.js.map +1 -1
- package/dist/web/RaftDeviceInfo.d.ts +1 -1
- package/dist/web/RaftDeviceManager.js +1 -1
- package/dist/web/RaftDeviceManager.js.map +1 -1
- package/dist/web/RaftMsgHandler.d.ts +5 -0
- package/dist/web/RaftMsgHandler.js +32 -0
- package/dist/web/RaftMsgHandler.js.map +1 -1
- package/dist/web/RaftStruct.js +1 -1
- package/dist/web/RaftStruct.js.map +1 -1
- package/dist/web/main.d.ts +2 -0
- package/dist/web/main.js +4 -1
- package/dist/web/main.js.map +1 -1
- package/package.json +1 -1
- package/src/RaftChannel.ts +2 -0
- package/src/RaftChannelBLE.native.ts +12 -0
- package/src/RaftChannelBLE.web.ts +13 -0
- package/src/RaftChannelSimulated.ts +482 -0
- package/src/RaftChannelWebSerial.ts +12 -0
- package/src/RaftChannelWebSocket.ts +13 -0
- package/src/RaftConnector.ts +4 -0
- package/src/RaftDeviceInfo.ts +1 -1
- package/src/RaftDeviceManager.ts +1 -1
- package/src/RaftMsgHandler.ts +36 -0
- package/src/RaftStruct.ts +1 -1
- 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.error(`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 = 1000;
|
|
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.error(`RaftChannelSimulated.connect - device type info not found for ${deviceName}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
RaftLog.error(`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": "dps",
|
|
427
|
+
"r": [-2000, 2000],
|
|
428
|
+
"d": 16.384,
|
|
429
|
+
"f": ".2f",
|
|
430
|
+
"o": "float"
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
"n": "gy",
|
|
434
|
+
"t": "<h",
|
|
435
|
+
"u": "dps",
|
|
436
|
+
"r": [-2000, 2000],
|
|
437
|
+
"d": 16.384,
|
|
438
|
+
"f": ".2f",
|
|
439
|
+
"o": "float"
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
"n": "gz",
|
|
443
|
+
"t": "<h",
|
|
444
|
+
"u": "dps",
|
|
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
|
+
}
|
|
@@ -405,4 +405,16 @@ export default class RaftChannelWebSerial implements RaftChannel {
|
|
|
405
405
|
this._isConnected = false;
|
|
406
406
|
RaftLog.debug("Finished read loop");
|
|
407
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
|
+
}
|
|
408
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
|
}
|
package/src/RaftConnector.ts
CHANGED
|
@@ -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 } from "./RaftTypes";
|
|
17
18
|
import RaftSystemUtils from "./RaftSystemUtils";
|
|
@@ -219,6 +220,9 @@ export default class RaftConnector {
|
|
|
219
220
|
} else if (method === 'WebSerial') {
|
|
220
221
|
this._raftChannel = new RaftChannelWebSerial();
|
|
221
222
|
this._channelConnMethod = 'WebSerial';
|
|
223
|
+
} else if (method === 'Simulated') {
|
|
224
|
+
this._raftChannel = new RaftChannelSimulated();
|
|
225
|
+
this._channelConnMethod = 'Simulated';
|
|
222
226
|
} else {
|
|
223
227
|
RaftLog.error('Unknown method: ' + method);
|
|
224
228
|
return false;
|
package/src/RaftDeviceInfo.ts
CHANGED
package/src/RaftDeviceManager.ts
CHANGED
|
@@ -480,7 +480,7 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
480
480
|
const msgHandler = this._systemUtils?.getMsgHandler();
|
|
481
481
|
if (msgHandler) {
|
|
482
482
|
const msgRslt = await msgHandler.sendRICRESTURL<RaftDevTypeInfoResponse>(cmd);
|
|
483
|
-
if (msgRslt.rslt === "ok") {
|
|
483
|
+
if (msgRslt && msgRslt.rslt && msgRslt.rslt === "ok") {
|
|
484
484
|
this._cachedDeviceTypeRecs[deviceType] = msgRslt.devinfo;
|
|
485
485
|
return msgRslt.devinfo
|
|
486
486
|
}
|
package/src/RaftMsgHandler.ts
CHANGED
|
@@ -60,6 +60,8 @@ export interface RaftMessageSender {
|
|
|
60
60
|
msg: Uint8Array,
|
|
61
61
|
sendWithResponse: boolean,
|
|
62
62
|
): Promise<boolean>;
|
|
63
|
+
sendTxMsgRaw(msg: string): boolean;
|
|
64
|
+
sendTxMsgRawAndWaitForReply<T>(msgPayload: Uint8Array): T;
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
export default class RaftMsgHandler {
|
|
@@ -87,6 +89,9 @@ export default class RaftMsgHandler {
|
|
|
87
89
|
// RaftMiniHDLC - handles part of RICSerial protocol
|
|
88
90
|
private _miniHDLC: RaftMiniHDLC;
|
|
89
91
|
|
|
92
|
+
// Raw message mode
|
|
93
|
+
private _rawMsgMode = false;
|
|
94
|
+
|
|
90
95
|
// Constructor
|
|
91
96
|
constructor(commsStats: RaftCommsStats) {
|
|
92
97
|
this._commsStats = commsStats;
|
|
@@ -120,6 +125,22 @@ export default class RaftMsgHandler {
|
|
|
120
125
|
// RaftLog.verbose(`handleNewRxMsg len ${rxMsg.length} ${RaftUtils.bufferToHex(rxMsg)}`)
|
|
121
126
|
}
|
|
122
127
|
|
|
128
|
+
// This is currently only for testing and simulated channels
|
|
129
|
+
handleNewRxMsgRaw(rxMsg: Uint8Array, rxMsgType: RaftCommsMsgTypeCode, rxMsgNum: number, rxMsgTimeMs: number): void {
|
|
130
|
+
// Check message types
|
|
131
|
+
if (rxMsgType == RaftCommsMsgTypeCode.MSG_TYPE_RESPONSE) {
|
|
132
|
+
// Convert raw Uint8Array to string assuming UTF-8
|
|
133
|
+
const restStr = new TextDecoder('utf-8').decode(rxMsg);
|
|
134
|
+
this._handleResponseMessages(restStr, rxMsgNum);
|
|
135
|
+
} else if (rxMsgType == RaftCommsMsgTypeCode.MSG_TYPE_REPORT) {
|
|
136
|
+
// Convert raw Uint8Array to string assuming UTF-8
|
|
137
|
+
const restStr = new TextDecoder('utf-8').decode(rxMsg);
|
|
138
|
+
this._handleReportMessages(restStr);
|
|
139
|
+
} else {
|
|
140
|
+
this._msgResultHandler?.onRxOtherMsgType(rxMsg, rxMsgTimeMs);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
123
144
|
reportMsgCallbacksSet(callbackName: string, callback: (report: RaftReportMsg) => void): void {
|
|
124
145
|
this._reportMsgCallbacks.set(callbackName, callback);
|
|
125
146
|
}
|
|
@@ -128,6 +149,11 @@ export default class RaftMsgHandler {
|
|
|
128
149
|
this._reportMsgCallbacks.delete(callbackName);
|
|
129
150
|
}
|
|
130
151
|
|
|
152
|
+
setRawMsgMode(isRawMode: boolean): void {
|
|
153
|
+
this._rawMsgMode = isRawMode;
|
|
154
|
+
RaftLog.debug(`setRawMsgMode ${isRawMode}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
131
157
|
_onHDLCFrameDecode(rxMsg: Uint8Array, frameTimeMs: number): void {
|
|
132
158
|
// Add to stats
|
|
133
159
|
this._commsStats.msgRx();
|
|
@@ -336,6 +362,11 @@ export default class RaftMsgHandler {
|
|
|
336
362
|
return false;
|
|
337
363
|
}
|
|
338
364
|
|
|
365
|
+
// Check for raw message mode (used for testing and simulated channels)
|
|
366
|
+
if (this._rawMsgMode) {
|
|
367
|
+
return this._msgSender.sendTxMsgRaw(cmdStr);
|
|
368
|
+
}
|
|
369
|
+
|
|
339
370
|
// Put cmdStr into buffer
|
|
340
371
|
const cmdBytes = new Uint8Array(cmdStr.length + 1);
|
|
341
372
|
RaftUtils.addStringToBuffer(cmdBytes, cmdStr, 0);
|
|
@@ -406,6 +437,11 @@ export default class RaftMsgHandler {
|
|
|
406
437
|
throw new Error('sendMsgAndWaitForReply failed no sender');
|
|
407
438
|
}
|
|
408
439
|
|
|
440
|
+
// Check for raw message mode (used for testing and simulated channels)
|
|
441
|
+
if (this._rawMsgMode) {
|
|
442
|
+
return this._msgSender.sendTxMsgRawAndWaitForReply(msgPayload);
|
|
443
|
+
}
|
|
444
|
+
|
|
409
445
|
// Frame the message
|
|
410
446
|
let framedMsg = this.frameCommsMsg(msgPayload, msgDirection, msgProtocol, true);
|
|
411
447
|
|
package/src/RaftStruct.ts
CHANGED
|
@@ -134,7 +134,7 @@ export function structPack(format: string, values: number[]): Uint8Array {
|
|
|
134
134
|
let offset = 0;
|
|
135
135
|
let littleEndian = false;
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
const valIdx = 0;
|
|
138
138
|
for (let i = 0; i < format.length; i++) {
|
|
139
139
|
const char = format[i];
|
|
140
140
|
const value = values[valIdx];
|
package/src/main.ts
CHANGED
|
@@ -16,6 +16,7 @@ export { default as RaftCommsStats } from './RaftCommsStats';
|
|
|
16
16
|
export { default as RaftConnector } from './RaftConnector';
|
|
17
17
|
export { default as RaftChannel } from './RaftChannel';
|
|
18
18
|
export { default as RaftChannelWebSocket } from './RaftChannelWebSocket';
|
|
19
|
+
export { default as RaftChannelSimulated } from './RaftChannelSimulated';
|
|
19
20
|
export { default as RaftFileHandler } from './RaftFileHandler';
|
|
20
21
|
export { default as RaftLog } from './RaftLog';
|
|
21
22
|
export { default as RaftMiniHDLC } from './RaftMiniHDLC';
|
|
@@ -34,3 +35,4 @@ export * from './RaftConnEvents';
|
|
|
34
35
|
export * from './RaftUpdateEvents';
|
|
35
36
|
export * from "./RaftProtocolDefs";
|
|
36
37
|
export * from "./RaftDeviceStates";
|
|
38
|
+
export * from "./RaftDeviceInfo";
|