@robdobsn/raftjs 1.8.5 → 1.11.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/.editorconfig +14 -0
  2. package/.gitattributes +11 -0
  3. package/.nvmrc +1 -0
  4. package/TODO.md +1 -0
  5. package/dist/react-native/RaftAttributeHandler.d.ts +14 -0
  6. package/dist/react-native/RaftAttributeHandler.js +375 -0
  7. package/dist/react-native/RaftAttributeHandler.js.map +1 -0
  8. package/dist/react-native/RaftChannel.d.ts +20 -0
  9. package/dist/react-native/RaftChannel.js +12 -0
  10. package/dist/react-native/RaftChannel.js.map +1 -0
  11. package/dist/react-native/RaftChannelBLE.native.d.ts +95 -0
  12. package/dist/react-native/RaftChannelBLE.native.js +483 -0
  13. package/dist/react-native/RaftChannelBLE.native.js.map +1 -0
  14. package/dist/react-native/RaftChannelBLE.web.d.ts +40 -0
  15. package/dist/react-native/RaftChannelBLE.web.js +302 -0
  16. package/dist/react-native/RaftChannelBLE.web.js.map +1 -0
  17. package/dist/react-native/RaftChannelBLEFactory.d.ts +10 -0
  18. package/dist/react-native/RaftChannelBLEFactory.js +17 -0
  19. package/dist/react-native/RaftChannelBLEFactory.js.map +1 -0
  20. package/dist/react-native/RaftChannelBLEScanner.native.d.ts +18 -0
  21. package/dist/react-native/RaftChannelBLEScanner.native.js +138 -0
  22. package/dist/react-native/RaftChannelBLEScanner.native.js.map +1 -0
  23. package/dist/react-native/RaftChannelSimulated.d.ts +42 -0
  24. package/dist/react-native/RaftChannelSimulated.js +1001 -0
  25. package/dist/react-native/RaftChannelSimulated.js.map +1 -0
  26. package/dist/react-native/RaftChannelWebSerial.d.ts +39 -0
  27. package/dist/react-native/RaftChannelWebSerial.js +329 -0
  28. package/dist/react-native/RaftChannelWebSerial.js.map +1 -0
  29. package/dist/react-native/RaftChannelWebSocket.d.ts +30 -0
  30. package/dist/react-native/RaftChannelWebSocket.js +222 -0
  31. package/dist/react-native/RaftChannelWebSocket.js.map +1 -0
  32. package/dist/react-native/RaftCommsStats.d.ts +39 -0
  33. package/dist/react-native/RaftCommsStats.js +128 -0
  34. package/dist/react-native/RaftCommsStats.js.map +1 -0
  35. package/dist/react-native/RaftConnEvents.d.ts +39 -0
  36. package/dist/react-native/RaftConnEvents.js +54 -0
  37. package/dist/react-native/RaftConnEvents.js.map +1 -0
  38. package/dist/react-native/RaftConnector.d.ts +257 -0
  39. package/dist/react-native/RaftConnector.js +671 -0
  40. package/dist/react-native/RaftConnector.js.map +1 -0
  41. package/dist/react-native/RaftCustomAttrHandler.d.ts +6 -0
  42. package/dist/react-native/RaftCustomAttrHandler.js +93 -0
  43. package/dist/react-native/RaftCustomAttrHandler.js.map +1 -0
  44. package/dist/react-native/RaftDeviceInfo.d.ts +71 -0
  45. package/dist/react-native/RaftDeviceInfo.js +50 -0
  46. package/dist/react-native/RaftDeviceInfo.js.map +1 -0
  47. package/dist/react-native/RaftDeviceManager.d.ts +73 -0
  48. package/dist/react-native/RaftDeviceManager.js +812 -0
  49. package/dist/react-native/RaftDeviceManager.js.map +1 -0
  50. package/dist/react-native/RaftDeviceMgrIF.d.ts +19 -0
  51. package/dist/react-native/RaftDeviceMgrIF.js +11 -0
  52. package/dist/react-native/RaftDeviceMgrIF.js.map +1 -0
  53. package/dist/react-native/RaftDeviceMsg.d.ts +9 -0
  54. package/dist/react-native/RaftDeviceMsg.js +11 -0
  55. package/dist/react-native/RaftDeviceMsg.js.map +1 -0
  56. package/dist/react-native/RaftDeviceStates.d.ts +55 -0
  57. package/dist/react-native/RaftDeviceStates.js +81 -0
  58. package/dist/react-native/RaftDeviceStates.js.map +1 -0
  59. package/dist/react-native/RaftFileHandler.d.ts +52 -0
  60. package/dist/react-native/RaftFileHandler.js +502 -0
  61. package/dist/react-native/RaftFileHandler.js.map +1 -0
  62. package/dist/react-native/RaftLog.d.ts +22 -0
  63. package/dist/react-native/RaftLog.js +63 -0
  64. package/dist/react-native/RaftLog.js.map +1 -0
  65. package/dist/react-native/RaftMiniHDLC.d.ts +18 -0
  66. package/dist/react-native/RaftMiniHDLC.js +383 -0
  67. package/dist/react-native/RaftMiniHDLC.js.map +1 -0
  68. package/dist/react-native/RaftMsgHandler.d.ts +62 -0
  69. package/dist/react-native/RaftMsgHandler.js +511 -0
  70. package/dist/react-native/RaftMsgHandler.js.map +1 -0
  71. package/dist/react-native/RaftMsgTrackInfo.d.ts +17 -0
  72. package/dist/react-native/RaftMsgTrackInfo.js +42 -0
  73. package/dist/react-native/RaftMsgTrackInfo.js.map +1 -0
  74. package/dist/react-native/RaftProtocolDefs.d.ts +30 -0
  75. package/dist/react-native/RaftProtocolDefs.js +48 -0
  76. package/dist/react-native/RaftProtocolDefs.js.map +1 -0
  77. package/dist/react-native/RaftPublish.d.ts +2 -0
  78. package/dist/react-native/RaftPublish.js +81 -0
  79. package/dist/react-native/RaftPublish.js.map +1 -0
  80. package/dist/react-native/RaftStreamHandler.d.ts +49 -0
  81. package/dist/react-native/RaftStreamHandler.js +324 -0
  82. package/dist/react-native/RaftStreamHandler.js.map +1 -0
  83. package/dist/react-native/RaftStruct.d.ts +3 -0
  84. package/dist/react-native/RaftStruct.js +258 -0
  85. package/dist/react-native/RaftStruct.js.map +1 -0
  86. package/dist/react-native/RaftSysTypeManager.d.ts +16 -0
  87. package/dist/react-native/RaftSysTypeManager.js +78 -0
  88. package/dist/react-native/RaftSysTypeManager.js.map +1 -0
  89. package/dist/react-native/RaftSystemType.d.ts +30 -0
  90. package/dist/react-native/RaftSystemType.js +3 -0
  91. package/dist/react-native/RaftSystemType.js.map +1 -0
  92. package/dist/react-native/RaftSystemUtils.d.ts +152 -0
  93. package/dist/react-native/RaftSystemUtils.js +463 -0
  94. package/dist/react-native/RaftSystemUtils.js.map +1 -0
  95. package/dist/react-native/RaftTypes.d.ts +216 -0
  96. package/dist/react-native/RaftTypes.js +153 -0
  97. package/dist/react-native/RaftTypes.js.map +1 -0
  98. package/dist/react-native/RaftUpdateEvents.d.ts +33 -0
  99. package/dist/react-native/RaftUpdateEvents.js +46 -0
  100. package/dist/react-native/RaftUpdateEvents.js.map +1 -0
  101. package/dist/react-native/RaftUpdateManager.d.ts +61 -0
  102. package/dist/react-native/RaftUpdateManager.js +621 -0
  103. package/dist/react-native/RaftUpdateManager.js.map +1 -0
  104. package/dist/react-native/RaftUtils.d.ts +128 -0
  105. package/dist/react-native/RaftUtils.js +487 -0
  106. package/dist/react-native/RaftUtils.js.map +1 -0
  107. package/dist/react-native/RaftWifiTypes.d.ts +23 -0
  108. package/dist/react-native/RaftWifiTypes.js +43 -0
  109. package/dist/react-native/RaftWifiTypes.js.map +1 -0
  110. package/dist/react-native/main.d.ts +27 -0
  111. package/dist/react-native/main.js +52 -0
  112. package/dist/react-native/main.js.map +1 -0
  113. package/dist/web/RaftAttributeHandler.js +1 -1
  114. package/dist/web/RaftAttributeHandler.js.map +1 -1
  115. package/dist/web/RaftChannelBLE.web.js +8 -6
  116. package/dist/web/RaftChannelBLE.web.js.map +1 -1
  117. package/dist/web/RaftChannelSimulated.d.ts +10 -0
  118. package/dist/web/RaftChannelSimulated.js +665 -82
  119. package/dist/web/RaftChannelSimulated.js.map +1 -1
  120. package/dist/web/RaftChannelWebSerial.js +2 -2
  121. package/dist/web/RaftChannelWebSerial.js.map +1 -1
  122. package/dist/web/RaftChannelWebSocket.js +16 -1
  123. package/dist/web/RaftChannelWebSocket.js.map +1 -1
  124. package/dist/web/RaftConnector.d.ts +12 -1
  125. package/dist/web/RaftConnector.js +45 -9
  126. package/dist/web/RaftConnector.js.map +1 -1
  127. package/dist/web/RaftCustomAttrHandler.d.ts +2 -0
  128. package/dist/web/RaftCustomAttrHandler.js +54 -26
  129. package/dist/web/RaftCustomAttrHandler.js.map +1 -1
  130. package/dist/web/RaftDeviceInfo.d.ts +3 -1
  131. package/dist/web/RaftDeviceInfo.js +17 -3
  132. package/dist/web/RaftDeviceInfo.js.map +1 -1
  133. package/dist/web/RaftDeviceManager.d.ts +32 -2
  134. package/dist/web/RaftDeviceManager.js +307 -74
  135. package/dist/web/RaftDeviceManager.js.map +1 -1
  136. package/dist/web/RaftDeviceMgrIF.d.ts +5 -1
  137. package/dist/web/RaftDeviceStates.d.ts +20 -2
  138. package/dist/web/RaftDeviceStates.js +25 -4
  139. package/dist/web/RaftDeviceStates.js.map +1 -1
  140. package/dist/web/RaftMsgHandler.js.map +1 -1
  141. package/dist/web/RaftPublish.d.ts +2 -0
  142. package/dist/web/RaftPublish.js +81 -0
  143. package/dist/web/RaftPublish.js.map +1 -0
  144. package/dist/web/RaftStreamHandler.d.ts +11 -0
  145. package/dist/web/RaftStreamHandler.js +68 -1
  146. package/dist/web/RaftStreamHandler.js.map +1 -1
  147. package/dist/web/RaftStruct.js +197 -147
  148. package/dist/web/RaftStruct.js.map +1 -1
  149. package/dist/web/RaftSystemUtils.d.ts +17 -1
  150. package/dist/web/RaftSystemUtils.js +51 -0
  151. package/dist/web/RaftSystemUtils.js.map +1 -1
  152. package/dist/web/RaftTypes.d.ts +21 -0
  153. package/dist/web/RaftTypes.js.map +1 -1
  154. package/dist/web/RaftUpdateManager.js +1 -1
  155. package/dist/web/RaftUpdateManager.js.map +1 -1
  156. package/dist/web/RaftUtils.d.ts +2 -0
  157. package/dist/web/RaftUtils.js +20 -0
  158. package/dist/web/RaftUtils.js.map +1 -1
  159. package/dist/web/main.d.ts +2 -0
  160. package/dist/web/main.js +1 -0
  161. package/dist/web/main.js.map +1 -1
  162. package/eslint.config.mjs +33 -0
  163. package/examples/dashboard/package.json +36 -0
  164. package/examples/dashboard/src/CommandPanel.tsx +147 -0
  165. package/examples/dashboard/src/ConnManager.ts +166 -0
  166. package/examples/dashboard/src/DeviceActionsForm.tsx +133 -0
  167. package/examples/dashboard/src/DeviceAttrsForm.tsx +49 -0
  168. package/examples/dashboard/src/DeviceLineChart.tsx +163 -0
  169. package/examples/dashboard/src/DevicePanel.tsx +247 -0
  170. package/examples/dashboard/src/DeviceStatsPanel.tsx +65 -0
  171. package/examples/dashboard/src/DevicesPanel.tsx +69 -0
  172. package/examples/dashboard/src/DispLedGrid.tsx +110 -0
  173. package/examples/dashboard/src/DispOneLed.tsx +20 -0
  174. package/examples/dashboard/src/LatencyTest.ts +130 -0
  175. package/examples/dashboard/src/LatencyTestPanel.tsx +92 -0
  176. package/examples/dashboard/src/Main.tsx +234 -0
  177. package/examples/dashboard/src/SettingsManager.ts +67 -0
  178. package/examples/dashboard/src/SettingsScreen.tsx +179 -0
  179. package/examples/dashboard/src/StatusPanel.tsx +71 -0
  180. package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +170 -0
  181. package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +125 -0
  182. package/examples/dashboard/src/SystemTypeGeneric/StateInfoGeneric.ts +38 -0
  183. package/examples/dashboard/src/SystemTypeGeneric/SystemTypeGeneric.ts +125 -0
  184. package/examples/dashboard/src/SystemTypeMarty/RICAddOn.ts +70 -0
  185. package/examples/dashboard/src/SystemTypeMarty/RICAddOnBase.ts +33 -0
  186. package/examples/dashboard/src/SystemTypeMarty/RICAddOnManager.ts +342 -0
  187. package/examples/dashboard/src/SystemTypeMarty/RICCommsStats.ts +170 -0
  188. package/examples/dashboard/src/SystemTypeMarty/RICHWElem.ts +123 -0
  189. package/examples/dashboard/src/SystemTypeMarty/RICLEDPatternChecker.ts +207 -0
  190. package/examples/dashboard/src/SystemTypeMarty/RICROSSerial.ts +464 -0
  191. package/examples/dashboard/src/SystemTypeMarty/RICServoFaultDetector.ts +146 -0
  192. package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +105 -0
  193. package/examples/dashboard/src/SystemTypeMarty/RICSystemUtils.ts +371 -0
  194. package/examples/dashboard/src/SystemTypeMarty/RICTypes.ts +20 -0
  195. package/examples/dashboard/src/SystemTypeMarty/SystemTypeMarty.ts +119 -0
  196. package/examples/dashboard/src/index.html +15 -0
  197. package/examples/dashboard/src/index.tsx +13 -0
  198. package/examples/dashboard/src/styles.css +570 -0
  199. package/examples/dashboard/tsconfig.json +18 -0
  200. package/jest.config.js +11 -0
  201. package/package.json +49 -52
  202. package/src/RaftAttributeHandler.ts +450 -0
  203. package/src/RaftChannel.ts +32 -0
  204. package/src/RaftChannelBLE.native.ts +617 -0
  205. package/src/RaftChannelBLE.web.ts +374 -0
  206. package/src/RaftChannelBLEFactory.ts +13 -0
  207. package/src/RaftChannelBLEScanner.native.ts +184 -0
  208. package/src/RaftChannelSimulated.ts +1177 -0
  209. package/src/RaftChannelWebSerial.ts +420 -0
  210. package/src/RaftChannelWebSocket.ts +272 -0
  211. package/src/RaftCommsStats.ts +142 -0
  212. package/src/RaftConnEvents.ts +58 -0
  213. package/src/RaftConnector.ts +806 -0
  214. package/src/RaftCustomAttrHandler.ts +117 -0
  215. package/src/RaftDeviceInfo.ts +125 -0
  216. package/src/RaftDeviceManager.ts +1014 -0
  217. package/src/RaftDeviceMgrIF.ts +37 -0
  218. package/src/RaftDeviceMsg.ts +20 -0
  219. package/src/RaftDeviceStates.ts +122 -0
  220. package/src/RaftFileHandler.ts +668 -0
  221. package/src/RaftLog.ts +70 -0
  222. package/src/RaftMiniHDLC.ts +396 -0
  223. package/src/RaftMsgHandler.ts +812 -0
  224. package/src/RaftMsgTrackInfo.ts +51 -0
  225. package/src/RaftProtocolDefs.ts +46 -0
  226. package/src/RaftPublish.ts +92 -0
  227. package/src/RaftStreamHandler.ts +412 -0
  228. package/src/RaftStruct.ts +282 -0
  229. package/src/RaftSysTypeManager.ts +87 -0
  230. package/src/RaftSystemType.ts +34 -0
  231. package/src/RaftSystemUtils.ts +548 -0
  232. package/src/RaftTypes.ts +306 -0
  233. package/src/RaftUpdateEvents.ts +48 -0
  234. package/src/RaftUpdateManager.ts +781 -0
  235. package/src/RaftUtils.ts +514 -0
  236. package/src/RaftWifiTypes.ts +36 -0
  237. package/src/main.ts +40 -0
  238. package/testdata/TestDeviceTypeRecs.json +492 -0
  239. package/tsconfig.json +30 -0
  240. package/tsconfig.react-native.json +29 -0
