@robdobsn/raftjs 1.10.7 → 1.11.6

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 (89) hide show
  1. package/dist/react-native/RaftAttributeHandler.js +11 -9
  2. package/dist/react-native/RaftAttributeHandler.js.map +1 -1
  3. package/dist/react-native/RaftChannelSimulated.js +4 -3
  4. package/dist/react-native/RaftChannelSimulated.js.map +1 -1
  5. package/dist/react-native/RaftConnector.d.ts +10 -1
  6. package/dist/react-native/RaftConnector.js +23 -10
  7. package/dist/react-native/RaftConnector.js.map +1 -1
  8. package/dist/react-native/RaftDeviceManager.d.ts +14 -2
  9. package/dist/react-native/RaftDeviceManager.js +224 -77
  10. package/dist/react-native/RaftDeviceManager.js.map +1 -1
  11. package/dist/react-native/RaftDeviceMgrIF.d.ts +5 -1
  12. package/dist/react-native/RaftDeviceStates.d.ts +21 -3
  13. package/dist/react-native/RaftDeviceStates.js +31 -6
  14. package/dist/react-native/RaftDeviceStates.js.map +1 -1
  15. package/dist/react-native/RaftPublish.d.ts +2 -0
  16. package/dist/react-native/RaftPublish.js +81 -0
  17. package/dist/react-native/RaftPublish.js.map +1 -0
  18. package/dist/react-native/RaftStreamHandler.d.ts +11 -0
  19. package/dist/react-native/RaftStreamHandler.js +66 -0
  20. package/dist/react-native/RaftStreamHandler.js.map +1 -1
  21. package/dist/react-native/RaftStruct.d.ts +2 -2
  22. package/dist/react-native/RaftStruct.js +97 -26
  23. package/dist/react-native/RaftStruct.js.map +1 -1
  24. package/dist/react-native/RaftSystemUtils.d.ts +17 -1
  25. package/dist/react-native/RaftSystemUtils.js +51 -0
  26. package/dist/react-native/RaftSystemUtils.js.map +1 -1
  27. package/dist/react-native/RaftTypes.d.ts +21 -0
  28. package/dist/react-native/RaftTypes.js.map +1 -1
  29. package/dist/react-native/main.d.ts +1 -0
  30. package/dist/react-native/main.js +1 -0
  31. package/dist/react-native/main.js.map +1 -1
  32. package/dist/web/RaftAttributeHandler.js +11 -9
  33. package/dist/web/RaftAttributeHandler.js.map +1 -1
  34. package/dist/web/RaftChannelSimulated.js +4 -3
  35. package/dist/web/RaftChannelSimulated.js.map +1 -1
  36. package/dist/web/RaftConnector.d.ts +10 -1
  37. package/dist/web/RaftConnector.js +23 -10
  38. package/dist/web/RaftConnector.js.map +1 -1
  39. package/dist/web/RaftDeviceManager.d.ts +14 -2
  40. package/dist/web/RaftDeviceManager.js +224 -77
  41. package/dist/web/RaftDeviceManager.js.map +1 -1
  42. package/dist/web/RaftDeviceMgrIF.d.ts +5 -1
  43. package/dist/web/RaftDeviceStates.d.ts +21 -3
  44. package/dist/web/RaftDeviceStates.js +31 -6
  45. package/dist/web/RaftDeviceStates.js.map +1 -1
  46. package/dist/web/RaftPublish.d.ts +2 -0
  47. package/dist/web/RaftPublish.js +81 -0
  48. package/dist/web/RaftPublish.js.map +1 -0
  49. package/dist/web/RaftStreamHandler.d.ts +11 -0
  50. package/dist/web/RaftStreamHandler.js +66 -0
  51. package/dist/web/RaftStreamHandler.js.map +1 -1
  52. package/dist/web/RaftStruct.d.ts +2 -2
  53. package/dist/web/RaftStruct.js +97 -26
  54. package/dist/web/RaftStruct.js.map +1 -1
  55. package/dist/web/RaftSystemUtils.d.ts +17 -1
  56. package/dist/web/RaftSystemUtils.js +51 -0
  57. package/dist/web/RaftSystemUtils.js.map +1 -1
  58. package/dist/web/RaftTypes.d.ts +21 -0
  59. package/dist/web/RaftTypes.js.map +1 -1
  60. package/dist/web/main.d.ts +1 -0
  61. package/dist/web/main.js +1 -0
  62. package/dist/web/main.js.map +1 -1
  63. package/examples/dashboard/package.json +1 -1
  64. package/examples/dashboard/src/DeviceActionsForm.tsx +2 -2
  65. package/examples/dashboard/src/DeviceLineChart.tsx +16 -3
  66. package/examples/dashboard/src/DevicePanel.tsx +79 -3
  67. package/examples/dashboard/src/DeviceStatsPanel.tsx +76 -0
  68. package/examples/dashboard/src/DevicesPanel.tsx +11 -0
  69. package/examples/dashboard/src/SettingsScreen.tsx +9 -4
  70. package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +10 -2
  71. package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +37 -3
  72. package/examples/dashboard/src/SystemTypeGeneric/StateInfoGeneric.ts +10 -2
  73. package/examples/dashboard/src/SystemTypeGeneric/SystemTypeGeneric.ts +38 -4
  74. package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +10 -2
  75. package/examples/dashboard/src/styles.css +162 -0
  76. package/package.json +52 -49
  77. package/src/RaftAttributeHandler.ts +25 -22
  78. package/src/RaftChannelSimulated.ts +4 -3
  79. package/src/RaftConnector.ts +34 -13
  80. package/src/RaftDeviceManager.ts +253 -83
  81. package/src/RaftDeviceMgrIF.ts +5 -1
  82. package/src/RaftDeviceStates.ts +42 -8
  83. package/src/RaftPublish.ts +92 -0
  84. package/src/RaftStreamHandler.ts +84 -1
  85. package/src/RaftStruct.test.ts +229 -0
  86. package/src/RaftStruct.ts +101 -37
  87. package/src/RaftSystemUtils.ts +59 -0
  88. package/src/RaftTypes.ts +27 -0
  89. package/src/main.ts +1 -0
