@robdobsn/raftjs 1.10.7 → 1.11.5
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/RaftChannelSimulated.js +4 -3
- package/dist/react-native/RaftChannelSimulated.js.map +1 -1
- package/dist/react-native/RaftConnector.d.ts +10 -1
- package/dist/react-native/RaftConnector.js +23 -10
- package/dist/react-native/RaftConnector.js.map +1 -1
- package/dist/react-native/RaftDeviceManager.d.ts +13 -1
- package/dist/react-native/RaftDeviceManager.js +224 -77
- package/dist/react-native/RaftDeviceManager.js.map +1 -1
- package/dist/react-native/RaftDeviceMgrIF.d.ts +5 -1
- package/dist/react-native/RaftDeviceStates.d.ts +20 -2
- package/dist/react-native/RaftDeviceStates.js +25 -4
- package/dist/react-native/RaftDeviceStates.js.map +1 -1
- package/dist/react-native/RaftPublish.d.ts +2 -0
- package/dist/react-native/RaftPublish.js +81 -0
- package/dist/react-native/RaftPublish.js.map +1 -0
- package/dist/react-native/RaftStreamHandler.d.ts +11 -0
- package/dist/react-native/RaftStreamHandler.js +66 -0
- package/dist/react-native/RaftStreamHandler.js.map +1 -1
- package/dist/react-native/RaftSystemUtils.d.ts +17 -1
- package/dist/react-native/RaftSystemUtils.js +51 -0
- package/dist/react-native/RaftSystemUtils.js.map +1 -1
- package/dist/react-native/RaftTypes.d.ts +21 -0
- package/dist/react-native/RaftTypes.js.map +1 -1
- package/dist/react-native/main.d.ts +1 -0
- package/dist/react-native/main.js +1 -0
- package/dist/react-native/main.js.map +1 -1
- package/dist/web/RaftChannelSimulated.js +4 -3
- package/dist/web/RaftChannelSimulated.js.map +1 -1
- package/dist/web/RaftConnector.d.ts +10 -1
- package/dist/web/RaftConnector.js +23 -10
- package/dist/web/RaftConnector.js.map +1 -1
- package/dist/web/RaftDeviceManager.d.ts +13 -1
- package/dist/web/RaftDeviceManager.js +224 -77
- package/dist/web/RaftDeviceManager.js.map +1 -1
- package/dist/web/RaftDeviceMgrIF.d.ts +5 -1
- package/dist/web/RaftDeviceStates.d.ts +20 -2
- package/dist/web/RaftDeviceStates.js +25 -4
- package/dist/web/RaftDeviceStates.js.map +1 -1
- package/dist/web/RaftPublish.d.ts +2 -0
- package/dist/web/RaftPublish.js +81 -0
- package/dist/web/RaftPublish.js.map +1 -0
- package/dist/web/RaftStreamHandler.d.ts +11 -0
- package/dist/web/RaftStreamHandler.js +66 -0
- package/dist/web/RaftStreamHandler.js.map +1 -1
- package/dist/web/RaftSystemUtils.d.ts +17 -1
- package/dist/web/RaftSystemUtils.js +51 -0
- package/dist/web/RaftSystemUtils.js.map +1 -1
- package/dist/web/RaftTypes.d.ts +21 -0
- package/dist/web/RaftTypes.js.map +1 -1
- package/dist/web/main.d.ts +1 -0
- package/dist/web/main.js +1 -0
- package/dist/web/main.js.map +1 -1
- package/examples/dashboard/package.json +1 -1
- package/examples/dashboard/src/DeviceActionsForm.tsx +2 -2
- package/examples/dashboard/src/DevicePanel.tsx +79 -3
- package/examples/dashboard/src/DeviceStatsPanel.tsx +65 -0
- package/examples/dashboard/src/DevicesPanel.tsx +11 -0
- package/examples/dashboard/src/SettingsScreen.tsx +9 -4
- package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +10 -2
- package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +37 -3
- package/examples/dashboard/src/SystemTypeGeneric/StateInfoGeneric.ts +10 -2
- package/examples/dashboard/src/SystemTypeGeneric/SystemTypeGeneric.ts +38 -4
- package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +10 -2
- package/examples/dashboard/src/styles.css +162 -0
- package/package.json +49 -49
- package/src/RaftChannelSimulated.ts +4 -3
- package/src/RaftConnector.ts +34 -13
- package/src/RaftDeviceManager.ts +251 -81
- package/src/RaftDeviceMgrIF.ts +5 -1
- package/src/RaftDeviceStates.ts +35 -5
- package/src/RaftPublish.ts +92 -0
- package/src/RaftStreamHandler.ts +84 -1
- package/src/RaftSystemUtils.ts +59 -0
- package/src/RaftTypes.ts +27 -0
- package/src/main.ts +1 -0
package/src/RaftDeviceManager.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
//
|
|
8
8
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
9
9
|
|
|
10
|
-
import { DeviceAttributeState, DeviceAttributesState, DevicesState, DeviceState, getDeviceKey } from "./RaftDeviceStates";
|
|
10
|
+
import { DeviceAttributeState, DeviceAttributesState, DevicesState, DeviceState, DeviceStats, DeviceOnlineState, formatDeviceAddrHex, getDeviceKey, parseDeviceKey } from "./RaftDeviceStates";
|
|
11
11
|
import { DeviceMsgJson } from "./RaftDeviceMsg";
|
|
12
12
|
import { RaftOKFail } from './RaftTypes';
|
|
13
13
|
import { DeviceTypeInfo, DeviceTypeAction, DeviceTypeInfoRecs, RaftDevTypeInfoResponse } from "./RaftDeviceInfo";
|
|
@@ -29,6 +29,10 @@ export interface DeviceDecodedData {
|
|
|
29
29
|
fromOfflineBuffer?: boolean;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
interface DeviceStatsInternal extends DeviceStats {
|
|
33
|
+
windowEvents: Array<{ timeMs: number; samples: number }>;
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
export class DeviceManager implements RaftDeviceMgrIF{
|
|
33
37
|
|
|
34
38
|
// Max data points to store
|
|
@@ -58,10 +62,15 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
58
62
|
private _newDeviceAttributeCallbacks: Array<(deviceKey: string, attrState: DeviceAttributeState) => void> = [];
|
|
59
63
|
private _newAttributeDataCallbacks: Array<(deviceKey: string, attrState: DeviceAttributeState) => void> = [];
|
|
60
64
|
private _decodedDataCallbacks: Array<(decoded: DeviceDecodedData) => void> = [];
|
|
65
|
+
private _deviceRemovedCallbacks: Array<(deviceKey: string, state: DeviceState) => void> = [];
|
|
61
66
|
|
|
62
67
|
// Debug message index (to help debug with async messages)
|
|
63
68
|
private _debugMsgIndex = 0;
|
|
64
69
|
|
|
70
|
+
// Device stats (sample counts, rates)
|
|
71
|
+
private _statsWindowMs = 5000;
|
|
72
|
+
private _deviceStats: { [deviceKey: string]: DeviceStatsInternal } = {};
|
|
73
|
+
|
|
65
74
|
public getDevicesState(): DevicesState {
|
|
66
75
|
return this._devicesState;
|
|
67
76
|
}
|
|
@@ -70,6 +79,14 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
70
79
|
return this._devicesState[deviceKey];
|
|
71
80
|
}
|
|
72
81
|
|
|
82
|
+
public getDeviceStats(deviceKey: string): DeviceStats {
|
|
83
|
+
return this.cloneDeviceStats(this.getOrCreateDeviceStats(deviceKey));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public resetDeviceStats(deviceKey: string): void {
|
|
87
|
+
this._deviceStats[deviceKey] = this.createEmptyStats();
|
|
88
|
+
}
|
|
89
|
+
|
|
73
90
|
// Cached device type data
|
|
74
91
|
private _cachedDeviceTypeRecs: DeviceTypeInfoRecs = {};
|
|
75
92
|
|
|
@@ -169,6 +186,16 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
169
186
|
this._decodedDataCallbacks = this._decodedDataCallbacks.filter((cb) => cb !== callback);
|
|
170
187
|
}
|
|
171
188
|
|
|
189
|
+
public addDeviceRemovedCallback(callback: (deviceKey: string, state: DeviceState) => void): void {
|
|
190
|
+
if (!this._deviceRemovedCallbacks.includes(callback)) {
|
|
191
|
+
this._deviceRemovedCallbacks.push(callback);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public removeDeviceRemovedCallback(callback: (deviceKey: string, state: DeviceState) => void): void {
|
|
196
|
+
this._deviceRemovedCallbacks = this._deviceRemovedCallbacks.filter((cb) => cb !== callback);
|
|
197
|
+
}
|
|
198
|
+
|
|
172
199
|
////////////////////////////////////////////////////////////////////////////
|
|
173
200
|
// Set the friendly name for the device
|
|
174
201
|
////////////////////////////////////////////////////////////////////////////
|
|
@@ -185,79 +212,129 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
185
212
|
public async handleClientMsgBinary(rxMsg: Uint8Array) {
|
|
186
213
|
// console.log(`DeviceManager client1 msg ${RaftUtils.bufferToHex(rxMsg)}`);
|
|
187
214
|
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
215
|
+
// DevBIN message format
|
|
216
|
+
//
|
|
217
|
+
// The rxMsg passed to this function has a 2-byte message type prefix (e.g. 0x0080)
|
|
218
|
+
// added by the transport layer. After that prefix comes a devbin frame:
|
|
219
|
+
//
|
|
220
|
+
// Devbin envelope (2 bytes):
|
|
221
|
+
// Byte 0: magic+version 0xDB = devbin v1 (valid range 0xDB–0xDF for v1–v5)
|
|
222
|
+
// Byte 1: topicIndex 0x00–0xFE = topic index; 0xFF = no topic
|
|
223
|
+
//
|
|
224
|
+
// Then zero or more per-device records, concatenated back-to-back:
|
|
225
|
+
// Bytes 0-1: recordLen uint16 big-endian — number of body bytes that follow (min 7)
|
|
226
|
+
// Byte 2: statusBus bit 7 = online flag, bit 6 = pending deletion, bits 5:4 = reserved, bits 3:0 = bus number (0-15)
|
|
227
|
+
// Bytes 3-6: address uint32 big-endian — device address on the bus
|
|
228
|
+
// Bytes 7-8: devTypeIdx uint16 big-endian — device type table index
|
|
229
|
+
// Bytes 9+: pollData variable length (recordLen − 7 bytes) — device data
|
|
230
|
+
//
|
|
231
|
+
// Example message (with transport prefix):
|
|
232
|
+
// 0080 DB01 0015 81 0000076a 000b bff10000ffffffff7a07d1f1221c 000e 80 00000000 001f bc340000030001
|
|
233
|
+
// ^^^^ ^^^^ ^^^^
|
|
234
|
+
// | | | || | | | Record 2 ...
|
|
235
|
+
// | | | || | | pollData (14 bytes)
|
|
236
|
+
// | | | || | devTypeIdx = 0x000b (11)
|
|
237
|
+
// | | | || address = 0x0000076a (slot 7, I2C addr 0x6a)
|
|
238
|
+
// | | | |busInfo = 0x81 (bus 1, online)
|
|
239
|
+
// | | | recordLen = 0x0015 (21 bytes)
|
|
240
|
+
// | | topicIndex = 0x01
|
|
241
|
+
// | magic+version = 0xDB (devbin v1)
|
|
242
|
+
// msgType prefix (transport layer)
|
|
205
243
|
|
|
206
244
|
// Debug
|
|
207
245
|
// const debugMsgTime = Date.now();
|
|
208
246
|
const debugMsgIndex = this._debugMsgIndex++;
|
|
209
247
|
|
|
210
|
-
// Message layout
|
|
211
|
-
const msgTypeLen = 2; //
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
const
|
|
248
|
+
// Message layout constants
|
|
249
|
+
const msgTypeLen = 2; // Transport-layer message type prefix (first two bytes, e.g. 0x0080)
|
|
250
|
+
const devbinEnvelopeLen = 2; // Devbin envelope: magic+version (1 byte) + topicIndex (1 byte)
|
|
251
|
+
const devbinMagicMin = 0xDB;
|
|
252
|
+
const devbinMagicMax = 0xDF;
|
|
253
|
+
const recordLenLen = 2; // Per-record length prefix (uint16 big-endian)
|
|
254
|
+
const busInfoLen = 1; // statusBus byte: bit 7 = online, bit 6 = pending deletion, bits 3:0 = bus number
|
|
255
|
+
const deviceAddrLen = 4; // Device address (uint32 big-endian)
|
|
256
|
+
const devTypeIdxLen = 2; // Device type index (uint16 big-endian)
|
|
257
|
+
const recordHeaderLen = busInfoLen + deviceAddrLen + devTypeIdxLen; // = 7, minimum record body
|
|
217
258
|
|
|
218
259
|
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} rxMsg.length ${rxMsg.length} rxMsg ${RaftUtils.bufferToHex(rxMsg)}`);
|
|
219
260
|
|
|
220
261
|
// Start after the message type
|
|
221
262
|
let msgPos = msgTypeLen;
|
|
222
263
|
|
|
223
|
-
//
|
|
264
|
+
// Check for devbin envelope (magic+version + topicIndex)
|
|
265
|
+
if (rxMsg.length >= msgTypeLen + devbinEnvelopeLen) {
|
|
266
|
+
const envelopeMagicVer = rxMsg[msgTypeLen];
|
|
267
|
+
if ((envelopeMagicVer & 0xF0) === 0xD0) {
|
|
268
|
+
if ((envelopeMagicVer < devbinMagicMin) || (envelopeMagicVer > devbinMagicMax)) {
|
|
269
|
+
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid devbin envelope magic/version ${envelopeMagicVer}`);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const topicIndex = rxMsg[msgTypeLen + 1];
|
|
274
|
+
if (topicIndex !== 0xFF) {
|
|
275
|
+
const topicName = this._systemUtils?.getPublishTopicName(topicIndex);
|
|
276
|
+
if (topicName && topicName !== "devbin") {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
msgPos += devbinEnvelopeLen;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Iterate through device records
|
|
224
286
|
while (msgPos < rxMsg.length) {
|
|
225
287
|
|
|
226
|
-
// Check length
|
|
288
|
+
// Check minimum length for record length prefix + record header
|
|
227
289
|
const remainingLen = rxMsg.length - msgPos;
|
|
228
|
-
if (remainingLen <
|
|
229
|
-
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid length ${rxMsg.length} < ${
|
|
290
|
+
if (remainingLen < recordLenLen + recordHeaderLen) {
|
|
291
|
+
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid length ${rxMsg.length} < ${recordLenLen + recordHeaderLen + msgPos}`);
|
|
230
292
|
return;
|
|
231
293
|
}
|
|
232
294
|
|
|
233
|
-
// Get the length
|
|
234
|
-
const
|
|
235
|
-
if (
|
|
236
|
-
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid msgPos ${msgPos}
|
|
295
|
+
// Get the record body length (bytes that follow the 2-byte length prefix)
|
|
296
|
+
const recordLen = (rxMsg[msgPos] << 8) + rxMsg[msgPos + 1];
|
|
297
|
+
if (recordLen > remainingLen - recordLenLen) {
|
|
298
|
+
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid msgPos ${msgPos} recordLen ${recordLen} remainingAfterLenBytes ${remainingLen - recordLenLen}`);
|
|
237
299
|
return;
|
|
238
300
|
}
|
|
239
301
|
|
|
240
|
-
// Extract
|
|
241
|
-
let
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
302
|
+
// Extract record header fields
|
|
303
|
+
let recordPos = msgPos + recordLenLen;
|
|
304
|
+
|
|
305
|
+
// statusBus byte: bit 7 = online, bit 6 = pending deletion, bits 3:0 = bus number
|
|
306
|
+
const statusByte = rxMsg[recordPos];
|
|
307
|
+
const busNum = statusByte & 0x0f;
|
|
308
|
+
const isOnline = (statusByte & 0x80) !== 0;
|
|
309
|
+
const isPendingDeletion = (statusByte & 0x40) !== 0;
|
|
310
|
+
recordPos += busInfoLen;
|
|
311
|
+
|
|
312
|
+
// Device address (uint32 big-endian)
|
|
313
|
+
const devAddr = (rxMsg[recordPos] << 24) + (rxMsg[recordPos + 1] << 16) + (rxMsg[recordPos + 2] << 8) + rxMsg[recordPos + 3];
|
|
314
|
+
recordPos += deviceAddrLen;
|
|
315
|
+
|
|
316
|
+
// Device type index (uint16 big-endian)
|
|
317
|
+
const devTypeIdx = (rxMsg[recordPos] << 8) + rxMsg[recordPos + 1];
|
|
318
|
+
let pollDataPos = recordPos + devTypeIdxLen;
|
|
250
319
|
|
|
251
320
|
// Debug
|
|
252
|
-
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} overallLen ${rxMsg.length}
|
|
253
|
-
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex}
|
|
321
|
+
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} overallLen ${rxMsg.length} recordStart ${msgPos} recordLen ${recordLen} ${pollDataPos} ${RaftUtils.bufferToHex(rxMsg.slice(msgPos, msgPos + recordLenLen + recordLen))}`);
|
|
322
|
+
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} bus ${busNum} isOnline ${isOnline} devAddr 0x${devAddr.toString(16)} devTypeIdx ${devTypeIdx} pollDataLen ${recordLen - recordHeaderLen}`);
|
|
254
323
|
|
|
255
|
-
//
|
|
256
|
-
const
|
|
324
|
+
// Format device address as canonical hex and build device key
|
|
325
|
+
const devAddrHex = formatDeviceAddrHex(devAddr);
|
|
326
|
+
const deviceKey = getDeviceKey(busNum.toString(), devAddrHex);
|
|
257
327
|
|
|
258
328
|
// Update the last update time
|
|
259
329
|
this._deviceLastUpdateTime[deviceKey] = Date.now();
|
|
260
330
|
|
|
331
|
+
// Handle pending deletion - remove device and skip further processing
|
|
332
|
+
if (isPendingDeletion) {
|
|
333
|
+
this.removeDevice(deviceKey);
|
|
334
|
+
msgPos += recordLenLen + recordLen;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
|
|
261
338
|
// Check if a device state already exists
|
|
262
339
|
if (!(deviceKey in this._devicesState) || (this._devicesState[deviceKey].deviceTypeInfo === undefined)) {
|
|
263
340
|
|
|
@@ -265,13 +342,13 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
265
342
|
const deviceTypeInfo = await this.getDeviceTypeInfo(busNum.toString(), devTypeIdx.toString());
|
|
266
343
|
|
|
267
344
|
// Debug
|
|
268
|
-
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex}
|
|
345
|
+
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} pollDataPos ${pollDataPos} busNum ${busNum} devAddr 0x${devAddr.toString(16)} devTypeIdx ${devTypeIdx} deviceTypeInfo ${JSON.stringify(deviceTypeInfo)}`);
|
|
269
346
|
|
|
270
347
|
// Handle case where device type info is not available
|
|
271
348
|
if (deviceTypeInfo === undefined) {
|
|
272
|
-
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} deviceType ${devTypeIdx} info not available, skipping attribute processing for this
|
|
273
|
-
// Skip to next
|
|
274
|
-
msgPos +=
|
|
349
|
+
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} deviceType ${devTypeIdx} info not available, skipping attribute processing for this record`);
|
|
350
|
+
// Skip to next record without processing attributes
|
|
351
|
+
msgPos += recordLenLen + recordLen;
|
|
275
352
|
continue;
|
|
276
353
|
}
|
|
277
354
|
|
|
@@ -281,7 +358,7 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
281
358
|
this._devicesState[deviceKey].deviceTypeInfo = deviceTypeInfo;
|
|
282
359
|
this._devicesState[deviceKey].deviceType = deviceTypeInfo.name || "";
|
|
283
360
|
this._devicesState[deviceKey].busName = busNum.toString();
|
|
284
|
-
this._devicesState[deviceKey].deviceAddress =
|
|
361
|
+
this._devicesState[deviceKey].deviceAddress = devAddrHex;
|
|
285
362
|
}
|
|
286
363
|
} else {
|
|
287
364
|
// Create device record - device type info may be undefined
|
|
@@ -295,8 +372,8 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
295
372
|
deviceAttributes: {},
|
|
296
373
|
deviceIsNew: true,
|
|
297
374
|
stateChanged: false,
|
|
298
|
-
|
|
299
|
-
deviceAddress:
|
|
375
|
+
onlineState: DeviceOnlineState.Online,
|
|
376
|
+
deviceAddress: devAddrHex,
|
|
300
377
|
deviceType: deviceTypeInfo?.name || "",
|
|
301
378
|
busName: busNum.toString()
|
|
302
379
|
};
|
|
@@ -305,7 +382,7 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
305
382
|
|
|
306
383
|
// Get device state
|
|
307
384
|
const deviceState = this._devicesState[deviceKey];
|
|
308
|
-
deviceState.
|
|
385
|
+
deviceState.onlineState = isOnline ? DeviceOnlineState.Online : DeviceOnlineState.Offline;
|
|
309
386
|
|
|
310
387
|
// Check if device type info is available and complete
|
|
311
388
|
if (deviceState.deviceTypeInfo && deviceState.deviceTypeInfo.resp) {
|
|
@@ -313,25 +390,25 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
313
390
|
// Iterate over attributes in the group
|
|
314
391
|
const pollRespMetadata = deviceState.deviceTypeInfo!.resp!;
|
|
315
392
|
|
|
316
|
-
//
|
|
317
|
-
const
|
|
318
|
-
const
|
|
393
|
+
// Process poll data (recordLen - recordHeaderLen bytes)
|
|
394
|
+
const pollDataLen = recordLen - recordHeaderLen;
|
|
395
|
+
const pollDataStartPos = pollDataPos;
|
|
319
396
|
const attrLengthsBefore = this.snapshotAttrLengths(deviceState.deviceAttributes, pollRespMetadata);
|
|
320
397
|
const timelineLenBefore = deviceState.deviceTimeline.timestampsUs.length;
|
|
321
|
-
while (
|
|
398
|
+
while (pollDataPos < pollDataStartPos + pollDataLen) {
|
|
322
399
|
|
|
323
400
|
// Add bounds checking
|
|
324
|
-
if (
|
|
325
|
-
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex}
|
|
401
|
+
if (pollDataPos >= rxMsg.length) {
|
|
402
|
+
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} pollDataPos ${pollDataPos} exceeds message length ${rxMsg.length}`);
|
|
326
403
|
break;
|
|
327
404
|
}
|
|
328
405
|
|
|
329
|
-
const newMsgBufIdx = this._attributeHandler.processMsgAttrGroup(rxMsg,
|
|
406
|
+
const newMsgBufIdx = this._attributeHandler.processMsgAttrGroup(rxMsg, pollDataPos,
|
|
330
407
|
deviceState.deviceTimeline, pollRespMetadata,
|
|
331
408
|
deviceState.deviceAttributes,
|
|
332
409
|
this._maxDatapointsToStore);
|
|
333
410
|
|
|
334
|
-
// console.log(`DevMan.handleClientMsgBinary decoded debugIdx ${debugMsgIndex} devType ${deviceState.deviceTypeInfo.name}
|
|
411
|
+
// console.log(`DevMan.handleClientMsgBinary decoded debugIdx ${debugMsgIndex} devType ${deviceState.deviceTypeInfo.name} pollDataLen ${pollDataLen} pollDataPos ${pollDataPos} recordLen ${recordLen} msgPos ${msgPos} rxMsgLen ${rxMsg.length} remainingLen ${remainingLen} pollRespMetadata ${JSON.stringify(pollRespMetadata)}`);
|
|
335
412
|
|
|
336
413
|
if (newMsgBufIdx < 0)
|
|
337
414
|
{
|
|
@@ -340,32 +417,34 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
340
417
|
}
|
|
341
418
|
|
|
342
419
|
// Prevent infinite loops
|
|
343
|
-
if (newMsgBufIdx <=
|
|
344
|
-
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} processMsgAttrGroup didn't advance position from ${
|
|
420
|
+
if (newMsgBufIdx <= pollDataPos) {
|
|
421
|
+
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} processMsgAttrGroup didn't advance position from ${pollDataPos} to ${newMsgBufIdx}`);
|
|
345
422
|
break;
|
|
346
423
|
}
|
|
347
424
|
|
|
348
|
-
|
|
425
|
+
pollDataPos = newMsgBufIdx;
|
|
349
426
|
deviceState.stateChanged = true;
|
|
350
427
|
|
|
351
428
|
// console.log(`debugMsgTime ${debugMsgTime} newPt debugMsgIdx ${debugMsgIndex} rxMsgLen ${rxMsg.length} devType ${deviceState.deviceTypeInfo!.name} timestampsUs ${deviceState.deviceTimeline.timestampsUs[deviceState.deviceTimeline.timestampsUs.length - 1]} curTimelineLen ${deviceState.deviceTimeline.timestampsUs.length}`);
|
|
352
429
|
|
|
353
|
-
|
|
354
|
-
// console.log(`DevMan.handleClientMsgBinary group done debugIdx ${debugMsgIndex} attrGroupPos ${attrGroupPos} sectionLen ${sectionLen} msgPos ${msgPos} rxMsgLen ${rxMsg.length} remainingLen ${remainingLen}`);
|
|
430
|
+
// console.log(`DevMan.handleClientMsgBinary group done debugIdx ${debugMsgIndex} pollDataPos ${pollDataPos} recordLen ${recordLen} msgPos ${msgPos} rxMsgLen ${rxMsg.length} remainingLen ${remainingLen}`);
|
|
355
431
|
}
|
|
356
432
|
|
|
357
433
|
// Inform decoded-data callbacks
|
|
358
|
-
this.emitDecodedData(deviceKey, busNum.toString(),
|
|
434
|
+
this.emitDecodedData(deviceKey, busNum.toString(), devAddrHex, deviceState,
|
|
359
435
|
pollRespMetadata, attrLengthsBefore, timelineLenBefore);
|
|
436
|
+
|
|
437
|
+
const newSamples = deviceState.deviceTimeline.timestampsUs.length - timelineLenBefore;
|
|
438
|
+
this.updateDeviceStats(deviceKey, newSamples, Date.now());
|
|
360
439
|
} else {
|
|
361
440
|
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} deviceState incomplete for device ${deviceKey}, skipping attribute processing`);
|
|
362
441
|
}
|
|
363
442
|
|
|
364
443
|
// Debug
|
|
365
|
-
// console.log(`DevMan.handleClientMsgBinary
|
|
444
|
+
// console.log(`DevMan.handleClientMsgBinary record done debugIdx ${debugMsgIndex} pollDataPos ${pollDataPos} recordLen ${recordLen} msgPos ${msgPos} newMsgPos ${msgPos + recordLenLen + recordLen} rxMsgLen ${rxMsg.length} remainingLen ${remainingLen}`);
|
|
366
445
|
|
|
367
|
-
//
|
|
368
|
-
msgPos +=
|
|
446
|
+
// Advance past this record (recordLenLen + recordLen bytes)
|
|
447
|
+
msgPos += recordLenLen + recordLen;
|
|
369
448
|
}
|
|
370
449
|
|
|
371
450
|
// Check for devices that have not been updated for a while
|
|
@@ -374,6 +453,7 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
374
453
|
Object.entries(this._deviceLastUpdateTime).forEach(([deviceKey, lastUpdateTime]) => {
|
|
375
454
|
if ((nowTime - lastUpdateTime) > this._removeDevicesTimeMs) {
|
|
376
455
|
delete this._devicesState[deviceKey];
|
|
456
|
+
delete this._deviceStats[deviceKey];
|
|
377
457
|
}
|
|
378
458
|
});
|
|
379
459
|
}
|
|
@@ -394,6 +474,11 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
394
474
|
// Iterate over the buses
|
|
395
475
|
Object.entries(data).forEach(([busName, devices]) => {
|
|
396
476
|
|
|
477
|
+
// Check the bus name doesn't start with _ which is reserved for non-device information such as topic name
|
|
478
|
+
if (busName.startsWith("_")) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
397
482
|
// Check for bus status info
|
|
398
483
|
if (devices && typeof devices === "object" && "_s" in devices) {
|
|
399
484
|
// console.log(`DeviceManager bus status ${JSON.stringify(devices._s)}`);
|
|
@@ -414,7 +499,7 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
414
499
|
if (attrGroups && typeof attrGroups === 'object' && "_t" in attrGroups && typeof attrGroups._t === "string") {
|
|
415
500
|
deviceTypeName = attrGroups._t || "";
|
|
416
501
|
} else if (attrGroups && typeof attrGroups === 'object' && "_i" in attrGroups && typeof attrGroups._i === "number") {
|
|
417
|
-
deviceTypeIdx = attrGroups._i
|
|
502
|
+
deviceTypeIdx = attrGroups._i ?? -1;
|
|
418
503
|
deviceTypeName = deviceTypeIdx.toString();
|
|
419
504
|
} else
|
|
420
505
|
{
|
|
@@ -454,7 +539,7 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
454
539
|
deviceAttributes: {},
|
|
455
540
|
deviceIsNew: true,
|
|
456
541
|
stateChanged: false,
|
|
457
|
-
|
|
542
|
+
onlineState: DeviceOnlineState.Online,
|
|
458
543
|
deviceAddress: devAddr,
|
|
459
544
|
deviceType: deviceTypeName,
|
|
460
545
|
busName: busName
|
|
@@ -465,9 +550,15 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
465
550
|
// Get device state
|
|
466
551
|
const deviceState = this._devicesState[deviceKey];
|
|
467
552
|
|
|
468
|
-
// Check for online/offline state information
|
|
553
|
+
// Check for online/offline/pending-deletion state information
|
|
469
554
|
if (attrGroups && typeof attrGroups === "object" && "_o" in attrGroups) {
|
|
470
|
-
|
|
555
|
+
const onlineStateVal = typeof attrGroups._o === 'number' ? attrGroups._o : parseInt(String(attrGroups._o), 10);
|
|
556
|
+
if (onlineStateVal === 2) {
|
|
557
|
+
// Pending deletion - remove device and skip further processing
|
|
558
|
+
this.removeDevice(deviceKey);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
deviceState.onlineState = onlineStateVal === 1 ? DeviceOnlineState.Online : DeviceOnlineState.Offline;
|
|
471
562
|
}
|
|
472
563
|
|
|
473
564
|
// Check if device type info is available
|
|
@@ -517,6 +608,9 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
517
608
|
|
|
518
609
|
this.emitDecodedData(deviceKey, busName, devAddr, deviceState, pollRespMetadata,
|
|
519
610
|
attrLengthsBefore, timelineLenBefore, attrGroupName, markers);
|
|
611
|
+
|
|
612
|
+
const newSamples = deviceState.deviceTimeline.timestampsUs.length - timelineLenBefore;
|
|
613
|
+
this.updateDeviceStats(deviceKey, newSamples, Date.now());
|
|
520
614
|
});
|
|
521
615
|
});
|
|
522
616
|
});
|
|
@@ -527,6 +621,7 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
527
621
|
Object.entries(this._deviceLastUpdateTime).forEach(([deviceKey, lastUpdateTime]) => {
|
|
528
622
|
if ((nowTime - lastUpdateTime) > this._removeDevicesTimeMs) {
|
|
529
623
|
delete this._devicesState[deviceKey];
|
|
624
|
+
delete this._deviceStats[deviceKey];
|
|
530
625
|
}
|
|
531
626
|
});
|
|
532
627
|
}
|
|
@@ -564,6 +659,22 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
564
659
|
});
|
|
565
660
|
}
|
|
566
661
|
|
|
662
|
+
////////////////////////////////////////////////////////////////////////////
|
|
663
|
+
// Remove a device (e.g. on pending deletion)
|
|
664
|
+
////////////////////////////////////////////////////////////////////////////
|
|
665
|
+
|
|
666
|
+
private removeDevice(deviceKey: string): void {
|
|
667
|
+
// Snapshot the state before removal for callbacks
|
|
668
|
+
const deviceState = this._devicesState[deviceKey];
|
|
669
|
+
if (deviceState) {
|
|
670
|
+
deviceState.onlineState = DeviceOnlineState.PendingDeletion;
|
|
671
|
+
this._deviceRemovedCallbacks.forEach((cb) => cb(deviceKey, deviceState));
|
|
672
|
+
}
|
|
673
|
+
delete this._devicesState[deviceKey];
|
|
674
|
+
delete this._deviceLastUpdateTime[deviceKey];
|
|
675
|
+
delete this._deviceStats[deviceKey];
|
|
676
|
+
}
|
|
677
|
+
|
|
567
678
|
////////////////////////////////////////////////////////////////////////////
|
|
568
679
|
// Get device type info
|
|
569
680
|
////////////////////////////////////////////////////////////////////////////
|
|
@@ -686,9 +797,8 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
686
797
|
// Add prefix and postfix
|
|
687
798
|
writeHexStr = (action.w ? action.w : "") + writeHexStr + (action.wz ? action.wz : "");
|
|
688
799
|
|
|
689
|
-
//
|
|
690
|
-
const devBus = deviceKey
|
|
691
|
-
const devAddr = deviceKey.split("_")[1]
|
|
800
|
+
// Parse the device key into bus and address components
|
|
801
|
+
const { bus: devBus, addr: devAddr } = parseDeviceKey(deviceKey);
|
|
692
802
|
|
|
693
803
|
// Send the action to the server
|
|
694
804
|
const cmd = "devman/cmdraw?bus=" + devBus + "&addr=" + devAddr + "&hexWr=" + writeHexStr;
|
|
@@ -755,6 +865,66 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
755
865
|
return bytes;
|
|
756
866
|
}
|
|
757
867
|
|
|
868
|
+
////////////////////////////////////////////////////////////////////////////
|
|
869
|
+
// Helpers for device stats
|
|
870
|
+
////////////////////////////////////////////////////////////////////////////
|
|
871
|
+
|
|
872
|
+
private createEmptyStats(): DeviceStatsInternal {
|
|
873
|
+
return {
|
|
874
|
+
totalSamples: 0,
|
|
875
|
+
windowMs: this._statsWindowMs,
|
|
876
|
+
windowSamples: 0,
|
|
877
|
+
sampleRateHz: 0,
|
|
878
|
+
lastSampleTimeMs: null,
|
|
879
|
+
lastUpdateTimeMs: null,
|
|
880
|
+
windowEvents: []
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
private getOrCreateDeviceStats(deviceKey: string): DeviceStatsInternal {
|
|
885
|
+
if (!this._deviceStats[deviceKey]) {
|
|
886
|
+
this._deviceStats[deviceKey] = this.createEmptyStats();
|
|
887
|
+
}
|
|
888
|
+
return this._deviceStats[deviceKey];
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
private cloneDeviceStats(stats: DeviceStatsInternal): DeviceStats {
|
|
892
|
+
return {
|
|
893
|
+
totalSamples: stats.totalSamples,
|
|
894
|
+
windowMs: stats.windowMs,
|
|
895
|
+
windowSamples: stats.windowSamples,
|
|
896
|
+
sampleRateHz: stats.sampleRateHz,
|
|
897
|
+
lastSampleTimeMs: stats.lastSampleTimeMs,
|
|
898
|
+
lastUpdateTimeMs: stats.lastUpdateTimeMs
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
private updateDeviceStats(deviceKey: string, newSamples: number, nowMs: number): void {
|
|
903
|
+
const stats = this.getOrCreateDeviceStats(deviceKey);
|
|
904
|
+
stats.lastUpdateTimeMs = nowMs;
|
|
905
|
+
|
|
906
|
+
if (newSamples > 0) {
|
|
907
|
+
stats.totalSamples += newSamples;
|
|
908
|
+
stats.lastSampleTimeMs = nowMs;
|
|
909
|
+
stats.windowEvents.push({ timeMs: nowMs, samples: newSamples });
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
const windowStartMs = nowMs - stats.windowMs;
|
|
913
|
+
while (stats.windowEvents.length > 0 && stats.windowEvents[0].timeMs < windowStartMs) {
|
|
914
|
+
stats.windowEvents.shift();
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const windowSamples = stats.windowEvents.reduce((sum, entry) => sum + entry.samples, 0);
|
|
918
|
+
stats.windowSamples = windowSamples;
|
|
919
|
+
if (stats.windowEvents.length === 0) {
|
|
920
|
+
stats.sampleRateHz = 0;
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const actualWindowMs = Math.max(1, nowMs - stats.windowEvents[0].timeMs);
|
|
925
|
+
stats.sampleRateHz = (windowSamples * 1000) / actualWindowMs;
|
|
926
|
+
}
|
|
927
|
+
|
|
758
928
|
////////////////////////////////////////////////////////////////////////////
|
|
759
929
|
// Helpers for decoded data callbacks
|
|
760
930
|
////////////////////////////////////////////////////////////////////////////
|
|
@@ -765,7 +935,7 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
765
935
|
return lengths;
|
|
766
936
|
}
|
|
767
937
|
pollRespMetadata.a.forEach((attr) => {
|
|
768
|
-
lengths[attr.n] = deviceAttrs[attr.n]?.values.length
|
|
938
|
+
lengths[attr.n] = deviceAttrs[attr.n]?.values.length ?? 0;
|
|
769
939
|
});
|
|
770
940
|
return lengths;
|
|
771
941
|
}
|
|
@@ -794,7 +964,7 @@ export class DeviceManager implements RaftDeviceMgrIF{
|
|
|
794
964
|
if (!attrState) {
|
|
795
965
|
return;
|
|
796
966
|
}
|
|
797
|
-
const prevLen = attrLengthsBefore[attr.n]
|
|
967
|
+
const prevLen = attrLengthsBefore[attr.n] ?? 0;
|
|
798
968
|
if (attrState.values.length > prevLen) {
|
|
799
969
|
attrValues[attr.n] = attrState.values.slice(prevLen);
|
|
800
970
|
hasValues = hasValues || attrValues[attr.n].length > 0;
|
package/src/RaftDeviceMgrIF.ts
CHANGED
|
@@ -8,13 +8,15 @@
|
|
|
8
8
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
9
9
|
|
|
10
10
|
import { DeviceTypeAction } from "./RaftDeviceInfo";
|
|
11
|
-
import { DeviceAttributeState, DevicesState, DeviceState } from "./RaftDeviceStates";
|
|
11
|
+
import { DeviceAttributeState, DevicesState, DeviceState, DeviceStats } from "./RaftDeviceStates";
|
|
12
12
|
|
|
13
13
|
export default interface RaftDeviceMgrIF {
|
|
14
14
|
|
|
15
15
|
// Get state of devices
|
|
16
16
|
getDevicesState(): DevicesState;
|
|
17
17
|
getDeviceState(deviceKey: string): DeviceState;
|
|
18
|
+
getDeviceStats(deviceKey: string): DeviceStats;
|
|
19
|
+
resetDeviceStats(deviceKey: string): void;
|
|
18
20
|
|
|
19
21
|
// Settings
|
|
20
22
|
setMaxDataPointsToStore(maxDataPointsToStore: number): void;
|
|
@@ -26,6 +28,8 @@ export default interface RaftDeviceMgrIF {
|
|
|
26
28
|
removeNewAttributeCallback(callback: (deviceKey: string, attrState: DeviceAttributeState) => void): void;
|
|
27
29
|
addAttributeDataCallback(callback: (deviceKey: string, attrState: DeviceAttributeState) => void): void;
|
|
28
30
|
removeAttributeDataCallback(callback: (deviceKey: string, attrState: DeviceAttributeState) => void): void;
|
|
31
|
+
addDeviceRemovedCallback(callback: (deviceKey: string, state: DeviceState) => void): void;
|
|
32
|
+
removeDeviceRemovedCallback(callback: (deviceKey: string, state: DeviceState) => void): void;
|
|
29
33
|
|
|
30
34
|
// Send action to device
|
|
31
35
|
sendAction(deviceKey: string, action: DeviceTypeAction, data: number[]): void;
|