@@ -0,0 +1,812 @@
1
+ "use strict";
2
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
3
+ //
4
+ // RaftDeviceManager
5
+ // Device manager for Raft devices
6
+ //
7
+ // Rob Dobson (C) 2024
8
+ //
9
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.DeviceManager = void 0;
12
+ const tslib_1 = require("tslib");
13
+ const RaftDeviceStates_1 = require("./RaftDeviceStates");
14
+ const RaftAttributeHandler_1 = tslib_1.__importDefault(require("./RaftAttributeHandler"));
15
+ const RaftStruct_1 = require("./RaftStruct");
16
+ class DeviceManager {
17
+ getDevicesState() {
18
+ return this._devicesState;
19
+ }
20
+ getDeviceState(deviceKey) {
21
+ return this._devicesState[deviceKey];
22
+ }
23
+ getDeviceStats(deviceKey) {
24
+ return this.cloneDeviceStats(this.getOrCreateDeviceStats(deviceKey));
25
+ }
26
+ resetDeviceStats(deviceKey) {
27
+ this._deviceStats[deviceKey] = this.createEmptyStats();
28
+ }
29
+ // Constructor
30
+ constructor() {
31
+ // Max data points to store
32
+ this._maxDatapointsToStore = 1000;
33
+ // Min time between attempts to retrieve device type info
34
+ this._minTimeBetweenDeviceTypeInfoRetrievalMs = 60000;
35
+ // Attribute handler
36
+ this._attributeHandler = new RaftAttributeHandler_1.default();
37
+ // Devices state
38
+ this._devicesState = new RaftDeviceStates_1.DevicesState();
39
+ // Last time each device was updated - used to detect devices that are no longer present
40
+ this._deviceLastUpdateTime = {};
41
+ // Flag indicating that removed devices should be removed from the state
42
+ this._removeDevicesFlag = true;
43
+ this._removeDevicesTimeMs = 60000;
44
+ // System utils
45
+ this._systemUtils = null;
46
+ // Device callbacks
47
+ this._newDeviceCallbacks = [];
48
+ this._newDeviceAttributeCallbacks = [];
49
+ this._newAttributeDataCallbacks = [];
50
+ this._decodedDataCallbacks = [];
51
+ this._deviceRemovedCallbacks = [];
52
+ // Debug message index (to help debug with async messages)
53
+ this._debugMsgIndex = 0;
54
+ // Device stats (sample counts, rates)
55
+ this._statsWindowMs = 5000;
56
+ this._deviceStats = {};
57
+ // Cached device type data
58
+ this._cachedDeviceTypeRecs = {};
59
+ // Cached device type previous attempt times
60
+ this._cachedDeviceTypePreviousAttemptTimes = {};
61
+ // Pending device type requests - queue-based to maintain order
62
+ this._pendingDeviceTypeRequests = {};
63
+ }
64
+ ////////////////////////////////////////////////////////////////////////////
65
+ // Settings
66
+ ////////////////////////////////////////////////////////////////////////////
67
+ setMaxDataPointsToStore(maxDatapointsToStore) {
68
+ this._maxDatapointsToStore = maxDatapointsToStore;
69
+ // console.log(`DeviceManager setMaxDataPointsToStore ${maxDatapointsToStore}`);
70
+ }
71
+ ////////////////////////////////////////////////////////////////////////////
72
+ // Send REST commands
73
+ ////////////////////////////////////////////////////////////////////////////
74
+ async sendCommand(cmd) {
75
+ var _a;
76
+ try {
77
+ // Get the msg handler
78
+ const msgHandler = (_a = this._systemUtils) === null || _a === void 0 ? void 0 : _a.getMsgHandler();
79
+ if (msgHandler) {
80
+ const msgRslt = await msgHandler.sendRICRESTURL(cmd);
81
+ return msgRslt.rslt === "ok";
82
+ }
83
+ return false;
84
+ }
85
+ catch (error) {
86
+ console.warn(`DeviceManager sendCommand error ${error}`);
87
+ return false;
88
+ }
89
+ }
90
+ ////////////////////////////////////////////////////////////////////////////
91
+ // Setup
92
+ ////////////////////////////////////////////////////////////////////////////
93
+ async setup(systemUtils) {
94
+ // Save the system utils
95
+ this._systemUtils = systemUtils;
96
+ return true;
97
+ }
98
+ ////////////////////////////////////////////////////////////////////////////
99
+ // Register callbacks
100
+ ////////////////////////////////////////////////////////////////////////////
101
+ addNewDeviceCallback(callback) {
102
+ if (!this._newDeviceCallbacks.includes(callback)) {
103
+ this._newDeviceCallbacks.push(callback);
104
+ }
105
+ }
106
+ removeNewDeviceCallback(callback) {
107
+ this._newDeviceCallbacks = this._newDeviceCallbacks.filter((cb) => cb !== callback);
108
+ }
109
+ addNewAttributeCallback(callback) {
110
+ if (!this._newDeviceAttributeCallbacks.includes(callback)) {
111
+ this._newDeviceAttributeCallbacks.push(callback);
112
+ }
113
+ }
114
+ removeNewAttributeCallback(callback) {
115
+ this._newDeviceAttributeCallbacks = this._newDeviceAttributeCallbacks.filter((cb) => cb !== callback);
116
+ }
117
+ addAttributeDataCallback(callback) {
118
+ if (!this._newAttributeDataCallbacks.includes(callback)) {
119
+ this._newAttributeDataCallbacks.push(callback);
120
+ }
121
+ }
122
+ removeAttributeDataCallback(callback) {
123
+ this._newAttributeDataCallbacks = this._newAttributeDataCallbacks.filter((cb) => cb !== callback);
124
+ }
125
+ addDecodedDataCallback(callback) {
126
+ if (!this._decodedDataCallbacks.includes(callback)) {
127
+ this._decodedDataCallbacks.push(callback);
128
+ }
129
+ }
130
+ removeDecodedDataCallback(callback) {
131
+ this._decodedDataCallbacks = this._decodedDataCallbacks.filter((cb) => cb !== callback);
132
+ }
133
+ addDeviceRemovedCallback(callback) {
134
+ if (!this._deviceRemovedCallbacks.includes(callback)) {
135
+ this._deviceRemovedCallbacks.push(callback);
136
+ }
137
+ }
138
+ removeDeviceRemovedCallback(callback) {
139
+ this._deviceRemovedCallbacks = this._deviceRemovedCallbacks.filter((cb) => cb !== callback);
140
+ }
141
+ ////////////////////////////////////////////////////////////////////////////
142
+ // Set the friendly name for the device
143
+ ////////////////////////////////////////////////////////////////////////////
144
+ async setFriendlyName(friendlyName) {
145
+ var _a;
146
+ // Set using utils
147
+ await ((_a = this._systemUtils) === null || _a === void 0 ? void 0 : _a.setRaftName(friendlyName));
148
+ }
149
+ ////////////////////////////////////////////////////////////////////////////
150
+ // Handle device message binary
151
+ ////////////////////////////////////////////////////////////////////////////
152
+ async handleClientMsgBinary(rxMsg) {
153
+ // console.log(`DeviceManager client1 msg ${RaftUtils.bufferToHex(rxMsg)}`);
154
+ var _a;
155
+ // DevBIN message format
156
+ //
157
+ // The rxMsg passed to this function has a 2-byte message type prefix (e.g. 0x0080)
158
+ // added by the transport layer. After that prefix comes a devbin frame:
159
+ //
160
+ // Devbin envelope (2 bytes):
161
+ // Byte 0: magic+version 0xDB = devbin v1 (valid range 0xDB–0xDF for v1–v5)
162
+ // Byte 1: topicIndex 0x00–0xFE = topic index; 0xFF = no topic
163
+ //
164
+ // Then zero or more per-device records, concatenated back-to-back:
165
+ // Bytes 0-1: recordLen uint16 big-endian — number of body bytes that follow (min 7)
166
+ // Byte 2: statusBus bit 7 = online flag, bit 6 = pending deletion, bits 5:4 = reserved, bits 3:0 = bus number (0-15)
167
+ // Bytes 3-6: address uint32 big-endian — device address on the bus
168
+ // Bytes 7-8: devTypeIdx uint16 big-endian — device type table index
169
+ // Bytes 9+: pollData variable length (recordLen − 7 bytes) — device data
170
+ //
171
+ // Example message (with transport prefix):
172
+ // 0080 DB01 0015 81 0000076a 000b bff10000ffffffff7a07d1f1221c 000e 80 00000000 001f bc340000030001
173
+ // ^^^^ ^^^^ ^^^^
174
+ // | | | || | | | Record 2 ...
175
+ // | | | || | | pollData (14 bytes)
176
+ // | | | || | devTypeIdx = 0x000b (11)
177
+ // | | | || address = 0x0000076a (slot 7, I2C addr 0x6a)
178
+ // | | | |busInfo = 0x81 (bus 1, online)
179
+ // | | | recordLen = 0x0015 (21 bytes)
180
+ // | | topicIndex = 0x01
181
+ // | magic+version = 0xDB (devbin v1)
182
+ // msgType prefix (transport layer)
183
+ // Debug
184
+ // const debugMsgTime = Date.now();
185
+ const debugMsgIndex = this._debugMsgIndex++;
186
+ // Message layout constants
187
+ const msgTypeLen = 2; // Transport-layer message type prefix (first two bytes, e.g. 0x0080)
188
+ const devbinEnvelopeLen = 2; // Devbin envelope: magic+version (1 byte) + topicIndex (1 byte)
189
+ const devbinMagicMin = 0xDB;
190
+ const devbinMagicMax = 0xDF;
191
+ const recordLenLen = 2; // Per-record length prefix (uint16 big-endian)
192
+ const busInfoLen = 1; // statusBus byte: bit 7 = online, bit 6 = pending deletion, bits 3:0 = bus number
193
+ const deviceAddrLen = 4; // Device address (uint32 big-endian)
194
+ const devTypeIdxLen = 2; // Device type index (uint16 big-endian)
195
+ const recordHeaderLen = busInfoLen + deviceAddrLen + devTypeIdxLen; // = 7, minimum record body
196
+ // console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} rxMsg.length ${rxMsg.length} rxMsg ${RaftUtils.bufferToHex(rxMsg)}`);
197
+ // Start after the message type
198
+ let msgPos = msgTypeLen;
199
+ // Check for devbin envelope (magic+version + topicIndex)
200
+ if (rxMsg.length >= msgTypeLen + devbinEnvelopeLen) {
201
+ const envelopeMagicVer = rxMsg[msgTypeLen];
202
+ if ((envelopeMagicVer & 0xF0) === 0xD0) {
203
+ if ((envelopeMagicVer < devbinMagicMin) || (envelopeMagicVer > devbinMagicMax)) {
204
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid devbin envelope magic/version ${envelopeMagicVer}`);
205
+ return;
206
+ }
207
+ const topicIndex = rxMsg[msgTypeLen + 1];
208
+ if (topicIndex !== 0xFF) {
209
+ const topicName = (_a = this._systemUtils) === null || _a === void 0 ? void 0 : _a.getPublishTopicName(topicIndex);
210
+ if (topicName && topicName !== "devbin") {
211
+ return;
212
+ }
213
+ }
214
+ msgPos += devbinEnvelopeLen;
215
+ }
216
+ }
217
+ // Iterate through device records
218
+ while (msgPos < rxMsg.length) {
219
+ // Check minimum length for record length prefix + record header
220
+ const remainingLen = rxMsg.length - msgPos;
221
+ if (remainingLen < recordLenLen + recordHeaderLen) {
222
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid length ${rxMsg.length} < ${recordLenLen + recordHeaderLen + msgPos}`);
223
+ return;
224
+ }
225
+ // Get the record body length (bytes that follow the 2-byte length prefix)
226
+ const recordLen = (rxMsg[msgPos] << 8) + rxMsg[msgPos + 1];
227
+ if (recordLen > remainingLen - recordLenLen) {
228
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid msgPos ${msgPos} recordLen ${recordLen} remainingAfterLenBytes ${remainingLen - recordLenLen}`);
229
+ return;
230
+ }
231
+ // Extract record header fields
232
+ let recordPos = msgPos + recordLenLen;
233
+ // statusBus byte: bit 7 = online, bit 6 = pending deletion, bits 3:0 = bus number
234
+ const statusByte = rxMsg[recordPos];
235
+ const busNum = statusByte & 0x0f;
236
+ const isOnline = (statusByte & 0x80) !== 0;
237
+ const isPendingDeletion = (statusByte & 0x40) !== 0;
238
+ recordPos += busInfoLen;
239
+ // Device address (uint32 big-endian)
240
+ const devAddr = (rxMsg[recordPos] << 24) + (rxMsg[recordPos + 1] << 16) + (rxMsg[recordPos + 2] << 8) + rxMsg[recordPos + 3];
241
+ recordPos += deviceAddrLen;
242
+ // Device type index (uint16 big-endian)
243
+ const devTypeIdx = (rxMsg[recordPos] << 8) + rxMsg[recordPos + 1];
244
+ let pollDataPos = recordPos + devTypeIdxLen;
245
+ // Debug
246
+ // console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} overallLen ${rxMsg.length} recordStart ${msgPos} recordLen ${recordLen} ${pollDataPos} ${RaftUtils.bufferToHex(rxMsg.slice(msgPos, msgPos + recordLenLen + recordLen))}`);
247
+ // console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} bus ${busNum} isOnline ${isOnline} devAddr 0x${devAddr.toString(16)} devTypeIdx ${devTypeIdx} pollDataLen ${recordLen - recordHeaderLen}`);
248
+ // Format device address as canonical hex and build device key
249
+ const devAddrHex = (0, RaftDeviceStates_1.formatDeviceAddrHex)(devAddr);
250
+ const deviceKey = (0, RaftDeviceStates_1.getDeviceKey)(busNum.toString(), devAddrHex);
251
+ // Update the last update time
252
+ this._deviceLastUpdateTime[deviceKey] = Date.now();
253
+ // Handle pending deletion - remove device and skip further processing
254
+ if (isPendingDeletion) {
255
+ this.removeDevice(deviceKey);
256
+ msgPos += recordLenLen + recordLen;
257
+ continue;
258
+ }
259
+ // Check if a device state already exists
260
+ if (!(deviceKey in this._devicesState) || (this._devicesState[deviceKey].deviceTypeInfo === undefined)) {
261
+ // Get the device type info
262
+ const deviceTypeInfo = await this.getDeviceTypeInfo(busNum.toString(), devTypeIdx.toString());
263
+ // Debug
264
+ // console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} pollDataPos ${pollDataPos} busNum ${busNum} devAddr 0x${devAddr.toString(16)} devTypeIdx ${devTypeIdx} deviceTypeInfo ${JSON.stringify(deviceTypeInfo)}`);
265
+ // Handle case where device type info is not available
266
+ if (deviceTypeInfo === undefined) {
267
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} deviceType ${devTypeIdx} info not available, skipping attribute processing for this record`);
268
+ // Skip to next record without processing attributes
269
+ msgPos += recordLenLen + recordLen;
270
+ continue;
271
+ }
272
+ // Check if device record exists
273
+ if (deviceKey in this._devicesState) {
274
+ if (deviceTypeInfo !== undefined) {
275
+ this._devicesState[deviceKey].deviceTypeInfo = deviceTypeInfo;
276
+ this._devicesState[deviceKey].deviceType = deviceTypeInfo.name || "";
277
+ this._devicesState[deviceKey].busName = busNum.toString();
278
+ this._devicesState[deviceKey].deviceAddress = devAddrHex;
279
+ }
280
+ }
281
+ else {
282
+ // Create device record - device type info may be undefined
283
+ this._devicesState[deviceKey] = {
284
+ deviceTypeInfo: deviceTypeInfo,
285
+ deviceTimeline: {
286
+ timestampsUs: [],
287
+ lastReportTimestampUs: 0,
288
+ reportTimestampOffsetUs: 0
289
+ },
290
+ deviceAttributes: {},
291
+ deviceIsNew: true,
292
+ stateChanged: false,
293
+ onlineState: RaftDeviceStates_1.DeviceOnlineState.Online,
294
+ deviceAddress: devAddrHex,
295
+ deviceType: (deviceTypeInfo === null || deviceTypeInfo === void 0 ? void 0 : deviceTypeInfo.name) || "",
296
+ busName: busNum.toString()
297
+ };
298
+ }
299
+ }
300
+ // Get device state
301
+ const deviceState = this._devicesState[deviceKey];
302
+ deviceState.onlineState = isOnline ? RaftDeviceStates_1.DeviceOnlineState.Online : RaftDeviceStates_1.DeviceOnlineState.Offline;
303
+ // Check if device type info is available and complete
304
+ if (deviceState.deviceTypeInfo && deviceState.deviceTypeInfo.resp) {
305
+ // Iterate over attributes in the group
306
+ const pollRespMetadata = deviceState.deviceTypeInfo.resp;
307
+ // Process poll data (recordLen - recordHeaderLen bytes)
308
+ const pollDataLen = recordLen - recordHeaderLen;
309
+ const pollDataStartPos = pollDataPos;
310
+ const attrLengthsBefore = this.snapshotAttrLengths(deviceState.deviceAttributes, pollRespMetadata);
311
+ const timelineLenBefore = deviceState.deviceTimeline.timestampsUs.length;
312
+ while (pollDataPos < pollDataStartPos + pollDataLen) {
313
+ // Add bounds checking
314
+ if (pollDataPos >= rxMsg.length) {
315
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} pollDataPos ${pollDataPos} exceeds message length ${rxMsg.length}`);
316
+ break;
317
+ }
318
+ const newMsgBufIdx = this._attributeHandler.processMsgAttrGroup(rxMsg, pollDataPos, deviceState.deviceTimeline, pollRespMetadata, deviceState.deviceAttributes, this._maxDatapointsToStore);
319
+ // console.log(`DevMan.handleClientMsgBinary decoded debugIdx ${debugMsgIndex} devType ${deviceState.deviceTypeInfo.name} pollDataLen ${pollDataLen} pollDataPos ${pollDataPos} recordLen ${recordLen} msgPos ${msgPos} rxMsgLen ${rxMsg.length} remainingLen ${remainingLen} pollRespMetadata ${JSON.stringify(pollRespMetadata)}`);
320
+ if (newMsgBufIdx < 0) {
321
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} processMsgAttrGroup failed newMsgBufIdx ${newMsgBufIdx}`);
322
+ break;
323
+ }
324
+ // Prevent infinite loops
325
+ if (newMsgBufIdx <= pollDataPos) {
326
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} processMsgAttrGroup didn't advance position from ${pollDataPos} to ${newMsgBufIdx}`);
327
+ break;
328
+ }
329
+ pollDataPos = newMsgBufIdx;
330
+ deviceState.stateChanged = true;
331
+ // 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}`);
332
+ // console.log(`DevMan.handleClientMsgBinary group done debugIdx ${debugMsgIndex} pollDataPos ${pollDataPos} recordLen ${recordLen} msgPos ${msgPos} rxMsgLen ${rxMsg.length} remainingLen ${remainingLen}`);
333
+ }
334
+ // Inform decoded-data callbacks
335
+ this.emitDecodedData(deviceKey, busNum.toString(), devAddrHex, deviceState, pollRespMetadata, attrLengthsBefore, timelineLenBefore);
336
+ const newSamples = deviceState.deviceTimeline.timestampsUs.length - timelineLenBefore;
337
+ this.updateDeviceStats(deviceKey, newSamples, Date.now());
338
+ }
339
+ else {
340
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} deviceState incomplete for device ${deviceKey}, skipping attribute processing`);
341
+ }
342
+ // Debug
343
+ // console.log(`DevMan.handleClientMsgBinary record done debugIdx ${debugMsgIndex} pollDataPos ${pollDataPos} recordLen ${recordLen} msgPos ${msgPos} newMsgPos ${msgPos + recordLenLen + recordLen} rxMsgLen ${rxMsg.length} remainingLen ${remainingLen}`);
344
+ // Advance past this record (recordLenLen + recordLen bytes)
345
+ msgPos += recordLenLen + recordLen;
346
+ }
347
+ // Check for devices that have not been updated for a while
348
+ if (this._removeDevicesFlag) {
349
+ const nowTime = Date.now();
350
+ Object.entries(this._deviceLastUpdateTime).forEach(([deviceKey, lastUpdateTime]) => {
351
+ if ((nowTime - lastUpdateTime) > this._removeDevicesTimeMs) {
352
+ delete this._devicesState[deviceKey];
353
+ delete this._deviceStats[deviceKey];
354
+ }
355
+ });
356
+ }
357
+ // Process the callback
358
+ this.processStateCallback();
359
+ }
360
+ ////////////////////////////////////////////////////////////////////////////
361
+ // Handle device message JSON
362
+ ////////////////////////////////////////////////////////////////////////////
363
+ async handleClientMsgJson(jsonMsg) {
364
+ const data = JSON.parse(jsonMsg);
365
+ // console.log(`DeviceManager client msg ${JSON.stringify(data)}`);
366
+ // Iterate over the buses
367
+ Object.entries(data).forEach(([busName, devices]) => {
368
+ // Check the bus name doesn't start with _ which is reserved for non-device information such as topic name
369
+ if (busName.startsWith("_")) {
370
+ return;
371
+ }
372
+ // Check for bus status info
373
+ if (devices && typeof devices === "object" && "_s" in devices) {
374
+ // console.log(`DeviceManager bus status ${JSON.stringify(devices._s)}`);
375
+ return;
376
+ }
377
+ // Iterate over the devices
378
+ Object.entries(devices).forEach(async ([devAddr, attrGroups]) => {
379
+ var _a;
380
+ // Check for non-device info (starts with _)
381
+ if (devAddr.startsWith("_")) {
382
+ return;
383
+ }
384
+ // Device type name
385
+ let deviceTypeName = "";
386
+ let deviceTypeIdx = -1;
387
+ if (attrGroups && typeof attrGroups === 'object' && "_t" in attrGroups && typeof attrGroups._t === "string") {
388
+ deviceTypeName = attrGroups._t || "";
389
+ }
390
+ else if (attrGroups && typeof attrGroups === 'object' && "_i" in attrGroups && typeof attrGroups._i === "number") {
391
+ deviceTypeIdx = (_a = attrGroups._i) !== null && _a !== void 0 ? _a : -1;
392
+ deviceTypeName = deviceTypeIdx.toString();
393
+ }
394
+ else {
395
+ console.warn(`DeviceManager missing device type attrGroups ${JSON.stringify(attrGroups)}`);
396
+ return;
397
+ }
398
+ // Device key
399
+ const deviceKey = (0, RaftDeviceStates_1.getDeviceKey)(busName, devAddr);
400
+ // Update the last update time
401
+ this._deviceLastUpdateTime[deviceKey] = Date.now();
402
+ // Check if a device state already exists
403
+ if (!(deviceKey in this._devicesState) || (this._devicesState[deviceKey].deviceTypeInfo === undefined)) {
404
+ // Get the device type info
405
+ const deviceTypeInfo = await this.getDeviceTypeInfo(busName, deviceTypeName);
406
+ // Check if device record exists
407
+ if (deviceKey in this._devicesState) {
408
+ if (deviceTypeInfo !== undefined) {
409
+ this._devicesState[deviceKey].deviceTypeInfo = deviceTypeInfo;
410
+ this._devicesState[deviceKey].deviceType = deviceTypeName;
411
+ this._devicesState[deviceKey].deviceAddress = devAddr;
412
+ this._devicesState[deviceKey].busName = busName;
413
+ }
414
+ }
415
+ else {
416
+ // Create device record - device type info may be undefined
417
+ this._devicesState[deviceKey] = {
418
+ deviceTypeInfo: deviceTypeInfo,
419
+ deviceTimeline: {
420
+ timestampsUs: [],
421
+ lastReportTimestampUs: 0,
422
+ reportTimestampOffsetUs: 0
423
+ },
424
+ deviceAttributes: {},
425
+ deviceIsNew: true,
426
+ stateChanged: false,
427
+ onlineState: RaftDeviceStates_1.DeviceOnlineState.Online,
428
+ deviceAddress: devAddr,
429
+ deviceType: deviceTypeName,
430
+ busName: busName
431
+ };
432
+ }
433
+ }
434
+ // Get device state
435
+ const deviceState = this._devicesState[deviceKey];
436
+ // Check for online/offline/pending-deletion state information
437
+ if (attrGroups && typeof attrGroups === "object" && "_o" in attrGroups) {
438
+ const onlineStateVal = typeof attrGroups._o === 'number' ? attrGroups._o : parseInt(String(attrGroups._o), 10);
439
+ if (onlineStateVal === 2) {
440
+ // Pending deletion - remove device and skip further processing
441
+ this.removeDevice(deviceKey);
442
+ return;
443
+ }
444
+ deviceState.onlineState = onlineStateVal === 1 ? RaftDeviceStates_1.DeviceOnlineState.Online : RaftDeviceStates_1.DeviceOnlineState.Offline;
445
+ }
446
+ // Check if device type info is available
447
+ if (!deviceState.deviceTypeInfo) {
448
+ return;
449
+ }
450
+ const markers = this.extractMarkers(attrGroups);
451
+ // Iterate attribute groups
452
+ Object.entries(attrGroups).forEach(([attrGroupName, msgHexStr]) => {
453
+ // Check valid
454
+ if (attrGroupName.startsWith("_") || (typeof msgHexStr != 'string')) {
455
+ return;
456
+ }
457
+ // Check the device type info
458
+ if (!deviceState.deviceTypeInfo.resp) {
459
+ return;
460
+ }
461
+ // Convert the hex string to an arraybuffer by converting each pair of hex chars to a byte
462
+ const msgBytes = this.hexToBytes(msgHexStr);
463
+ // Work through the message which may contain multiple data instances
464
+ let msgBufIdx = 0;
465
+ // Iterate over attributes in the group
466
+ const pollRespMetadata = deviceState.deviceTypeInfo.resp;
467
+ const attrLengthsBefore = this.snapshotAttrLengths(deviceState.deviceAttributes, pollRespMetadata);
468
+ const timelineLenBefore = deviceState.deviceTimeline.timestampsUs.length;
469
+ // Loop
470
+ while (msgBufIdx < msgBytes.length) {
471
+ const newMsgBufIdx = this._attributeHandler.processMsgAttrGroup(msgBytes, msgBufIdx, deviceState.deviceTimeline, pollRespMetadata, deviceState.deviceAttributes, this._maxDatapointsToStore);
472
+ if (newMsgBufIdx < 0)
473
+ break;
474
+ msgBufIdx = newMsgBufIdx;
475
+ deviceState.stateChanged = true;
476
+ }
477
+ this.emitDecodedData(deviceKey, busName, devAddr, deviceState, pollRespMetadata, attrLengthsBefore, timelineLenBefore, attrGroupName, markers);
478
+ const newSamples = deviceState.deviceTimeline.timestampsUs.length - timelineLenBefore;
479
+ this.updateDeviceStats(deviceKey, newSamples, Date.now());
480
+ });
481
+ });
482
+ });
483
+ // Check for devices that have not been updated for a while
484
+ if (this._removeDevicesFlag) {
485
+ const nowTime = Date.now();
486
+ Object.entries(this._deviceLastUpdateTime).forEach(([deviceKey, lastUpdateTime]) => {
487
+ if ((nowTime - lastUpdateTime) > this._removeDevicesTimeMs) {
488
+ delete this._devicesState[deviceKey];
489
+ delete this._deviceStats[deviceKey];
490
+ }
491
+ });
492
+ }
493
+ // Process the callback
494
+ this.processStateCallback();
495
+ }
496
+ ////////////////////////////////////////////////////////////////////////////
497
+ // Process state change callback
498
+ ////////////////////////////////////////////////////////////////////////////
499
+ processStateCallback() {
500
+ // Iterate over the devices
501
+ Object.entries(this._devicesState).forEach(([deviceKey, deviceState]) => {
502
+ // Check if device record is new
503
+ if (deviceState.deviceIsNew) {
504
+ this._newDeviceCallbacks.forEach((cb) => cb(deviceKey, deviceState));
505
+ deviceState.deviceIsNew = false;
506
+ }
507
+ // Iterate over the attributes
508
+ Object.entries(deviceState.deviceAttributes).forEach(([, attrState]) => {
509
+ if (attrState.newAttribute) {
510
+ this._newDeviceAttributeCallbacks.forEach((cb) => cb(deviceKey, attrState));
511
+ attrState.newAttribute = false;
512
+ }
513
+ if (attrState.newData) {
514
+ this._newAttributeDataCallbacks.forEach((cb) => cb(deviceKey, attrState));
515
+ attrState.newData = false;
516
+ }
517
+ });
518
+ });
519
+ }
520
+ ////////////////////////////////////////////////////////////////////////////
521
+ // Remove a device (e.g. on pending deletion)
522
+ ////////////////////////////////////////////////////////////////////////////
523
+ removeDevice(deviceKey) {
524
+ // Snapshot the state before removal for callbacks
525
+ const deviceState = this._devicesState[deviceKey];
526
+ if (deviceState) {
527
+ deviceState.onlineState = RaftDeviceStates_1.DeviceOnlineState.PendingDeletion;
528
+ this._deviceRemovedCallbacks.forEach((cb) => cb(deviceKey, deviceState));
529
+ }
530
+ delete this._devicesState[deviceKey];
531
+ delete this._deviceLastUpdateTime[deviceKey];
532
+ delete this._deviceStats[deviceKey];
533
+ }
534
+ ////////////////////////////////////////////////////////////////////////////
535
+ // Get device type info
536
+ ////////////////////////////////////////////////////////////////////////////
537
+ async getDeviceTypeInfo(busName, deviceType) {
538
+ // Check if already in cache
539
+ if (deviceType in this._cachedDeviceTypeRecs) {
540
+ return this._cachedDeviceTypeRecs[deviceType];
541
+ }
542
+ // Check if there's already a pending request for this device type
543
+ if (deviceType in this._pendingDeviceTypeRequests) {
544
+ // console.log(`DevMan.getDeviceTypeInfo joining existing request queue for deviceType ${deviceType}`);
545
+ // Add this request to the waiting queue
546
+ return new Promise((resolve, reject) => {
547
+ this._pendingDeviceTypeRequests[deviceType].waitingQueue.push({ resolve, reject });
548
+ });
549
+ }
550
+ // Check rate limiting for new requests
551
+ if (deviceType in this._cachedDeviceTypePreviousAttemptTimes) {
552
+ const timeSinceLastAttempt = Date.now() - this._cachedDeviceTypePreviousAttemptTimes[deviceType];
553
+ if (timeSinceLastAttempt < this._minTimeBetweenDeviceTypeInfoRetrievalMs) {
554
+ console.log(`DevMan.getDeviceTypeInfo rate limited for deviceType ${deviceType}`);
555
+ return undefined;
556
+ }
557
+ }
558
+ // Create and cache the promise with an empty waiting queue
559
+ const requestPromise = this.executeDeviceTypeInfoRequest(busName, deviceType);
560
+ this._pendingDeviceTypeRequests[deviceType] = {
561
+ promise: requestPromise,
562
+ waitingQueue: []
563
+ };
564
+ try {
565
+ const result = await requestPromise;
566
+ // Resolve all waiting requests with the same result
567
+ const waitingQueue = this._pendingDeviceTypeRequests[deviceType].waitingQueue;
568
+ waitingQueue.forEach(({ resolve }) => resolve(result));
569
+ return result;
570
+ }
571
+ catch (error) {
572
+ // Reject all waiting requests with the same error
573
+ const waitingQueue = this._pendingDeviceTypeRequests[deviceType].waitingQueue;
574
+ waitingQueue.forEach(({ reject }) => reject(error));
575
+ console.warn(`DevMan.getDeviceTypeInfo failed for ${deviceType}: ${error}`);
576
+ return undefined;
577
+ }
578
+ finally {
579
+ // Clean up the pending request
580
+ delete this._pendingDeviceTypeRequests[deviceType];
581
+ }
582
+ }
583
+ async executeDeviceTypeInfoRequest(busName, deviceType) {
584
+ var _a;
585
+ this._cachedDeviceTypePreviousAttemptTimes[deviceType] = Date.now();
586
+ try {
587
+ const cmd = "devman/typeinfo?bus=" + busName + "&type=" + deviceType;
588
+ const msgHandler = (_a = this._systemUtils) === null || _a === void 0 ? void 0 : _a.getMsgHandler();
589
+ if (msgHandler) {
590
+ const msgRslt = await msgHandler.sendRICRESTURL(cmd);
591
+ if (msgRslt && msgRslt.rslt === "ok") {
592
+ this._cachedDeviceTypeRecs[deviceType] = msgRslt.devinfo;
593
+ return msgRslt.devinfo;
594
+ }
595
+ }
596
+ return undefined;
597
+ }
598
+ catch (error) {
599
+ console.warn(`DeviceManager getDeviceTypeInfo error ${error}`);
600
+ return undefined;
601
+ }
602
+ }
603
+ ////////////////////////////////////////////////////////////////////////////
604
+ // Send action to device
605
+ ////////////////////////////////////////////////////////////////////////////
606
+ toHex(data) {
607
+ return Array.from(data)
608
+ .map(byte => byte.toString(16).padStart(2, "0"))
609
+ .join("");
610
+ }
611
+ async sendAction(deviceKey, action, data) {
612
+ // console.log(`DeviceManager sendAction ${deviceKey} action name ${action.n} value ${value} prefix ${action.w}`);
613
+ var _a;
614
+ let writeBytes;
615
+ // Check for one data item
616
+ if (data.length === 1) {
617
+ let value = data[0];
618
+ // Check for conversion
619
+ if (action.sub !== undefined) {
620
+ value = value - action.sub;
621
+ }
622
+ if (action.mul !== undefined) {
623
+ value = value * action.mul;
624
+ }
625
+ // Form the write bytes
626
+ writeBytes = action.t ? (0, RaftStruct_1.structPack)(action.t, [value]) : new Uint8Array(0);
627
+ }
628
+ else {
629
+ // Form the write bytes which may have multiple data items
630
+ writeBytes = action.t ? (0, RaftStruct_1.structPack)(action.t, data) : new Uint8Array(0);
631
+ }
632
+ // Convert to hex string
633
+ let writeHexStr = this.toHex(writeBytes);
634
+ // Add prefix and postfix
635
+ writeHexStr = (action.w ? action.w : "") + writeHexStr + (action.wz ? action.wz : "");
636
+ // Parse the device key into bus and address components
637
+ const { bus: devBus, addr: devAddr } = (0, RaftDeviceStates_1.parseDeviceKey)(deviceKey);
638
+ // Send the action to the server
639
+ const cmd = "devman/cmdraw?bus=" + devBus + "&addr=" + devAddr + "&hexWr=" + writeHexStr;
640
+ console.log(`DeviceManager deviceKey ${deviceKey} action name ${action.n} value ${data} prefix ${action.w} sendAction ${cmd}`);
641
+ // Send the command
642
+ try {
643
+ // Get the msg handler
644
+ const msgHandler = (_a = this._systemUtils) === null || _a === void 0 ? void 0 : _a.getMsgHandler();
645
+ if (msgHandler) {
646
+ const msgRslt = await msgHandler.sendRICRESTURL(cmd);
647
+ return msgRslt.rslt === "ok";
648
+ }
649
+ return false;
650
+ }
651
+ catch (error) {
652
+ console.warn(`DeviceManager sendAction error ${error}`);
653
+ return false;
654
+ }
655
+ }
656
+ ////////////////////////////////////////////////////////////////////////////
657
+ // Send a compound action to the device
658
+ ////////////////////////////////////////////////////////////////////////////
659
+ async sendCompoundAction(deviceKey, action, data) {
660
+ // console.log(`DeviceManager sendAction ${deviceKey} action name ${action.n} value ${value} prefix ${action.w}`);
661
+ // Check if all data to be sent at once
662
+ if (action.concat) {
663
+ // Form a single list by flattening data
664
+ let dataToWrite = [];
665
+ for (let dataIdx = 0; dataIdx < data.length; dataIdx++) {
666
+ dataToWrite = dataToWrite.concat(data[dataIdx]);
667
+ }
668
+ // Use sendAction to send this
669
+ return await this.sendAction(deviceKey, action, dataToWrite);
670
+ }
671
+ else {
672
+ // Iterate over the data
673
+ let allOk = true;
674
+ for (let dataIdx = 0; dataIdx < data.length; dataIdx++) {
675
+ // Create the data to write by prepending the index to the data for this index
676
+ const dataToWrite = [dataIdx].concat(data[dataIdx]);
677
+ // Use sendAction to send this
678
+ allOk = allOk && await this.sendAction(deviceKey, action, dataToWrite);
679
+ }
680
+ }
681
+ return false;
682
+ }
683
+ ////////////////////////////////////////////////////////////////////////////
684
+ // Convert hex to bytes
685
+ ////////////////////////////////////////////////////////////////////////////
686
+ hexToBytes(hex) {
687
+ const bytes = new Uint8Array(hex.length / 2);
688
+ for (let i = 0; i < bytes.length; i++) {
689
+ bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
690
+ }
691
+ return bytes;
692
+ }
693
+ ////////////////////////////////////////////////////////////////////////////
694
+ // Helpers for device stats
695
+ ////////////////////////////////////////////////////////////////////////////
696
+ createEmptyStats() {
697
+ return {
698
+ totalSamples: 0,
699
+ windowMs: this._statsWindowMs,
700
+ windowSamples: 0,
701
+ sampleRateHz: 0,
702
+ lastSampleTimeMs: null,
703
+ lastUpdateTimeMs: null,
704
+ windowEvents: []
705
+ };
706
+ }
707
+ getOrCreateDeviceStats(deviceKey) {
708
+ if (!this._deviceStats[deviceKey]) {
709
+ this._deviceStats[deviceKey] = this.createEmptyStats();
710
+ }
711
+ return this._deviceStats[deviceKey];
712
+ }
713
+ cloneDeviceStats(stats) {
714
+ return {
715
+ totalSamples: stats.totalSamples,
716
+ windowMs: stats.windowMs,
717
+ windowSamples: stats.windowSamples,
718
+ sampleRateHz: stats.sampleRateHz,
719
+ lastSampleTimeMs: stats.lastSampleTimeMs,
720
+ lastUpdateTimeMs: stats.lastUpdateTimeMs
721
+ };
722
+ }
723
+ updateDeviceStats(deviceKey, newSamples, nowMs) {
724
+ const stats = this.getOrCreateDeviceStats(deviceKey);
725
+ stats.lastUpdateTimeMs = nowMs;
726
+ if (newSamples > 0) {
727
+ stats.totalSamples += newSamples;
728
+ stats.lastSampleTimeMs = nowMs;
729
+ stats.windowEvents.push({ timeMs: nowMs, samples: newSamples });
730
+ }
731
+ const windowStartMs = nowMs - stats.windowMs;
732
+ while (stats.windowEvents.length > 0 && stats.windowEvents[0].timeMs < windowStartMs) {
733
+ stats.windowEvents.shift();
734
+ }
735
+ const windowSamples = stats.windowEvents.reduce((sum, entry) => sum + entry.samples, 0);
736
+ stats.windowSamples = windowSamples;
737
+ if (stats.windowEvents.length === 0) {
738
+ stats.sampleRateHz = 0;
739
+ return;
740
+ }
741
+ const actualWindowMs = Math.max(1, nowMs - stats.windowEvents[0].timeMs);
742
+ stats.sampleRateHz = (windowSamples * 1000) / actualWindowMs;
743
+ }
744
+ ////////////////////////////////////////////////////////////////////////////
745
+ // Helpers for decoded data callbacks
746
+ ////////////////////////////////////////////////////////////////////////////
747
+ snapshotAttrLengths(deviceAttrs, pollRespMetadata) {
748
+ const lengths = {};
749
+ if (!pollRespMetadata) {
750
+ return lengths;
751
+ }
752
+ pollRespMetadata.a.forEach((attr) => {
753
+ var _a, _b;
754
+ lengths[attr.n] = (_b = (_a = deviceAttrs[attr.n]) === null || _a === void 0 ? void 0 : _a.values.length) !== null && _b !== void 0 ? _b : 0;
755
+ });
756
+ return lengths;
757
+ }
758
+ emitDecodedData(deviceKey, busName, devAddr, deviceState, pollRespMetadata, attrLengthsBefore, timelineLenBefore, attrGroupName = "", markers) {
759
+ if (!pollRespMetadata) {
760
+ return;
761
+ }
762
+ const attrValues = {};
763
+ let hasValues = false;
764
+ pollRespMetadata.a.forEach((attr) => {
765
+ var _a;
766
+ const attrState = deviceState.deviceAttributes[attr.n];
767
+ if (!attrState) {
768
+ return;
769
+ }
770
+ const prevLen = (_a = attrLengthsBefore[attr.n]) !== null && _a !== void 0 ? _a : 0;
771
+ if (attrState.values.length > prevLen) {
772
+ attrValues[attr.n] = attrState.values.slice(prevLen);
773
+ hasValues = hasValues || attrValues[attr.n].length > 0;
774
+ }
775
+ });
776
+ if (!hasValues) {
777
+ return;
778
+ }
779
+ const timestampsUs = deviceState.deviceTimeline.timestampsUs.slice(timelineLenBefore);
780
+ const decoded = {
781
+ deviceKey,
782
+ busName,
783
+ deviceAddress: devAddr,
784
+ deviceType: deviceState.deviceType,
785
+ attrGroupName: attrGroupName || undefined,
786
+ attrValues,
787
+ timestampsUs,
788
+ };
789
+ if (markers && Object.keys(markers).length > 0) {
790
+ decoded.markers = markers;
791
+ decoded.fromOfflineBuffer = this.isTruthy(markers["_buf"]);
792
+ }
793
+ this._decodedDataCallbacks.forEach((cb) => cb(decoded));
794
+ }
795
+ extractMarkers(attrGroups) {
796
+ const markers = {};
797
+ if (!attrGroups || typeof attrGroups !== "object") {
798
+ return markers;
799
+ }
800
+ Object.entries(attrGroups).forEach(([key, value]) => {
801
+ if (key.startsWith("_") && key !== "_t" && key !== "_o") {
802
+ markers[key] = value;
803
+ }
804
+ });
805
+ return markers;
806
+ }
807
+ isTruthy(val) {
808
+ return val === true || val === 1 || val === "1";
809
+ }
810
+ }
811
+ exports.DeviceManager = DeviceManager;
812
+ //# sourceMappingURL=RaftDeviceManager.js.map