@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
@@ -15,6 +15,7 @@ import { RaftOKFail, RaftStreamStartResp, RaftStreamType } from './RaftTypes';
15
15
  import RaftConnector from './RaftConnector';
16
16
  import { RaftConnEvent } from './RaftConnEvents';
17
17
  import { RICRESTElemCode } from './RaftProtocolDefs'
18
+ import RaftUtils from './RaftUtils';
18
19
 
19
20
  export default class RaftStreamHandler {
20
21
 
@@ -49,7 +50,7 @@ export default class RaftStreamHandler {
49
50
 
50
51
  private _isStreaming = false;
51
52
  private _isPaused = false;
52
- private _streamBuffer = new Uint8Array();
53
+ private _streamBuffer: Uint8Array<ArrayBuffer> = new Uint8Array();
53
54
  private _audioDuration = 0;
54
55
  private _audioByteRate = 0;
55
56
  private _streamPos = 0;
@@ -67,11 +68,11 @@ export default class RaftStreamHandler {
67
68
  this.onSoktoMsg = this.onSoktoMsg.bind(this);
68
69
  }
69
70
 
70
- setNumBlocksWithoutPause(numBlocks: number){
71
+ setNumBlocksWithoutPause(numBlocks: number) {
71
72
  this._numBlocksWithoutPause = numBlocks;
72
73
  }
73
74
 
74
- setLegacySoktoMode(legacyMode: boolean){
75
+ setLegacySoktoMode(legacyMode: boolean) {
75
76
  RaftLog.debug(`Setting legacy sokto mode to ${legacyMode}`);
76
77
  this._legacySoktoMode = legacyMode;
77
78
  }
@@ -83,7 +84,7 @@ export default class RaftStreamHandler {
83
84
 
84
85
  // TODO - if clearExisting is not set, form a queue
85
86
  if (this._streamIsStarting || this._lastStreamStartTime > (Date.now() - 500)) {
86
- RaftLog.error(`Unable to start sound, too soon since last request`);
87
+ RaftLog.warn(`Unable to start sound, too soon since last request`);
87
88
  return;
88
89
  }
89
90
 
@@ -94,9 +95,9 @@ export default class RaftStreamHandler {
94
95
  this._soktoReceived = false;
95
96
  this._soktoPos = 0;
96
97
  this._streamPos = 0;
97
- this._streamBuffer = streamContents;
98
+ this._streamBuffer = RaftUtils.toArrayBufferView(streamContents);
98
99
  this._audioDuration = audioDuration;
99
- this._audioByteRate = (streamContents.length / audioDuration)*1000;
100
+ this._audioByteRate = (streamContents.length / audioDuration) * 1000;
100
101
 
101
102
  this.clearFinishPointTimeout();
102
103
 
@@ -104,12 +105,12 @@ export default class RaftStreamHandler {
104
105
  (result: boolean) => {
105
106
  this._isPaused = false;
106
107
  this._streamIsStarting = false;
107
- if (!result){
108
- RaftLog.error(`Unable to start stream. ufStart message send failed`);
108
+ if (!result) {
109
+ RaftLog.warn(`Unable to start stream. ufStart message send failed`);
109
110
  return;
110
111
  }
111
112
  //this.streamingPerformanceChecker();
112
- if (!this._isStreaming){
113
+ if (!this._isStreaming) {
113
114
  this._isStreaming = true;
114
115
  this._sendStreamBuffer();
115
116
  }
@@ -127,7 +128,7 @@ export default class RaftStreamHandler {
127
128
  return this._streamIsStarting;
128
129
  }
129
130
 
130
-
131
+
131
132
  clearFinishPointTimeout() {
132
133
  if (this.soundFinishPoint) {
133
134
  clearTimeout(this.soundFinishPoint);
@@ -172,7 +173,7 @@ export default class RaftStreamHandler {
172
173
  RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
173
174
  );
174
175
  } catch (err) {
175
- RaftLog.error(`sendStreamStartMsg error ${err}`);
176
+ RaftLog.warn(`sendStreamStartMsg error ${err}`);
176
177
  return false;
177
178
  }
178
179
 
@@ -191,11 +192,11 @@ export default class RaftStreamHandler {
191
192
  return true;
192
193
  }
193
194
 
194
- get maxBlockSize () {
195
+ get maxBlockSize() {
195
196
  return this._maxBlockSize;
196
197
  }
197
198
 
198
- set maxBlockSize (maxBlockSize: number) {
199
+ set maxBlockSize(maxBlockSize: number) {
199
200
  this._maxBlockSize = maxBlockSize;
200
201
  this.DEFAULT_MAX_BLOCK_SIZE = maxBlockSize;
201
202
  }
@@ -220,43 +221,43 @@ export default class RaftStreamHandler {
220
221
  RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
221
222
  );
222
223
  } catch (err) {
223
- RaftLog.error(`sendStreamEndMsg error ${err}`);
224
+ RaftLog.warn(`sendStreamEndMsg error ${err}`);
224
225
  return false;
225
226
  }
226
227
  return streamEndResp.rslt === 'ok';
227
228
  }
228
229
 
229
- /*
230
- private async _sendAudioStopMsg(): Promise<RaftOKFail> {
231
- const cmdMsg = `{"cmdName":"audio/stop"}`;
232
-
233
- // Debug
234
- RaftLog.debug(`sendAudioStopMsg ${cmdMsg}`);
235
-
236
- // Send
237
- return this._msgHandler.sendRICREST<RaftOKFail>(
238
- cmdMsg,
239
- RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
240
- );
241
- }
242
-
243
-
244
- private async _sendStreamCancelMsg(): Promise<RaftOKFail> {
245
- // File cancel command message
246
- const cmdMsg = `{"cmdName":"ufCancel","reqStr":"ufCancel","streamID":${this._streamID}}`;
247
-
248
- // Debug
249
- RaftLog.debug(`sendStreamCancelMsg ${cmdMsg}`);
250
-
251
- // Send
252
- return this._msgHandler.sendRICREST<RaftOKFail>(
253
- cmdMsg,
254
- RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
255
- );
256
- }
257
- */
230
+ /*
231
+ private async _sendAudioStopMsg(): Promise<RaftOKFail> {
232
+ const cmdMsg = `{"cmdName":"audio/stop"}`;
233
+
234
+ // Debug
235
+ RaftLog.debug(`sendAudioStopMsg ${cmdMsg}`);
236
+
237
+ // Send
238
+ return this._msgHandler.sendRICREST<RaftOKFail>(
239
+ cmdMsg,
240
+ RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
241
+ );
242
+ }
243
+
244
+
245
+ private async _sendStreamCancelMsg(): Promise<RaftOKFail> {
246
+ // File cancel command message
247
+ const cmdMsg = `{"cmdName":"ufCancel","reqStr":"ufCancel","streamID":${this._streamID}}`;
248
+
249
+ // Debug
250
+ RaftLog.debug(`sendStreamCancelMsg ${cmdMsg}`);
251
+
252
+ // Send
253
+ return this._msgHandler.sendRICREST<RaftOKFail>(
254
+ cmdMsg,
255
+ RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
256
+ );
257
+ }
258
+ */
258
259
 
259
- private async _sendStreamBuffer(): Promise<boolean> {
260
+ private async _sendStreamBuffer(): Promise<boolean> {
260
261
  const streamStartTime = Date.now();
261
262
 
262
263
  // Check streamID is valid
@@ -267,7 +268,7 @@ private async _sendStreamBuffer(): Promise<boolean> {
267
268
  let blockNum = 0;
268
269
  // Send stream blocks
269
270
  while (this._soktoPos < this._streamBuffer.length || this._isPaused) {
270
- if (this._isPaused){
271
+ if (this._isPaused) {
271
272
  await new Promise((resolve) => setTimeout(resolve, 5));
272
273
  continue;
273
274
  }
@@ -301,8 +302,8 @@ private async _sendStreamBuffer(): Promise<boolean> {
301
302
  this._streamPos += blockSize;
302
303
  blockNum += 1;
303
304
 
304
- if (this._audioByteRate && blockNum > this._numBlocksWithoutPause){
305
- const pauseTime = ((blockSize / this._audioByteRate)*1000) - 10;
305
+ if (this._audioByteRate && blockNum > this._numBlocksWithoutPause) {
306
+ const pauseTime = ((blockSize / this._audioByteRate) * 1000) - 10;
306
307
  RaftLog.verbose(`Pausing for ${pauseTime} ms between audio packets. Bit rate ${this._audioByteRate * 8}`)
307
308
  await new Promise((resolve) => setTimeout(resolve, pauseTime));
308
309
  }
package/src/RaftStruct.ts CHANGED
@@ -8,77 +8,124 @@
8
8
  //
9
9
  /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
10
10
 
11
+ type FormatInstruction =
12
+ | { kind: "endian"; littleEndian: boolean }
13
+ | { kind: "spec"; code: string; repeat: number };
14
+
15
+ function parseFormatInstructions(format: string): FormatInstruction[] {
16
+ const instructions: FormatInstruction[] = [];
17
+ let idx = 0;
18
+
19
+ while (idx < format.length) {
20
+
21
+ const char = format[idx];
22
+
23
+ // Endianness specifiers
24
+ if (char === "<" || char === ">") {
25
+ instructions.push({ kind: "endian", littleEndian: char === "<" });
26
+ idx++;
27
+ continue;
28
+ }
29
+
30
+ // Ignore whitespace
31
+ if (/\s/.test(char)) {
32
+ idx++;
33
+ continue;
34
+ }
35
+
36
+ // Attribute code
37
+ const code = char;
38
+ idx++;
39
+
40
+ // Check for repeat count using [N] syntax
41
+ let repeat = 1;
42
+ if (idx < format.length && format[idx] === "[") {
43
+ const endIdx = format.indexOf("]", idx + 1);
44
+ if (endIdx === -1) {
45
+ throw new Error(`Invalid format string: missing closing ] in "${format}"`);
46
+ }
47
+ const repeatStr = format.slice(idx + 1, endIdx);
48
+ repeat = parseInt(repeatStr, 10);
49
+ if (!Number.isFinite(repeat) || repeat <= 0) {
50
+ throw new Error(`Invalid repeat count "${repeatStr}" in format string "${format}"`);
51
+ }
52
+ idx = endIdx + 1;
53
+ }
54
+
55
+ instructions.push({ kind: "spec", code, repeat });
56
+ }
57
+
58
+ return instructions;
59
+ }
60
+
11
61
  export function structUnpack(format: string, data: Uint8Array): number[] {
12
62
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
13
63
  const results: number[] = [];
14
64
  let offset = 0;
15
65
  let littleEndian = false;
16
66
 
17
- for (const char of format) {
18
- switch (char) {
19
- case "<":
20
- littleEndian = true;
21
- break;
22
- case ">":
23
- littleEndian = false;
24
- break;
25
- case "x": // Padding byte
26
- offset += 1;
27
- break;
28
- case "c": // Char
29
- results.push(view.getUint8(offset));
30
- offset += 1;
31
- break;
32
- case "b": // Signed 8-bit integer
33
- results.push(view.getInt8(offset));
34
- offset += 1;
35
- break;
36
- case "B": // Unsigned 8-bit integer
37
- results.push(view.getUint8(offset));
38
- offset += 1;
39
- break;
40
- case "h": // Signed 16-bit integer
41
- results.push(view.getInt16(offset, littleEndian));
42
- offset += 2;
43
- break;
44
- case "H": // Unsigned 16-bit integer (big-endian)
45
- results.push(view.getUint16(offset, littleEndian));
46
- offset += 2;
47
- break;
48
- case "i": // Signed 32-bit integer (big-endian)
49
- results.push(view.getInt32(offset, littleEndian));
50
- offset += 4;
51
- break;
52
- case "I": // Unsigned 32-bit integer (big-endian)
53
- results.push(view.getUint32(offset, littleEndian));
54
- offset += 4;
55
- break;
56
- case "l": // Signed 32-bit integer (big-endian)
57
- results.push(view.getInt32(offset, littleEndian));
58
- offset += 4;
59
- break;
60
- case "L": // Unsigned 32-bit integer (big-endian)
61
- results.push(view.getUint32(offset, littleEndian));
62
- offset += 4;
63
- break;
64
- // case "q": // Signed 64-bit integer (big-endian)
65
- // results.push(view.getBigInt64(offset, littleEndian));
66
- // offset += 8;
67
- // break;
68
- // case "Q": // Unsigned 64-bit integer (big-endian)
69
- // results.push(view.getBigUint64(offset, littleEndian));
70
- // offset += 8;
71
- // break;
72
- case "f": // 32-bit float (big-endian)
73
- results.push(view.getFloat32(offset, littleEndian));
74
- offset += 4;
75
- break;
76
- case "d": // 64-bit float (big-endian)
77
- results.push(view.getFloat64(offset, littleEndian));
78
- offset += 8;
79
- break;
80
- default:
81
- throw new Error(`Unknown format character: ${char}`);
67
+ const instructions = parseFormatInstructions(format);
68
+
69
+ for (const instruction of instructions) {
70
+ if (instruction.kind === "endian") {
71
+ littleEndian = instruction.littleEndian;
72
+ continue;
73
+ }
74
+
75
+ const { code, repeat } = instruction;
76
+
77
+ for (let count = 0; count < repeat; count++) {
78
+ switch (code) {
79
+ case "x": // Padding byte
80
+ offset += 1;
81
+ break;
82
+ case "c": // Char
83
+ case "B": // Unsigned 8-bit integer
84
+ case "?": // Boolean (stored as uint8)
85
+ results.push(view.getUint8(offset));
86
+ offset += 1;
87
+ break;
88
+ case "b": // Signed 8-bit integer
89
+ results.push(view.getInt8(offset));
90
+ offset += 1;
91
+ break;
92
+ case "h": // Signed 16-bit integer
93
+ results.push(view.getInt16(offset, littleEndian));
94
+ offset += 2;
95
+ break;
96
+ case "H": // Unsigned 16-bit integer
97
+ results.push(view.getUint16(offset, littleEndian));
98
+ offset += 2;
99
+ break;
100
+ case "i": // Signed 32-bit integer
101
+ case "l": // Signed 32-bit integer
102
+ results.push(view.getInt32(offset, littleEndian));
103
+ offset += 4;
104
+ break;
105
+ case "I": // Unsigned 32-bit integer
106
+ case "L": // Unsigned 32-bit integer
107
+ results.push(view.getUint32(offset, littleEndian));
108
+ offset += 4;
109
+ 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;
118
+ case "f": // 32-bit float
119
+ results.push(view.getFloat32(offset, littleEndian));
120
+ offset += 4;
121
+ break;
122
+ case "d": // 64-bit float
123
+ results.push(view.getFloat64(offset, littleEndian));
124
+ offset += 8;
125
+ break;
126
+ default:
127
+ throw new Error(`Unknown format character: ${code}`);
128
+ }
82
129
  }
83
130
  }
84
131
 
@@ -87,42 +134,49 @@ export function structUnpack(format: string, data: Uint8Array): number[] {
87
134
 
88
135
  export function structSizeOf(format: string): number {
89
136
  let size = 0;
90
- for (const char of format) {
91
- switch (char) {
92
- case "<":
93
- case ">":
94
- break;
137
+ const instructions = parseFormatInstructions(format);
138
+
139
+ for (const instruction of instructions) {
140
+ if (instruction.kind === "endian") {
141
+ continue;
142
+ }
143
+
144
+ const { code, repeat } = instruction;
145
+ let unitSize: number;
146
+
147
+ switch (code) {
95
148
  case "x": // Padding byte
96
- size += 1;
97
- break;
98
149
  case "c": // Char
99
150
  case "b": // Signed 8-bit integer
100
151
  case "B": // Unsigned 8-bit integer
101
- size += 1;
152
+ case "?": // Boolean (uint8)
153
+ unitSize = 1;
102
154
  break;
103
155
  case "h": // Signed 16-bit integer
104
156
  case "H": // Unsigned 16-bit integer
105
- size += 2;
157
+ unitSize = 2;
106
158
  break;
107
159
  case "i": // Signed 32-bit integer
108
160
  case "I": // Unsigned 32-bit integer
109
161
  case "l": // Signed 32-bit integer
110
162
  case "L": // Unsigned 32-bit integer
111
- size += 4;
163
+ unitSize = 4;
112
164
  break;
113
165
  // case "q": // Signed 64-bit integer
114
166
  // case "Q": // Unsigned 64-bit integer
115
- // size += 8;
167
+ // unitSize = 8;
116
168
  // break;
117
169
  case "f": // 32-bit float
118
- size += 4;
170
+ unitSize = 4;
119
171
  break;
120
172
  case "d": // 64-bit float
121
- size += 8;
173
+ unitSize = 8;
122
174
  break;
123
175
  default:
124
- throw new Error(`Unknown format character: ${char}`);
176
+ throw new Error(`Unknown format character: ${code}`);
125
177
  }
178
+
179
+ size += unitSize * repeat;
126
180
  }
127
181
  return size;
128
182
  }
@@ -134,74 +188,93 @@ export function structPack(format: string, values: number[]): Uint8Array {
134
188
  let offset = 0;
135
189
  let littleEndian = false;
136
190
 
137
- let valIdx = 0;
138
- for (let i = 0; i < format.length; i++) {
139
- const char = format[i];
140
- const value = values[valIdx];
141
- switch (char) {
142
- case "<":
143
- littleEndian = true;
144
- break;
145
- case ">":
146
- littleEndian = false;
147
- break;
148
- case "x": // Padding byte
149
- offset += 1;
150
- break;
151
- case "c": // Char
152
- view.setInt8(offset, value);
153
- offset += 1;
154
- break;
155
- case "b": // Signed 8-bit integer
156
- view.setInt8(offset, value);
157
- offset += 1;
158
- break;
159
- case "B": // Unsigned 8-bit integer
160
- view.setUint8(offset, value);
161
- offset += 1;
162
- break;
163
- case "h": // Signed 16-bit integer
164
- view.setInt16(offset, value, littleEndian);
165
- offset += 2;
166
- break;
167
- case "H": // Unsigned 16-bit integer
168
- view.setUint16(offset, value, littleEndian);
169
- offset += 2;
170
- break;
171
- case "i": // Signed 32-bit integer
172
- view.setInt32(offset, value, littleEndian);
173
- offset += 4;
174
- break;
175
- case "I": // Unsigned 32-bit integer
176
- view.setUint32(offset, value, littleEndian);
177
- offset += 4;
178
- break;
179
- case "l": // Signed 32-bit integer
180
- view.setInt32(offset, value, littleEndian);
181
- offset += 4;
182
- break;
183
- case "L": // Unsigned 32-bit integer
184
- view.setUint32(offset, value, littleEndian);
185
- offset += 4;
186
- break;
187
- // case "q": // Signed 64-bit integer
188
- // view.setBigInt64(offset, BigInt(value), littleEndian);
189
- // offset += 8;
190
- // break;
191
- // case "Q": // Unsigned 64-bit integer
192
- // view.setBigUint64(offset, BigInt(value), littleEndian);
193
- // offset += 8;
194
- // break;
195
- case "f": // 32-bit float
196
- view.setFloat32(offset, value, littleEndian);
197
- offset += 4;
198
- break;
199
- case "d": // 64-bit float
200
- view.setFloat64(offset, value, littleEndian);
201
- offset += 8;
202
- break;
203
- default:
204
- throw new Error(`Unknown format character: ${char}`);
191
+ const instructions = parseFormatInstructions(format);
192
+ let valueIdx = 0;
193
+
194
+ for (const instruction of instructions) {
195
+ if (instruction.kind === "endian") {
196
+ littleEndian = instruction.littleEndian;
197
+ continue;
198
+ }
199
+
200
+ const { code, repeat } = instruction;
201
+
202
+ for (let count = 0; count < repeat; count++) {
203
+ switch (code) {
204
+ case "x": // Padding byte
205
+ offset += 1;
206
+ break;
207
+ case "c": // Char
208
+ case "b": // Signed 8-bit integer
209
+ if (valueIdx >= values.length) {
210
+ throw new Error("Insufficient values provided for structPack");
211
+ }
212
+ view.setInt8(offset, values[valueIdx++]);
213
+ offset += 1;
214
+ break;
215
+ case "B": // Unsigned 8-bit integer
216
+ case "?": // Boolean (uint8)
217
+ if (valueIdx >= values.length) {
218
+ throw new Error("Insufficient values provided for structPack");
219
+ }
220
+ view.setUint8(offset, values[valueIdx++]);
221
+ offset += 1;
222
+ break;
223
+ case "h": // Signed 16-bit integer
224
+ if (valueIdx >= values.length) {
225
+ throw new Error("Insufficient values provided for structPack");
226
+ }
227
+ view.setInt16(offset, values[valueIdx++], littleEndian);
228
+ offset += 2;
229
+ break;
230
+ case "H": // Unsigned 16-bit integer
231
+ if (valueIdx >= values.length) {
232
+ throw new Error("Insufficient values provided for structPack");
233
+ }
234
+ view.setUint16(offset, values[valueIdx++], littleEndian);
235
+ offset += 2;
236
+ break;
237
+ case "i": // Signed 32-bit integer
238
+ case "l": // Signed 32-bit integer
239
+ if (valueIdx >= values.length) {
240
+ throw new Error("Insufficient values provided for structPack");
241
+ }
242
+ view.setInt32(offset, values[valueIdx++], littleEndian);
243
+ offset += 4;
244
+ break;
245
+ case "I": // Unsigned 32-bit integer
246
+ case "L": // Unsigned 32-bit integer
247
+ if (valueIdx >= values.length) {
248
+ throw new Error("Insufficient values provided for structPack");
249
+ }
250
+ view.setUint32(offset, values[valueIdx++], littleEndian);
251
+ offset += 4;
252
+ 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;
261
+ case "f": // 32-bit float
262
+ if (valueIdx >= values.length) {
263
+ throw new Error("Insufficient values provided for structPack");
264
+ }
265
+ view.setFloat32(offset, values[valueIdx++], littleEndian);
266
+ offset += 4;
267
+ break;
268
+ case "d": // 64-bit float
269
+ if (valueIdx >= values.length) {
270
+ throw new Error("Insufficient values provided for structPack");
271
+ }
272
+ view.setFloat64(offset, values[valueIdx++], littleEndian);
273
+ offset += 8;
274
+ break;
275
+ default:
276
+ throw new Error(`Unknown format character: ${code}`);
277
+ }
205
278
  }
206
279
  }
207
280
 
@@ -53,8 +53,35 @@ export default class RaftSysTypeManager {
53
53
  const serviceUUIDs = new Set<string>();
54
54
  this._sysTypes.forEach((factory) => {
55
55
  const sysType = factory();
56
+ if (!sysType.BLEServiceUUIDs) {
57
+ return;
58
+ }
56
59
  sysType.BLEServiceUUIDs.forEach((uuid) => serviceUUIDs.add(uuid));
57
60
  });
58
61
  return Array.from(serviceUUIDs);
59
62
  }
63
+
64
+ // Get a list of all device name prefixes to filter on
65
+ getAllNamePrefixes(): string[] {
66
+ const deviceNames = new Set<string>();
67
+ this._sysTypes.forEach((factory) => {
68
+ const sysType = factory();
69
+ if (!sysType.BLEDeviceNames) {
70
+ return;
71
+ }
72
+ sysType.BLEDeviceNames.forEach((name) => deviceNames.add(name));
73
+ });
74
+ return Array.from(deviceNames);
75
+ }
76
+
77
+ // Find the system type for a given BLE device name prefix
78
+ getSystemTypeByBLENamePrefix(name: string): RaftSystemType | null {
79
+ for (const factory of this._sysTypes.values()) {
80
+ const sysType = factory();
81
+ if (sysType.BLEDeviceNames && sysType.BLEDeviceNames.some((prefix) => name.startsWith(prefix))) {
82
+ return sysType;
83
+ }
84
+ }
85
+ return null;
86
+ }
60
87
  }