@robotical/raftjs 1.4.7 → 2.0.4

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 (137) hide show
  1. package/dist/react-native/RaftAttributeHandler.d.ts +2 -0
  2. package/dist/react-native/RaftAttributeHandler.js +136 -10
  3. package/dist/react-native/RaftAttributeHandler.js.map +1 -1
  4. package/dist/react-native/RaftChannel.d.ts +2 -0
  5. package/dist/react-native/RaftChannelBLE.native.d.ts +2 -0
  6. package/dist/react-native/RaftChannelBLE.native.js +14 -7
  7. package/dist/react-native/RaftChannelBLE.native.js.map +1 -1
  8. package/dist/react-native/RaftChannelBLE.web.d.ts +2 -0
  9. package/dist/react-native/RaftChannelBLE.web.js +24 -15
  10. package/dist/react-native/RaftChannelBLE.web.js.map +1 -1
  11. package/dist/react-native/RaftChannelSimulated.d.ts +32 -0
  12. package/dist/react-native/RaftChannelSimulated.js +418 -0
  13. package/dist/react-native/RaftChannelSimulated.js.map +1 -0
  14. package/dist/react-native/RaftChannelWebSerial.d.ts +2 -0
  15. package/dist/react-native/RaftChannelWebSerial.js +18 -9
  16. package/dist/react-native/RaftChannelWebSerial.js.map +1 -1
  17. package/dist/react-native/RaftChannelWebSocket.d.ts +2 -0
  18. package/dist/react-native/RaftChannelWebSocket.js +10 -0
  19. package/dist/react-native/RaftChannelWebSocket.js.map +1 -1
  20. package/dist/react-native/RaftConnector.js +13 -6
  21. package/dist/react-native/RaftConnector.js.map +1 -1
  22. package/dist/react-native/RaftCustomAttrHandler.js +15 -0
  23. package/dist/react-native/RaftCustomAttrHandler.js.map +1 -1
  24. package/dist/react-native/RaftDeviceInfo.d.ts +8 -1
  25. package/dist/react-native/RaftDeviceInfo.js +17 -3
  26. package/dist/react-native/RaftDeviceInfo.js.map +1 -1
  27. package/dist/react-native/RaftDeviceManager.d.ts +3 -0
  28. package/dist/react-native/RaftDeviceManager.js +123 -43
  29. package/dist/react-native/RaftDeviceManager.js.map +1 -1
  30. package/dist/react-native/RaftFileHandler.js +8 -8
  31. package/dist/react-native/RaftFileHandler.js.map +1 -1
  32. package/dist/react-native/RaftLog.js +1 -1
  33. package/dist/react-native/RaftLog.js.map +1 -1
  34. package/dist/react-native/RaftMsgHandler.d.ts +5 -0
  35. package/dist/react-native/RaftMsgHandler.js +32 -0
  36. package/dist/react-native/RaftMsgHandler.js.map +1 -1
  37. package/dist/react-native/RaftStreamHandler.js +6 -5
  38. package/dist/react-native/RaftStreamHandler.js.map +1 -1
  39. package/dist/react-native/RaftStruct.js +197 -147
  40. package/dist/react-native/RaftStruct.js.map +1 -1
  41. package/dist/react-native/RaftSysTypeManager.d.ts +2 -0
  42. package/dist/react-native/RaftSysTypeManager.js +25 -0
  43. package/dist/react-native/RaftSysTypeManager.js.map +1 -1
  44. package/dist/react-native/RaftSystemType.d.ts +9 -7
  45. package/dist/react-native/RaftSystemUtils.js +3 -1
  46. package/dist/react-native/RaftSystemUtils.js.map +1 -1
  47. package/dist/react-native/RaftUpdateManager.js +2 -2
  48. package/dist/react-native/RaftUpdateManager.js.map +1 -1
  49. package/dist/react-native/RaftUtils.d.ts +2 -0
  50. package/dist/react-native/RaftUtils.js +22 -2
  51. package/dist/react-native/RaftUtils.js.map +1 -1
  52. package/dist/react-native/main.d.ts +2 -0
  53. package/dist/react-native/main.js +4 -1
  54. package/dist/react-native/main.js.map +1 -1
  55. package/dist/web/RaftAttributeHandler.d.ts +2 -0
  56. package/dist/web/RaftAttributeHandler.js +136 -10
  57. package/dist/web/RaftAttributeHandler.js.map +1 -1
  58. package/dist/web/RaftChannel.d.ts +2 -0
  59. package/dist/web/RaftChannelBLE.web.d.ts +2 -0
  60. package/dist/web/RaftChannelBLE.web.js +24 -15
  61. package/dist/web/RaftChannelBLE.web.js.map +1 -1
  62. package/dist/web/RaftChannelSimulated.d.ts +32 -0
  63. package/dist/web/RaftChannelSimulated.js +418 -0
  64. package/dist/web/RaftChannelSimulated.js.map +1 -0
  65. package/dist/web/RaftChannelWebSerial.d.ts +2 -0
  66. package/dist/web/RaftChannelWebSerial.js +18 -9
  67. package/dist/web/RaftChannelWebSerial.js.map +1 -1
  68. package/dist/web/RaftChannelWebSocket.d.ts +2 -0
  69. package/dist/web/RaftChannelWebSocket.js +10 -0
  70. package/dist/web/RaftChannelWebSocket.js.map +1 -1
  71. package/dist/web/RaftConnector.js +13 -6
  72. package/dist/web/RaftConnector.js.map +1 -1
  73. package/dist/web/RaftCustomAttrHandler.js +15 -0
  74. package/dist/web/RaftCustomAttrHandler.js.map +1 -1
  75. package/dist/web/RaftDeviceInfo.d.ts +8 -1
  76. package/dist/web/RaftDeviceInfo.js +17 -3
  77. package/dist/web/RaftDeviceInfo.js.map +1 -1
  78. package/dist/web/RaftDeviceManager.d.ts +3 -0
  79. package/dist/web/RaftDeviceManager.js +123 -43
  80. package/dist/web/RaftDeviceManager.js.map +1 -1
  81. package/dist/web/RaftFileHandler.js +8 -8
  82. package/dist/web/RaftFileHandler.js.map +1 -1
  83. package/dist/web/RaftLog.js +1 -1
  84. package/dist/web/RaftLog.js.map +1 -1
  85. package/dist/web/RaftMsgHandler.d.ts +5 -0
  86. package/dist/web/RaftMsgHandler.js +32 -0
  87. package/dist/web/RaftMsgHandler.js.map +1 -1
  88. package/dist/web/RaftStreamHandler.js +6 -5
  89. package/dist/web/RaftStreamHandler.js.map +1 -1
  90. package/dist/web/RaftStruct.js +197 -147
  91. package/dist/web/RaftStruct.js.map +1 -1
  92. package/dist/web/RaftSysTypeManager.d.ts +2 -0
  93. package/dist/web/RaftSysTypeManager.js +25 -0
  94. package/dist/web/RaftSysTypeManager.js.map +1 -1
  95. package/dist/web/RaftSystemType.d.ts +9 -7
  96. package/dist/web/RaftSystemUtils.js +3 -1
  97. package/dist/web/RaftSystemUtils.js.map +1 -1
  98. package/dist/web/RaftUpdateManager.js +2 -2
  99. package/dist/web/RaftUpdateManager.js.map +1 -1
  100. package/dist/web/RaftUtils.d.ts +2 -0
  101. package/dist/web/RaftUtils.js +22 -2
  102. package/dist/web/RaftUtils.js.map +1 -1
  103. package/dist/web/main.d.ts +2 -0
  104. package/dist/web/main.js +4 -1
  105. package/dist/web/main.js.map +1 -1
  106. package/examples/dashboard/package.json +1 -1
  107. package/examples/dashboard/src/CommandPanel.tsx +3 -3
  108. package/examples/dashboard/src/ConnManager.ts +83 -6
  109. package/examples/dashboard/src/DeviceActionsForm.tsx +2 -2
  110. package/examples/dashboard/src/DevicePanel.tsx +2 -2
  111. package/examples/dashboard/src/Main.tsx +14 -4
  112. package/examples/dashboard/src/SystemTypeMarty/RICSystemUtils.ts +4 -4
  113. package/examples/dashboard/src/styles.css +8 -0
  114. package/examples/dashboard/tsconfig.json +1 -1
  115. package/package.json +10 -11
  116. package/src/RaftAttributeHandler.ts +163 -11
  117. package/src/RaftChannel.ts +2 -0
  118. package/src/RaftChannelBLE.native.ts +17 -8
  119. package/src/RaftChannelBLE.web.ts +28 -16
  120. package/src/RaftChannelSimulated.ts +482 -0
  121. package/src/RaftChannelWebSerial.ts +18 -7
  122. package/src/RaftChannelWebSocket.ts +13 -0
  123. package/src/RaftConnector.ts +13 -7
  124. package/src/RaftCustomAttrHandler.ts +17 -0
  125. package/src/RaftDeviceInfo.ts +27 -5
  126. package/src/RaftDeviceManager.ts +155 -47
  127. package/src/RaftFileHandler.ts +8 -8
  128. package/src/RaftLog.ts +1 -1
  129. package/src/RaftMsgHandler.ts +36 -0
  130. package/src/RaftStreamHandler.ts +48 -47
  131. package/src/RaftStruct.ts +220 -147
  132. package/src/RaftSysTypeManager.ts +27 -0
  133. package/src/RaftSystemType.ts +9 -7
  134. package/src/RaftSystemUtils.ts +3 -1
  135. package/src/RaftUpdateManager.ts +2 -2
  136. package/src/RaftUtils.ts +25 -5
  137. package/src/main.ts +2 -0