package/src/RaftStruct.ts CHANGED
@@ -33,22 +33,40 @@ function parseFormatInstructions(format: string): FormatInstruction[] {
33
33
  continue;
34
34
  }
35
35
 
36
+ // Check for prefix digit count (e.g. 3H, 16s)
37
+ let repeat = 1;
38
+ if (/\d/.test(char)) {
39
+ let numStr = char;
40
+ idx++;
41
+ while (idx < format.length && /\d/.test(format[idx])) {
42
+ numStr += format[idx];
43
+ idx++;
44
+ }
45
+ repeat = parseInt(numStr, 10);
46
+ if (!Number.isFinite(repeat) || repeat <= 0) {
47
+ throw new Error(`Invalid prefix count "${numStr}" in format string "${format}"`);
48
+ }
49
+ if (idx >= format.length || /[\s<>\d]/.test(format[idx])) {
50
+ throw new Error(`Expected format code after prefix count "${numStr}" in format string "${format}"`);
51
+ }
52
+ }
53
+
36
54
  // Attribute code
37
- const code = char;
38
- idx++;
55
+ const code = /\d/.test(char) ? format[idx] : char;
56
+ if (/\d/.test(char)) idx++; else idx++;
39
57
 
40
- // Check for repeat count using [N] syntax
41
- let repeat = 1;
58
+ // Check for [N] suffix count (e.g. B[3]) — multiplied with any prefix count
42
59
  if (idx < format.length && format[idx] === "[") {
43
60
  const endIdx = format.indexOf("]", idx + 1);
44
61
  if (endIdx === -1) {
45
62
  throw new Error(`Invalid format string: missing closing ] in "${format}"`);
46
63
  }
47
64
  const repeatStr = format.slice(idx + 1, endIdx);
48
- repeat = parseInt(repeatStr, 10);
49
- if (!Number.isFinite(repeat) || repeat <= 0) {
65
+ const bracketRepeat = parseInt(repeatStr, 10);
66
+ if (!Number.isFinite(bracketRepeat) || bracketRepeat <= 0) {
50
67
  throw new Error(`Invalid repeat count "${repeatStr}" in format string "${format}"`);
51
68
  }
69
+ repeat *= bracketRepeat;
52
70
  idx = endIdx + 1;
53
71
  }
54
72
 
@@ -58,9 +76,9 @@ function parseFormatInstructions(format: string): FormatInstruction[] {
58
76
  return instructions;
59
77
  }
60
78
 
61
- export function structUnpack(format: string, data: Uint8Array): number[] {
79
+ export function structUnpack(format: string, data: Uint8Array): (number | string)[] {
62
80
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
63
- const results: number[] = [];
81
+ const results: (number | string)[] = [];
64
82
  let offset = 0;
65
83
  let littleEndian = false;
66
84
 
@@ -107,14 +125,14 @@ export function structUnpack(format: string, data: Uint8Array): number[] {
107
125
  results.push(view.getUint32(offset, littleEndian));
108
126
  offset += 4;
109
127
  break;
110
- // case "q": // Signed 64-bit integer
111
- // results.push(Number(view.getBigInt64(offset, littleEndian)));
112
- // offset += 8;
113
- // break;
114
- // case "Q": // Unsigned 64-bit integer
115
- // results.push(Number(view.getBigUint64(offset, littleEndian)));
116
- // offset += 8;
117
- // break;
128
+ case "q": // Signed 64-bit integer
129
+ results.push(Number(view.getBigInt64(offset, littleEndian)));
130
+ offset += 8;
131
+ break;
132
+ case "Q": // Unsigned 64-bit integer
133
+ results.push(Number(view.getBigUint64(offset, littleEndian)));
134
+ offset += 8;
135
+ break;
118
136
  case "f": // 32-bit float
119
137
  results.push(view.getFloat32(offset, littleEndian));
120
138
  offset += 4;
@@ -123,9 +141,20 @@ export function structUnpack(format: string, data: Uint8Array): number[] {
123
141
  results.push(view.getFloat64(offset, littleEndian));
124
142
  offset += 8;
125
143
  break;
144
+ case "s": { // Byte string (repeat = byte length, produces one string value)
145
+ const bytes = data.slice(offset, offset + repeat);
146
+ // Trim trailing null bytes for C-string compatibility
147
+ let end = bytes.length;
148
+ while (end > 0 && bytes[end - 1] === 0) end--;
149
+ results.push(new TextDecoder().decode(bytes.subarray(0, end)));
150
+ offset += repeat;
151
+ break;
152
+ }
126
153
  default:
127
154
  throw new Error(`Unknown format character: ${code}`);
128
155
  }
156
+ // For 's', the repeat is consumed as byte-length in one go
157
+ if (code === "s") break;
129
158
  }
130
159
  }
131
160
 
@@ -162,16 +191,19 @@ export function structSizeOf(format: string): number {
162
191
  case "L": // Unsigned 32-bit integer
163
192
  unitSize = 4;
164
193
  break;
165
- // case "q": // Signed 64-bit integer
166
- // case "Q": // Unsigned 64-bit integer
167
- // unitSize = 8;
168
- // break;
194
+ case "q": // Signed 64-bit integer
195
+ case "Q": // Unsigned 64-bit integer
196
+ unitSize = 8;
197
+ break;
169
198
  case "f": // 32-bit float
170
199
  unitSize = 4;
171
200
  break;
172
201
  case "d": // 64-bit float
173
202
  unitSize = 8;
174
203
  break;
204
+ case "s": // Byte string (repeat = byte length)
205
+ size += repeat;
206
+ continue;
175
207
  default:
176
208
  throw new Error(`Unknown format character: ${code}`);
177
209
  }
@@ -181,7 +213,7 @@ export function structSizeOf(format: string): number {
181
213
  return size;
182
214
  }
183
215
 
184
- export function structPack(format: string, values: number[]): Uint8Array {
216
+ export function structPack(format: string, values: (number | string | Uint8Array)[]): Uint8Array {
185
217
  const size = structSizeOf(format);
186
218
  const buffer = new ArrayBuffer(size);
187
219
  const view = new DataView(buffer);
@@ -209,7 +241,7 @@ export function structPack(format: string, values: number[]): Uint8Array {
209
241
  if (valueIdx >= values.length) {
210
242
  throw new Error("Insufficient values provided for structPack");
211
243
  }
212
- view.setInt8(offset, values[valueIdx++]);
244
+ view.setInt8(offset, values[valueIdx++] as number);
213
245
  offset += 1;
214
246
  break;
215
247
  case "B": // Unsigned 8-bit integer
@@ -217,21 +249,21 @@ export function structPack(format: string, values: number[]): Uint8Array {
217
249
  if (valueIdx >= values.length) {
218
250
  throw new Error("Insufficient values provided for structPack");
219
251
  }
220
- view.setUint8(offset, values[valueIdx++]);
252
+ view.setUint8(offset, values[valueIdx++] as number);
221
253
  offset += 1;
222
254
  break;
223
255
  case "h": // Signed 16-bit integer
224
256
  if (valueIdx >= values.length) {
225
257
  throw new Error("Insufficient values provided for structPack");
226
258
  }
227
- view.setInt16(offset, values[valueIdx++], littleEndian);
259
+ view.setInt16(offset, values[valueIdx++] as number, littleEndian);
228
260
  offset += 2;
229
261
  break;
230
262
  case "H": // Unsigned 16-bit integer
231
263
  if (valueIdx >= values.length) {
232
264
  throw new Error("Insufficient values provided for structPack");
233
265
  }
234
- view.setUint16(offset, values[valueIdx++], littleEndian);
266
+ view.setUint16(offset, values[valueIdx++] as number, littleEndian);
235
267
  offset += 2;
236
268
  break;
237
269
  case "i": // Signed 32-bit integer
@@ -239,7 +271,7 @@ export function structPack(format: string, values: number[]): Uint8Array {
239
271
  if (valueIdx >= values.length) {
240
272
  throw new Error("Insufficient values provided for structPack");
241
273
  }
242
- view.setInt32(offset, values[valueIdx++], littleEndian);
274
+ view.setInt32(offset, values[valueIdx++] as number, littleEndian);
243
275
  offset += 4;
244
276
  break;
245
277
  case "I": // Unsigned 32-bit integer
@@ -247,34 +279,66 @@ export function structPack(format: string, values: number[]): Uint8Array {
247
279
  if (valueIdx >= values.length) {
248
280
  throw new Error("Insufficient values provided for structPack");
249
281
  }
250
- view.setUint32(offset, values[valueIdx++], littleEndian);
282
+ view.setUint32(offset, values[valueIdx++] as number, littleEndian);
251
283
  offset += 4;
252
284
  break;
253
- // case "q": // Signed 64-bit integer
254
- // view.setBigInt64(offset, BigInt(values[valueIdx++]), littleEndian);
255
- // offset += 8;
256
- // break;
257
- // case "Q": // Unsigned 64-bit integer
258
- // view.setBigUint64(offset, BigInt(values[valueIdx++]), littleEndian);
259
- // offset += 8;
260
- // break;
285
+ case "q": // Signed 64-bit integer
286
+ if (valueIdx >= values.length) {
287
+ throw new Error("Insufficient values provided for structPack");
288
+ }
289
+ view.setBigInt64(offset, BigInt(values[valueIdx++] as number), littleEndian);
290
+ offset += 8;
291
+ break;
292
+ case "Q": // Unsigned 64-bit integer
293
+ if (valueIdx >= values.length) {
294
+ throw new Error("Insufficient values provided for structPack");
295
+ }
296
+ view.setBigUint64(offset, BigInt(values[valueIdx++] as number), littleEndian);
297
+ offset += 8;
298
+ break;
261
299
  case "f": // 32-bit float
262
300
  if (valueIdx >= values.length) {
263
301
  throw new Error("Insufficient values provided for structPack");
264
302
  }
265
- view.setFloat32(offset, values[valueIdx++], littleEndian);
303
+ view.setFloat32(offset, values[valueIdx++] as number, littleEndian);
266
304
  offset += 4;
267
305
  break;
268
306
  case "d": // 64-bit float
269
307
  if (valueIdx >= values.length) {
270
308
  throw new Error("Insufficient values provided for structPack");
271
309
  }
272
- view.setFloat64(offset, values[valueIdx++], littleEndian);
310
+ view.setFloat64(offset, values[valueIdx++] as number, littleEndian);
273
311
  offset += 8;
274
312
  break;
313
+ case "s": { // Byte string (repeat = byte length, consumes one value)
314
+ if (valueIdx >= values.length) {
315
+ throw new Error("Insufficient values provided for structPack");
316
+ }
317
+ const val = values[valueIdx++];
318
+ let bytes: Uint8Array;
319
+ if (val instanceof Uint8Array) {
320
+ bytes = val;
321
+ } else if (typeof val === "string") {
322
+ bytes = new TextEncoder().encode(val);
323
+ } else {
324
+ throw new Error(`Expected string or Uint8Array for 's' format, got number`);
325
+ }
326
+ // Copy bytes, zero-pad if shorter than repeat
327
+ const copyLen = Math.min(bytes.length, repeat);
328
+ for (let j = 0; j < copyLen; j++) {
329
+ view.setUint8(offset + j, bytes[j]);
330
+ }
331
+ for (let j = copyLen; j < repeat; j++) {
332
+ view.setUint8(offset + j, 0);
333
+ }
334
+ offset += repeat;
335
+ break;
336
+ }
275
337
  default:
276
338
  throw new Error(`Unknown format character: ${code}`);
277
339
  }
340
+ // For 's', the repeat is consumed as byte-length in one go
341
+ if (code === "s") break;
278
342
  }
279
343
  }
280
344
 
@@ -20,6 +20,9 @@ import {
20
20
  RaftFileList,
21
21
  RaftFriendlyName,
22
22
  RaftOKFail,
23
+ RaftPubTopicRec,
24
+ RaftPubTopicsResponse,
25
+ RaftSubscriptionUpdateResponse,
23
26
  RaftSysModInfoBLEMan,
24
27
  RaftSystemInfo,
25
28
  RaftWifiScanResults,
@@ -40,6 +43,10 @@ export default class RaftSystemUtils {
40
43
  private _defaultWiFiHostname = "Raft";
41
44
  private _maxSecsToWaitForWiFiConn = 20;
42
45
 
46
+ // Publish topic index/name lookup tables (session scoped)
47
+ private _pubTopicIdxToName: { [idx: number]: string } = {};
48
+ private _pubTopicNameToIdx: { [name: string]: number } = {};
49
+
43
50
  /**
44
51
  * constructor
45
52
  * @param raftMsgHandler
@@ -56,6 +63,58 @@ export default class RaftSystemUtils {
56
63
  return this._msgHandler;
57
64
  }
58
65
 
66
+ /**
67
+ * Update publish topic maps from topic records.
68
+ */
69
+ updatePublishTopicMap(topicRecs: Array<RaftPubTopicRec> | undefined): void {
70
+ if (!topicRecs || !Array.isArray(topicRecs)) {
71
+ return;
72
+ }
73
+ for (const topicRec of topicRecs) {
74
+ if (!topicRec || typeof topicRec.name !== "string" || typeof topicRec.idx !== "number") {
75
+ continue;
76
+ }
77
+ this._pubTopicIdxToName[topicRec.idx] = topicRec.name;
78
+ this._pubTopicNameToIdx[topicRec.name] = topicRec.idx;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Update publish topic maps from subscription response.
84
+ */
85
+ updatePublishTopicMapFromSubscriptionResponse(resp: RaftSubscriptionUpdateResponse | RaftOKFail | null | undefined): void {
86
+ if (!resp || typeof resp !== "object") {
87
+ return;
88
+ }
89
+ if ("topics" in resp) {
90
+ this.updatePublishTopicMap((resp as RaftSubscriptionUpdateResponse).topics);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Fetch publish topic map from firmware endpoint.
96
+ */
97
+ async refreshPublishTopicMap(): Promise<boolean> {
98
+ try {
99
+ const pubTopicsResp = await this._msgHandler.sendRICRESTURL<RaftPubTopicsResponse>("pubtopics");
100
+ if (pubTopicsResp && pubTopicsResp.rslt === "ok") {
101
+ this.updatePublishTopicMap(pubTopicsResp.topics);
102
+ return true;
103
+ }
104
+ } catch (error) {
105
+ RaftLog.debug(`refreshPublishTopicMap failed ${error}`);
106
+ }
107
+ return false;
108
+ }
109
+
110
+ getPublishTopicName(topicIndex: number): string | undefined {
111
+ return this._pubTopicIdxToName[topicIndex];
112
+ }
113
+
114
+ getPublishTopicIndex(topicName: string): number | undefined {
115
+ return this._pubTopicNameToIdx[topicName];
116
+ }
117
+
59
118
  /**
60
119
  * setDefaultWiFiHostname
61
120
  * @param defaultWiFiHostname
package/src/RaftTypes.ts CHANGED
@@ -72,6 +72,31 @@ export class RaftOKFail {
72
72
  rslt = 'failComms';
73
73
  }
74
74
 
75
+ export type RaftPubTopicRec = {
76
+ name: string;
77
+ idx: number;
78
+ };
79
+
80
+ export type RaftSubscriptionUpdateResponse = RaftOKFail & {
81
+ topics?: Array<RaftPubTopicRec>;
82
+ };
83
+
84
+ export type RaftPubTopicsResponse = RaftOKFail & {
85
+ topics?: Array<RaftPubTopicRec>;
86
+ };
87
+
88
+ export type RaftPublishFrameType = 'json' | 'binary' | 'unknown';
89
+
90
+ export type RaftPublishFrameMeta = {
91
+ frameType: RaftPublishFrameType;
92
+ topicIndex?: number;
93
+ topicName?: string;
94
+ version?: number;
95
+ binaryHasEnvelope?: boolean;
96
+ binaryPayloadOffset?: number;
97
+ jsonString?: string;
98
+ };
99
+
75
100
  export type RaftReportMsg = {
76
101
  msgType?: string;
77
102
  rslt?: string;
@@ -181,6 +206,8 @@ export class RaftSysModInfoBLEMan {
181
206
 
182
207
  export type RaftProgressCBType = (received: number, total: number) => void;
183
208
 
209
+ export type RaftStreamDataProgressCBType = (sent: number, total: number, progress: number) => void;
210
+
184
211
  export class RaftFileDownloadResult {
185
212
  fileData: Uint8Array | null = null;
186
213
  downloadedOk = false;
package/src/main.ts CHANGED
@@ -29,6 +29,7 @@ export { default as RaftDeviceMgrIF } from './RaftDeviceMgrIF';
29
29
  export { DeviceManager as RaftDeviceManager } from './RaftDeviceManager';
30
30
  export type { DeviceDecodedData } from './RaftDeviceManager';
31
31
 
32
+ export * from './RaftPublish';
32
33
  export * from './RaftTypes';
33
34
  export * from './RaftSystemType';
34
35
  export * from './RaftWifiTypes';