@robdobsn/raftjs 1.8.5 → 1.10.7

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 (224) 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 +1000 -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 +248 -0
  39. package/dist/react-native/RaftConnector.js +658 -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 +61 -0
  48. package/dist/react-native/RaftDeviceManager.js +665 -0
  49. package/dist/react-native/RaftDeviceManager.js.map +1 -0
  50. package/dist/react-native/RaftDeviceMgrIF.d.ts +15 -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 +37 -0
  57. package/dist/react-native/RaftDeviceStates.js +60 -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/RaftStreamHandler.d.ts +38 -0
  78. package/dist/react-native/RaftStreamHandler.js +258 -0
  79. package/dist/react-native/RaftStreamHandler.js.map +1 -0
  80. package/dist/react-native/RaftStruct.d.ts +3 -0
  81. package/dist/react-native/RaftStruct.js +258 -0
  82. package/dist/react-native/RaftStruct.js.map +1 -0
  83. package/dist/react-native/RaftSysTypeManager.d.ts +16 -0
  84. package/dist/react-native/RaftSysTypeManager.js +78 -0
  85. package/dist/react-native/RaftSysTypeManager.js.map +1 -0
  86. package/dist/react-native/RaftSystemType.d.ts +30 -0
  87. package/dist/react-native/RaftSystemType.js +3 -0
  88. package/dist/react-native/RaftSystemType.js.map +1 -0
  89. package/dist/react-native/RaftSystemUtils.d.ts +136 -0
  90. package/dist/react-native/RaftSystemUtils.js +412 -0
  91. package/dist/react-native/RaftSystemUtils.js.map +1 -0
  92. package/dist/react-native/RaftTypes.d.ts +195 -0
  93. package/dist/react-native/RaftTypes.js +153 -0
  94. package/dist/react-native/RaftTypes.js.map +1 -0
  95. package/dist/react-native/RaftUpdateEvents.d.ts +33 -0
  96. package/dist/react-native/RaftUpdateEvents.js +46 -0
  97. package/dist/react-native/RaftUpdateEvents.js.map +1 -0
  98. package/dist/react-native/RaftUpdateManager.d.ts +61 -0
  99. package/dist/react-native/RaftUpdateManager.js +621 -0
  100. package/dist/react-native/RaftUpdateManager.js.map +1 -0
  101. package/dist/react-native/RaftUtils.d.ts +128 -0
  102. package/dist/react-native/RaftUtils.js +487 -0
  103. package/dist/react-native/RaftUtils.js.map +1 -0
  104. package/dist/react-native/RaftWifiTypes.d.ts +23 -0
  105. package/dist/react-native/RaftWifiTypes.js +43 -0
  106. package/dist/react-native/RaftWifiTypes.js.map +1 -0
  107. package/dist/react-native/main.d.ts +26 -0
  108. package/dist/react-native/main.js +51 -0
  109. package/dist/react-native/main.js.map +1 -0
  110. package/dist/web/RaftAttributeHandler.js +1 -1
  111. package/dist/web/RaftAttributeHandler.js.map +1 -1
  112. package/dist/web/RaftChannelBLE.web.js +8 -6
  113. package/dist/web/RaftChannelBLE.web.js.map +1 -1
  114. package/dist/web/RaftChannelSimulated.d.ts +10 -0
  115. package/dist/web/RaftChannelSimulated.js +662 -80
  116. package/dist/web/RaftChannelSimulated.js.map +1 -1
  117. package/dist/web/RaftChannelWebSerial.js +2 -2
  118. package/dist/web/RaftChannelWebSerial.js.map +1 -1
  119. package/dist/web/RaftChannelWebSocket.js +16 -1
  120. package/dist/web/RaftChannelWebSocket.js.map +1 -1
  121. package/dist/web/RaftConnector.d.ts +2 -0
  122. package/dist/web/RaftConnector.js +38 -15
  123. package/dist/web/RaftConnector.js.map +1 -1
  124. package/dist/web/RaftCustomAttrHandler.d.ts +2 -0
  125. package/dist/web/RaftCustomAttrHandler.js +54 -26
  126. package/dist/web/RaftCustomAttrHandler.js.map +1 -1
  127. package/dist/web/RaftDeviceInfo.d.ts +3 -1
  128. package/dist/web/RaftDeviceInfo.js +17 -3
  129. package/dist/web/RaftDeviceInfo.js.map +1 -1
  130. package/dist/web/RaftDeviceManager.d.ts +19 -1
  131. package/dist/web/RaftDeviceManager.js +89 -3
  132. package/dist/web/RaftDeviceManager.js.map +1 -1
  133. package/dist/web/RaftDeviceStates.d.ts +1 -1
  134. package/dist/web/RaftDeviceStates.js +2 -2
  135. package/dist/web/RaftDeviceStates.js.map +1 -1
  136. package/dist/web/RaftMsgHandler.js.map +1 -1
  137. package/dist/web/RaftStreamHandler.js +2 -1
  138. package/dist/web/RaftStreamHandler.js.map +1 -1
  139. package/dist/web/RaftStruct.js +197 -147
  140. package/dist/web/RaftStruct.js.map +1 -1
  141. package/dist/web/RaftUpdateManager.js +1 -1
  142. package/dist/web/RaftUpdateManager.js.map +1 -1
  143. package/dist/web/RaftUtils.d.ts +2 -0
  144. package/dist/web/RaftUtils.js +20 -0
  145. package/dist/web/RaftUtils.js.map +1 -1
  146. package/dist/web/main.d.ts +1 -0
  147. package/dist/web/main.js.map +1 -1
  148. package/eslint.config.mjs +33 -0
  149. package/examples/dashboard/package.json +36 -0
  150. package/examples/dashboard/src/CommandPanel.tsx +147 -0
  151. package/examples/dashboard/src/ConnManager.ts +166 -0
  152. package/examples/dashboard/src/DeviceActionsForm.tsx +133 -0
  153. package/examples/dashboard/src/DeviceAttrsForm.tsx +49 -0
  154. package/examples/dashboard/src/DeviceLineChart.tsx +163 -0
  155. package/examples/dashboard/src/DevicePanel.tsx +171 -0
  156. package/examples/dashboard/src/DevicesPanel.tsx +58 -0
  157. package/examples/dashboard/src/DispLedGrid.tsx +110 -0
  158. package/examples/dashboard/src/DispOneLed.tsx +20 -0
  159. package/examples/dashboard/src/LatencyTest.ts +130 -0
  160. package/examples/dashboard/src/LatencyTestPanel.tsx +92 -0
  161. package/examples/dashboard/src/Main.tsx +234 -0
  162. package/examples/dashboard/src/SettingsManager.ts +67 -0
  163. package/examples/dashboard/src/SettingsScreen.tsx +174 -0
  164. package/examples/dashboard/src/StatusPanel.tsx +71 -0
  165. package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +162 -0
  166. package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +91 -0
  167. package/examples/dashboard/src/SystemTypeGeneric/StateInfoGeneric.ts +30 -0
  168. package/examples/dashboard/src/SystemTypeGeneric/SystemTypeGeneric.ts +91 -0
  169. package/examples/dashboard/src/SystemTypeMarty/RICAddOn.ts +70 -0
  170. package/examples/dashboard/src/SystemTypeMarty/RICAddOnBase.ts +33 -0
  171. package/examples/dashboard/src/SystemTypeMarty/RICAddOnManager.ts +342 -0
  172. package/examples/dashboard/src/SystemTypeMarty/RICCommsStats.ts +170 -0
  173. package/examples/dashboard/src/SystemTypeMarty/RICHWElem.ts +123 -0
  174. package/examples/dashboard/src/SystemTypeMarty/RICLEDPatternChecker.ts +207 -0
  175. package/examples/dashboard/src/SystemTypeMarty/RICROSSerial.ts +464 -0
  176. package/examples/dashboard/src/SystemTypeMarty/RICServoFaultDetector.ts +146 -0
  177. package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +97 -0
  178. package/examples/dashboard/src/SystemTypeMarty/RICSystemUtils.ts +371 -0
  179. package/examples/dashboard/src/SystemTypeMarty/RICTypes.ts +20 -0
  180. package/examples/dashboard/src/SystemTypeMarty/SystemTypeMarty.ts +119 -0
  181. package/examples/dashboard/src/index.html +15 -0
  182. package/examples/dashboard/src/index.tsx +13 -0
  183. package/examples/dashboard/src/styles.css +408 -0
  184. package/examples/dashboard/tsconfig.json +18 -0
  185. package/jest.config.js +11 -0
  186. package/package.json +4 -7
  187. package/src/RaftAttributeHandler.ts +450 -0
  188. package/src/RaftChannel.ts +32 -0
  189. package/src/RaftChannelBLE.native.ts +617 -0
  190. package/src/RaftChannelBLE.web.ts +374 -0
  191. package/src/RaftChannelBLEFactory.ts +13 -0
  192. package/src/RaftChannelBLEScanner.native.ts +184 -0
  193. package/src/RaftChannelSimulated.ts +1176 -0
  194. package/src/RaftChannelWebSerial.ts +420 -0
  195. package/src/RaftChannelWebSocket.ts +272 -0
  196. package/src/RaftCommsStats.ts +142 -0
  197. package/src/RaftConnEvents.ts +58 -0
  198. package/src/RaftConnector.ts +785 -0
  199. package/src/RaftCustomAttrHandler.ts +117 -0
  200. package/src/RaftDeviceInfo.ts +125 -0
  201. package/src/RaftDeviceManager.ts +844 -0
  202. package/src/RaftDeviceMgrIF.ts +33 -0
  203. package/src/RaftDeviceMsg.ts +20 -0
  204. package/src/RaftDeviceStates.ts +92 -0
  205. package/src/RaftFileHandler.ts +668 -0
  206. package/src/RaftLog.ts +70 -0
  207. package/src/RaftMiniHDLC.ts +396 -0
  208. package/src/RaftMsgHandler.ts +812 -0
  209. package/src/RaftMsgTrackInfo.ts +51 -0
  210. package/src/RaftProtocolDefs.ts +46 -0
  211. package/src/RaftStreamHandler.ts +329 -0
  212. package/src/RaftStruct.ts +282 -0
  213. package/src/RaftSysTypeManager.ts +87 -0
  214. package/src/RaftSystemType.ts +34 -0
  215. package/src/RaftSystemUtils.ts +489 -0
  216. package/src/RaftTypes.ts +279 -0
  217. package/src/RaftUpdateEvents.ts +48 -0
  218. package/src/RaftUpdateManager.ts +781 -0
  219. package/src/RaftUtils.ts +514 -0
  220. package/src/RaftWifiTypes.ts +36 -0
  221. package/src/main.ts +39 -0
  222. package/testdata/TestDeviceTypeRecs.json +492 -0
  223. package/tsconfig.json +30 -0
  224. package/tsconfig.react-native.json +29 -0
