@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.
- package/dist/react-native/RaftAttributeHandler.d.ts +2 -0
- package/dist/react-native/RaftAttributeHandler.js +136 -10
- package/dist/react-native/RaftAttributeHandler.js.map +1 -1
- 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 +14 -7
- 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 +24 -15
- 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 +18 -9
- 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 +13 -6
- package/dist/react-native/RaftConnector.js.map +1 -1
- package/dist/react-native/RaftCustomAttrHandler.js +15 -0
- package/dist/react-native/RaftCustomAttrHandler.js.map +1 -1
- package/dist/react-native/RaftDeviceInfo.d.ts +8 -1
- package/dist/react-native/RaftDeviceInfo.js +17 -3
- package/dist/react-native/RaftDeviceInfo.js.map +1 -1
- package/dist/react-native/RaftDeviceManager.d.ts +3 -0
- package/dist/react-native/RaftDeviceManager.js +123 -43
- package/dist/react-native/RaftDeviceManager.js.map +1 -1
- package/dist/react-native/RaftFileHandler.js +8 -8
- package/dist/react-native/RaftFileHandler.js.map +1 -1
- package/dist/react-native/RaftLog.js +1 -1
- package/dist/react-native/RaftLog.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/RaftStreamHandler.js +6 -5
- package/dist/react-native/RaftStreamHandler.js.map +1 -1
- package/dist/react-native/RaftStruct.js +197 -147
- package/dist/react-native/RaftStruct.js.map +1 -1
- package/dist/react-native/RaftSysTypeManager.d.ts +2 -0
- package/dist/react-native/RaftSysTypeManager.js +25 -0
- package/dist/react-native/RaftSysTypeManager.js.map +1 -1
- package/dist/react-native/RaftSystemType.d.ts +9 -7
- package/dist/react-native/RaftSystemUtils.js +3 -1
- package/dist/react-native/RaftSystemUtils.js.map +1 -1
- package/dist/react-native/RaftUpdateManager.js +2 -2
- package/dist/react-native/RaftUpdateManager.js.map +1 -1
- package/dist/react-native/RaftUtils.d.ts +2 -0
- package/dist/react-native/RaftUtils.js +22 -2
- package/dist/react-native/RaftUtils.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/RaftAttributeHandler.d.ts +2 -0
- package/dist/web/RaftAttributeHandler.js +136 -10
- package/dist/web/RaftAttributeHandler.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 +24 -15
- 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 +18 -9
- 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 +13 -6
- package/dist/web/RaftConnector.js.map +1 -1
- package/dist/web/RaftCustomAttrHandler.js +15 -0
- package/dist/web/RaftCustomAttrHandler.js.map +1 -1
- package/dist/web/RaftDeviceInfo.d.ts +8 -1
- package/dist/web/RaftDeviceInfo.js +17 -3
- package/dist/web/RaftDeviceInfo.js.map +1 -1
- package/dist/web/RaftDeviceManager.d.ts +3 -0
- package/dist/web/RaftDeviceManager.js +123 -43
- package/dist/web/RaftDeviceManager.js.map +1 -1
- package/dist/web/RaftFileHandler.js +8 -8
- package/dist/web/RaftFileHandler.js.map +1 -1
- package/dist/web/RaftLog.js +1 -1
- package/dist/web/RaftLog.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/RaftStreamHandler.js +6 -5
- package/dist/web/RaftStreamHandler.js.map +1 -1
- package/dist/web/RaftStruct.js +197 -147
- package/dist/web/RaftStruct.js.map +1 -1
- package/dist/web/RaftSysTypeManager.d.ts +2 -0
- package/dist/web/RaftSysTypeManager.js +25 -0
- package/dist/web/RaftSysTypeManager.js.map +1 -1
- package/dist/web/RaftSystemType.d.ts +9 -7
- package/dist/web/RaftSystemUtils.js +3 -1
- package/dist/web/RaftSystemUtils.js.map +1 -1
- package/dist/web/RaftUpdateManager.js +2 -2
- package/dist/web/RaftUpdateManager.js.map +1 -1
- package/dist/web/RaftUtils.d.ts +2 -0
- package/dist/web/RaftUtils.js +22 -2
- package/dist/web/RaftUtils.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/examples/dashboard/package.json +1 -1
- package/examples/dashboard/src/CommandPanel.tsx +3 -3
- package/examples/dashboard/src/ConnManager.ts +83 -6
- package/examples/dashboard/src/DeviceActionsForm.tsx +2 -2
- package/examples/dashboard/src/DevicePanel.tsx +2 -2
- package/examples/dashboard/src/Main.tsx +14 -4
- package/examples/dashboard/src/SystemTypeMarty/RICSystemUtils.ts +4 -4
- package/examples/dashboard/src/styles.css +8 -0
- package/examples/dashboard/tsconfig.json +1 -1
- package/package.json +10 -11
- package/src/RaftAttributeHandler.ts +163 -11
- package/src/RaftChannel.ts +2 -0
- package/src/RaftChannelBLE.native.ts +17 -8
- package/src/RaftChannelBLE.web.ts +28 -16
- package/src/RaftChannelSimulated.ts +482 -0
- package/src/RaftChannelWebSerial.ts +18 -7
- package/src/RaftChannelWebSocket.ts +13 -0
- package/src/RaftConnector.ts +13 -7
- package/src/RaftCustomAttrHandler.ts +17 -0
- package/src/RaftDeviceInfo.ts +27 -5
- package/src/RaftDeviceManager.ts +155 -47
- package/src/RaftFileHandler.ts +8 -8
- package/src/RaftLog.ts +1 -1
- package/src/RaftMsgHandler.ts +36 -0
- package/src/RaftStreamHandler.ts +48 -47
- package/src/RaftStruct.ts +220 -147
- package/src/RaftSysTypeManager.ts +27 -0
- package/src/RaftSystemType.ts +9 -7
- package/src/RaftSystemUtils.ts +3 -1
- package/src/RaftUpdateManager.ts +2 -2
- package/src/RaftUtils.ts +25 -5
- 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": "°/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": "°/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": "°/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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|
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, 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|