@robotical/raftjs 2.0.11 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +10 -6
- package/dist/react-native/RaftChannelSimulated.js.map +1 -1
- package/dist/react-native/RaftChannelWebSerial.js +1 -1
- package/dist/react-native/RaftChannelWebSerial.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 +11 -1
- package/dist/react-native/RaftConnector.js +75 -9
- 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 +47 -2
- package/dist/react-native/RaftDeviceManager.js +696 -104
- 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 +0 -1
- package/dist/react-native/RaftFileHandler.js +61 -23
- package/dist/react-native/RaftFileHandler.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 +27 -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 +6 -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 +10 -6
- package/dist/web/RaftChannelSimulated.js.map +1 -1
- package/dist/web/RaftChannelWebSerial.js +1 -1
- package/dist/web/RaftChannelWebSerial.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 +11 -1
- package/dist/web/RaftConnector.js +75 -9
- 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 +47 -2
- package/dist/web/RaftDeviceManager.js +696 -104
- 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 +0 -1
- package/dist/web/RaftFileHandler.js +61 -23
- package/dist/web/RaftFileHandler.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 +27 -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 +6 -1
- package/dist/web/main.js.map +1 -1
- package/examples/dashboard/package.json +2 -2
- package/examples/dashboard/src/DeviceActionsForm.tsx +158 -8
- 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 +11 -6
- package/src/RaftChannelWebSerial.ts +1 -1
- package/src/RaftChannelWebSocket.ts +16 -2
- package/src/RaftConnector.ts +93 -15
- package/src/RaftCustomAttrHandler.ts +35 -45
- package/src/RaftDeviceInfo.ts +27 -0
- package/src/RaftDeviceManager.test.ts +164 -0
- package/src/RaftDeviceManager.ts +823 -121
- package/src/RaftDeviceMgrIF.ts +13 -2
- package/src/RaftDeviceStates.ts +49 -8
- package/src/RaftFileHandler.ts +69 -28
- 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 +34 -1
- package/src/RaftUpdateManager.ts +1 -1
- package/src/main.ts +3 -0
|
@@ -11,9 +11,9 @@ 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
|
-
// import RaftUtils from "./RaftUtils";
|
|
17
17
|
class DeviceManager {
|
|
18
18
|
getDevicesState() {
|
|
19
19
|
return this._devicesState;
|
|
@@ -21,6 +21,12 @@ class DeviceManager {
|
|
|
21
21
|
getDeviceState(deviceKey) {
|
|
22
22
|
return this._devicesState[deviceKey];
|
|
23
23
|
}
|
|
24
|
+
getDeviceStats(deviceKey) {
|
|
25
|
+
return this.cloneDeviceStats(this.getOrCreateDeviceStats(deviceKey));
|
|
26
|
+
}
|
|
27
|
+
resetDeviceStats(deviceKey) {
|
|
28
|
+
this._deviceStats[deviceKey] = this.createEmptyStats();
|
|
29
|
+
}
|
|
24
30
|
// Constructor
|
|
25
31
|
constructor() {
|
|
26
32
|
// Max data points to store
|
|
@@ -42,8 +48,14 @@ class DeviceManager {
|
|
|
42
48
|
this._newDeviceCallbacks = [];
|
|
43
49
|
this._newDeviceAttributeCallbacks = [];
|
|
44
50
|
this._newAttributeDataCallbacks = [];
|
|
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
|
|
@@ -112,6 +124,22 @@ class DeviceManager {
|
|
|
112
124
|
removeAttributeDataCallback(callback) {
|
|
113
125
|
this._newAttributeDataCallbacks = this._newAttributeDataCallbacks.filter((cb) => cb !== callback);
|
|
114
126
|
}
|
|
127
|
+
addDecodedDataCallback(callback) {
|
|
128
|
+
if (!this._decodedDataCallbacks.includes(callback)) {
|
|
129
|
+
this._decodedDataCallbacks.push(callback);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
removeDecodedDataCallback(callback) {
|
|
133
|
+
this._decodedDataCallbacks = this._decodedDataCallbacks.filter((cb) => cb !== callback);
|
|
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
|
+
}
|
|
115
143
|
////////////////////////////////////////////////////////////////////////////
|
|
116
144
|
// Set the friendly name for the device
|
|
117
145
|
////////////////////////////////////////////////////////////////////////////
|
|
@@ -125,75 +153,183 @@ class DeviceManager {
|
|
|
125
153
|
////////////////////////////////////////////////////////////////////////////
|
|
126
154
|
async handleClientMsgBinary(rxMsg) {
|
|
127
155
|
// console.log(`DeviceManager client1 msg ${RaftUtils.bufferToHex(rxMsg)}`);
|
|
128
|
-
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
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
|
|
142
206
|
// Debug
|
|
143
207
|
// const debugMsgTime = Date.now();
|
|
144
208
|
const debugMsgIndex = this._debugMsgIndex++;
|
|
145
|
-
// Message layout
|
|
146
|
-
const msgTypeLen = 2; //
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
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
|
|
152
222
|
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} rxMsg.length ${rxMsg.length} rxMsg ${RaftUtils.bufferToHex(rxMsg)}`);
|
|
153
223
|
// Start after the message type
|
|
154
224
|
let msgPos = msgTypeLen;
|
|
155
|
-
|
|
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
|
|
156
261
|
while (msgPos < rxMsg.length) {
|
|
157
|
-
// Check length
|
|
262
|
+
// Check minimum length for record length prefix + record header
|
|
158
263
|
const remainingLen = rxMsg.length - msgPos;
|
|
159
|
-
if (remainingLen <
|
|
160
|
-
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}`);
|
|
161
266
|
return;
|
|
162
267
|
}
|
|
163
|
-
// Get the length
|
|
164
|
-
const
|
|
165
|
-
if (
|
|
166
|
-
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid msgPos ${msgPos}
|
|
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}`);
|
|
167
272
|
return;
|
|
168
273
|
}
|
|
169
|
-
// Extract
|
|
170
|
-
let
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
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}`);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
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;
|
|
179
308
|
// Debug
|
|
180
|
-
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} overallLen ${rxMsg.length}
|
|
181
|
-
// console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex}
|
|
182
|
-
//
|
|
183
|
-
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);
|
|
184
314
|
// Update the last update time
|
|
185
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
|
+
}
|
|
186
322
|
// Check if a device state already exists
|
|
187
323
|
if (!(deviceKey in this._devicesState) || (this._devicesState[deviceKey].deviceTypeInfo === undefined)) {
|
|
188
324
|
// Get the device type info
|
|
189
|
-
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());
|
|
190
326
|
// Debug
|
|
191
|
-
// 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)}`);
|
|
192
328
|
// Handle case where device type info is not available
|
|
193
329
|
if (deviceTypeInfo === undefined) {
|
|
194
|
-
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} deviceType ${devTypeIdx} info not available, skipping attribute processing for this
|
|
195
|
-
// Skip to next
|
|
196
|
-
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;
|
|
197
333
|
continue;
|
|
198
334
|
}
|
|
199
335
|
// Check if device record exists
|
|
@@ -202,7 +338,7 @@ class DeviceManager {
|
|
|
202
338
|
this._devicesState[deviceKey].deviceTypeInfo = deviceTypeInfo;
|
|
203
339
|
this._devicesState[deviceKey].deviceType = deviceTypeInfo.name || "";
|
|
204
340
|
this._devicesState[deviceKey].busName = busNum.toString();
|
|
205
|
-
this._devicesState[deviceKey].deviceAddress =
|
|
341
|
+
this._devicesState[deviceKey].deviceAddress = devAddrHex;
|
|
206
342
|
}
|
|
207
343
|
}
|
|
208
344
|
else {
|
|
@@ -212,13 +348,19 @@ class DeviceManager {
|
|
|
212
348
|
deviceTimeline: {
|
|
213
349
|
timestampsUs: [],
|
|
214
350
|
lastReportTimestampUs: 0,
|
|
215
|
-
reportTimestampOffsetUs: 0
|
|
351
|
+
reportTimestampOffsetUs: 0,
|
|
352
|
+
totalSamplesAdded: 0,
|
|
353
|
+
emaLastSampleTimeUs: 0,
|
|
354
|
+
emaIntervalUs: 0,
|
|
355
|
+
emaPrevPollTimeUs: 0,
|
|
356
|
+
emaCalibrated: false,
|
|
357
|
+
emaCalibrationPolls: 0
|
|
216
358
|
},
|
|
217
359
|
deviceAttributes: {},
|
|
218
360
|
deviceIsNew: true,
|
|
219
361
|
stateChanged: false,
|
|
220
|
-
|
|
221
|
-
deviceAddress:
|
|
362
|
+
onlineState: RaftDeviceStates_1.DeviceOnlineState.Online,
|
|
363
|
+
deviceAddress: devAddrHex,
|
|
222
364
|
deviceType: (deviceTypeInfo === null || deviceTypeInfo === void 0 ? void 0 : deviceTypeInfo.name) || "",
|
|
223
365
|
busName: busNum.toString()
|
|
224
366
|
};
|
|
@@ -226,44 +368,79 @@ class DeviceManager {
|
|
|
226
368
|
}
|
|
227
369
|
// Get device state
|
|
228
370
|
const deviceState = this._devicesState[deviceKey];
|
|
229
|
-
deviceState.
|
|
371
|
+
deviceState.onlineState = isOnline ? RaftDeviceStates_1.DeviceOnlineState.Online : RaftDeviceStates_1.DeviceOnlineState.Offline;
|
|
230
372
|
// Check if device type info is available and complete
|
|
231
373
|
if (deviceState.deviceTypeInfo && deviceState.deviceTypeInfo.resp) {
|
|
232
374
|
// Iterate over attributes in the group
|
|
233
375
|
const pollRespMetadata = deviceState.deviceTypeInfo.resp;
|
|
234
|
-
//
|
|
235
|
-
const
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
376
|
+
// Process samples within this record
|
|
377
|
+
const attrLengthsBefore = this.snapshotAttrLengths(deviceState.deviceAttributes, pollRespMetadata);
|
|
378
|
+
const timelineLenBefore = deviceState.deviceTimeline.timestampsUs.length;
|
|
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;
|
|
242
404
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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}`);
|
|
248
411
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
+
}
|
|
253
430
|
}
|
|
254
|
-
attrGroupPos = newMsgBufIdx;
|
|
255
|
-
deviceState.stateChanged = true;
|
|
256
|
-
// 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}`);
|
|
257
|
-
// console.log(`DevMan.handleClientMsgBinary group done debugIdx ${debugMsgIndex} attrGroupPos ${attrGroupPos} sectionLen ${sectionLen} msgPos ${msgPos} rxMsgLen ${rxMsg.length} remainingLen ${remainingLen}`);
|
|
258
431
|
}
|
|
432
|
+
// Inform decoded-data callbacks
|
|
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());
|
|
259
436
|
}
|
|
260
437
|
else {
|
|
261
438
|
console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} deviceState incomplete for device ${deviceKey}, skipping attribute processing`);
|
|
262
439
|
}
|
|
263
440
|
// Debug
|
|
264
|
-
// console.log(`DevMan.handleClientMsgBinary
|
|
265
|
-
//
|
|
266
|
-
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;
|
|
267
444
|
}
|
|
268
445
|
// Check for devices that have not been updated for a while
|
|
269
446
|
if (this._removeDevicesFlag) {
|
|
@@ -271,6 +448,7 @@ class DeviceManager {
|
|
|
271
448
|
Object.entries(this._deviceLastUpdateTime).forEach(([deviceKey, lastUpdateTime]) => {
|
|
272
449
|
if ((nowTime - lastUpdateTime) > this._removeDevicesTimeMs) {
|
|
273
450
|
delete this._devicesState[deviceKey];
|
|
451
|
+
delete this._deviceStats[deviceKey];
|
|
274
452
|
}
|
|
275
453
|
});
|
|
276
454
|
}
|
|
@@ -285,6 +463,10 @@ class DeviceManager {
|
|
|
285
463
|
// console.log(`DeviceManager client msg ${JSON.stringify(data)}`);
|
|
286
464
|
// Iterate over the buses
|
|
287
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
|
+
}
|
|
288
470
|
// Check for bus status info
|
|
289
471
|
if (devices && typeof devices === "object" && "_s" in devices) {
|
|
290
472
|
// console.log(`DeviceManager bus status ${JSON.stringify(devices._s)}`);
|
|
@@ -292,21 +474,27 @@ class DeviceManager {
|
|
|
292
474
|
}
|
|
293
475
|
// Iterate over the devices
|
|
294
476
|
Object.entries(devices).forEach(async ([devAddr, attrGroups]) => {
|
|
477
|
+
var _a;
|
|
295
478
|
// Check for non-device info (starts with _)
|
|
296
479
|
if (devAddr.startsWith("_")) {
|
|
297
480
|
return;
|
|
298
481
|
}
|
|
299
482
|
// Device type name
|
|
300
483
|
let deviceTypeName = "";
|
|
484
|
+
let deviceTypeIdx = -1;
|
|
301
485
|
if (attrGroups && typeof attrGroups === 'object' && "_t" in attrGroups && typeof attrGroups._t === "string") {
|
|
302
486
|
deviceTypeName = attrGroups._t || "";
|
|
303
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
|
+
}
|
|
304
492
|
else {
|
|
305
493
|
console.warn(`DeviceManager missing device type attrGroups ${JSON.stringify(attrGroups)}`);
|
|
306
494
|
return;
|
|
307
495
|
}
|
|
308
496
|
// Device key
|
|
309
|
-
const deviceKey = (0, RaftDeviceStates_1.getDeviceKey)(busName, devAddr
|
|
497
|
+
const deviceKey = (0, RaftDeviceStates_1.getDeviceKey)(busName, devAddr);
|
|
310
498
|
// Update the last update time
|
|
311
499
|
this._deviceLastUpdateTime[deviceKey] = Date.now();
|
|
312
500
|
// Check if a device state already exists
|
|
@@ -329,12 +517,18 @@ class DeviceManager {
|
|
|
329
517
|
deviceTimeline: {
|
|
330
518
|
timestampsUs: [],
|
|
331
519
|
lastReportTimestampUs: 0,
|
|
332
|
-
reportTimestampOffsetUs: 0
|
|
520
|
+
reportTimestampOffsetUs: 0,
|
|
521
|
+
totalSamplesAdded: 0,
|
|
522
|
+
emaLastSampleTimeUs: 0,
|
|
523
|
+
emaIntervalUs: 0,
|
|
524
|
+
emaPrevPollTimeUs: 0,
|
|
525
|
+
emaCalibrated: false,
|
|
526
|
+
emaCalibrationPolls: 0
|
|
333
527
|
},
|
|
334
528
|
deviceAttributes: {},
|
|
335
529
|
deviceIsNew: true,
|
|
336
530
|
stateChanged: false,
|
|
337
|
-
|
|
531
|
+
onlineState: RaftDeviceStates_1.DeviceOnlineState.Online,
|
|
338
532
|
deviceAddress: devAddr,
|
|
339
533
|
deviceType: deviceTypeName,
|
|
340
534
|
busName: busName
|
|
@@ -343,14 +537,21 @@ class DeviceManager {
|
|
|
343
537
|
}
|
|
344
538
|
// Get device state
|
|
345
539
|
const deviceState = this._devicesState[deviceKey];
|
|
346
|
-
// Check for online/offline state information
|
|
540
|
+
// Check for online/offline/pending-deletion state information
|
|
347
541
|
if (attrGroups && typeof attrGroups === "object" && "_o" in attrGroups) {
|
|
348
|
-
|
|
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;
|
|
349
549
|
}
|
|
350
550
|
// Check if device type info is available
|
|
351
551
|
if (!deviceState.deviceTypeInfo) {
|
|
352
552
|
return;
|
|
353
553
|
}
|
|
554
|
+
const markers = this.extractMarkers(attrGroups);
|
|
354
555
|
// Iterate attribute groups
|
|
355
556
|
Object.entries(attrGroups).forEach(([attrGroupName, msgHexStr]) => {
|
|
356
557
|
// Check valid
|
|
@@ -367,6 +568,9 @@ class DeviceManager {
|
|
|
367
568
|
let msgBufIdx = 0;
|
|
368
569
|
// Iterate over attributes in the group
|
|
369
570
|
const pollRespMetadata = deviceState.deviceTypeInfo.resp;
|
|
571
|
+
const attrLengthsBefore = this.snapshotAttrLengths(deviceState.deviceAttributes, pollRespMetadata);
|
|
572
|
+
const timelineLenBefore = deviceState.deviceTimeline.timestampsUs.length;
|
|
573
|
+
const totalSamplesBefore = deviceState.deviceTimeline.totalSamplesAdded;
|
|
370
574
|
// Loop
|
|
371
575
|
while (msgBufIdx < msgBytes.length) {
|
|
372
576
|
const newMsgBufIdx = this._attributeHandler.processMsgAttrGroup(msgBytes, msgBufIdx, deviceState.deviceTimeline, pollRespMetadata, deviceState.deviceAttributes, this._maxDatapointsToStore);
|
|
@@ -375,6 +579,9 @@ class DeviceManager {
|
|
|
375
579
|
msgBufIdx = newMsgBufIdx;
|
|
376
580
|
deviceState.stateChanged = true;
|
|
377
581
|
}
|
|
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());
|
|
378
585
|
});
|
|
379
586
|
});
|
|
380
587
|
});
|
|
@@ -384,6 +591,7 @@ class DeviceManager {
|
|
|
384
591
|
Object.entries(this._deviceLastUpdateTime).forEach(([deviceKey, lastUpdateTime]) => {
|
|
385
592
|
if ((nowTime - lastUpdateTime) > this._removeDevicesTimeMs) {
|
|
386
593
|
delete this._devicesState[deviceKey];
|
|
594
|
+
delete this._deviceStats[deviceKey];
|
|
387
595
|
}
|
|
388
596
|
});
|
|
389
597
|
}
|
|
@@ -415,6 +623,20 @@ class DeviceManager {
|
|
|
415
623
|
});
|
|
416
624
|
}
|
|
417
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
|
+
////////////////////////////////////////////////////////////////////////////
|
|
418
640
|
// Get device type info
|
|
419
641
|
////////////////////////////////////////////////////////////////////////////
|
|
420
642
|
async getDeviceTypeInfo(busName, deviceType) {
|
|
@@ -483,6 +705,117 @@ class DeviceManager {
|
|
|
483
705
|
return undefined;
|
|
484
706
|
}
|
|
485
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
|
+
}
|
|
486
819
|
////////////////////////////////////////////////////////////////////////////
|
|
487
820
|
// Send action to device
|
|
488
821
|
////////////////////////////////////////////////////////////////////////////
|
|
@@ -492,40 +825,77 @@ class DeviceManager {
|
|
|
492
825
|
.join("");
|
|
493
826
|
}
|
|
494
827
|
async sendAction(deviceKey, action, data) {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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;
|
|
504
860
|
}
|
|
505
|
-
|
|
506
|
-
|
|
861
|
+
catch (error) {
|
|
862
|
+
console.warn(`DeviceManager sendAction error ${error}`);
|
|
863
|
+
return false;
|
|
507
864
|
}
|
|
508
|
-
// Form the write bytes
|
|
509
|
-
writeBytes = action.t ? (0, RaftStruct_1.structPack)(action.t, [value]) : new Uint8Array(0);
|
|
510
865
|
}
|
|
511
866
|
else {
|
|
512
|
-
|
|
513
|
-
|
|
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);
|
|
514
887
|
}
|
|
515
|
-
// Convert to hex string
|
|
516
|
-
let writeHexStr = this.toHex(writeBytes);
|
|
517
888
|
// Add prefix and postfix
|
|
518
889
|
writeHexStr = (action.w ? action.w : "") + writeHexStr + (action.wz ? action.wz : "");
|
|
519
|
-
//
|
|
520
|
-
const devBus =
|
|
521
|
-
const devAddr = deviceKey.split("_")[1];
|
|
890
|
+
// Parse the device key into bus and address components
|
|
891
|
+
const { bus: devBus, addr: devAddr } = this.parseDeviceKeyForCommand(deviceKey);
|
|
522
892
|
// Send the action to the server
|
|
523
893
|
const cmd = "devman/cmdraw?bus=" + devBus + "&addr=" + devAddr + "&hexWr=" + writeHexStr;
|
|
524
894
|
console.log(`DeviceManager deviceKey ${deviceKey} action name ${action.n} value ${data} prefix ${action.w} sendAction ${cmd}`);
|
|
525
895
|
// Send the command
|
|
526
896
|
try {
|
|
527
897
|
// Get the msg handler
|
|
528
|
-
const msgHandler = (
|
|
898
|
+
const msgHandler = (_b = this._systemUtils) === null || _b === void 0 ? void 0 : _b.getMsgHandler();
|
|
529
899
|
if (msgHandler) {
|
|
530
900
|
const msgRslt = await msgHandler.sendRICRESTURL(cmd);
|
|
531
901
|
return msgRslt.rslt === "ok";
|
|
@@ -565,6 +935,111 @@ class DeviceManager {
|
|
|
565
935
|
return false;
|
|
566
936
|
}
|
|
567
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
|
+
////////////////////////////////////////////////////////////////////////////
|
|
568
1043
|
// Convert hex to bytes
|
|
569
1044
|
////////////////////////////////////////////////////////////////////////////
|
|
570
1045
|
hexToBytes(hex) {
|
|
@@ -574,6 +1049,123 @@ class DeviceManager {
|
|
|
574
1049
|
}
|
|
575
1050
|
return bytes;
|
|
576
1051
|
}
|
|
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
|
+
////////////////////////////////////////////////////////////////////////////
|
|
1104
|
+
// Helpers for decoded data callbacks
|
|
1105
|
+
////////////////////////////////////////////////////////////////////////////
|
|
1106
|
+
snapshotAttrLengths(deviceAttrs, pollRespMetadata) {
|
|
1107
|
+
const lengths = {};
|
|
1108
|
+
if (!pollRespMetadata) {
|
|
1109
|
+
return lengths;
|
|
1110
|
+
}
|
|
1111
|
+
pollRespMetadata.a.forEach((attr) => {
|
|
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;
|
|
1114
|
+
});
|
|
1115
|
+
return lengths;
|
|
1116
|
+
}
|
|
1117
|
+
emitDecodedData(deviceKey, busName, devAddr, deviceState, pollRespMetadata, attrLengthsBefore, timelineLenBefore, attrGroupName = "", markers) {
|
|
1118
|
+
if (!pollRespMetadata) {
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
const attrValues = {};
|
|
1122
|
+
let hasValues = false;
|
|
1123
|
+
pollRespMetadata.a.forEach((attr) => {
|
|
1124
|
+
var _a;
|
|
1125
|
+
const attrState = deviceState.deviceAttributes[attr.n];
|
|
1126
|
+
if (!attrState) {
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
const prevLen = (_a = attrLengthsBefore[attr.n]) !== null && _a !== void 0 ? _a : 0;
|
|
1130
|
+
if (attrState.values.length > prevLen) {
|
|
1131
|
+
attrValues[attr.n] = attrState.values.slice(prevLen);
|
|
1132
|
+
hasValues = hasValues || attrValues[attr.n].length > 0;
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
if (!hasValues) {
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
const timestampsUs = deviceState.deviceTimeline.timestampsUs.slice(timelineLenBefore);
|
|
1139
|
+
const decoded = {
|
|
1140
|
+
deviceKey,
|
|
1141
|
+
busName,
|
|
1142
|
+
deviceAddress: devAddr,
|
|
1143
|
+
deviceType: deviceState.deviceType,
|
|
1144
|
+
attrGroupName: attrGroupName || undefined,
|
|
1145
|
+
attrValues,
|
|
1146
|
+
timestampsUs,
|
|
1147
|
+
};
|
|
1148
|
+
if (markers && Object.keys(markers).length > 0) {
|
|
1149
|
+
decoded.markers = markers;
|
|
1150
|
+
decoded.fromOfflineBuffer = this.isTruthy(markers["_buf"]);
|
|
1151
|
+
}
|
|
1152
|
+
this._decodedDataCallbacks.forEach((cb) => cb(decoded));
|
|
1153
|
+
}
|
|
1154
|
+
extractMarkers(attrGroups) {
|
|
1155
|
+
const markers = {};
|
|
1156
|
+
if (!attrGroups || typeof attrGroups !== "object") {
|
|
1157
|
+
return markers;
|
|
1158
|
+
}
|
|
1159
|
+
Object.entries(attrGroups).forEach(([key, value]) => {
|
|
1160
|
+
if (key.startsWith("_") && key !== "_t" && key !== "_o") {
|
|
1161
|
+
markers[key] = value;
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
return markers;
|
|
1165
|
+
}
|
|
1166
|
+
isTruthy(val) {
|
|
1167
|
+
return val === true || val === 1 || val === "1";
|
|
1168
|
+
}
|
|
577
1169
|
}
|
|
578
1170
|
exports.DeviceManager = DeviceManager;
|
|
579
1171
|
//# sourceMappingURL=RaftDeviceManager.js.map
|