@@ -0,0 +1,844 @@
1
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2
+ //
3
+ // RaftDeviceManager
4
+ // Device manager for Raft devices
5
+ //
6
+ // Rob Dobson (C) 2024
7
+ //
8
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
9
+
10
+ import { DeviceAttributeState, DeviceAttributesState, DevicesState, DeviceState, getDeviceKey } from "./RaftDeviceStates";
11
+ import { DeviceMsgJson } from "./RaftDeviceMsg";
12
+ import { RaftOKFail } from './RaftTypes';
13
+ import { DeviceTypeInfo, DeviceTypeAction, DeviceTypeInfoRecs, RaftDevTypeInfoResponse } from "./RaftDeviceInfo";
14
+ import AttributeHandler from "./RaftAttributeHandler";
15
+ import RaftSystemUtils from "./RaftSystemUtils";
16
+ import RaftDeviceMgrIF from "./RaftDeviceMgrIF";
17
+ import { structPack } from "./RaftStruct";
18
+ // import RaftUtils from "./RaftUtils";
19
+
20
+ export interface DeviceDecodedData {
21
+ deviceKey: string;
22
+ busName: string;
23
+ deviceAddress: string;
24
+ deviceType: string;
25
+ attrGroupName?: string;
26
+ attrValues: Record<string, number[]>;
27
+ timestampsUs: number[];
28
+ markers?: Record<string, unknown>;
29
+ fromOfflineBuffer?: boolean;
30
+ }
31
+
32
+ export class DeviceManager implements RaftDeviceMgrIF{
33
+
34
+ // Max data points to store
35
+ private _maxDatapointsToStore = 1000;
36
+
37
+ // Min time between attempts to retrieve device type info
38
+ private _minTimeBetweenDeviceTypeInfoRetrievalMs = 60000;
39
+
40
+ // Attribute handler
41
+ private _attributeHandler = new AttributeHandler();
42
+
43
+ // Devices state
44
+ public _devicesState = new DevicesState();
45
+
46
+ // Last time each device was updated - used to detect devices that are no longer present
47
+ private _deviceLastUpdateTime: { [deviceKey: string]: number } = {};
48
+
49
+ // Flag indicating that removed devices should be removed from the state
50
+ private _removeDevicesFlag = true;
51
+ private _removeDevicesTimeMs = 60000;
52
+
53
+ // System utils
54
+ private _systemUtils: RaftSystemUtils | null = null;
55
+
56
+ // Device callbacks
57
+ private _newDeviceCallbacks: Array<(deviceKey: string, state: DeviceState) => void> = [];
58
+ private _newDeviceAttributeCallbacks: Array<(deviceKey: string, attrState: DeviceAttributeState) => void> = [];
59
+ private _newAttributeDataCallbacks: Array<(deviceKey: string, attrState: DeviceAttributeState) => void> = [];
60
+ private _decodedDataCallbacks: Array<(decoded: DeviceDecodedData) => void> = [];
61
+
62
+ // Debug message index (to help debug with async messages)
63
+ private _debugMsgIndex = 0;
64
+
65
+ public getDevicesState(): DevicesState {
66
+ return this._devicesState;
67
+ }
68
+
69
+ public getDeviceState(deviceKey: string): DeviceState {
70
+ return this._devicesState[deviceKey];
71
+ }
72
+
73
+ // Cached device type data
74
+ private _cachedDeviceTypeRecs: DeviceTypeInfoRecs = {};
75
+
76
+ // Cached device type previous attempt times
77
+ private _cachedDeviceTypePreviousAttemptTimes: { [deviceType: string]: number } = {};
78
+
79
+ // Pending device type requests - queue-based to maintain order
80
+ private _pendingDeviceTypeRequests: { [deviceType: string]: {
81
+ promise: Promise<DeviceTypeInfo | undefined>;
82
+ waitingQueue: Array<{resolve: (value: DeviceTypeInfo | undefined) => void, reject: (reason?: any) => void}>;
83
+ } } = {};
84
+
85
+ // Constructor
86
+ constructor() {
87
+ }
88
+
89
+ ////////////////////////////////////////////////////////////////////////////
90
+ // Settings
91
+ ////////////////////////////////////////////////////////////////////////////
92
+
93
+ public setMaxDataPointsToStore(maxDatapointsToStore: number): void {
94
+ this._maxDatapointsToStore = maxDatapointsToStore;
95
+ // console.log(`DeviceManager setMaxDataPointsToStore ${maxDatapointsToStore}`);
96
+ }
97
+
98
+ ////////////////////////////////////////////////////////////////////////////
99
+ // Send REST commands
100
+ ////////////////////////////////////////////////////////////////////////////
101
+
102
+ async sendCommand(cmd: string): Promise<boolean> {
103
+ try {
104
+ // Get the msg handler
105
+ const msgHandler = this._systemUtils?.getMsgHandler();
106
+ if (msgHandler) {
107
+ const msgRslt = await msgHandler.sendRICRESTURL<RaftOKFail>(cmd);
108
+ return msgRslt.rslt === "ok";
109
+ }
110
+ return false;
111
+ } catch (error) {
112
+ console.warn(`DeviceManager sendCommand error ${error}`);
113
+ return false;
114
+ }
115
+ }
116
+
117
+ ////////////////////////////////////////////////////////////////////////////
118
+ // Setup
119
+ ////////////////////////////////////////////////////////////////////////////
120
+
121
+ public async setup(systemUtils: RaftSystemUtils): Promise<boolean> {
122
+
123
+ // Save the system utils
124
+ this._systemUtils = systemUtils;
125
+ return true;
126
+ }
127
+
128
+ ////////////////////////////////////////////////////////////////////////////
129
+ // Register callbacks
130
+ ////////////////////////////////////////////////////////////////////////////
131
+
132
+ public addNewDeviceCallback(callback: (deviceKey: string, state: DeviceState) => void): void {
133
+ if (!this._newDeviceCallbacks.includes(callback)) {
134
+ this._newDeviceCallbacks.push(callback);
135
+ }
136
+ }
137
+
138
+ public removeNewDeviceCallback(callback: (deviceKey: string, state: DeviceState) => void): void {
139
+ this._newDeviceCallbacks = this._newDeviceCallbacks.filter((cb) => cb !== callback);
140
+ }
141
+
142
+ public addNewAttributeCallback(callback: (deviceKey: string, attrState: DeviceAttributeState) => void): void {
143
+ if (!this._newDeviceAttributeCallbacks.includes(callback)) {
144
+ this._newDeviceAttributeCallbacks.push(callback);
145
+ }
146
+ }
147
+
148
+ public removeNewAttributeCallback(callback: (deviceKey: string, attrState: DeviceAttributeState) => void): void {
149
+ this._newDeviceAttributeCallbacks = this._newDeviceAttributeCallbacks.filter((cb) => cb !== callback);
150
+ }
151
+
152
+ public addAttributeDataCallback(callback: (deviceKey: string, attrState: DeviceAttributeState) => void): void {
153
+ if (!this._newAttributeDataCallbacks.includes(callback)) {
154
+ this._newAttributeDataCallbacks.push(callback);
155
+ }
156
+ }
157
+
158
+ public removeAttributeDataCallback(callback: (deviceKey: string, attrState: DeviceAttributeState) => void): void {
159
+ this._newAttributeDataCallbacks = this._newAttributeDataCallbacks.filter((cb) => cb !== callback);
160
+ }
161
+
162
+ public addDecodedDataCallback(callback: (decoded: DeviceDecodedData) => void): void {
163
+ if (!this._decodedDataCallbacks.includes(callback)) {
164
+ this._decodedDataCallbacks.push(callback);
165
+ }
166
+ }
167
+
168
+ public removeDecodedDataCallback(callback: (decoded: DeviceDecodedData) => void): void {
169
+ this._decodedDataCallbacks = this._decodedDataCallbacks.filter((cb) => cb !== callback);
170
+ }
171
+
172
+ ////////////////////////////////////////////////////////////////////////////
173
+ // Set the friendly name for the device
174
+ ////////////////////////////////////////////////////////////////////////////
175
+
176
+ public async setFriendlyName(friendlyName: string): Promise<void> {
177
+ // Set using utils
178
+ await this._systemUtils?.setRaftName(friendlyName);
179
+ }
180
+
181
+ ////////////////////////////////////////////////////////////////////////////
182
+ // Handle device message binary
183
+ ////////////////////////////////////////////////////////////////////////////
184
+
185
+ public async handleClientMsgBinary(rxMsg: Uint8Array) {
186
+ // console.log(`DeviceManager client1 msg ${RaftUtils.bufferToHex(rxMsg)}`);
187
+
188
+ // Example messages
189
+ // 0080 0015 81 0000006a 0004 53b7 feff00000100081857079314 0011 80 00000000 0011 53b2 075106e400d60054 0010 80 00000000 0012 5231 000d0000010e01
190
+ // 0080 0011 80 00000000 0002 4ae1 0787052606240007 000e 80 00000000 0003 0006 030001af01
191
+ // 0080 0011 80 00000000 0002 e46e 061e05a206830433 0010 80000000000003e4760006030001c701
192
+ // 0080 0010 81 00000015 0004 e4a2 0650fe00305002 0011 80000000000002e4a8061f059f06850438 001080000000000003e4aa0006030001c701
193
+
194
+ // 0080 0011 80 00000000 0002 31e4 05ea05a506660137 000e 80 00000000 0003 0007 030001d901
195
+
196
+ // 0080 0011 80 00000000 0002 4d63 0792053e06500061 000e 80 00000000 0003 0005 030001de01
197
+
198
+ // First two bytes of each message are the message type (0080)
199
+ // There are then a series of sections each of which is the data for a device
200
+ // First two bytes of each section is the section length (big endian) not including the section length bytes themselves
201
+ // Next byte is the connection mode (0 for direct connect, 1+ for bus number) and the MSB of this byte is 1 if the device is online
202
+ // Next is the device address (4 bytes big endian)
203
+ // Next is the device type index (2 bytes big endian)
204
+ // Finally the device data which can be one or more groups of attributes defined by the schema
205
+
206
+ // Debug
207
+ // const debugMsgTime = Date.now();
208
+ const debugMsgIndex = this._debugMsgIndex++;
209
+
210
+ // Message layout
211
+ const msgTypeLen = 2; // Length of the message type (first two bytes)
212
+ const sectionLengthLen = 2; // Length of the inclusive section length (first two bytes of each section)
213
+ const sectionConnectionModeLen = 1; // Length of the connection mode (next byte after section length)
214
+ const sectionDeviceAddrLen = 4; // Length of the device address (next 4 bytes after connection mode)
215
+ const sectionDeviceTypeIdxLen = 2; // Length of the device type index (next 2 bytes after device address)
216
+ const sectionHeaderLen = sectionConnectionModeLen + sectionDeviceAddrLen + sectionDeviceTypeIdxLen;
217
+
218
+ // console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} rxMsg.length ${rxMsg.length} rxMsg ${RaftUtils.bufferToHex(rxMsg)}`);
219
+
220
+ // Start after the message type
221
+ let msgPos = msgTypeLen;
222
+
223
+ // Iterate through sections
224
+ while (msgPos < rxMsg.length) {
225
+
226
+ // Check length
227
+ const remainingLen = rxMsg.length - msgPos;
228
+ if (remainingLen < sectionLengthLen + sectionHeaderLen) {
229
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid length ${rxMsg.length} < ${sectionLengthLen + sectionHeaderLen + msgPos}`);
230
+ return;
231
+ }
232
+
233
+ // Get the length of the section
234
+ const sectionLen = (rxMsg[msgPos] << 8) + rxMsg[msgPos + 1];
235
+ if (sectionLen > remainingLen + sectionLengthLen) {
236
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid msgPos ${msgPos} msgLen ${sectionLen} remainingLenAfterLenBytes ${remainingLen-sectionLengthLen}`);
237
+ return;
238
+ }
239
+
240
+ // Extract message elements
241
+ let sectionPos = msgPos + sectionLengthLen;
242
+ const busNum = rxMsg[sectionPos] & 0x7f;
243
+ const isOnline = (rxMsg[sectionPos] & 0x80) !== 0;
244
+ sectionPos += sectionConnectionModeLen;
245
+ // Get the device address and type index
246
+ const devAddr = (rxMsg[sectionPos] << 24) + (rxMsg[sectionPos + 1] << 16) + (rxMsg[sectionPos + 2] << 8) + rxMsg[sectionPos + 3];
247
+ sectionPos += sectionDeviceAddrLen;
248
+ const devTypeIdx = (rxMsg[sectionPos] << 8) + rxMsg[sectionPos + 1];
249
+ let attrGroupPos = sectionPos + sectionDeviceTypeIdxLen;
250
+
251
+ // Debug
252
+ // console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} overallLen ${rxMsg.length} sectionPos ${msgPos} sectionLen ${sectionLen} ${attrGroupPos} ${RaftUtils.bufferToHex(rxMsg.slice(msgPos, msgPos + sectionLen))}`);
253
+ // console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} connMode ${busNum} isOnline ${isOnline} devAddr ${devAddr} devTypeIdx ${devTypeIdx} attrGroupDataLen ${sectionLen - sectionHeaderLen}`);
254
+
255
+ // Device key
256
+ const deviceKey = getDeviceKey(busNum.toString(), devAddr.toString(16));
257
+
258
+ // Update the last update time
259
+ this._deviceLastUpdateTime[deviceKey] = Date.now();
260
+
261
+ // Check if a device state already exists
262
+ if (!(deviceKey in this._devicesState) || (this._devicesState[deviceKey].deviceTypeInfo === undefined)) {
263
+
264
+ // Get the device type info
265
+ const deviceTypeInfo = await this.getDeviceTypeInfo(busNum.toString(), devTypeIdx.toString());
266
+
267
+ // Debug
268
+ // console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} attrGroupPos ${attrGroupPos} busNum ${busNum} devAddr ${devAddr} devTypeIdx ${devTypeIdx} deviceTypeInfo ${JSON.stringify(deviceTypeInfo)}`);
269
+
270
+ // Handle case where device type info is not available
271
+ if (deviceTypeInfo === undefined) {
272
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} deviceType ${devTypeIdx} info not available, skipping attribute processing for this section`);
273
+ // Skip to next section without processing attributes
274
+ msgPos += sectionLengthLen + sectionLen;
275
+ continue;
276
+ }
277
+
278
+ // Check if device record exists
279
+ if (deviceKey in this._devicesState) {
280
+ if (deviceTypeInfo !== undefined) {
281
+ this._devicesState[deviceKey].deviceTypeInfo = deviceTypeInfo;
282
+ this._devicesState[deviceKey].deviceType = deviceTypeInfo.name || "";
283
+ this._devicesState[deviceKey].busName = busNum.toString();
284
+ this._devicesState[deviceKey].deviceAddress = devAddr.toString();
285
+ }
286
+ } else {
287
+ // Create device record - device type info may be undefined
288
+ this._devicesState[deviceKey] = {
289
+ deviceTypeInfo: deviceTypeInfo,
290
+ deviceTimeline: {
291
+ timestampsUs: [],
292
+ lastReportTimestampUs: 0,
293
+ reportTimestampOffsetUs: 0
294
+ },
295
+ deviceAttributes: {},
296
+ deviceIsNew: true,
297
+ stateChanged: false,
298
+ isOnline: true,
299
+ deviceAddress: devAddr.toString(),
300
+ deviceType: deviceTypeInfo?.name || "",
301
+ busName: busNum.toString()
302
+ };
303
+ }
304
+ }
305
+
306
+ // Get device state
307
+ const deviceState = this._devicesState[deviceKey];
308
+ deviceState.isOnline = isOnline;
309
+
310
+ // Check if device type info is available and complete
311
+ if (deviceState.deviceTypeInfo && deviceState.deviceTypeInfo.resp) {
312
+
313
+ // Iterate over attributes in the group
314
+ const pollRespMetadata = deviceState.deviceTypeInfo!.resp!;
315
+
316
+ // Iterate over attribute groups
317
+ const attrGroupDataLen = sectionLen - sectionHeaderLen;
318
+ const attrGroupStartPos = attrGroupPos;
319
+ const attrLengthsBefore = this.snapshotAttrLengths(deviceState.deviceAttributes, pollRespMetadata);
320
+ const timelineLenBefore = deviceState.deviceTimeline.timestampsUs.length;
321
+ while (attrGroupPos < attrGroupStartPos + attrGroupDataLen) {
322
+
323
+ // Add bounds checking
324
+ if (attrGroupPos >= rxMsg.length) {
325
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} attrGroupPos ${attrGroupPos} exceeds message length ${rxMsg.length}`);
326
+ break;
327
+ }
328
+
329
+ const newMsgBufIdx = this._attributeHandler.processMsgAttrGroup(rxMsg, attrGroupPos,
330
+ deviceState.deviceTimeline, pollRespMetadata,
331
+ deviceState.deviceAttributes,
332
+ this._maxDatapointsToStore);
333
+
334
+ // console.log(`DevMan.handleClientMsgBinary decoded debugIdx ${debugMsgIndex} devType ${deviceState.deviceTypeInfo.name} attrGroupDataLen ${attrGroupDataLen} attrGroupPos ${attrGroupPos} sectionLen ${sectionLen} msgPos ${msgPos} rxMsgLen ${rxMsg.length} remainingLen ${remainingLen} pollRespMetadata ${JSON.stringify(pollRespMetadata)}`);
335
+
336
+ if (newMsgBufIdx < 0)
337
+ {
338
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} processMsgAttrGroup failed newMsgBufIdx ${newMsgBufIdx}`);
339
+ break;
340
+ }
341
+
342
+ // Prevent infinite loops
343
+ if (newMsgBufIdx <= attrGroupPos) {
344
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} processMsgAttrGroup didn't advance position from ${attrGroupPos} to ${newMsgBufIdx}`);
345
+ break;
346
+ }
347
+
348
+ attrGroupPos = newMsgBufIdx;
349
+ deviceState.stateChanged = true;
350
+
351
+ // console.log(`debugMsgTime ${debugMsgTime} newPt debugMsgIdx ${debugMsgIndex} rxMsgLen ${rxMsg.length} devType ${deviceState.deviceTypeInfo!.name} timestampsUs ${deviceState.deviceTimeline.timestampsUs[deviceState.deviceTimeline.timestampsUs.length - 1]} curTimelineLen ${deviceState.deviceTimeline.timestampsUs.length}`);
352
+
353
+
354
+ // console.log(`DevMan.handleClientMsgBinary group done debugIdx ${debugMsgIndex} attrGroupPos ${attrGroupPos} sectionLen ${sectionLen} msgPos ${msgPos} rxMsgLen ${rxMsg.length} remainingLen ${remainingLen}`);
355
+ }
356
+
357
+ // Inform decoded-data callbacks
358
+ this.emitDecodedData(deviceKey, busNum.toString(), devAddr.toString(), deviceState,
359
+ pollRespMetadata, attrLengthsBefore, timelineLenBefore);
360
+ } else {
361
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} deviceState incomplete for device ${deviceKey}, skipping attribute processing`);
362
+ }
363
+
364
+ // Debug
365
+ // console.log(`DevMan.handleClientMsgBinary section done debugIdx ${debugMsgIndex} attrGroupPos ${attrGroupPos} sectionLen ${sectionLen} msgPos ${msgPos} newMsgPos ${msgPos + sectionLengthLen + sectionLen} rxMsgLen ${rxMsg.length} remainingLen ${remainingLen}`);
366
+
367
+ // Move to next message
368
+ msgPos += sectionLengthLen + sectionLen;
369
+ }
370
+
371
+ // Check for devices that have not been updated for a while
372
+ if (this._removeDevicesFlag) {
373
+ const nowTime = Date.now();
374
+ Object.entries(this._deviceLastUpdateTime).forEach(([deviceKey, lastUpdateTime]) => {
375
+ if ((nowTime - lastUpdateTime) > this._removeDevicesTimeMs) {
376
+ delete this._devicesState[deviceKey];
377
+ }
378
+ });
379
+ }
380
+
381
+ // Process the callback
382
+ this.processStateCallback();
383
+ }
384
+
385
+ ////////////////////////////////////////////////////////////////////////////
386
+ // Handle device message JSON
387
+ ////////////////////////////////////////////////////////////////////////////
388
+
389
+ public async handleClientMsgJson(jsonMsg: string) {
390
+
391
+ const data = JSON.parse(jsonMsg) as DeviceMsgJson;
392
+ // console.log(`DeviceManager client msg ${JSON.stringify(data)}`);
393
+
394
+ // Iterate over the buses
395
+ Object.entries(data).forEach(([busName, devices]) => {
396
+
397
+ // Check for bus status info
398
+ if (devices && typeof devices === "object" && "_s" in devices) {
399
+ // console.log(`DeviceManager bus status ${JSON.stringify(devices._s)}`);
400
+ return;
401
+ }
402
+
403
+ // Iterate over the devices
404
+ Object.entries(devices).forEach(async ([devAddr, attrGroups]) => {
405
+
406
+ // Check for non-device info (starts with _)
407
+ if (devAddr.startsWith("_")) {
408
+ return;
409
+ }
410
+
411
+ // Device type name
412
+ let deviceTypeName = "";
413
+ let deviceTypeIdx = -1;
414
+ if (attrGroups && typeof attrGroups === 'object' && "_t" in attrGroups && typeof attrGroups._t === "string") {
415
+ deviceTypeName = attrGroups._t || "";
416
+ } else if (attrGroups && typeof attrGroups === 'object' && "_i" in attrGroups && typeof attrGroups._i === "number") {
417
+ deviceTypeIdx = attrGroups._i || -1;
418
+ deviceTypeName = deviceTypeIdx.toString();
419
+ } else
420
+ {
421
+ console.warn(`DeviceManager missing device type attrGroups ${JSON.stringify(attrGroups)}`);
422
+ return;
423
+ }
424
+
425
+ // Device key
426
+ const deviceKey = getDeviceKey(busName, devAddr);
427
+
428
+ // Update the last update time
429
+ this._deviceLastUpdateTime[deviceKey] = Date.now();
430
+
431
+ // Check if a device state already exists
432
+ if (!(deviceKey in this._devicesState) || (this._devicesState[deviceKey].deviceTypeInfo === undefined)) {
433
+
434
+ // Get the device type info
435
+ const deviceTypeInfo = await this.getDeviceTypeInfo(busName, deviceTypeName);
436
+
437
+ // Check if device record exists
438
+ if (deviceKey in this._devicesState) {
439
+ if (deviceTypeInfo !== undefined) {
440
+ this._devicesState[deviceKey].deviceTypeInfo = deviceTypeInfo;
441
+ this._devicesState[deviceKey].deviceType = deviceTypeName;
442
+ this._devicesState[deviceKey].deviceAddress = devAddr;
443
+ this._devicesState[deviceKey].busName = busName;
444
+ }
445
+ } else {
446
+ // Create device record - device type info may be undefined
447
+ this._devicesState[deviceKey] = {
448
+ deviceTypeInfo: deviceTypeInfo,
449
+ deviceTimeline: {
450
+ timestampsUs: [],
451
+ lastReportTimestampUs: 0,
452
+ reportTimestampOffsetUs: 0
453
+ },
454
+ deviceAttributes: {},
455
+ deviceIsNew: true,
456
+ stateChanged: false,
457
+ isOnline: true,
458
+ deviceAddress: devAddr,
459
+ deviceType: deviceTypeName,
460
+ busName: busName
461
+ };
462
+ }
463
+ }
464
+
465
+ // Get device state
466
+ const deviceState = this._devicesState[deviceKey];
467
+
468
+ // Check for online/offline state information
469
+ if (attrGroups && typeof attrGroups === "object" && "_o" in attrGroups) {
470
+ deviceState.isOnline = ((attrGroups._o === true) || (attrGroups._o === "1") || (attrGroups._o === 1));
471
+ }
472
+
473
+ // Check if device type info is available
474
+ if (!deviceState.deviceTypeInfo) {
475
+ return;
476
+ }
477
+
478
+ const markers = this.extractMarkers(attrGroups);
479
+
480
+ // Iterate attribute groups
481
+ Object.entries(attrGroups).forEach(([attrGroupName, msgHexStr]) => {
482
+
483
+ // Check valid
484
+ if (attrGroupName.startsWith("_") || (typeof msgHexStr != 'string')) {
485
+ return;
486
+ }
487
+
488
+ // Check the device type info
489
+ if (!deviceState.deviceTypeInfo!.resp) {
490
+ return;
491
+ }
492
+
493
+ // Convert the hex string to an arraybuffer by converting each pair of hex chars to a byte
494
+ const msgBytes = this.hexToBytes(msgHexStr);
495
+
496
+ // Work through the message which may contain multiple data instances
497
+ let msgBufIdx = 0;
498
+
499
+ // Iterate over attributes in the group
500
+ const pollRespMetadata = deviceState.deviceTypeInfo!.resp!;
501
+
502
+ const attrLengthsBefore = this.snapshotAttrLengths(deviceState.deviceAttributes, pollRespMetadata);
503
+ const timelineLenBefore = deviceState.deviceTimeline.timestampsUs.length;
504
+
505
+ // Loop
506
+ while (msgBufIdx < msgBytes.length) {
507
+
508
+ const newMsgBufIdx = this._attributeHandler.processMsgAttrGroup(msgBytes, msgBufIdx,
509
+ deviceState.deviceTimeline, pollRespMetadata,
510
+ deviceState.deviceAttributes,
511
+ this._maxDatapointsToStore);
512
+ if (newMsgBufIdx < 0)
513
+ break;
514
+ msgBufIdx = newMsgBufIdx;
515
+ deviceState.stateChanged = true;
516
+ }
517
+
518
+ this.emitDecodedData(deviceKey, busName, devAddr, deviceState, pollRespMetadata,
519
+ attrLengthsBefore, timelineLenBefore, attrGroupName, markers);
520
+ });
521
+ });
522
+ });
523
+
524
+ // Check for devices that have not been updated for a while
525
+ if (this._removeDevicesFlag) {
526
+ const nowTime = Date.now();
527
+ Object.entries(this._deviceLastUpdateTime).forEach(([deviceKey, lastUpdateTime]) => {
528
+ if ((nowTime - lastUpdateTime) > this._removeDevicesTimeMs) {
529
+ delete this._devicesState[deviceKey];
530
+ }
531
+ });
532
+ }
533
+
534
+ // Process the callback
535
+ this.processStateCallback();
536
+ }
537
+
538
+ ////////////////////////////////////////////////////////////////////////////
539
+ // Process state change callback
540
+ ////////////////////////////////////////////////////////////////////////////
541
+
542
+ private processStateCallback() {
543
+
544
+ // Iterate over the devices
545
+ Object.entries(this._devicesState).forEach(([deviceKey, deviceState]) => {
546
+
547
+ // Check if device record is new
548
+ if (deviceState.deviceIsNew) {
549
+ this._newDeviceCallbacks.forEach((cb) => cb(deviceKey, deviceState));
550
+ deviceState.deviceIsNew = false;
551
+ }
552
+
553
+ // Iterate over the attributes
554
+ Object.entries(deviceState.deviceAttributes).forEach(([, attrState]): void => {
555
+ if (attrState.newAttribute) {
556
+ this._newDeviceAttributeCallbacks.forEach((cb) => cb(deviceKey, attrState));
557
+ attrState.newAttribute = false;
558
+ }
559
+ if (attrState.newData) {
560
+ this._newAttributeDataCallbacks.forEach((cb) => cb(deviceKey, attrState));
561
+ attrState.newData = false;
562
+ }
563
+ });
564
+ });
565
+ }
566
+
567
+ ////////////////////////////////////////////////////////////////////////////
568
+ // Get device type info
569
+ ////////////////////////////////////////////////////////////////////////////
570
+
571
+ private async getDeviceTypeInfo(busName: string, deviceType: string): Promise<DeviceTypeInfo | undefined> {
572
+ // Check if already in cache
573
+ if (deviceType in this._cachedDeviceTypeRecs) {
574
+ return this._cachedDeviceTypeRecs[deviceType];
575
+ }
576
+
577
+ // Check if there's already a pending request for this device type
578
+ if (deviceType in this._pendingDeviceTypeRequests) {
579
+ // console.log(`DevMan.getDeviceTypeInfo joining existing request queue for deviceType ${deviceType}`);
580
+
581
+ // Add this request to the waiting queue
582
+ return new Promise<DeviceTypeInfo | undefined>((resolve, reject) => {
583
+ this._pendingDeviceTypeRequests[deviceType].waitingQueue.push({ resolve, reject });
584
+ });
585
+ }
586
+
587
+ // Check rate limiting for new requests
588
+ if (deviceType in this._cachedDeviceTypePreviousAttemptTimes) {
589
+ const timeSinceLastAttempt = Date.now() - this._cachedDeviceTypePreviousAttemptTimes[deviceType];
590
+ if (timeSinceLastAttempt < this._minTimeBetweenDeviceTypeInfoRetrievalMs) {
591
+ console.log(`DevMan.getDeviceTypeInfo rate limited for deviceType ${deviceType}`);
592
+ return undefined;
593
+ }
594
+ }
595
+
596
+ // Create and cache the promise with an empty waiting queue
597
+ const requestPromise = this.executeDeviceTypeInfoRequest(busName, deviceType);
598
+ this._pendingDeviceTypeRequests[deviceType] = {
599
+ promise: requestPromise,
600
+ waitingQueue: []
601
+ };
602
+
603
+ try {
604
+ const result = await requestPromise;
605
+
606
+ // Resolve all waiting requests with the same result
607
+ const waitingQueue = this._pendingDeviceTypeRequests[deviceType].waitingQueue;
608
+ waitingQueue.forEach(({ resolve }) => resolve(result));
609
+
610
+ return result;
611
+ } catch (error) {
612
+ // Reject all waiting requests with the same error
613
+ const waitingQueue = this._pendingDeviceTypeRequests[deviceType].waitingQueue;
614
+ waitingQueue.forEach(({ reject }) => reject(error));
615
+
616
+ console.warn(`DevMan.getDeviceTypeInfo failed for ${deviceType}: ${error}`);
617
+ return undefined;
618
+ } finally {
619
+ // Clean up the pending request
620
+ delete this._pendingDeviceTypeRequests[deviceType];
621
+ }
622
+ }
623
+
624
+ private async executeDeviceTypeInfoRequest(busName: string, deviceType: string): Promise<DeviceTypeInfo | undefined> {
625
+ this._cachedDeviceTypePreviousAttemptTimes[deviceType] = Date.now();
626
+
627
+ try {
628
+ const cmd = "devman/typeinfo?bus=" + busName + "&type=" + deviceType;
629
+ const msgHandler = this._systemUtils?.getMsgHandler();
630
+
631
+ if (msgHandler) {
632
+ const msgRslt = await msgHandler.sendRICRESTURL<RaftDevTypeInfoResponse>(cmd);
633
+ if (msgRslt && msgRslt.rslt === "ok") {
634
+ this._cachedDeviceTypeRecs[deviceType] = msgRslt.devinfo;
635
+ return msgRslt.devinfo;
636
+ }
637
+ }
638
+ return undefined;
639
+ } catch (error) {
640
+ console.warn(`DeviceManager getDeviceTypeInfo error ${error}`);
641
+ return undefined;
642
+ }
643
+ }
644
+
645
+ ////////////////////////////////////////////////////////////////////////////
646
+ // Send action to device
647
+ ////////////////////////////////////////////////////////////////////////////
648
+
649
+ private toHex(data: Uint8Array): string {
650
+ return Array.from(data)
651
+ .map(byte => byte.toString(16).padStart(2, "0"))
652
+ .join("");
653
+ }
654
+
655
+ public async sendAction(deviceKey: string, action: DeviceTypeAction, data: number[]): Promise<boolean> {
656
+ // console.log(`DeviceManager sendAction ${deviceKey} action name ${action.n} value ${value} prefix ${action.w}`);
657
+
658
+ let writeBytes: Uint8Array;
659
+
660
+ // Check for one data item
661
+ if (data.length === 1) {
662
+
663
+ let value = data[0];
664
+
665
+ // Check for conversion
666
+ if (action.sub !== undefined) {
667
+ value = value - action.sub;
668
+ }
669
+ if (action.mul !== undefined) {
670
+ value = value * action.mul;
671
+ }
672
+
673
+ // Form the write bytes
674
+ writeBytes = action.t ? structPack(action.t, [value]) : new Uint8Array(0);
675
+
676
+ } else
677
+ {
678
+
679
+ // Form the write bytes which may have multiple data items
680
+ writeBytes = action.t ? structPack(action.t, data) : new Uint8Array(0);
681
+ }
682
+
683
+ // Convert to hex string
684
+ let writeHexStr = this.toHex(writeBytes);
685
+
686
+ // Add prefix and postfix
687
+ writeHexStr = (action.w ? action.w : "") + writeHexStr + (action.wz ? action.wz : "");
688
+
689
+ // Separate the bus and address in the deviceKey (_ char)
690
+ const devBus = deviceKey.split("_")[0]
691
+ const devAddr = deviceKey.split("_")[1]
692
+
693
+ // Send the action to the server
694
+ const cmd = "devman/cmdraw?bus=" + devBus + "&addr=" + devAddr + "&hexWr=" + writeHexStr;
695
+
696
+ console.log(`DeviceManager deviceKey ${deviceKey} action name ${action.n} value ${data} prefix ${action.w} sendAction ${cmd}`);
697
+
698
+ // Send the command
699
+ try {
700
+
701
+ // Get the msg handler
702
+ const msgHandler = this._systemUtils?.getMsgHandler();
703
+ if (msgHandler) {
704
+ const msgRslt = await msgHandler.sendRICRESTURL<RaftOKFail>(cmd);
705
+ return msgRslt.rslt === "ok";
706
+ }
707
+ return false;
708
+ } catch (error) {
709
+ console.warn(`DeviceManager sendAction error ${error}`);
710
+ return false;
711
+ }
712
+ }
713
+
714
+ ////////////////////////////////////////////////////////////////////////////
715
+ // Send a compound action to the device
716
+ ////////////////////////////////////////////////////////////////////////////
717
+
718
+ public async sendCompoundAction(deviceKey: string, action: DeviceTypeAction, data: number[][]): Promise<boolean> {
719
+ // console.log(`DeviceManager sendAction ${deviceKey} action name ${action.n} value ${value} prefix ${action.w}`);
720
+
721
+ // Check if all data to be sent at once
722
+ if (action.concat) {
723
+ // Form a single list by flattening data
724
+ let dataToWrite: number[] = [];
725
+ for (let dataIdx = 0; dataIdx < data.length; dataIdx++) {
726
+ dataToWrite = dataToWrite.concat(data[dataIdx]);
727
+ }
728
+
729
+ // Use sendAction to send this
730
+ return await this.sendAction(deviceKey, action, dataToWrite);
731
+ } else {
732
+ // Iterate over the data
733
+ let allOk = true;
734
+ for (let dataIdx = 0; dataIdx < data.length; dataIdx++) {
735
+
736
+ // Create the data to write by prepending the index to the data for this index
737
+ const dataToWrite = [dataIdx as number].concat(data[dataIdx]);
738
+
739
+ // Use sendAction to send this
740
+ allOk = allOk && await this.sendAction(deviceKey, action, dataToWrite);
741
+ }
742
+ }
743
+ return false;
744
+ }
745
+
746
+ ////////////////////////////////////////////////////////////////////////////
747
+ // Convert hex to bytes
748
+ ////////////////////////////////////////////////////////////////////////////
749
+
750
+ private hexToBytes(hex: string): Uint8Array {
751
+ const bytes = new Uint8Array(hex.length / 2);
752
+ for (let i = 0; i < bytes.length; i++) {
753
+ bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
754
+ }
755
+ return bytes;
756
+ }
757
+
758
+ ////////////////////////////////////////////////////////////////////////////
759
+ // Helpers for decoded data callbacks
760
+ ////////////////////////////////////////////////////////////////////////////
761
+
762
+ private snapshotAttrLengths(deviceAttrs: DeviceAttributesState, pollRespMetadata: DeviceTypeInfo["resp"]): Record<string, number> {
763
+ const lengths: Record<string, number> = {};
764
+ if (!pollRespMetadata) {
765
+ return lengths;
766
+ }
767
+ pollRespMetadata.a.forEach((attr) => {
768
+ lengths[attr.n] = deviceAttrs[attr.n]?.values.length || 0;
769
+ });
770
+ return lengths;
771
+ }
772
+
773
+ private emitDecodedData(
774
+ deviceKey: string,
775
+ busName: string,
776
+ devAddr: string,
777
+ deviceState: DeviceState,
778
+ pollRespMetadata: DeviceTypeInfo["resp"],
779
+ attrLengthsBefore: Record<string, number>,
780
+ timelineLenBefore: number,
781
+ attrGroupName = "",
782
+ markers?: Record<string, unknown>,
783
+ ): void {
784
+
785
+ if (!pollRespMetadata) {
786
+ return;
787
+ }
788
+
789
+ const attrValues: Record<string, number[]> = {};
790
+ let hasValues = false;
791
+
792
+ pollRespMetadata.a.forEach((attr) => {
793
+ const attrState = deviceState.deviceAttributes[attr.n];
794
+ if (!attrState) {
795
+ return;
796
+ }
797
+ const prevLen = attrLengthsBefore[attr.n] || 0;
798
+ if (attrState.values.length > prevLen) {
799
+ attrValues[attr.n] = attrState.values.slice(prevLen);
800
+ hasValues = hasValues || attrValues[attr.n].length > 0;
801
+ }
802
+ });
803
+
804
+ if (!hasValues) {
805
+ return;
806
+ }
807
+
808
+ const timestampsUs = deviceState.deviceTimeline.timestampsUs.slice(timelineLenBefore);
809
+
810
+ const decoded: DeviceDecodedData = {
811
+ deviceKey,
812
+ busName,
813
+ deviceAddress: devAddr,
814
+ deviceType: deviceState.deviceType,
815
+ attrGroupName: attrGroupName || undefined,
816
+ attrValues,
817
+ timestampsUs,
818
+ };
819
+
820
+ if (markers && Object.keys(markers).length > 0) {
821
+ decoded.markers = markers;
822
+ decoded.fromOfflineBuffer = this.isTruthy(markers["_buf"]);
823
+ }
824
+
825
+ this._decodedDataCallbacks.forEach((cb) => cb(decoded));
826
+ }
827
+
828
+ private extractMarkers(attrGroups: any): Record<string, unknown> {
829
+ const markers: Record<string, unknown> = {};
830
+ if (!attrGroups || typeof attrGroups !== "object") {
831
+ return markers;
832
+ }
833
+ Object.entries(attrGroups).forEach(([key, value]) => {
834
+ if (key.startsWith("_") && key !== "_t" && key !== "_o") {
835
+ markers[key] = value;
836
+ }
837
+ });
838
+ return markers;
839
+ }
840
+
841
+ private isTruthy(val: unknown): boolean {
842
+ return val === true || val === 1 || val === "1";
843
+ }
844
+ }