@@ -17,10 +17,26 @@ const attrTypeBits: { [key: string]: number } = {
17
17
  };
18
18
 
19
19
  export function getAttrTypeBits(attrType: string): number {
20
- if (attrType in attrTypeBits) {
21
- return attrTypeBits[attrType];
20
+ let repeat = 1;
21
+ let baseAttrType = attrType;
22
+
23
+ const repeatStartIdx = attrType.indexOf("[");
24
+ if (repeatStartIdx >= 0) {
25
+ const repeatEndIdx = attrType.indexOf("]", repeatStartIdx + 1);
26
+ if (repeatEndIdx > repeatStartIdx) {
27
+ const repeatStr = attrType.slice(repeatStartIdx + 1, repeatEndIdx);
28
+ const parsedRepeat = parseInt(repeatStr, 10);
29
+ if (Number.isFinite(parsedRepeat) && parsedRepeat > 0) {
30
+ repeat = parsedRepeat;
31
+ }
32
+ baseAttrType = attrType.slice(0, repeatStartIdx);
33
+ }
34
+ }
35
+
36
+ if (baseAttrType in attrTypeBits) {
37
+ return attrTypeBits[baseAttrType] * repeat;
22
38
  }
23
- return 8;
39
+ return 8 * repeat;
24
40
  }
25
41
 
26
42
  export function isAttrTypeSigned(attrType: string): boolean {
@@ -33,10 +49,14 @@ export function decodeAttrUnitsEncoding(unitsEncoding: string): string {
33
49
  return unitsEncoding.replace(/°/g, "°");
34
50
  }
35
51
 
52
+ export interface LUTRow {
53
+ r: string;
54
+ v: number;
55
+ }
36
56
  export interface DeviceTypeAttribute {
37
57
  n: string; // Name
38
58
  t: string; // Type in python struct module format (e.g. 'H' uint16, 'h' int16, 'f' float etc.)
39
- at?: number; // Start pos in buffer (after timestamp) if present (otherwise use relative position)
59
+ at?: number | number[]; // Start pos in buffer (after timestamp) if present (otherwise use relative position) or array of byte positions for non-contiguous data
40
60
  u?: string; // Units (e.g. mm)
41
61
  r?: number[]; // Range (either min, max or min, max, step or discrete values)
42
62
  x?: number; // XOR bit mask to invert bits in the attribute value
@@ -51,6 +71,8 @@ export interface DeviceTypeAttribute {
51
71
  v?: boolean | number; // Visibility of the attribute in all locations (mainly used to hide attributes that are not useful to the user)
52
72
  vs?: boolean | number; // Display attribute value in time-series graphs
53
73
  vf?: boolean | number; // Display attribute value in the device info panel
74
+ vft?: string; // Attribute validity based on the value of another named attribute
75
+ lut?: Array<LUTRow>; // Lookup table for the attribute value - each row is a lookup table for a range of values e.g. [{"r":"0x20-0x30","v":0},{"r":"1,2,3","v":42},{r:"","v":1}]
54
76
  }
55
77
 
56
78
  export interface CustomFunctionDefinition {
@@ -86,6 +108,7 @@ export interface DeviceTypeInfo {
86
108
  manu: string;
87
109
  type: string;
88
110
  resp?: DeviceTypePollRespMetadata;
111
+ clas?: Array<string>;
89
112
  actions?: DeviceTypeAction[];
90
113
  }
91
114
 
@@ -98,4 +121,3 @@ export type RaftDevTypeInfoResponse = {
98
121
  rslt: string;
99
122
  devinfo: DeviceTypeInfo;
100
123
  };
101
-
@@ -15,12 +15,10 @@ import AttributeHandler from "./RaftAttributeHandler";
15
15
  import RaftSystemUtils from "./RaftSystemUtils";
16
16
  import RaftDeviceMgrIF from "./RaftDeviceMgrIF";
17
17
  import { structPack } from "./RaftStruct";
18
+ // import RaftUtils from "./RaftUtils";
18
19
 
19
20
  export class DeviceManager implements RaftDeviceMgrIF{
20
21
 
21
- // Singleton
22
- // private static _instance: DeviceManager;
23
-
24
22
  // Max data points to store
25
23
  private _maxDatapointsToStore = 1000;
26
24
 
@@ -48,6 +46,9 @@ export class DeviceManager implements RaftDeviceMgrIF{
48
46
  private _newDeviceAttributeCallbacks: Array<(deviceKey: string, attrState: DeviceAttributeState) => void> = [];
49
47
  private _newAttributeDataCallbacks: Array<(deviceKey: string, attrState: DeviceAttributeState) => void> = [];
50
48
 
49
+ // Debug message index (to help debug with async messages)
50
+ private _debugMsgIndex = 0;
51
+
51
52
  public getDevicesState(): DevicesState {
52
53
  return this._devicesState;
53
54
  }
@@ -62,6 +63,12 @@ export class DeviceManager implements RaftDeviceMgrIF{
62
63
  // Cached device type previous attempt times
63
64
  private _cachedDeviceTypePreviousAttemptTimes: { [deviceType: string]: number } = {};
64
65
 
66
+ // Pending device type requests - queue-based to maintain order
67
+ private _pendingDeviceTypeRequests: { [deviceType: string]: {
68
+ promise: Promise<DeviceTypeInfo | undefined>;
69
+ waitingQueue: Array<{resolve: (value: DeviceTypeInfo | undefined) => void, reject: (reason?: any) => void}>;
70
+ } } = {};
71
+
65
72
  // Constructor
66
73
  constructor() {
67
74
  }
@@ -156,44 +163,71 @@ export class DeviceManager implements RaftDeviceMgrIF{
156
163
  // console.log(`DeviceManager client1 msg ${RaftUtils.bufferToHex(rxMsg)}`);
157
164
 
158
165
  // Example messages
159
- // 0080 0015 81 0000006a 0004 53b7 feff00000100081857079314 0011 80 00000000 0011 53b2 075106e400d60054 0010 80 00000000 0012 5231 000d0000010e01
160
- // 0080 0011 80 00000000 0011 53f1 075806e900d70052 0010 80 00000000 0012 5231 000d0000010e01
166
+ // 0080 0015 81 0000006a 0004 53b7 feff00000100081857079314 0011 80 00000000 0011 53b2 075106e400d60054 0010 80 00000000 0012 5231 000d0000010e01
167
+ // 0080 0011 80 00000000 0002 4ae1 0787052606240007 000e 80 00000000 0003 0006 030001af01
168
+ // 0080 0011 80 00000000 0002 e46e 061e05a206830433 0010 80000000000003e4760006030001c701
169
+ // 0080 0010 81 00000015 0004 e4a2 0650fe00305002 0011 80000000000002e4a8061f059f06850438 001080000000000003e4aa0006030001c701
170
+
171
+ // 0080 0011 80 00000000 0002 31e4 05ea05a506660137 000e 80 00000000 0003 0007 030001d901
172
+
173
+ // 0080 0011 80 00000000 0002 4d63 0792053e06500061 000e 80 00000000 0003 0005 030001de01
161
174
 
162
175
  // First two bytes of each message are the message type (0080)
163
176
  // There are then a series of sections each of which is the data for a device
164
- // First two bytes of each section is the section length (big endian) not including the length bytes
177
+ // First two bytes of each section is the section length (big endian) not including the section length bytes themselves
165
178
  // 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
166
179
  // Next is the device address (4 bytes big endian)
167
180
  // Next is the device type index (2 bytes big endian)
168
181
  // Finally the device data which can be one or more groups of attributes defined by the schema
169
182
 
183
+ // Debug
184
+ // const debugMsgTime = Date.now();
185
+ const debugMsgIndex = this._debugMsgIndex++;
186
+
187
+ // Message layout
188
+ const msgTypeLen = 2; // Length of the message type (first two bytes)
189
+ const sectionLengthLen = 2; // Length of the inclusive section length (first two bytes of each section)
190
+ const sectionConnectionModeLen = 1; // Length of the connection mode (next byte after section length)
191
+ const sectionDeviceAddrLen = 4; // Length of the device address (next 4 bytes after connection mode)
192
+ const sectionDeviceTypeIdxLen = 2; // Length of the device type index (next 2 bytes after device address)
193
+ const sectionHeaderLen = sectionConnectionModeLen + sectionDeviceAddrLen + sectionDeviceTypeIdxLen;
194
+
195
+ // console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} rxMsg.length ${rxMsg.length} rxMsg ${RaftUtils.bufferToHex(rxMsg)}`);
196
+
197
+ // Start after the message type
198
+ let msgPos = msgTypeLen;
199
+
170
200
  // Iterate through sections
171
- let msgPos = 2;
172
201
  while (msgPos < rxMsg.length) {
173
202
 
174
203
  // Check length
175
- if (rxMsg.length < msgPos + 11) {
176
- console.warn(`DeviceManager handleClientMsgBinary invalid length ${rxMsg.length}`);
204
+ const remainingLen = rxMsg.length - msgPos;
205
+ if (remainingLen < sectionLengthLen + sectionHeaderLen) {
206
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid length ${rxMsg.length} < ${sectionLengthLen + sectionHeaderLen + msgPos}`);
177
207
  return;
178
208
  }
179
209
 
180
210
  // Get the length of the section
181
211
  const sectionLen = (rxMsg[msgPos] << 8) + rxMsg[msgPos + 1];
182
- if (sectionLen > rxMsg.length) {
183
- console.warn(`DeviceManager handleClientMsgBinary invalid msgPos ${msgPos} msgLen ${sectionLen} rxMsgLen ${rxMsg.length}`);
212
+ if (sectionLen > remainingLen + sectionLengthLen) {
213
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} invalid msgPos ${msgPos} msgLen ${sectionLen} remainingLenAfterLenBytes ${remainingLen-sectionLengthLen}`);
184
214
  return;
185
215
  }
186
216
 
187
217
  // Extract message elements
188
- const busNum = rxMsg[msgPos + 2] & 0x7f;
189
- const isOnline = (rxMsg[msgPos + 2] & 0x80) !== 0;
190
- const devAddr = (rxMsg[msgPos + 3] << 24) + (rxMsg[msgPos + 4] << 16) + (rxMsg[msgPos + 5] << 8) + rxMsg[msgPos + 6];
191
- const devTypeIdx = (rxMsg[msgPos + 7] << 8) + rxMsg[msgPos + 8];
192
- let attrGroupPos = msgPos + 9;
218
+ let sectionPos = msgPos + sectionLengthLen;
219
+ const busNum = rxMsg[sectionPos] & 0x7f;
220
+ const isOnline = (rxMsg[sectionPos] & 0x80) !== 0;
221
+ sectionPos += sectionConnectionModeLen;
222
+ // Get the device address and type index
223
+ const devAddr = (rxMsg[sectionPos] << 24) + (rxMsg[sectionPos + 1] << 16) + (rxMsg[sectionPos + 2] << 8) + rxMsg[sectionPos + 3];
224
+ sectionPos += sectionDeviceAddrLen;
225
+ const devTypeIdx = (rxMsg[sectionPos] << 8) + rxMsg[sectionPos + 1];
226
+ let attrGroupPos = sectionPos + sectionDeviceTypeIdxLen;
193
227
 
194
228
  // Debug
195
- // console.log(`DeviceManager overallLen ${rxMsg.length} sectionPos ${msgPos} sectionLen ${sectionLen} ${RaftUtils.bufferToHex(rxMsg.slice(msgPos, msgPos + sectionLen))}`);
196
- // console.log(`DeviceManager connMode ${busNum} isOnline ${isOnline} devAddr ${devAddr} devTypeIdx ${devTypeIdx} attrGroupDataLen ${sectionLen - 9}`);
229
+ // console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} overallLen ${rxMsg.length} sectionPos ${msgPos} sectionLen ${sectionLen} ${attrGroupPos} ${RaftUtils.bufferToHex(rxMsg.slice(msgPos, msgPos + sectionLen))}`);
230
+ // console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} connMode ${busNum} isOnline ${isOnline} devAddr ${devAddr} devTypeIdx ${devTypeIdx} attrGroupDataLen ${sectionLen - sectionHeaderLen}`);
197
231
 
198
232
  // Device key
199
233
  const deviceKey = getDeviceKey(busNum.toString(), devAddr.toString(), devTypeIdx.toString());
@@ -203,9 +237,20 @@ export class DeviceManager implements RaftDeviceMgrIF{
203
237
 
204
238
  // Check if a device state already exists
205
239
  if (!(deviceKey in this._devicesState) || (this._devicesState[deviceKey].deviceTypeInfo === undefined)) {
240
+
206
241
  // Get the device type info
207
- const deviceTypeInfo = await this.getDeviceTypeInfo(busNum.toString(), devAddr.toString(), devTypeIdx.toString());
208
- // console.log(`DeviceManager deviceTypeInfo ${JSON.stringify(deviceTypeInfo)}`);
242
+ const deviceTypeInfo = await this.getDeviceTypeInfo(busNum.toString(), devTypeIdx.toString());
243
+
244
+ // Debug
245
+ // console.log(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} attrGroupPos ${attrGroupPos} busNum ${busNum} devAddr ${devAddr} devTypeIdx ${devTypeIdx} deviceTypeInfo ${JSON.stringify(deviceTypeInfo)}`);
246
+
247
+ // Handle case where device type info is not available
248
+ if (deviceTypeInfo === undefined) {
249
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} deviceType ${devTypeIdx} info not available, skipping attribute processing for this section`);
250
+ // Skip to next section without processing attributes
251
+ msgPos += sectionLengthLen + sectionLen;
252
+ continue;
253
+ }
209
254
 
210
255
  // Check if device record exists
211
256
  if (deviceKey in this._devicesState) {
@@ -239,30 +284,59 @@ export class DeviceManager implements RaftDeviceMgrIF{
239
284
  const deviceState = this._devicesState[deviceKey];
240
285
  deviceState.isOnline = isOnline;
241
286
 
242
- // Check if device type info is available
287
+ // Check if device type info is available and complete
243
288
  if (deviceState.deviceTypeInfo && deviceState.deviceTypeInfo.resp) {
244
289
 
245
290
  // Iterate over attributes in the group
246
291
  const pollRespMetadata = deviceState.deviceTypeInfo!.resp!;
247
292
 
248
293
  // Iterate over attribute groups
249
- while (attrGroupPos < msgPos + sectionLen + 2) {
250
- const curTimelineLen = deviceState.deviceTimeline.timestampsUs.length;
294
+ const attrGroupDataLen = sectionLen - sectionHeaderLen;
295
+ const attrGroupStartPos = attrGroupPos;
296
+ while (attrGroupPos < attrGroupStartPos + attrGroupDataLen) {
297
+
298
+ // Add bounds checking
299
+ if (attrGroupPos >= rxMsg.length) {
300
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} attrGroupPos ${attrGroupPos} exceeds message length ${rxMsg.length}`);
301
+ break;
302
+ }
303
+
251
304
  const newMsgBufIdx = this._attributeHandler.processMsgAttrGroup(rxMsg, attrGroupPos,
252
305
  deviceState.deviceTimeline, pollRespMetadata,
253
306
  deviceState.deviceAttributes,
254
307
  this._maxDatapointsToStore);
308
+
309
+ // 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)}`);
310
+
255
311
  if (newMsgBufIdx < 0)
312
+ {
313
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} processMsgAttrGroup failed newMsgBufIdx ${newMsgBufIdx}`);
256
314
  break;
257
- attrGroupPos = newMsgBufIdx;
258
- if (deviceState.deviceTimeline.timestampsUs.length !== curTimelineLen) {
259
- deviceState.stateChanged = true;
260
315
  }
316
+
317
+ // Prevent infinite loops
318
+ if (newMsgBufIdx <= attrGroupPos) {
319
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} processMsgAttrGroup didn't advance position from ${attrGroupPos} to ${newMsgBufIdx}`);
320
+ break;
321
+ }
322
+
323
+ attrGroupPos = newMsgBufIdx;
324
+ deviceState.stateChanged = true;
325
+
326
+ // 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}`);
327
+
328
+
329
+ // console.log(`DevMan.handleClientMsgBinary group done debugIdx ${debugMsgIndex} attrGroupPos ${attrGroupPos} sectionLen ${sectionLen} msgPos ${msgPos} rxMsgLen ${rxMsg.length} remainingLen ${remainingLen}`);
261
330
  }
331
+ } else {
332
+ console.warn(`DevMan.handleClientMsgBinary debugIdx ${debugMsgIndex} deviceState incomplete for device ${deviceKey}, skipping attribute processing`);
262
333
  }
263
334
 
335
+ // Debug
336
+ // console.log(`DevMan.handleClientMsgBinary section done debugIdx ${debugMsgIndex} attrGroupPos ${attrGroupPos} sectionLen ${sectionLen} msgPos ${msgPos} newMsgPos ${msgPos + sectionLengthLen + sectionLen} rxMsgLen ${rxMsg.length} remainingLen ${remainingLen}`);
337
+
264
338
  // Move to next message
265
- msgPos += sectionLen + 2;
339
+ msgPos += sectionLengthLen + sectionLen;
266
340
  }
267
341
 
268
342
  // Check for devices that have not been updated for a while
@@ -276,7 +350,7 @@ export class DeviceManager implements RaftDeviceMgrIF{
276
350
  }
277
351
 
278
352
  // Process the callback
279
- this.processStateCallback();
353
+ this.processStateCallback();
280
354
  }
281
355
 
282
356
  ////////////////////////////////////////////////////////////////////////////
@@ -324,7 +398,7 @@ export class DeviceManager implements RaftDeviceMgrIF{
324
398
  if (!(deviceKey in this._devicesState) || (this._devicesState[deviceKey].deviceTypeInfo === undefined)) {
325
399
 
326
400
  // Get the device type info
327
- const deviceTypeInfo = await this.getDeviceTypeInfo(busName, devAddr, deviceTypeName);
401
+ const deviceTypeInfo = await this.getDeviceTypeInfo(busName, deviceTypeName);
328
402
 
329
403
  // Check if device record exists
330
404
  if (deviceKey in this._devicesState) {
@@ -392,7 +466,6 @@ export class DeviceManager implements RaftDeviceMgrIF{
392
466
  // Loop
393
467
  while (msgBufIdx < msgBytes.length) {
394
468
 
395
- const curTimelineLen = deviceState.deviceTimeline.timestampsUs.length;
396
469
  const newMsgBufIdx = this._attributeHandler.processMsgAttrGroup(msgBytes, msgBufIdx,
397
470
  deviceState.deviceTimeline, pollRespMetadata,
398
471
  deviceState.deviceAttributes,
@@ -400,9 +473,7 @@ export class DeviceManager implements RaftDeviceMgrIF{
400
473
  if (newMsgBufIdx < 0)
401
474
  break;
402
475
  msgBufIdx = newMsgBufIdx;
403
- if (deviceState.deviceTimeline.timestampsUs.length !== curTimelineLen) {
404
- deviceState.stateChanged = true;
405
- }
476
+ deviceState.stateChanged = true;
406
477
  }
407
478
  });
408
479
  });
@@ -455,39 +526,76 @@ export class DeviceManager implements RaftDeviceMgrIF{
455
526
  // Get device type info
456
527
  ////////////////////////////////////////////////////////////////////////////
457
528
 
458
- private async getDeviceTypeInfo(busName: string, _devAddr: string, deviceType: string): Promise<DeviceTypeInfo | undefined> {
459
-
460
- // Check if already in the cache
529
+ private async getDeviceTypeInfo(busName: string, deviceType: string): Promise<DeviceTypeInfo | undefined> {
530
+ // Check if already in cache
461
531
  if (deviceType in this._cachedDeviceTypeRecs) {
462
532
  return this._cachedDeviceTypeRecs[deviceType];
463
533
  }
464
534
 
465
- // Check if we have tried to get device info previously (and failed presumably since it isn't in the cache)
535
+ // Check if there's already a pending request for this device type
536
+ if (deviceType in this._pendingDeviceTypeRequests) {
537
+ // console.log(`DevMan.getDeviceTypeInfo joining existing request queue for deviceType ${deviceType}`);
538
+
539
+ // Add this request to the waiting queue
540
+ return new Promise<DeviceTypeInfo | undefined>((resolve, reject) => {
541
+ this._pendingDeviceTypeRequests[deviceType].waitingQueue.push({ resolve, reject });
542
+ });
543
+ }
544
+
545
+ // Check rate limiting for new requests
466
546
  if (deviceType in this._cachedDeviceTypePreviousAttemptTimes) {
467
- // Check if we should retry
468
- if ((Date.now() - this._cachedDeviceTypePreviousAttemptTimes[deviceType]) < this._minTimeBetweenDeviceTypeInfoRetrievalMs) {
547
+ const timeSinceLastAttempt = Date.now() - this._cachedDeviceTypePreviousAttemptTimes[deviceType];
548
+ if (timeSinceLastAttempt < this._minTimeBetweenDeviceTypeInfoRetrievalMs) {
549
+ console.log(`DevMan.getDeviceTypeInfo rate limited for deviceType ${deviceType}`);
469
550
  return undefined;
470
551
  }
471
552
  }
472
- this._cachedDeviceTypePreviousAttemptTimes[deviceType] = Date.now();
473
553
 
474
- // Get the device type info from the server
554
+ // Create and cache the promise with an empty waiting queue
555
+ const requestPromise = this.executeDeviceTypeInfoRequest(busName, deviceType);
556
+ this._pendingDeviceTypeRequests[deviceType] = {
557
+ promise: requestPromise,
558
+ waitingQueue: []
559
+ };
560
+
475
561
  try {
476
- // Form the request
477
- const cmd = "devman/typeinfo?bus=" + busName + "&type=" + deviceType;
562
+ const result = await requestPromise;
563
+
564
+ // Resolve all waiting requests with the same result
565
+ const waitingQueue = this._pendingDeviceTypeRequests[deviceType].waitingQueue;
566
+ waitingQueue.forEach(({ resolve }) => resolve(result));
567
+
568
+ return result;
569
+ } catch (error) {
570
+ // Reject all waiting requests with the same error
571
+ const waitingQueue = this._pendingDeviceTypeRequests[deviceType].waitingQueue;
572
+ waitingQueue.forEach(({ reject }) => reject(error));
573
+
574
+ console.warn(`DevMan.getDeviceTypeInfo failed for ${deviceType}: ${error}`);
575
+ return undefined;
576
+ } finally {
577
+ // Clean up the pending request
578
+ delete this._pendingDeviceTypeRequests[deviceType];
579
+ }
580
+ }
478
581
 
479
- // Get the msg handler
582
+ private async executeDeviceTypeInfoRequest(busName: string, deviceType: string): Promise<DeviceTypeInfo | undefined> {
583
+ this._cachedDeviceTypePreviousAttemptTimes[deviceType] = Date.now();
584
+
585
+ try {
586
+ const cmd = "devman/typeinfo?bus=" + busName + "&type=" + deviceType;
480
587
  const msgHandler = this._systemUtils?.getMsgHandler();
588
+
481
589
  if (msgHandler) {
482
590
  const msgRslt = await msgHandler.sendRICRESTURL<RaftDevTypeInfoResponse>(cmd);
483
- if (msgRslt.rslt === "ok") {
591
+ if (msgRslt && msgRslt.rslt === "ok") {
484
592
  this._cachedDeviceTypeRecs[deviceType] = msgRslt.devinfo;
485
- return msgRslt.devinfo
593
+ return msgRslt.devinfo;
486
594
  }
487
595
  }
488
596
  return undefined;
489
597
  } catch (error) {
490
- console.error(`DeviceManager getDeviceTypeInfo error ${error}`);
598
+ console.warn(`DeviceManager getDeviceTypeInfo error ${error}`);
491
599
  return undefined;
492
600
  }
493
601
  }
@@ -162,11 +162,11 @@ export default class RaftFileHandler {
162
162
  RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
163
163
  );
164
164
  } catch (err) {
165
- RaftLog.error(`sendFileStartMsg error ${err}`);
165
+ RaftLog.warn(`sendFileStartMsg error ${err}`);
166
166
  return false;
167
167
  }
168
168
  if (fileStartResp.rslt !== 'ok') {
169
- RaftLog.error(`sendFileStartMsg error ${fileStartResp.rslt}`);
169
+ RaftLog.warn(`sendFileStartMsg error ${fileStartResp.rslt}`);
170
170
  return false;
171
171
  }
172
172
 
@@ -209,7 +209,7 @@ export default class RaftFileHandler {
209
209
  await this.awaitOutstandingMsgPromises(true);
210
210
  } catch (err) {
211
211
  // Ignore
212
- RaftLog.error(`sendFileEndMsg awaitOutstandingMsgPromises error ${err}`);
212
+ RaftLog.warn(`sendFileEndMsg awaitOutstandingMsgPromises error ${err}`);
213
213
  }
214
214
 
215
215
  // Send
@@ -220,7 +220,7 @@ export default class RaftFileHandler {
220
220
  RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
221
221
  );
222
222
  } catch (err) {
223
- RaftLog.error(`sendFileEndMsg error ${err}`);
223
+ RaftLog.warn(`sendFileEndMsg error ${err}`);
224
224
  return false;
225
225
  }
226
226
  return fileEndResp.rslt === 'ok';
@@ -240,7 +240,7 @@ export default class RaftFileHandler {
240
240
  RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
241
241
  );
242
242
  } catch (err) {
243
- RaftLog.error(`sendFileCancelMsg error ${err}`);
243
+ RaftLog.warn(`sendFileCancelMsg error ${err}`);
244
244
  }
245
245
  }
246
246
 
@@ -438,7 +438,7 @@ export default class RaftFileHandler {
438
438
  const bridgedDeviceSerialPort = "Serial" + fileSource.slice(bridgeSerialPrefix.length);
439
439
  const cmdResp = await this._msgHandler.createCommsBridge(bridgedDeviceSerialPort, "fileSource");
440
440
  if (cmdResp.rslt != "ok") {
441
- RaftLog.error(`fileReceive - failed to setup bridge ${cmdResp.rslt}`);
441
+ RaftLog.warn(`fileReceive - failed to setup bridge ${cmdResp.rslt}`);
442
442
  return new RaftFileDownloadResult();
443
443
  }
444
444
  bridgeID = cmdResp.bridgeID;
@@ -490,7 +490,7 @@ export default class RaftFileHandler {
490
490
  bridgeID,
491
491
  );
492
492
  } catch (err) {
493
- RaftLog.error(`_receiveFileStartMsg error ${err}`);
493
+ RaftLog.warn(`_receiveFileStartMsg error ${err}`);
494
494
  return false;
495
495
  }
496
496
  RaftLog.info(`_receiveFileStartMsg rslt ${JSON.stringify(cmdResp)}`);
@@ -530,7 +530,7 @@ export default class RaftFileHandler {
530
530
  // Check CRC
531
531
  const crc16 = RaftMiniHDLC.crc16(this._fileRxBuffer);
532
532
  if (crc16 !== this._fileRxCrc16) {
533
- RaftLog.error(`_receiveFileContents - CRC error ${crc16} ${this._fileRxCrc16}`);
533
+ RaftLog.warn(`_receiveFileContents - CRC error ${crc16} ${this._fileRxCrc16}`);
534
534
  reject(new Error('fileReceive CRC error'));
535
535
  return;
536
536
  } else {
package/src/RaftLog.ts CHANGED
@@ -44,7 +44,7 @@ export default class RaftLog {
44
44
 
45
45
  static error(msg: string) {
46
46
  if (!this.doLogging(RaftLogLevel.ERROR, msg))
47
- console.error(RaftLog.format(msg));
47
+ console.warn(RaftLog.format(msg));
48
48
  }
49
49
 
50
50
  static verbose(msg: string) {
@@ -60,6 +60,8 @@ export interface RaftMessageSender {
60
60
  msg: Uint8Array,
61
61
  sendWithResponse: boolean,
62
62
  ): Promise<boolean>;
63
+ sendTxMsgRaw(msg: string): boolean;
64
+ sendTxMsgRawAndWaitForReply<T>(msgPayload: Uint8Array): T;
63
65
  }
64
66
 
65
67
  export default class RaftMsgHandler {
@@ -87,6 +89,9 @@ export default class RaftMsgHandler {
87
89
  // RaftMiniHDLC - handles part of RICSerial protocol
88
90
  private _miniHDLC: RaftMiniHDLC;
89
91
 
92
+ // Raw message mode
93
+ private _rawMsgMode = false;
94
+
90
95
  // Constructor
91
96
  constructor(commsStats: RaftCommsStats) {
92
97
  this._commsStats = commsStats;
@@ -120,6 +125,22 @@ export default class RaftMsgHandler {
120
125
  // RaftLog.verbose(`handleNewRxMsg len ${rxMsg.length} ${RaftUtils.bufferToHex(rxMsg)}`)
121
126
  }
122
127
 
128
+ // This is currently only for testing and simulated channels
129
+ handleNewRxMsgRaw(rxMsg: Uint8Array, rxMsgType: RaftCommsMsgTypeCode, rxMsgNum: number, rxMsgTimeMs: number): void {
130
+ // Check message types
131
+ if (rxMsgType == RaftCommsMsgTypeCode.MSG_TYPE_RESPONSE) {
132
+ // Convert raw Uint8Array to string assuming UTF-8
133
+ const restStr = new TextDecoder('utf-8').decode(rxMsg);
134
+ this._handleResponseMessages(restStr, rxMsgNum);
135
+ } else if (rxMsgType == RaftCommsMsgTypeCode.MSG_TYPE_REPORT) {
136
+ // Convert raw Uint8Array to string assuming UTF-8
137
+ const restStr = new TextDecoder('utf-8').decode(rxMsg);
138
+ this._handleReportMessages(restStr);
139
+ } else {
140
+ this._msgResultHandler?.onRxOtherMsgType(rxMsg, rxMsgTimeMs);
141
+ }
142
+ }
143
+
123
144
  reportMsgCallbacksSet(callbackName: string, callback: (report: RaftReportMsg) => Promise<void>): void {
124
145
  this._reportMsgCallbacks.set(callbackName, callback);
125
146
  }
@@ -128,6 +149,11 @@ export default class RaftMsgHandler {
128
149
  this._reportMsgCallbacks.delete(callbackName);
129
150
  }
130
151
 
152
+ setRawMsgMode(isRawMode: boolean): void {
153
+ this._rawMsgMode = isRawMode;
154
+ RaftLog.debug(`setRawMsgMode ${isRawMode}`);
155
+ }
156
+
131
157
  _onHDLCFrameDecode(rxMsg: Uint8Array, frameTimeMs: number): void {
132
158
  // Add to stats
133
159
  this._commsStats.msgRx();
@@ -336,6 +362,11 @@ export default class RaftMsgHandler {
336
362
  return false;
337
363
  }
338
364
 
365
+ // Check for raw message mode (used for testing and simulated channels)
366
+ if (this._rawMsgMode) {
367
+ return this._msgSender.sendTxMsgRaw(cmdStr);
368
+ }
369
+
339
370
  // Put cmdStr into buffer
340
371
  const cmdBytes = new Uint8Array(cmdStr.length + 1);
341
372
  RaftUtils.addStringToBuffer(cmdBytes, cmdStr, 0);
@@ -406,6 +437,11 @@ export default class RaftMsgHandler {
406
437
  throw new Error('sendMsgAndWaitForReply failed no sender');
407
438
  }
408
439
 
440
+ // Check for raw message mode (used for testing and simulated channels)
441
+ if (this._rawMsgMode) {
442
+ return this._msgSender.sendTxMsgRawAndWaitForReply(msgPayload);
443
+ }
444
+
409
445
  // Frame the message
410
446
  let framedMsg = this.frameCommsMsg(msgPayload, msgDirection, msgProtocol, true);
411
447