@robotical/raftjs 2.1.0 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/devdocs/devbin-backwards-compatibility.md +105 -0
- package/devdocs/pseudocode-to-js-transpiler.md +563 -0
- package/dist/react-native/PseudocodeTranspiler.d.ts +6 -0
- package/dist/react-native/PseudocodeTranspiler.js +115 -0
- package/dist/react-native/PseudocodeTranspiler.js.map +1 -0
- package/dist/react-native/RaftAttributeHandler.d.ts +1 -1
- package/dist/react-native/RaftAttributeHandler.js +108 -32
- package/dist/react-native/RaftAttributeHandler.js.map +1 -1
- package/dist/react-native/RaftChannelBLE.web.d.ts +4 -0
- package/dist/react-native/RaftChannelBLE.web.js +59 -21
- package/dist/react-native/RaftChannelBLE.web.js.map +1 -1
- package/dist/react-native/RaftChannelSimulated.d.ts +1 -0
- package/dist/react-native/RaftChannelSimulated.js +9 -5
- package/dist/react-native/RaftChannelSimulated.js.map +1 -1
- package/dist/react-native/RaftChannelWebSocket.js +16 -1
- package/dist/react-native/RaftChannelWebSocket.js.map +1 -1
- package/dist/react-native/RaftConnector.d.ts +29 -1
- package/dist/react-native/RaftConnector.js +177 -11
- package/dist/react-native/RaftConnector.js.map +1 -1
- package/dist/react-native/RaftCustomAttrHandler.d.ts +2 -2
- package/dist/react-native/RaftCustomAttrHandler.js +32 -44
- package/dist/react-native/RaftCustomAttrHandler.js.map +1 -1
- package/dist/react-native/RaftDeviceInfo.d.ts +18 -0
- package/dist/react-native/RaftDeviceInfo.js +8 -0
- package/dist/react-native/RaftDeviceInfo.js.map +1 -1
- package/dist/react-native/RaftDeviceManager.d.ts +30 -3
- package/dist/react-native/RaftDeviceManager.js +618 -107
- package/dist/react-native/RaftDeviceManager.js.map +1 -1
- package/dist/react-native/RaftDeviceMgrIF.d.ts +11 -2
- package/dist/react-native/RaftDeviceStates.d.ts +27 -3
- package/dist/react-native/RaftDeviceStates.js +31 -6
- package/dist/react-native/RaftDeviceStates.js.map +1 -1
- package/dist/react-native/RaftFileHandler.d.ts +1 -1
- package/dist/react-native/RaftFileHandler.js +101 -34
- package/dist/react-native/RaftFileHandler.js.map +1 -1
- package/dist/react-native/RaftMicroPythonConsoleClient.d.ts +38 -0
- package/dist/react-native/RaftMicroPythonConsoleClient.js +45 -0
- package/dist/react-native/RaftMicroPythonConsoleClient.js.map +1 -0
- package/dist/react-native/RaftMsgHandler.d.ts +1 -1
- package/dist/react-native/RaftMsgHandler.js +6 -3
- package/dist/react-native/RaftMsgHandler.js.map +1 -1
- package/dist/react-native/RaftPublish.d.ts +2 -0
- package/dist/react-native/RaftPublish.js +81 -0
- package/dist/react-native/RaftPublish.js.map +1 -0
- package/dist/react-native/RaftStreamHandler.d.ts +11 -0
- package/dist/react-native/RaftStreamHandler.js +66 -0
- package/dist/react-native/RaftStreamHandler.js.map +1 -1
- package/dist/react-native/RaftStruct.d.ts +2 -2
- package/dist/react-native/RaftStruct.js +97 -26
- package/dist/react-native/RaftStruct.js.map +1 -1
- package/dist/react-native/RaftSystemType.d.ts +1 -0
- package/dist/react-native/RaftSystemUtils.d.ts +17 -1
- package/dist/react-native/RaftSystemUtils.js +51 -0
- package/dist/react-native/RaftSystemUtils.js.map +1 -1
- package/dist/react-native/RaftTimezone.d.ts +16 -0
- package/dist/react-native/RaftTimezone.js +153 -0
- package/dist/react-native/RaftTimezone.js.map +1 -0
- package/dist/react-native/RaftTypes.d.ts +46 -1
- package/dist/react-native/RaftTypes.js.map +1 -1
- package/dist/react-native/RaftUpdateManager.js +1 -1
- package/dist/react-native/RaftUpdateManager.js.map +1 -1
- package/dist/react-native/main.d.ts +3 -0
- package/dist/react-native/main.js +8 -1
- package/dist/react-native/main.js.map +1 -1
- package/dist/web/PseudocodeTranspiler.d.ts +6 -0
- package/dist/web/PseudocodeTranspiler.js +115 -0
- package/dist/web/PseudocodeTranspiler.js.map +1 -0
- package/dist/web/RaftAttributeHandler.d.ts +1 -1
- package/dist/web/RaftAttributeHandler.js +108 -32
- package/dist/web/RaftAttributeHandler.js.map +1 -1
- package/dist/web/RaftChannelBLE.web.d.ts +4 -0
- package/dist/web/RaftChannelBLE.web.js +59 -21
- package/dist/web/RaftChannelBLE.web.js.map +1 -1
- package/dist/web/RaftChannelSimulated.d.ts +1 -0
- package/dist/web/RaftChannelSimulated.js +9 -5
- package/dist/web/RaftChannelSimulated.js.map +1 -1
- package/dist/web/RaftChannelWebSocket.js +16 -1
- package/dist/web/RaftChannelWebSocket.js.map +1 -1
- package/dist/web/RaftConnector.d.ts +29 -1
- package/dist/web/RaftConnector.js +177 -11
- package/dist/web/RaftConnector.js.map +1 -1
- package/dist/web/RaftCustomAttrHandler.d.ts +2 -2
- package/dist/web/RaftCustomAttrHandler.js +32 -44
- package/dist/web/RaftCustomAttrHandler.js.map +1 -1
- package/dist/web/RaftDeviceInfo.d.ts +18 -0
- package/dist/web/RaftDeviceInfo.js +8 -0
- package/dist/web/RaftDeviceInfo.js.map +1 -1
- package/dist/web/RaftDeviceManager.d.ts +30 -3
- package/dist/web/RaftDeviceManager.js +618 -107
- package/dist/web/RaftDeviceManager.js.map +1 -1
- package/dist/web/RaftDeviceMgrIF.d.ts +11 -2
- package/dist/web/RaftDeviceStates.d.ts +27 -3
- package/dist/web/RaftDeviceStates.js +31 -6
- package/dist/web/RaftDeviceStates.js.map +1 -1
- package/dist/web/RaftFileHandler.d.ts +1 -1
- package/dist/web/RaftFileHandler.js +101 -34
- package/dist/web/RaftFileHandler.js.map +1 -1
- package/dist/web/RaftMicroPythonConsoleClient.d.ts +38 -0
- package/dist/web/RaftMicroPythonConsoleClient.js +45 -0
- package/dist/web/RaftMicroPythonConsoleClient.js.map +1 -0
- package/dist/web/RaftMsgHandler.d.ts +1 -1
- package/dist/web/RaftMsgHandler.js +6 -3
- package/dist/web/RaftMsgHandler.js.map +1 -1
- package/dist/web/RaftPublish.d.ts +2 -0
- package/dist/web/RaftPublish.js +81 -0
- package/dist/web/RaftPublish.js.map +1 -0
- package/dist/web/RaftStreamHandler.d.ts +11 -0
- package/dist/web/RaftStreamHandler.js +66 -0
- package/dist/web/RaftStreamHandler.js.map +1 -1
- package/dist/web/RaftStruct.d.ts +2 -2
- package/dist/web/RaftStruct.js +97 -26
- package/dist/web/RaftStruct.js.map +1 -1
- package/dist/web/RaftSystemType.d.ts +1 -0
- package/dist/web/RaftSystemUtils.d.ts +17 -1
- package/dist/web/RaftSystemUtils.js +51 -0
- package/dist/web/RaftSystemUtils.js.map +1 -1
- package/dist/web/RaftTimezone.d.ts +16 -0
- package/dist/web/RaftTimezone.js +153 -0
- package/dist/web/RaftTimezone.js.map +1 -0
- package/dist/web/RaftTypes.d.ts +46 -1
- package/dist/web/RaftTypes.js.map +1 -1
- package/dist/web/RaftUpdateManager.js +1 -1
- package/dist/web/RaftUpdateManager.js.map +1 -1
- package/dist/web/main.d.ts +3 -0
- package/dist/web/main.js +8 -1
- package/dist/web/main.js.map +1 -1
- package/examples/dashboard/package.json +2 -2
- package/examples/dashboard/src/DeviceActionsForm.tsx +177 -17
- package/examples/dashboard/src/DeviceLineChart.tsx +16 -3
- package/examples/dashboard/src/DevicePanel.tsx +92 -11
- package/examples/dashboard/src/DeviceSelectDialog.tsx +224 -0
- package/examples/dashboard/src/DeviceStatsPanel.tsx +76 -0
- package/examples/dashboard/src/DevicesPanel.tsx +11 -0
- package/examples/dashboard/src/LogConfigPanel.tsx +357 -0
- package/examples/dashboard/src/LogFilesPanel.tsx +200 -0
- package/examples/dashboard/src/LoggingPanel.tsx +264 -0
- package/examples/dashboard/src/Main.tsx +12 -2
- package/examples/dashboard/src/SettingsScreen.tsx +9 -4
- package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +10 -3
- package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +37 -3
- package/examples/dashboard/src/SystemTypeGeneric/StateInfoGeneric.ts +10 -2
- package/examples/dashboard/src/SystemTypeGeneric/SystemTypeGeneric.ts +41 -7
- package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +34 -3
- package/examples/dashboard/src/styles.css +766 -1
- package/notes/web-ble-reconnect-retry.md +69 -0
- package/package.json +10 -7
- package/src/PseudocodeTranspiler.test.ts +372 -0
- package/src/PseudocodeTranspiler.ts +127 -0
- package/src/RaftAttributeHandler.ts +152 -76
- package/src/RaftChannelBLE.web.ts +62 -20
- package/src/RaftChannelSimulated.ts +10 -5
- package/src/RaftChannelWebSocket.ts +16 -2
- package/src/RaftConnector.ts +204 -17
- package/src/RaftCustomAttrHandler.ts +35 -45
- package/src/RaftDeviceInfo.ts +27 -0
- package/src/RaftDeviceManager.test.ts +164 -0
- package/src/RaftDeviceManager.ts +705 -127
- package/src/RaftDeviceMgrIF.ts +13 -2
- package/src/RaftDeviceStates.ts +49 -8
- package/src/RaftFileHandler.ts +112 -39
- package/src/RaftMicroPythonConsoleClient.ts +78 -0
- package/src/RaftMsgHandler.ts +8 -4
- package/src/RaftPublish.ts +92 -0
- package/src/RaftStreamHandler.ts +84 -1
- package/src/RaftStruct.test.ts +229 -0
- package/src/RaftStruct.ts +101 -37
- package/src/RaftSystemType.ts +1 -0
- package/src/RaftSystemUtils.ts +59 -0
- package/src/RaftTimezone.ts +151 -0
- package/src/RaftTypes.ts +57 -1
- package/src/RaftUpdateManager.ts +1 -1
- package/src/main.ts +3 -0
|
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
11
11
|
exports.DeviceManager = void 0;
|
|
12
12
|
const tslib_1 = require("tslib");
|
|
13
13
|
const RaftDeviceStates_1 = require("./RaftDeviceStates");
|
|
14
|
+
const RaftDeviceInfo_1 = require("./RaftDeviceInfo");
|
|
14
15
|
const RaftAttributeHandler_1 = tslib_1.__importDefault(require("./RaftAttributeHandler"));
|
|
15
16
|
const RaftStruct_1 = require("./RaftStruct");
|
|
16
17
|
class DeviceManager {
|
|
@@ -20,6 +21,12 @@ class DeviceManager {
|
|
|
20
21
|
getDeviceState(deviceKey) {
|
|
21
22
|
return this._devicesState[deviceKey];
|
|
22
23
|
}
|
|
24
|
+
getDeviceStats(deviceKey) {
|
|
25
|
+
return this.cloneDeviceStats(this.getOrCreateDeviceStats(deviceKey));
|
|
26
|
+
}
|
|
27
|
+
resetDeviceStats(deviceKey) {
|
|
28
|
+
this._deviceStats[deviceKey] = this.createEmptyStats();
|
|
29
|
+
}
|
|
23
30
|
// Constructor
|
|
24
31
|
constructor() {
|
|
25
32
|
// Max data points to store
|
|
@@ -42,8 +49,13 @@ class DeviceManager {
|
|
|
42
49
|
this._newDeviceAttributeCallbacks = [];
|
|
43
50
|
this._newAttributeDataCallbacks = [];
|
|
44
51
|
this._decodedDataCallbacks = [];
|
|
52
|
+
this._deviceRemovedCallbacks = [];
|
|
45
53
|
// Debug message index (to help debug with async messages)
|
|
46
54
|
this._debugMsgIndex = 0;
|
|
55
|
+
// Device stats (sample counts, rates)
|
|
56
|
+
this._statsWindowMs = 5000;
|
|
57
|
+
this._deviceStats = {};
|
|
58
|
+
this._malformedSampleWarnLastMs = {};
|
|
47
59
|
// Cached device type data
|
|
48
60
|
this._cachedDeviceTypeRecs = {};
|
|
49
61
|
// Cached device type previous attempt times
|
|
@@ -120,6 +132,14 @@ class DeviceManager {
|
|
|
120
132
|
removeDecodedDataCallback(callback) {
|
|
121
133
|
this._decodedDataCallbacks = this._decodedDataCallbacks.filter((cb) => cb !== callback);
|
|
122
134
|
}
|
|
135
|
+
addDeviceRemovedCallback(callback) {
|
|
136
|
+
if (!this._deviceRemovedCallbacks.includes(callback)) {
|
|
137
|
+
this._deviceRemovedCallbacks.push(callback);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
removeDeviceRemovedCallback(callback) {
|
|
141
|
+
this._deviceRemovedCallbacks = this._deviceRemovedCallbacks.filter((cb) => cb !== callback);
|
|
142
|
+
}
|
|
123
143
|
////////////////////////////////////////////////////////////////////////////
|
|
124
144
|
// Set the friendly name for the device
|
|
125
145
|
////////////////////////////////////////////////////////////////////////////
|
|
@@ -133,75 +153,183 @@ class DeviceManager {
|
|
|
133
153
|
////////////////////////////////////////////////////////////////////////////
|
|
134
154
|
async handleClientMsgBinary(rxMsg) {
|
|
135
155
|
// console.log(`DeviceManager client1 msg ${RaftUtils.bufferToHex(rxMsg)}`);
|
|
136
|
-
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
156
|
+
var _a;
|
|
157
|
+
// DevBIN message format
|
|
158
|
+
//
|
|
159
|
+
// The rxMsg passed to this function has a 2-byte message type prefix (e.g. 0x0080)
|
|
160
|
+
// added by the transport layer. After that prefix comes a devbin frame:
|
|
161
|
+
//
|
|
162
|
+
// Current devbin envelope (3 bytes):
|
|
163
|
+
// Byte 0: magic+version 0xDB (valid range 0xDB–0xDF)
|
|
164
|
+
// Byte 1: topicIndex 0x00–0xFE = topic index; 0xFF = no topic
|
|
165
|
+
// Byte 2: envelopeSeqNum uint8, wrapping — detects whole-frame drops
|
|
166
|
+
//
|
|
167
|
+
// Then zero or more per-device records, concatenated back-to-back:
|
|
168
|
+
// Bytes 0-1: recordLen uint16 big-endian — number of body bytes that follow (min 8)
|
|
169
|
+
// Byte 2: statusBus bit 7 = online flag, bit 6 = pending deletion, bits 3:0 = bus number
|
|
170
|
+
// Bytes 3-6: address uint32 big-endian — device address on the bus
|
|
171
|
+
// Bytes 7-8: devTypeIdx uint16 big-endian — device type table index
|
|
172
|
+
// Byte 9: deviceSeqNum uint8, wrapping — per-device drop detection
|
|
173
|
+
// Bytes 10+: samples length-prefixed: [sampleLen(1B)][sampleData(sampleLen B)] × N
|
|
174
|
+
//
|
|
175
|
+
// Backwards compatibility:
|
|
176
|
+
// Cog v1.9.5 is already in production and sends the older RaftCore devbin layout:
|
|
177
|
+
// no 3-byte envelope, no deviceSeqNum byte, and raw fixed-size samples
|
|
178
|
+
// [timestamp(2B)][payload] × N. Keep that path separate so current Axiom/Cog
|
|
179
|
+
// frames continue to use the length-prefixed parser above.
|
|
180
|
+
//
|
|
181
|
+
// Example message (two device records; first record has two samples):
|
|
182
|
+
// 0080 DB 01 07 0018 81 0000076a 000b 2a 07feff0000010008 07185707931400 01 000e 80 00000000 001f 05 05030001af01
|
|
183
|
+
// ^^^^ ^^^^
|
|
184
|
+
// | ^^ ^^ ^^ Record 2 ...
|
|
185
|
+
// | | | envelopeSeqNum = 0x07 (same layout as Record 1)
|
|
186
|
+
// | | topicIndex = 0x01
|
|
187
|
+
// | magic+version = 0xDB (devbin v1)
|
|
188
|
+
// msgType prefix (transport layer)
|
|
189
|
+
//
|
|
190
|
+
// Record 1 breakdown:
|
|
191
|
+
// 0018 recordLen = 24 body bytes follow
|
|
192
|
+
// 81 statusBus: online=1, pendDel=0, bus=1
|
|
193
|
+
// 0000076a address = 0x0000076A (slot 7, I2C addr 0x6A)
|
|
194
|
+
// 000b devTypeIdx = 11
|
|
195
|
+
// 2a deviceSeqNum = 42
|
|
196
|
+
// 07 feff0000010008 sample 1: sampleLen=7, 7 bytes of attribute data
|
|
197
|
+
// 07 18570793140001 sample 2: sampleLen=7, 7 bytes of attribute data
|
|
198
|
+
//
|
|
199
|
+
// Record 2 breakdown:
|
|
200
|
+
// 000e recordLen = 14 body bytes follow
|
|
201
|
+
// 80 statusBus: online=1, pendDel=0, bus=0
|
|
202
|
+
// 00000000 address = 0x00000000 (direct-connect)
|
|
203
|
+
// 001f devTypeIdx = 31
|
|
204
|
+
// 05 deviceSeqNum = 5
|
|
205
|
+
// 05 030001af01 sample 1: sampleLen=5, 5 bytes of attribute data
|
|
150
206
|
// Debug
|
|
151
207
|
// const debugMsgTime = Date.now();
|
|
152
208
|
const debugMsgIndex = this._debugMsgIndex++;
|
|
153
|
-
// Message layout
|
|
154
|
-
const msgTypeLen = 2; //
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const
|
|
209
|
+
// Message layout constants
|
|
210
|
+
const msgTypeLen = 2; // Transport-layer message type prefix (first two bytes, e.g. 0x0080)
|
|
211
|
+
const devbinEnvelopeLen = 3; // Devbin envelope: magic+version (1B) + topicIndex (1B) + envelopeSeqNum (1B)
|
|
212
|
+
const legacyDevbinEnvelopeLen = 2; // Intermediate/legacy envelope: magic+version (1B) + topicIndex (1B)
|
|
213
|
+
const devbinMagicMin = 0xDB;
|
|
214
|
+
const devbinMagicMax = 0xDF;
|
|
215
|
+
const recordLenLen = 2; // Per-record length prefix (uint16 big-endian)
|
|
216
|
+
const busInfoLen = 1; // statusBus byte: bit 7 = online, bit 6 = pending deletion, bits 3:0 = bus number
|
|
217
|
+
const deviceAddrLen = 4; // Device address (uint32 big-endian)
|
|
218
|
+
const devTypeIdxLen = 2; // Device type index (uint16 big-endian)
|
|
219
|
+
const deviceSeqNumLen = 1; // Per-device sequence counter
|
|
220
|
+
const currentRecordHeaderLen = busInfoLen + deviceAddrLen + devTypeIdxLen + deviceSeqNumLen; // = 8, minimum record body
|
|
221
|
+
const legacyRecordHeaderLen = busInfoLen + deviceAddrLen + devTypeIdxLen; // = 7, Cog v1.9.5 record body header
|
|
160
222
|
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} rxMsg.length ${rxMsg.length} rxMsg ${RaftUtils.bufferToHex(rxMsg)}`);
|
|
161
223
|
// Start after the message type
|
|
162
224
|
let msgPos = msgTypeLen;
|
|
163
|
-
|
|
225
|
+
let payloadFormat = "legacyRaw";
|
|
226
|
+
// Check for devbin envelope (magic+version + topicIndex)
|
|
227
|
+
if (rxMsg.length >= msgTypeLen + legacyDevbinEnvelopeLen) {
|
|
228
|
+
const envelopeMagicVer = rxMsg[msgTypeLen];
|
|
229
|
+
if ((envelopeMagicVer & 0xF0) === 0xD0) {
|
|
230
|
+
if ((envelopeMagicVer < devbinMagicMin) || (envelopeMagicVer > devbinMagicMax)) {
|
|
231
|
+
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid devbin envelope magic/version ${envelopeMagicVer}`);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const topicIndex = rxMsg[msgTypeLen + 1];
|
|
235
|
+
if (topicIndex !== 0xFF) {
|
|
236
|
+
const topicName = (_a = this._systemUtils) === null || _a === void 0 ? void 0 : _a.getPublishTopicName(topicIndex);
|
|
237
|
+
if (topicName && topicName !== "devbin") {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const currentMsgPos = msgTypeLen + devbinEnvelopeLen;
|
|
242
|
+
const legacyMsgPos = msgTypeLen + legacyDevbinEnvelopeLen;
|
|
243
|
+
if (this.hasValidRecordAt(rxMsg, currentMsgPos, recordLenLen, currentRecordHeaderLen)) {
|
|
244
|
+
msgPos = currentMsgPos;
|
|
245
|
+
payloadFormat = "lengthPrefixed";
|
|
246
|
+
}
|
|
247
|
+
else if (this.hasValidRecordAt(rxMsg, legacyMsgPos, recordLenLen, legacyRecordHeaderLen)) {
|
|
248
|
+
msgPos = legacyMsgPos;
|
|
249
|
+
payloadFormat = "legacyRaw";
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid devbin envelope payload`);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else if (this.hasValidRecordAt(rxMsg, msgPos, recordLenLen, currentRecordHeaderLen)) {
|
|
258
|
+
payloadFormat = "lengthPrefixed";
|
|
259
|
+
}
|
|
260
|
+
// Iterate through device records
|
|
164
261
|
while (msgPos < rxMsg.length) {
|
|
165
|
-
// Check length
|
|
262
|
+
// Check minimum length for record length prefix + record header
|
|
166
263
|
const remainingLen = rxMsg.length - msgPos;
|
|
167
|
-
if (remainingLen <
|
|
168
|
-
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid length ${rxMsg.length} < ${
|
|
264
|
+
if (remainingLen < recordLenLen + legacyRecordHeaderLen) {
|
|
265
|
+
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid length ${rxMsg.length} < ${recordLenLen + legacyRecordHeaderLen + msgPos}`);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
// Get the record body length (bytes that follow the 2-byte length prefix)
|
|
269
|
+
const recordLen = (rxMsg[msgPos] << 8) + rxMsg[msgPos + 1];
|
|
270
|
+
if ((recordLen < legacyRecordHeaderLen) || (recordLen > remainingLen - recordLenLen)) {
|
|
271
|
+
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid msgPos ${msgPos} recordLen ${recordLen} remainingAfterLenBytes ${remainingLen - recordLenLen}`);
|
|
169
272
|
return;
|
|
170
273
|
}
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
274
|
+
// Extract record header fields
|
|
275
|
+
let recordPos = msgPos + recordLenLen;
|
|
276
|
+
// statusBus byte: bit 7 = online, bit 6 = pending deletion, bits 3:0 = bus number
|
|
277
|
+
const statusByte = rxMsg[recordPos];
|
|
278
|
+
const busNum = statusByte & 0x0f;
|
|
279
|
+
const isOnline = (statusByte & 0x80) !== 0;
|
|
280
|
+
recordPos += busInfoLen;
|
|
281
|
+
// Device address (uint32 big-endian)
|
|
282
|
+
const devAddr = (rxMsg[recordPos] << 24) + (rxMsg[recordPos + 1] << 16) + (rxMsg[recordPos + 2] << 8) + rxMsg[recordPos + 3];
|
|
283
|
+
recordPos += deviceAddrLen;
|
|
284
|
+
// Device type index (uint16 big-endian)
|
|
285
|
+
const devTypeIdx = (rxMsg[recordPos] << 8) + rxMsg[recordPos + 1];
|
|
286
|
+
recordPos += devTypeIdxLen;
|
|
287
|
+
const commonRecordHeaderEndPos = recordPos;
|
|
288
|
+
const samplesEndPos = msgPos + recordLenLen + recordLen;
|
|
289
|
+
let recordPayloadFormat = payloadFormat;
|
|
290
|
+
let recordHeaderLen = recordPayloadFormat === "lengthPrefixed" ? currentRecordHeaderLen : legacyRecordHeaderLen;
|
|
291
|
+
const resolvedDeviceTypeInfo = await this.getDeviceTypeInfo(busNum.toString(), devTypeIdx.toString());
|
|
292
|
+
if (resolvedDeviceTypeInfo === null || resolvedDeviceTypeInfo === void 0 ? void 0 : resolvedDeviceTypeInfo.resp) {
|
|
293
|
+
recordPayloadFormat = this.resolveRecordPayloadFormat(rxMsg, commonRecordHeaderEndPos, samplesEndPos, resolvedDeviceTypeInfo.resp, recordPayloadFormat, deviceSeqNumLen);
|
|
294
|
+
recordHeaderLen = recordPayloadFormat === "lengthPrefixed" ? currentRecordHeaderLen : legacyRecordHeaderLen;
|
|
295
|
+
}
|
|
296
|
+
if (recordLen < recordHeaderLen) {
|
|
297
|
+
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid msgPos ${msgPos} recordLen ${recordLen} recordHeaderLen ${recordHeaderLen}`);
|
|
175
298
|
return;
|
|
176
299
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const devTypeIdx = (rxMsg[sectionPos] << 8) + rxMsg[sectionPos + 1];
|
|
186
|
-
let attrGroupPos = sectionPos + sectionDeviceTypeIdxLen;
|
|
300
|
+
const isPendingDeletion = (recordPayloadFormat === "lengthPrefixed") && ((statusByte & 0x40) !== 0);
|
|
301
|
+
recordPos = commonRecordHeaderEndPos;
|
|
302
|
+
if (recordPayloadFormat === "lengthPrefixed") {
|
|
303
|
+
// Per-device sequence counter (reserved for future drop detection)
|
|
304
|
+
// const deviceSeqNum = rxMsg[recordPos];
|
|
305
|
+
recordPos += deviceSeqNumLen;
|
|
306
|
+
}
|
|
307
|
+
let pollDataPos = recordPos;
|
|
187
308
|
// Debug
|
|
188
|
-
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} overallLen ${rxMsg.length}
|
|
189
|
-
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex}
|
|
190
|
-
//
|
|
191
|
-
const
|
|
309
|
+
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} overallLen ${rxMsg.length} recordStart ${msgPos} recordLen ${recordLen} ${pollDataPos} ${RaftUtils.bufferToHex(rxMsg.slice(msgPos, msgPos + recordLenLen + recordLen))}`);
|
|
310
|
+
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} bus ${busNum} isOnline ${isOnline} devAddr 0x${devAddr.toString(16)} devTypeIdx ${devTypeIdx} pollDataLen ${recordLen - recordHeaderLen}`);
|
|
311
|
+
// Format device address as canonical hex and build device key
|
|
312
|
+
const devAddrHex = (0, RaftDeviceStates_1.formatDeviceAddrHex)(devAddr);
|
|
313
|
+
const deviceKey = this.getBinaryDeviceKey(busNum, devAddrHex, devTypeIdx, recordPayloadFormat);
|
|
192
314
|
// Update the last update time
|
|
193
315
|
this._deviceLastUpdateTime[deviceKey] = Date.now();
|
|
316
|
+
// Handle pending deletion - remove device and skip further processing
|
|
317
|
+
if (isPendingDeletion) {
|
|
318
|
+
this.removeDevice(deviceKey);
|
|
319
|
+
msgPos += recordLenLen + recordLen;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
194
322
|
// Check if a device state already exists
|
|
195
323
|
if (!(deviceKey in this._devicesState) || (this._devicesState[deviceKey].deviceTypeInfo === undefined)) {
|
|
196
324
|
// Get the device type info
|
|
197
|
-
const deviceTypeInfo = await this.getDeviceTypeInfo(busNum.toString(), devTypeIdx.toString());
|
|
325
|
+
const deviceTypeInfo = resolvedDeviceTypeInfo !== null && resolvedDeviceTypeInfo !== void 0 ? resolvedDeviceTypeInfo : await this.getDeviceTypeInfo(busNum.toString(), devTypeIdx.toString());
|
|
198
326
|
// Debug
|
|
199
|
-
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex}
|
|
327
|
+
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} pollDataPos ${pollDataPos} busNum ${busNum} devAddr 0x${devAddr.toString(16)} devTypeIdx ${devTypeIdx} deviceTypeInfo ${JSON.stringify(deviceTypeInfo)}`);
|
|
200
328
|
// Handle case where device type info is not available
|
|
201
329
|
if (deviceTypeInfo === undefined) {
|
|
202
|
-
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} deviceType ${devTypeIdx} info not available, skipping attribute processing for this
|
|
203
|
-
// Skip to next
|
|
204
|
-
msgPos +=
|
|
330
|
+
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} deviceType ${devTypeIdx} info not available, skipping attribute processing for this record`);
|
|
331
|
+
// Skip to next record without processing attributes
|
|
332
|
+
msgPos += recordLenLen + recordLen;
|
|
205
333
|
continue;
|
|
206
334
|
}
|
|
207
335
|
// Check if device record exists
|
|
@@ -210,7 +338,7 @@ class DeviceManager {
|
|
|
210
338
|
this._devicesState[deviceKey].deviceTypeInfo = deviceTypeInfo;
|
|
211
339
|
this._devicesState[deviceKey].deviceType = deviceTypeInfo.name || "";
|
|
212
340
|
this._devicesState[deviceKey].busName = busNum.toString();
|
|
213
|
-
this._devicesState[deviceKey].deviceAddress =
|
|
341
|
+
this._devicesState[deviceKey].deviceAddress = devAddrHex;
|
|
214
342
|
}
|
|
215
343
|
}
|
|
216
344
|
else {
|
|
@@ -220,13 +348,19 @@ class DeviceManager {
|
|
|
220
348
|
deviceTimeline: {
|
|
221
349
|
timestampsUs: [],
|
|
222
350
|
lastReportTimestampUs: 0,
|
|
223
|
-
reportTimestampOffsetUs: 0
|
|
351
|
+
reportTimestampOffsetUs: 0,
|
|
352
|
+
totalSamplesAdded: 0,
|
|
353
|
+
emaLastSampleTimeUs: 0,
|
|
354
|
+
emaIntervalUs: 0,
|
|
355
|
+
emaPrevPollTimeUs: 0,
|
|
356
|
+
emaCalibrated: false,
|
|
357
|
+
emaCalibrationPolls: 0
|
|
224
358
|
},
|
|
225
359
|
deviceAttributes: {},
|
|
226
360
|
deviceIsNew: true,
|
|
227
361
|
stateChanged: false,
|
|
228
|
-
|
|
229
|
-
deviceAddress:
|
|
362
|
+
onlineState: RaftDeviceStates_1.DeviceOnlineState.Online,
|
|
363
|
+
deviceAddress: devAddrHex,
|
|
230
364
|
deviceType: (deviceTypeInfo === null || deviceTypeInfo === void 0 ? void 0 : deviceTypeInfo.name) || "",
|
|
231
365
|
busName: busNum.toString()
|
|
232
366
|
};
|
|
@@ -234,48 +368,79 @@ class DeviceManager {
|
|
|
234
368
|
}
|
|
235
369
|
// Get device state
|
|
236
370
|
const deviceState = this._devicesState[deviceKey];
|
|
237
|
-
deviceState.
|
|
371
|
+
deviceState.onlineState = isOnline ? RaftDeviceStates_1.DeviceOnlineState.Online : RaftDeviceStates_1.DeviceOnlineState.Offline;
|
|
238
372
|
// Check if device type info is available and complete
|
|
239
373
|
if (deviceState.deviceTypeInfo && deviceState.deviceTypeInfo.resp) {
|
|
240
374
|
// Iterate over attributes in the group
|
|
241
375
|
const pollRespMetadata = deviceState.deviceTypeInfo.resp;
|
|
242
|
-
//
|
|
243
|
-
const attrGroupDataLen = sectionLen - sectionHeaderLen;
|
|
244
|
-
const attrGroupStartPos = attrGroupPos;
|
|
376
|
+
// Process samples within this record
|
|
245
377
|
const attrLengthsBefore = this.snapshotAttrLengths(deviceState.deviceAttributes, pollRespMetadata);
|
|
246
378
|
const timelineLenBefore = deviceState.deviceTimeline.timestampsUs.length;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
379
|
+
const totalSamplesBefore = deviceState.deviceTimeline.totalSamplesAdded;
|
|
380
|
+
if (recordPayloadFormat === "lengthPrefixed") {
|
|
381
|
+
while (pollDataPos < samplesEndPos) {
|
|
382
|
+
// Read sample length prefix
|
|
383
|
+
if (pollDataPos >= rxMsg.length) {
|
|
384
|
+
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} pollDataPos ${pollDataPos} exceeds message length ${rxMsg.length}`);
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
const sampleLen = rxMsg[pollDataPos];
|
|
388
|
+
pollDataPos += 1;
|
|
389
|
+
if (sampleLen === 0 || pollDataPos + sampleLen > samplesEndPos) {
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
const sampleStartPos = pollDataPos;
|
|
393
|
+
const sampleEndPos = pollDataPos + sampleLen;
|
|
394
|
+
const newMsgBufIdx = this._attributeHandler.processMsgAttrGroup(rxMsg, sampleStartPos, deviceState.deviceTimeline, pollRespMetadata, deviceState.deviceAttributes, this._maxDatapointsToStore, sampleEndPos);
|
|
395
|
+
if (newMsgBufIdx < 0) {
|
|
396
|
+
this.warnMalformedSample(`${deviceKey}:${devTypeIdx}:lengthPrefixed`, `DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} skipped malformed sample ` +
|
|
397
|
+
`device=${deviceKey} devTypeIdx=${devTypeIdx} sampleLen=${sampleLen} respBytes=${pollRespMetadata.b}`);
|
|
398
|
+
pollDataPos += sampleLen;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
// Advance by sampleLen regardless of how much processMsgAttrGroup consumed
|
|
402
|
+
pollDataPos += sampleLen;
|
|
403
|
+
deviceState.stateChanged = true;
|
|
252
404
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
const legacySampleLen = this.getLegacyRawSampleLen(pollRespMetadata);
|
|
408
|
+
if (legacySampleLen <= 0) {
|
|
409
|
+
this.warnMalformedSample(`${deviceKey}:${devTypeIdx}:legacyRawLen`, `DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid legacy sample length ` +
|
|
410
|
+
`device=${deviceKey} devTypeIdx=${devTypeIdx} respBytes=${pollRespMetadata.b}`);
|
|
258
411
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
412
|
+
else {
|
|
413
|
+
while (pollDataPos + legacySampleLen <= samplesEndPos) {
|
|
414
|
+
const sampleStartPos = pollDataPos;
|
|
415
|
+
const sampleEndPos = pollDataPos + legacySampleLen;
|
|
416
|
+
const newMsgBufIdx = this._attributeHandler.processMsgAttrGroup(rxMsg, sampleStartPos, deviceState.deviceTimeline, pollRespMetadata, deviceState.deviceAttributes, this._maxDatapointsToStore, sampleEndPos);
|
|
417
|
+
if (newMsgBufIdx < 0) {
|
|
418
|
+
this.warnMalformedSample(`${deviceKey}:${devTypeIdx}:legacyRaw`, `DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} skipped malformed legacy sample ` +
|
|
419
|
+
`device=${deviceKey} devTypeIdx=${devTypeIdx} sampleLen=${legacySampleLen} respBytes=${pollRespMetadata.b}`);
|
|
420
|
+
pollDataPos += legacySampleLen;
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
pollDataPos += legacySampleLen;
|
|
424
|
+
deviceState.stateChanged = true;
|
|
425
|
+
}
|
|
426
|
+
if (pollDataPos < samplesEndPos) {
|
|
427
|
+
this.warnMalformedSample(`${deviceKey}:${devTypeIdx}:legacyRawRemainder`, `DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} skipped trailing legacy sample bytes ` +
|
|
428
|
+
`device=${deviceKey} devTypeIdx=${devTypeIdx} remaining=${samplesEndPos - pollDataPos} sampleLen=${legacySampleLen} respBytes=${pollRespMetadata.b}`);
|
|
429
|
+
}
|
|
263
430
|
}
|
|
264
|
-
attrGroupPos = newMsgBufIdx;
|
|
265
|
-
deviceState.stateChanged = true;
|
|
266
|
-
// 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}`);
|
|
267
|
-
// console.log(`DevMan.handleClientMsgBinary group done debugIdx ${debugMsgIndex} attrGroupPos ${attrGroupPos} sectionLen ${sectionLen} msgPos ${msgPos} rxMsgLen ${rxMsg.length} remainingLen ${remainingLen}`);
|
|
268
431
|
}
|
|
269
432
|
// Inform decoded-data callbacks
|
|
270
|
-
this.emitDecodedData(deviceKey, busNum.toString(),
|
|
433
|
+
this.emitDecodedData(deviceKey, busNum.toString(), devAddrHex, deviceState, pollRespMetadata, attrLengthsBefore, timelineLenBefore);
|
|
434
|
+
const newSamples = deviceState.deviceTimeline.totalSamplesAdded - totalSamplesBefore;
|
|
435
|
+
this.updateDeviceStats(deviceKey, newSamples, Date.now());
|
|
271
436
|
}
|
|
272
437
|
else {
|
|
273
438
|
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} deviceState incomplete for device ${deviceKey}, skipping attribute processing`);
|
|
274
439
|
}
|
|
275
440
|
// Debug
|
|
276
|
-
// console.log(`DevMan.handleClientMsgBinary
|
|
277
|
-
//
|
|
278
|
-
msgPos +=
|
|
441
|
+
// console.log(`DevMan.handleClientMsgBinary record done debugIdx ${debugMsgIndex} pollDataPos ${pollDataPos} recordLen ${recordLen} msgPos ${msgPos} newMsgPos ${msgPos + recordLenLen + recordLen} rxMsgLen ${rxMsg.length} remainingLen ${remainingLen}`);
|
|
442
|
+
// Advance past this record (recordLenLen + recordLen bytes)
|
|
443
|
+
msgPos += recordLenLen + recordLen;
|
|
279
444
|
}
|
|
280
445
|
// Check for devices that have not been updated for a while
|
|
281
446
|
if (this._removeDevicesFlag) {
|
|
@@ -283,6 +448,7 @@ class DeviceManager {
|
|
|
283
448
|
Object.entries(this._deviceLastUpdateTime).forEach(([deviceKey, lastUpdateTime]) => {
|
|
284
449
|
if ((nowTime - lastUpdateTime) > this._removeDevicesTimeMs) {
|
|
285
450
|
delete this._devicesState[deviceKey];
|
|
451
|
+
delete this._deviceStats[deviceKey];
|
|
286
452
|
}
|
|
287
453
|
});
|
|
288
454
|
}
|
|
@@ -297,6 +463,10 @@ class DeviceManager {
|
|
|
297
463
|
// console.log(`DeviceManager client msg ${JSON.stringify(data)}`);
|
|
298
464
|
// Iterate over the buses
|
|
299
465
|
Object.entries(data).forEach(([busName, devices]) => {
|
|
466
|
+
// Check the bus name doesn't start with _ which is reserved for non-device information such as topic name
|
|
467
|
+
if (busName.startsWith("_")) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
300
470
|
// Check for bus status info
|
|
301
471
|
if (devices && typeof devices === "object" && "_s" in devices) {
|
|
302
472
|
// console.log(`DeviceManager bus status ${JSON.stringify(devices._s)}`);
|
|
@@ -304,21 +474,27 @@ class DeviceManager {
|
|
|
304
474
|
}
|
|
305
475
|
// Iterate over the devices
|
|
306
476
|
Object.entries(devices).forEach(async ([devAddr, attrGroups]) => {
|
|
477
|
+
var _a;
|
|
307
478
|
// Check for non-device info (starts with _)
|
|
308
479
|
if (devAddr.startsWith("_")) {
|
|
309
480
|
return;
|
|
310
481
|
}
|
|
311
482
|
// Device type name
|
|
312
483
|
let deviceTypeName = "";
|
|
484
|
+
let deviceTypeIdx = -1;
|
|
313
485
|
if (attrGroups && typeof attrGroups === 'object' && "_t" in attrGroups && typeof attrGroups._t === "string") {
|
|
314
486
|
deviceTypeName = attrGroups._t || "";
|
|
315
487
|
}
|
|
488
|
+
else if (attrGroups && typeof attrGroups === 'object' && "_i" in attrGroups && typeof attrGroups._i === "number") {
|
|
489
|
+
deviceTypeIdx = (_a = attrGroups._i) !== null && _a !== void 0 ? _a : -1;
|
|
490
|
+
deviceTypeName = deviceTypeIdx.toString();
|
|
491
|
+
}
|
|
316
492
|
else {
|
|
317
493
|
console.warn(`DeviceManager missing device type attrGroups ${JSON.stringify(attrGroups)}`);
|
|
318
494
|
return;
|
|
319
495
|
}
|
|
320
496
|
// Device key
|
|
321
|
-
const deviceKey = (0, RaftDeviceStates_1.getDeviceKey)(busName, devAddr
|
|
497
|
+
const deviceKey = (0, RaftDeviceStates_1.getDeviceKey)(busName, devAddr);
|
|
322
498
|
// Update the last update time
|
|
323
499
|
this._deviceLastUpdateTime[deviceKey] = Date.now();
|
|
324
500
|
// Check if a device state already exists
|
|
@@ -341,12 +517,18 @@ class DeviceManager {
|
|
|
341
517
|
deviceTimeline: {
|
|
342
518
|
timestampsUs: [],
|
|
343
519
|
lastReportTimestampUs: 0,
|
|
344
|
-
reportTimestampOffsetUs: 0
|
|
520
|
+
reportTimestampOffsetUs: 0,
|
|
521
|
+
totalSamplesAdded: 0,
|
|
522
|
+
emaLastSampleTimeUs: 0,
|
|
523
|
+
emaIntervalUs: 0,
|
|
524
|
+
emaPrevPollTimeUs: 0,
|
|
525
|
+
emaCalibrated: false,
|
|
526
|
+
emaCalibrationPolls: 0
|
|
345
527
|
},
|
|
346
528
|
deviceAttributes: {},
|
|
347
529
|
deviceIsNew: true,
|
|
348
530
|
stateChanged: false,
|
|
349
|
-
|
|
531
|
+
onlineState: RaftDeviceStates_1.DeviceOnlineState.Online,
|
|
350
532
|
deviceAddress: devAddr,
|
|
351
533
|
deviceType: deviceTypeName,
|
|
352
534
|
busName: busName
|
|
@@ -355,9 +537,15 @@ class DeviceManager {
|
|
|
355
537
|
}
|
|
356
538
|
// Get device state
|
|
357
539
|
const deviceState = this._devicesState[deviceKey];
|
|
358
|
-
// Check for online/offline state information
|
|
540
|
+
// Check for online/offline/pending-deletion state information
|
|
359
541
|
if (attrGroups && typeof attrGroups === "object" && "_o" in attrGroups) {
|
|
360
|
-
|
|
542
|
+
const onlineStateVal = typeof attrGroups._o === 'number' ? attrGroups._o : parseInt(String(attrGroups._o), 10);
|
|
543
|
+
if (onlineStateVal === 2) {
|
|
544
|
+
// Pending deletion - remove device and skip further processing
|
|
545
|
+
this.removeDevice(deviceKey);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
deviceState.onlineState = onlineStateVal === 1 ? RaftDeviceStates_1.DeviceOnlineState.Online : RaftDeviceStates_1.DeviceOnlineState.Offline;
|
|
361
549
|
}
|
|
362
550
|
// Check if device type info is available
|
|
363
551
|
if (!deviceState.deviceTypeInfo) {
|
|
@@ -382,6 +570,7 @@ class DeviceManager {
|
|
|
382
570
|
const pollRespMetadata = deviceState.deviceTypeInfo.resp;
|
|
383
571
|
const attrLengthsBefore = this.snapshotAttrLengths(deviceState.deviceAttributes, pollRespMetadata);
|
|
384
572
|
const timelineLenBefore = deviceState.deviceTimeline.timestampsUs.length;
|
|
573
|
+
const totalSamplesBefore = deviceState.deviceTimeline.totalSamplesAdded;
|
|
385
574
|
// Loop
|
|
386
575
|
while (msgBufIdx < msgBytes.length) {
|
|
387
576
|
const newMsgBufIdx = this._attributeHandler.processMsgAttrGroup(msgBytes, msgBufIdx, deviceState.deviceTimeline, pollRespMetadata, deviceState.deviceAttributes, this._maxDatapointsToStore);
|
|
@@ -391,6 +580,8 @@ class DeviceManager {
|
|
|
391
580
|
deviceState.stateChanged = true;
|
|
392
581
|
}
|
|
393
582
|
this.emitDecodedData(deviceKey, busName, devAddr, deviceState, pollRespMetadata, attrLengthsBefore, timelineLenBefore, attrGroupName, markers);
|
|
583
|
+
const newSamples = deviceState.deviceTimeline.totalSamplesAdded - totalSamplesBefore;
|
|
584
|
+
this.updateDeviceStats(deviceKey, newSamples, Date.now());
|
|
394
585
|
});
|
|
395
586
|
});
|
|
396
587
|
});
|
|
@@ -400,6 +591,7 @@ class DeviceManager {
|
|
|
400
591
|
Object.entries(this._deviceLastUpdateTime).forEach(([deviceKey, lastUpdateTime]) => {
|
|
401
592
|
if ((nowTime - lastUpdateTime) > this._removeDevicesTimeMs) {
|
|
402
593
|
delete this._devicesState[deviceKey];
|
|
594
|
+
delete this._deviceStats[deviceKey];
|
|
403
595
|
}
|
|
404
596
|
});
|
|
405
597
|
}
|
|
@@ -431,6 +623,20 @@ class DeviceManager {
|
|
|
431
623
|
});
|
|
432
624
|
}
|
|
433
625
|
////////////////////////////////////////////////////////////////////////////
|
|
626
|
+
// Remove a device (e.g. on pending deletion)
|
|
627
|
+
////////////////////////////////////////////////////////////////////////////
|
|
628
|
+
removeDevice(deviceKey) {
|
|
629
|
+
// Snapshot the state before removal for callbacks
|
|
630
|
+
const deviceState = this._devicesState[deviceKey];
|
|
631
|
+
if (deviceState) {
|
|
632
|
+
deviceState.onlineState = RaftDeviceStates_1.DeviceOnlineState.PendingDeletion;
|
|
633
|
+
this._deviceRemovedCallbacks.forEach((cb) => cb(deviceKey, deviceState));
|
|
634
|
+
}
|
|
635
|
+
delete this._devicesState[deviceKey];
|
|
636
|
+
delete this._deviceLastUpdateTime[deviceKey];
|
|
637
|
+
delete this._deviceStats[deviceKey];
|
|
638
|
+
}
|
|
639
|
+
////////////////////////////////////////////////////////////////////////////
|
|
434
640
|
// Get device type info
|
|
435
641
|
////////////////////////////////////////////////////////////////////////////
|
|
436
642
|
async getDeviceTypeInfo(busName, deviceType) {
|
|
@@ -499,6 +705,117 @@ class DeviceManager {
|
|
|
499
705
|
return undefined;
|
|
500
706
|
}
|
|
501
707
|
}
|
|
708
|
+
hasValidRecordAt(rxMsg, msgPos, recordLenLen, recordHeaderLen) {
|
|
709
|
+
if (msgPos === rxMsg.length) {
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
if (msgPos < 0 || msgPos > rxMsg.length) {
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
const remainingLen = rxMsg.length - msgPos;
|
|
716
|
+
if (remainingLen < recordLenLen + recordHeaderLen) {
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
const recordLen = (rxMsg[msgPos] << 8) + rxMsg[msgPos + 1];
|
|
720
|
+
return (recordLen >= recordHeaderLen) && (recordLen <= remainingLen - recordLenLen);
|
|
721
|
+
}
|
|
722
|
+
resolveRecordPayloadFormat(rxMsg, commonRecordHeaderEndPos, samplesEndPos, pollRespMetadata, preferredFormat, deviceSeqNumLen) {
|
|
723
|
+
const lengthPrefixedStartPos = commonRecordHeaderEndPos + deviceSeqNumLen;
|
|
724
|
+
const lengthPrefixedValid = this.areLengthPrefixedSamplesValid(rxMsg, lengthPrefixedStartPos, samplesEndPos, pollRespMetadata);
|
|
725
|
+
const legacySampleLen = this.getLegacyRawSampleLen(pollRespMetadata);
|
|
726
|
+
const legacyRawValid = this.areLegacyRawSamplesValid(commonRecordHeaderEndPos, samplesEndPos, legacySampleLen);
|
|
727
|
+
if (legacyRawValid && !lengthPrefixedValid) {
|
|
728
|
+
return "legacyRaw";
|
|
729
|
+
}
|
|
730
|
+
if (lengthPrefixedValid && !legacyRawValid) {
|
|
731
|
+
return "lengthPrefixed";
|
|
732
|
+
}
|
|
733
|
+
if (lengthPrefixedValid && legacyRawValid) {
|
|
734
|
+
return preferredFormat;
|
|
735
|
+
}
|
|
736
|
+
return preferredFormat;
|
|
737
|
+
}
|
|
738
|
+
areLengthPrefixedSamplesValid(rxMsg, pollDataPos, samplesEndPos, pollRespMetadata) {
|
|
739
|
+
if ((pollDataPos < 0) || (pollDataPos > samplesEndPos) || (samplesEndPos > rxMsg.length)) {
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
if (pollDataPos === samplesEndPos) {
|
|
743
|
+
return true;
|
|
744
|
+
}
|
|
745
|
+
const fixedSampleLen = pollRespMetadata.c ? 0 : this.getLegacyRawSampleLen(pollRespMetadata);
|
|
746
|
+
let sampleCount = 0;
|
|
747
|
+
while (pollDataPos < samplesEndPos) {
|
|
748
|
+
const sampleLen = rxMsg[pollDataPos];
|
|
749
|
+
pollDataPos += 1;
|
|
750
|
+
if ((sampleLen === 0) || (pollDataPos + sampleLen > samplesEndPos)) {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
if ((fixedSampleLen > 0) && (sampleLen !== fixedSampleLen)) {
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
756
|
+
pollDataPos += sampleLen;
|
|
757
|
+
sampleCount++;
|
|
758
|
+
}
|
|
759
|
+
return sampleCount > 0;
|
|
760
|
+
}
|
|
761
|
+
areLegacyRawSamplesValid(pollDataPos, samplesEndPos, legacySampleLen) {
|
|
762
|
+
if ((legacySampleLen <= 0) || (pollDataPos < 0) || (pollDataPos > samplesEndPos)) {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
const payloadLen = samplesEndPos - pollDataPos;
|
|
766
|
+
return (payloadLen > 0) && (payloadLen % legacySampleLen === 0);
|
|
767
|
+
}
|
|
768
|
+
getBinaryDeviceKey(busNum, devAddrHex, devTypeIdx, payloadFormat) {
|
|
769
|
+
const baseDeviceKey = (0, RaftDeviceStates_1.getDeviceKey)(busNum.toString(), devAddrHex);
|
|
770
|
+
if ((payloadFormat === "legacyRaw") && (busNum === 0) && (devAddrHex === "0")) {
|
|
771
|
+
return `${baseDeviceKey}_${devTypeIdx}`;
|
|
772
|
+
}
|
|
773
|
+
return baseDeviceKey;
|
|
774
|
+
}
|
|
775
|
+
getLegacyRawSampleLen(pollRespMetadata) {
|
|
776
|
+
const legacyTimestampLen = 2;
|
|
777
|
+
return legacyTimestampLen + this.getPollRespPayloadSize(pollRespMetadata);
|
|
778
|
+
}
|
|
779
|
+
getPollRespPayloadSize(pollRespMetadata) {
|
|
780
|
+
if (pollRespMetadata.c) {
|
|
781
|
+
return pollRespMetadata.b;
|
|
782
|
+
}
|
|
783
|
+
let attrPayloadLen = 0;
|
|
784
|
+
for (const attrDef of pollRespMetadata.a) {
|
|
785
|
+
if (!attrDef.t) {
|
|
786
|
+
return pollRespMetadata.b;
|
|
787
|
+
}
|
|
788
|
+
try {
|
|
789
|
+
attrPayloadLen += (0, RaftStruct_1.structSizeOf)(attrDef.t);
|
|
790
|
+
}
|
|
791
|
+
catch (_a) {
|
|
792
|
+
return pollRespMetadata.b;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
// Cog v1.9.5 light metadata reports the direct-sensor payload size doubled,
|
|
796
|
+
// but the legacy raw record contains one fixed payload matching the attribute schema.
|
|
797
|
+
if ((attrPayloadLen > 0) && (pollRespMetadata.b > 0) && (attrPayloadLen <= pollRespMetadata.b)) {
|
|
798
|
+
return attrPayloadLen;
|
|
799
|
+
}
|
|
800
|
+
return pollRespMetadata.b;
|
|
801
|
+
}
|
|
802
|
+
parseDeviceKeyForCommand(deviceKey) {
|
|
803
|
+
const deviceState = this._devicesState[deviceKey];
|
|
804
|
+
if (deviceState) {
|
|
805
|
+
return { bus: deviceState.busName, addr: deviceState.deviceAddress };
|
|
806
|
+
}
|
|
807
|
+
return (0, RaftDeviceStates_1.parseDeviceKey)(deviceKey);
|
|
808
|
+
}
|
|
809
|
+
warnMalformedSample(warningKey, message) {
|
|
810
|
+
var _a;
|
|
811
|
+
const nowMs = Date.now();
|
|
812
|
+
const lastWarnMs = (_a = this._malformedSampleWarnLastMs[warningKey]) !== null && _a !== void 0 ? _a : 0;
|
|
813
|
+
if (nowMs - lastWarnMs < 5000) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
this._malformedSampleWarnLastMs[warningKey] = nowMs;
|
|
817
|
+
console.warn(message);
|
|
818
|
+
}
|
|
502
819
|
////////////////////////////////////////////////////////////////////////////
|
|
503
820
|
// Send action to device
|
|
504
821
|
////////////////////////////////////////////////////////////////////////////
|
|
@@ -508,40 +825,77 @@ class DeviceManager {
|
|
|
508
825
|
.join("");
|
|
509
826
|
}
|
|
510
827
|
async sendAction(deviceKey, action, data) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
828
|
+
var _a, _b;
|
|
829
|
+
console.log(`DeviceManager sendAction ${deviceKey} action ${action.n} data ${data} map ${JSON.stringify(action.map)} keys ${action.map ? Object.keys(action.map) : 'none'}`);
|
|
830
|
+
// For _conf.* actions, delegate to setSampleRate() which coordinates polling params
|
|
831
|
+
if (action.n.startsWith('_conf.') && action.map && data.length === 1) {
|
|
832
|
+
const result = await this.setSampleRate(deviceKey, data[0]);
|
|
833
|
+
return result.ok;
|
|
834
|
+
}
|
|
835
|
+
let writeHexStr;
|
|
836
|
+
// Check if action has a map - use mapped hex value directly
|
|
837
|
+
if (action.map && data.length === 1) {
|
|
838
|
+
const mapKey = String(data[0]);
|
|
839
|
+
const mapEntry = action.map[mapKey];
|
|
840
|
+
if (!mapEntry) {
|
|
841
|
+
console.warn(`DeviceManager sendAction: no map entry for value ${mapKey} in action ${action.n}`);
|
|
842
|
+
return false;
|
|
843
|
+
}
|
|
844
|
+
const mappedHex = (0, RaftDeviceInfo_1.getActionMapHex)(mapEntry);
|
|
845
|
+
// Map values may contain &-separated multi-writes (e.g. "1048&114C&0a26")
|
|
846
|
+
const writes = mappedHex.split('&');
|
|
847
|
+
const { bus: devBus, addr: devAddr } = this.parseDeviceKeyForCommand(deviceKey);
|
|
848
|
+
try {
|
|
849
|
+
const msgHandler = (_a = this._systemUtils) === null || _a === void 0 ? void 0 : _a.getMsgHandler();
|
|
850
|
+
if (!msgHandler)
|
|
851
|
+
return false;
|
|
852
|
+
for (const hexWr of writes) {
|
|
853
|
+
const cmd = "devman/cmdraw?bus=" + devBus + "&addr=" + devAddr + "&hexWr=" + hexWr;
|
|
854
|
+
console.log(`DeviceManager sendAction ${action.n} ${cmd}`);
|
|
855
|
+
const msgRslt = await msgHandler.sendRICRESTURL(cmd);
|
|
856
|
+
if (msgRslt.rslt !== "ok")
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
return true;
|
|
520
860
|
}
|
|
521
|
-
|
|
522
|
-
|
|
861
|
+
catch (error) {
|
|
862
|
+
console.warn(`DeviceManager sendAction error ${error}`);
|
|
863
|
+
return false;
|
|
523
864
|
}
|
|
524
|
-
// Form the write bytes
|
|
525
|
-
writeBytes = action.t ? (0, RaftStruct_1.structPack)(action.t, [value]) : new Uint8Array(0);
|
|
526
865
|
}
|
|
527
866
|
else {
|
|
528
|
-
|
|
529
|
-
|
|
867
|
+
let writeBytes;
|
|
868
|
+
// Check for one data item
|
|
869
|
+
if (data.length === 1) {
|
|
870
|
+
let value = data[0];
|
|
871
|
+
// Check for conversion
|
|
872
|
+
if (action.sub !== undefined) {
|
|
873
|
+
value = value - action.sub;
|
|
874
|
+
}
|
|
875
|
+
if (action.mul !== undefined) {
|
|
876
|
+
value = value * action.mul;
|
|
877
|
+
}
|
|
878
|
+
// Form the write bytes
|
|
879
|
+
writeBytes = action.t ? (0, RaftStruct_1.structPack)(action.t, [value]) : new Uint8Array(0);
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
882
|
+
// Form the write bytes which may have multiple data items
|
|
883
|
+
writeBytes = action.t ? (0, RaftStruct_1.structPack)(action.t, data) : new Uint8Array(0);
|
|
884
|
+
}
|
|
885
|
+
// Convert to hex string
|
|
886
|
+
writeHexStr = this.toHex(writeBytes);
|
|
530
887
|
}
|
|
531
|
-
// Convert to hex string
|
|
532
|
-
let writeHexStr = this.toHex(writeBytes);
|
|
533
888
|
// Add prefix and postfix
|
|
534
889
|
writeHexStr = (action.w ? action.w : "") + writeHexStr + (action.wz ? action.wz : "");
|
|
535
|
-
//
|
|
536
|
-
const devBus =
|
|
537
|
-
const devAddr = deviceKey.split("_")[1];
|
|
890
|
+
// Parse the device key into bus and address components
|
|
891
|
+
const { bus: devBus, addr: devAddr } = this.parseDeviceKeyForCommand(deviceKey);
|
|
538
892
|
// Send the action to the server
|
|
539
893
|
const cmd = "devman/cmdraw?bus=" + devBus + "&addr=" + devAddr + "&hexWr=" + writeHexStr;
|
|
540
894
|
console.log(`DeviceManager deviceKey ${deviceKey} action name ${action.n} value ${data} prefix ${action.w} sendAction ${cmd}`);
|
|
541
895
|
// Send the command
|
|
542
896
|
try {
|
|
543
897
|
// Get the msg handler
|
|
544
|
-
const msgHandler = (
|
|
898
|
+
const msgHandler = (_b = this._systemUtils) === null || _b === void 0 ? void 0 : _b.getMsgHandler();
|
|
545
899
|
if (msgHandler) {
|
|
546
900
|
const msgRslt = await msgHandler.sendRICRESTURL(cmd);
|
|
547
901
|
return msgRslt.rslt === "ok";
|
|
@@ -581,6 +935,111 @@ class DeviceManager {
|
|
|
581
935
|
return false;
|
|
582
936
|
}
|
|
583
937
|
////////////////////////////////////////////////////////////////////////////
|
|
938
|
+
// Set sample rate with coordinated polling parameters
|
|
939
|
+
// Finds the closest supported rate from the device's _conf.rate action,
|
|
940
|
+
// calculates optimal intervalUs and numSamples, and sends a single
|
|
941
|
+
// /devman/devconfig call to set all parameters atomically.
|
|
942
|
+
////////////////////////////////////////////////////////////////////////////
|
|
943
|
+
async setSampleRate(deviceKey, sampleRateHz, options) {
|
|
944
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
945
|
+
// Look up device state and type info
|
|
946
|
+
const deviceState = this._devicesState[deviceKey];
|
|
947
|
+
if (!(deviceState === null || deviceState === void 0 ? void 0 : deviceState.deviceTypeInfo)) {
|
|
948
|
+
return { ok: false, requestedRateHz: sampleRateHz, actualRateHz: 0, intervalUs: 0, numSamples: 0, error: 'Device not found or type info not loaded' };
|
|
949
|
+
}
|
|
950
|
+
const typeInfo = deviceState.deviceTypeInfo;
|
|
951
|
+
// Find the _conf.rate action
|
|
952
|
+
const confRateAction = (_a = typeInfo.actions) === null || _a === void 0 ? void 0 : _a.find(a => a.n === '_conf.rate');
|
|
953
|
+
if (!(confRateAction === null || confRateAction === void 0 ? void 0 : confRateAction.map)) {
|
|
954
|
+
// No _conf.rate action — use generic sample rate setting
|
|
955
|
+
// Non-FIFO devices: poll once per sample period, 1 sample per read
|
|
956
|
+
const samplePeriodUs = Math.round(1000000 / sampleRateHz);
|
|
957
|
+
const numSamples = (_b = options === null || options === void 0 ? void 0 : options.numSamples) !== null && _b !== void 0 ? _b : 1;
|
|
958
|
+
const intervalUs = (_c = options === null || options === void 0 ? void 0 : options.intervalUs) !== null && _c !== void 0 ? _c : Math.max(5000, samplePeriodUs);
|
|
959
|
+
const { bus: devBus, addr: devAddr } = this.parseDeviceKeyForCommand(deviceKey);
|
|
960
|
+
const cmd = `devman/devconfig?bus=${devBus}&addr=${devAddr}&intervalUs=${intervalUs}&numSamples=${numSamples}`;
|
|
961
|
+
try {
|
|
962
|
+
const msgHandler = (_d = this._systemUtils) === null || _d === void 0 ? void 0 : _d.getMsgHandler();
|
|
963
|
+
if (!msgHandler) {
|
|
964
|
+
return { ok: false, requestedRateHz: sampleRateHz, actualRateHz: sampleRateHz, intervalUs, numSamples, error: 'No message handler available' };
|
|
965
|
+
}
|
|
966
|
+
const msgRslt = await msgHandler.sendRICRESTURL(cmd);
|
|
967
|
+
const ok = msgRslt.rslt === 'ok';
|
|
968
|
+
return { ok, requestedRateHz: sampleRateHz, actualRateHz: sampleRateHz, intervalUs, numSamples, error: ok ? undefined : `Firmware returned: ${msgRslt.rslt}` };
|
|
969
|
+
}
|
|
970
|
+
catch (error) {
|
|
971
|
+
return { ok: false, requestedRateHz: sampleRateHz, actualRateHz: sampleRateHz, intervalUs, numSamples, error: `${error}` };
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
// Find the closest supported rate from the map keys
|
|
975
|
+
const supportedRates = Object.keys(confRateAction.map).map(Number).filter(r => !isNaN(r)).sort((a, b) => a - b);
|
|
976
|
+
if (supportedRates.length === 0) {
|
|
977
|
+
return { ok: false, requestedRateHz: sampleRateHz, actualRateHz: 0, intervalUs: 0, numSamples: 0, error: 'No valid rates in _conf.rate map' };
|
|
978
|
+
}
|
|
979
|
+
let actualRate = supportedRates[0];
|
|
980
|
+
let minDist = Math.abs(sampleRateHz - actualRate);
|
|
981
|
+
for (const rate of supportedRates) {
|
|
982
|
+
const dist = Math.abs(sampleRateHz - rate);
|
|
983
|
+
if (dist < minDist) {
|
|
984
|
+
minDist = dist;
|
|
985
|
+
actualRate = rate;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
// Look up map entry for the matched rate — may be object with recommended polling params
|
|
989
|
+
const mapEntry = confRateAction.map[String(actualRate)];
|
|
990
|
+
const mapObj = typeof mapEntry === 'object' ? mapEntry : null;
|
|
991
|
+
const recommendedIntervalUs = mapObj === null || mapObj === void 0 ? void 0 : mapObj.i;
|
|
992
|
+
const recommendedNumSamples = mapObj === null || mapObj === void 0 ? void 0 : mapObj.s;
|
|
993
|
+
// Calculate inter-sample period
|
|
994
|
+
const samplePeriodUs = Math.round(1000000 / actualRate);
|
|
995
|
+
// Calculate optimal numSamples and intervalUs
|
|
996
|
+
// Priority: explicit options > map entry recommendations > auto-calculation
|
|
997
|
+
const maxNumSamples = (_e = options === null || options === void 0 ? void 0 : options.maxNumSamples) !== null && _e !== void 0 ? _e : 20;
|
|
998
|
+
let numSamples;
|
|
999
|
+
let intervalUs;
|
|
1000
|
+
if ((options === null || options === void 0 ? void 0 : options.numSamples) !== undefined && (options === null || options === void 0 ? void 0 : options.intervalUs) !== undefined) {
|
|
1001
|
+
// Both explicitly specified — use as-is
|
|
1002
|
+
numSamples = options.numSamples;
|
|
1003
|
+
intervalUs = options.intervalUs;
|
|
1004
|
+
}
|
|
1005
|
+
else if ((options === null || options === void 0 ? void 0 : options.intervalUs) !== undefined) {
|
|
1006
|
+
// intervalUs specified, derive numSamples from it
|
|
1007
|
+
intervalUs = options.intervalUs;
|
|
1008
|
+
numSamples = (_g = (_f = options === null || options === void 0 ? void 0 : options.numSamples) !== null && _f !== void 0 ? _f : recommendedNumSamples) !== null && _g !== void 0 ? _g : Math.max(1, Math.min(maxNumSamples, Math.floor(intervalUs / samplePeriodUs)));
|
|
1009
|
+
}
|
|
1010
|
+
else if ((options === null || options === void 0 ? void 0 : options.numSamples) !== undefined) {
|
|
1011
|
+
// numSamples specified, derive intervalUs from it
|
|
1012
|
+
numSamples = options.numSamples;
|
|
1013
|
+
intervalUs = recommendedIntervalUs !== null && recommendedIntervalUs !== void 0 ? recommendedIntervalUs : Math.round(numSamples * samplePeriodUs * 0.8);
|
|
1014
|
+
}
|
|
1015
|
+
else if (recommendedIntervalUs !== undefined && recommendedNumSamples !== undefined) {
|
|
1016
|
+
// Use map entry recommendations
|
|
1017
|
+
intervalUs = recommendedIntervalUs;
|
|
1018
|
+
numSamples = recommendedNumSamples;
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
// Auto-calculate: target ~50ms poll interval, bounded by sample rate
|
|
1022
|
+
const targetPollIntervalUs = 50000;
|
|
1023
|
+
numSamples = recommendedNumSamples !== null && recommendedNumSamples !== void 0 ? recommendedNumSamples : Math.max(1, Math.min(maxNumSamples, Math.floor(targetPollIntervalUs / samplePeriodUs)));
|
|
1024
|
+
intervalUs = recommendedIntervalUs !== null && recommendedIntervalUs !== void 0 ? recommendedIntervalUs : Math.max(5000, Math.min(1000000, Math.round(numSamples * samplePeriodUs * 0.8)));
|
|
1025
|
+
}
|
|
1026
|
+
// Send single devconfig call with all parameters
|
|
1027
|
+
const { bus: devBus, addr: devAddr } = this.parseDeviceKeyForCommand(deviceKey);
|
|
1028
|
+
const cmd = `devman/devconfig?bus=${devBus}&addr=${devAddr}&sampleRateHz=${actualRate}&intervalUs=${intervalUs}&numSamples=${numSamples}`;
|
|
1029
|
+
try {
|
|
1030
|
+
const msgHandler = (_h = this._systemUtils) === null || _h === void 0 ? void 0 : _h.getMsgHandler();
|
|
1031
|
+
if (!msgHandler) {
|
|
1032
|
+
return { ok: false, requestedRateHz: sampleRateHz, actualRateHz: actualRate, intervalUs, numSamples, error: 'No message handler available' };
|
|
1033
|
+
}
|
|
1034
|
+
const msgRslt = await msgHandler.sendRICRESTURL(cmd);
|
|
1035
|
+
const ok = msgRslt.rslt === 'ok';
|
|
1036
|
+
return { ok, requestedRateHz: sampleRateHz, actualRateHz: actualRate, intervalUs, numSamples, error: ok ? undefined : `Firmware returned: ${msgRslt.rslt}` };
|
|
1037
|
+
}
|
|
1038
|
+
catch (error) {
|
|
1039
|
+
return { ok: false, requestedRateHz: sampleRateHz, actualRateHz: actualRate, intervalUs, numSamples, error: `${error}` };
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
////////////////////////////////////////////////////////////////////////////
|
|
584
1043
|
// Convert hex to bytes
|
|
585
1044
|
////////////////////////////////////////////////////////////////////////////
|
|
586
1045
|
hexToBytes(hex) {
|
|
@@ -591,6 +1050,57 @@ class DeviceManager {
|
|
|
591
1050
|
return bytes;
|
|
592
1051
|
}
|
|
593
1052
|
////////////////////////////////////////////////////////////////////////////
|
|
1053
|
+
// Helpers for device stats
|
|
1054
|
+
////////////////////////////////////////////////////////////////////////////
|
|
1055
|
+
createEmptyStats() {
|
|
1056
|
+
return {
|
|
1057
|
+
totalSamples: 0,
|
|
1058
|
+
windowMs: this._statsWindowMs,
|
|
1059
|
+
windowSamples: 0,
|
|
1060
|
+
sampleRateHz: 0,
|
|
1061
|
+
lastSampleTimeMs: null,
|
|
1062
|
+
lastUpdateTimeMs: null,
|
|
1063
|
+
windowEvents: []
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
getOrCreateDeviceStats(deviceKey) {
|
|
1067
|
+
if (!this._deviceStats[deviceKey]) {
|
|
1068
|
+
this._deviceStats[deviceKey] = this.createEmptyStats();
|
|
1069
|
+
}
|
|
1070
|
+
return this._deviceStats[deviceKey];
|
|
1071
|
+
}
|
|
1072
|
+
cloneDeviceStats(stats) {
|
|
1073
|
+
return {
|
|
1074
|
+
totalSamples: stats.totalSamples,
|
|
1075
|
+
windowMs: stats.windowMs,
|
|
1076
|
+
windowSamples: stats.windowSamples,
|
|
1077
|
+
sampleRateHz: stats.sampleRateHz,
|
|
1078
|
+
lastSampleTimeMs: stats.lastSampleTimeMs,
|
|
1079
|
+
lastUpdateTimeMs: stats.lastUpdateTimeMs
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
updateDeviceStats(deviceKey, newSamples, nowMs) {
|
|
1083
|
+
const stats = this.getOrCreateDeviceStats(deviceKey);
|
|
1084
|
+
stats.lastUpdateTimeMs = nowMs;
|
|
1085
|
+
if (newSamples > 0) {
|
|
1086
|
+
stats.totalSamples += newSamples;
|
|
1087
|
+
stats.lastSampleTimeMs = nowMs;
|
|
1088
|
+
stats.windowEvents.push({ timeMs: nowMs, samples: newSamples });
|
|
1089
|
+
}
|
|
1090
|
+
const windowStartMs = nowMs - stats.windowMs;
|
|
1091
|
+
while (stats.windowEvents.length > 0 && stats.windowEvents[0].timeMs < windowStartMs) {
|
|
1092
|
+
stats.windowEvents.shift();
|
|
1093
|
+
}
|
|
1094
|
+
const windowSamples = stats.windowEvents.reduce((sum, entry) => sum + entry.samples, 0);
|
|
1095
|
+
stats.windowSamples = windowSamples;
|
|
1096
|
+
if (stats.windowEvents.length === 0) {
|
|
1097
|
+
stats.sampleRateHz = 0;
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
const actualWindowMs = Math.max(1, nowMs - stats.windowEvents[0].timeMs);
|
|
1101
|
+
stats.sampleRateHz = (windowSamples * 1000) / actualWindowMs;
|
|
1102
|
+
}
|
|
1103
|
+
////////////////////////////////////////////////////////////////////////////
|
|
594
1104
|
// Helpers for decoded data callbacks
|
|
595
1105
|
////////////////////////////////////////////////////////////////////////////
|
|
596
1106
|
snapshotAttrLengths(deviceAttrs, pollRespMetadata) {
|
|
@@ -599,8 +1109,8 @@ class DeviceManager {
|
|
|
599
1109
|
return lengths;
|
|
600
1110
|
}
|
|
601
1111
|
pollRespMetadata.a.forEach((attr) => {
|
|
602
|
-
var _a;
|
|
603
|
-
lengths[attr.n] = ((_a = deviceAttrs[attr.n]) === null || _a === void 0 ? void 0 : _a.values.length)
|
|
1112
|
+
var _a, _b;
|
|
1113
|
+
lengths[attr.n] = (_b = (_a = deviceAttrs[attr.n]) === null || _a === void 0 ? void 0 : _a.values.length) !== null && _b !== void 0 ? _b : 0;
|
|
604
1114
|
});
|
|
605
1115
|
return lengths;
|
|
606
1116
|
}
|
|
@@ -611,11 +1121,12 @@ class DeviceManager {
|
|
|
611
1121
|
const attrValues = {};
|
|
612
1122
|
let hasValues = false;
|
|
613
1123
|
pollRespMetadata.a.forEach((attr) => {
|
|
1124
|
+
var _a;
|
|
614
1125
|
const attrState = deviceState.deviceAttributes[attr.n];
|
|
615
1126
|
if (!attrState) {
|
|
616
1127
|
return;
|
|
617
1128
|
}
|
|
618
|
-
const prevLen = attrLengthsBefore[attr.n]
|
|
1129
|
+
const prevLen = (_a = attrLengthsBefore[attr.n]) !== null && _a !== void 0 ? _a : 0;
|
|
619
1130
|
if (attrState.values.length > prevLen) {
|
|
620
1131
|
attrValues[attr.n] = attrState.values.slice(prevLen);
|
|
621
1132
|
hasValues = hasValues || attrValues[attr.n].length > 0;
|