@robdobsn/raftjs 1.8.5 → 1.11.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/.editorconfig +14 -0
  2. package/.gitattributes +11 -0
  3. package/.nvmrc +1 -0
  4. package/TODO.md +1 -0
  5. package/dist/react-native/RaftAttributeHandler.d.ts +14 -0
  6. package/dist/react-native/RaftAttributeHandler.js +375 -0
  7. package/dist/react-native/RaftAttributeHandler.js.map +1 -0
  8. package/dist/react-native/RaftChannel.d.ts +20 -0
  9. package/dist/react-native/RaftChannel.js +12 -0
  10. package/dist/react-native/RaftChannel.js.map +1 -0
  11. package/dist/react-native/RaftChannelBLE.native.d.ts +95 -0
  12. package/dist/react-native/RaftChannelBLE.native.js +483 -0
  13. package/dist/react-native/RaftChannelBLE.native.js.map +1 -0
  14. package/dist/react-native/RaftChannelBLE.web.d.ts +40 -0
  15. package/dist/react-native/RaftChannelBLE.web.js +302 -0
  16. package/dist/react-native/RaftChannelBLE.web.js.map +1 -0
  17. package/dist/react-native/RaftChannelBLEFactory.d.ts +10 -0
  18. package/dist/react-native/RaftChannelBLEFactory.js +17 -0
  19. package/dist/react-native/RaftChannelBLEFactory.js.map +1 -0
  20. package/dist/react-native/RaftChannelBLEScanner.native.d.ts +18 -0
  21. package/dist/react-native/RaftChannelBLEScanner.native.js +138 -0
  22. package/dist/react-native/RaftChannelBLEScanner.native.js.map +1 -0
  23. package/dist/react-native/RaftChannelSimulated.d.ts +42 -0
  24. package/dist/react-native/RaftChannelSimulated.js +1001 -0
  25. package/dist/react-native/RaftChannelSimulated.js.map +1 -0
  26. package/dist/react-native/RaftChannelWebSerial.d.ts +39 -0
  27. package/dist/react-native/RaftChannelWebSerial.js +329 -0
  28. package/dist/react-native/RaftChannelWebSerial.js.map +1 -0
  29. package/dist/react-native/RaftChannelWebSocket.d.ts +30 -0
  30. package/dist/react-native/RaftChannelWebSocket.js +222 -0
  31. package/dist/react-native/RaftChannelWebSocket.js.map +1 -0
  32. package/dist/react-native/RaftCommsStats.d.ts +39 -0
  33. package/dist/react-native/RaftCommsStats.js +128 -0
  34. package/dist/react-native/RaftCommsStats.js.map +1 -0
  35. package/dist/react-native/RaftConnEvents.d.ts +39 -0
  36. package/dist/react-native/RaftConnEvents.js +54 -0
  37. package/dist/react-native/RaftConnEvents.js.map +1 -0
  38. package/dist/react-native/RaftConnector.d.ts +257 -0
  39. package/dist/react-native/RaftConnector.js +671 -0
  40. package/dist/react-native/RaftConnector.js.map +1 -0
  41. package/dist/react-native/RaftCustomAttrHandler.d.ts +6 -0
  42. package/dist/react-native/RaftCustomAttrHandler.js +93 -0
  43. package/dist/react-native/RaftCustomAttrHandler.js.map +1 -0
  44. package/dist/react-native/RaftDeviceInfo.d.ts +71 -0
  45. package/dist/react-native/RaftDeviceInfo.js +50 -0
  46. package/dist/react-native/RaftDeviceInfo.js.map +1 -0
  47. package/dist/react-native/RaftDeviceManager.d.ts +73 -0
  48. package/dist/react-native/RaftDeviceManager.js +812 -0
  49. package/dist/react-native/RaftDeviceManager.js.map +1 -0
  50. package/dist/react-native/RaftDeviceMgrIF.d.ts +19 -0
  51. package/dist/react-native/RaftDeviceMgrIF.js +11 -0
  52. package/dist/react-native/RaftDeviceMgrIF.js.map +1 -0
  53. package/dist/react-native/RaftDeviceMsg.d.ts +9 -0
  54. package/dist/react-native/RaftDeviceMsg.js +11 -0
  55. package/dist/react-native/RaftDeviceMsg.js.map +1 -0
  56. package/dist/react-native/RaftDeviceStates.d.ts +55 -0
  57. package/dist/react-native/RaftDeviceStates.js +81 -0
  58. package/dist/react-native/RaftDeviceStates.js.map +1 -0
  59. package/dist/react-native/RaftFileHandler.d.ts +52 -0
  60. package/dist/react-native/RaftFileHandler.js +502 -0
  61. package/dist/react-native/RaftFileHandler.js.map +1 -0
  62. package/dist/react-native/RaftLog.d.ts +22 -0
  63. package/dist/react-native/RaftLog.js +63 -0
  64. package/dist/react-native/RaftLog.js.map +1 -0
  65. package/dist/react-native/RaftMiniHDLC.d.ts +18 -0
  66. package/dist/react-native/RaftMiniHDLC.js +383 -0
  67. package/dist/react-native/RaftMiniHDLC.js.map +1 -0
  68. package/dist/react-native/RaftMsgHandler.d.ts +62 -0
  69. package/dist/react-native/RaftMsgHandler.js +511 -0
  70. package/dist/react-native/RaftMsgHandler.js.map +1 -0
  71. package/dist/react-native/RaftMsgTrackInfo.d.ts +17 -0
  72. package/dist/react-native/RaftMsgTrackInfo.js +42 -0
  73. package/dist/react-native/RaftMsgTrackInfo.js.map +1 -0
  74. package/dist/react-native/RaftProtocolDefs.d.ts +30 -0
  75. package/dist/react-native/RaftProtocolDefs.js +48 -0
  76. package/dist/react-native/RaftProtocolDefs.js.map +1 -0
  77. package/dist/react-native/RaftPublish.d.ts +2 -0
  78. package/dist/react-native/RaftPublish.js +81 -0
  79. package/dist/react-native/RaftPublish.js.map +1 -0
  80. package/dist/react-native/RaftStreamHandler.d.ts +49 -0
  81. package/dist/react-native/RaftStreamHandler.js +324 -0
  82. package/dist/react-native/RaftStreamHandler.js.map +1 -0
  83. package/dist/react-native/RaftStruct.d.ts +3 -0
  84. package/dist/react-native/RaftStruct.js +258 -0
  85. package/dist/react-native/RaftStruct.js.map +1 -0
  86. package/dist/react-native/RaftSysTypeManager.d.ts +16 -0
  87. package/dist/react-native/RaftSysTypeManager.js +78 -0
  88. package/dist/react-native/RaftSysTypeManager.js.map +1 -0
  89. package/dist/react-native/RaftSystemType.d.ts +30 -0
  90. package/dist/react-native/RaftSystemType.js +3 -0
  91. package/dist/react-native/RaftSystemType.js.map +1 -0
  92. package/dist/react-native/RaftSystemUtils.d.ts +152 -0
  93. package/dist/react-native/RaftSystemUtils.js +463 -0
  94. package/dist/react-native/RaftSystemUtils.js.map +1 -0
  95. package/dist/react-native/RaftTypes.d.ts +216 -0
  96. package/dist/react-native/RaftTypes.js +153 -0
  97. package/dist/react-native/RaftTypes.js.map +1 -0
  98. package/dist/react-native/RaftUpdateEvents.d.ts +33 -0
  99. package/dist/react-native/RaftUpdateEvents.js +46 -0
  100. package/dist/react-native/RaftUpdateEvents.js.map +1 -0
  101. package/dist/react-native/RaftUpdateManager.d.ts +61 -0
  102. package/dist/react-native/RaftUpdateManager.js +621 -0
  103. package/dist/react-native/RaftUpdateManager.js.map +1 -0
  104. package/dist/react-native/RaftUtils.d.ts +128 -0
  105. package/dist/react-native/RaftUtils.js +487 -0
  106. package/dist/react-native/RaftUtils.js.map +1 -0
  107. package/dist/react-native/RaftWifiTypes.d.ts +23 -0
  108. package/dist/react-native/RaftWifiTypes.js +43 -0
  109. package/dist/react-native/RaftWifiTypes.js.map +1 -0
  110. package/dist/react-native/main.d.ts +27 -0
  111. package/dist/react-native/main.js +52 -0
  112. package/dist/react-native/main.js.map +1 -0
  113. package/dist/web/RaftAttributeHandler.js +1 -1
  114. package/dist/web/RaftAttributeHandler.js.map +1 -1
  115. package/dist/web/RaftChannelBLE.web.js +8 -6
  116. package/dist/web/RaftChannelBLE.web.js.map +1 -1
  117. package/dist/web/RaftChannelSimulated.d.ts +10 -0
  118. package/dist/web/RaftChannelSimulated.js +665 -82
  119. package/dist/web/RaftChannelSimulated.js.map +1 -1
  120. package/dist/web/RaftChannelWebSerial.js +2 -2
  121. package/dist/web/RaftChannelWebSerial.js.map +1 -1
  122. package/dist/web/RaftChannelWebSocket.js +16 -1
  123. package/dist/web/RaftChannelWebSocket.js.map +1 -1
  124. package/dist/web/RaftConnector.d.ts +12 -1
  125. package/dist/web/RaftConnector.js +45 -9
  126. package/dist/web/RaftConnector.js.map +1 -1
  127. package/dist/web/RaftCustomAttrHandler.d.ts +2 -0
  128. package/dist/web/RaftCustomAttrHandler.js +54 -26
  129. package/dist/web/RaftCustomAttrHandler.js.map +1 -1
  130. package/dist/web/RaftDeviceInfo.d.ts +3 -1
  131. package/dist/web/RaftDeviceInfo.js +17 -3
  132. package/dist/web/RaftDeviceInfo.js.map +1 -1
  133. package/dist/web/RaftDeviceManager.d.ts +32 -2
  134. package/dist/web/RaftDeviceManager.js +307 -74
  135. package/dist/web/RaftDeviceManager.js.map +1 -1
  136. package/dist/web/RaftDeviceMgrIF.d.ts +5 -1
  137. package/dist/web/RaftDeviceStates.d.ts +20 -2
  138. package/dist/web/RaftDeviceStates.js +25 -4
  139. package/dist/web/RaftDeviceStates.js.map +1 -1
  140. package/dist/web/RaftMsgHandler.js.map +1 -1
  141. package/dist/web/RaftPublish.d.ts +2 -0
  142. package/dist/web/RaftPublish.js +81 -0
  143. package/dist/web/RaftPublish.js.map +1 -0
  144. package/dist/web/RaftStreamHandler.d.ts +11 -0
  145. package/dist/web/RaftStreamHandler.js +68 -1
  146. package/dist/web/RaftStreamHandler.js.map +1 -1
  147. package/dist/web/RaftStruct.js +197 -147
  148. package/dist/web/RaftStruct.js.map +1 -1
  149. package/dist/web/RaftSystemUtils.d.ts +17 -1
  150. package/dist/web/RaftSystemUtils.js +51 -0
  151. package/dist/web/RaftSystemUtils.js.map +1 -1
  152. package/dist/web/RaftTypes.d.ts +21 -0
  153. package/dist/web/RaftTypes.js.map +1 -1
  154. package/dist/web/RaftUpdateManager.js +1 -1
  155. package/dist/web/RaftUpdateManager.js.map +1 -1
  156. package/dist/web/RaftUtils.d.ts +2 -0
  157. package/dist/web/RaftUtils.js +20 -0
  158. package/dist/web/RaftUtils.js.map +1 -1
  159. package/dist/web/main.d.ts +2 -0
  160. package/dist/web/main.js +1 -0
  161. package/dist/web/main.js.map +1 -1
  162. package/eslint.config.mjs +33 -0
  163. package/examples/dashboard/package.json +36 -0
  164. package/examples/dashboard/src/CommandPanel.tsx +147 -0
  165. package/examples/dashboard/src/ConnManager.ts +166 -0
  166. package/examples/dashboard/src/DeviceActionsForm.tsx +133 -0
  167. package/examples/dashboard/src/DeviceAttrsForm.tsx +49 -0
  168. package/examples/dashboard/src/DeviceLineChart.tsx +163 -0
  169. package/examples/dashboard/src/DevicePanel.tsx +247 -0
  170. package/examples/dashboard/src/DeviceStatsPanel.tsx +65 -0
  171. package/examples/dashboard/src/DevicesPanel.tsx +69 -0
  172. package/examples/dashboard/src/DispLedGrid.tsx +110 -0
  173. package/examples/dashboard/src/DispOneLed.tsx +20 -0
  174. package/examples/dashboard/src/LatencyTest.ts +130 -0
  175. package/examples/dashboard/src/LatencyTestPanel.tsx +92 -0
  176. package/examples/dashboard/src/Main.tsx +234 -0
  177. package/examples/dashboard/src/SettingsManager.ts +67 -0
  178. package/examples/dashboard/src/SettingsScreen.tsx +179 -0
  179. package/examples/dashboard/src/StatusPanel.tsx +71 -0
  180. package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +170 -0
  181. package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +125 -0
  182. package/examples/dashboard/src/SystemTypeGeneric/StateInfoGeneric.ts +38 -0
  183. package/examples/dashboard/src/SystemTypeGeneric/SystemTypeGeneric.ts +125 -0
  184. package/examples/dashboard/src/SystemTypeMarty/RICAddOn.ts +70 -0
  185. package/examples/dashboard/src/SystemTypeMarty/RICAddOnBase.ts +33 -0
  186. package/examples/dashboard/src/SystemTypeMarty/RICAddOnManager.ts +342 -0
  187. package/examples/dashboard/src/SystemTypeMarty/RICCommsStats.ts +170 -0
  188. package/examples/dashboard/src/SystemTypeMarty/RICHWElem.ts +123 -0
  189. package/examples/dashboard/src/SystemTypeMarty/RICLEDPatternChecker.ts +207 -0
  190. package/examples/dashboard/src/SystemTypeMarty/RICROSSerial.ts +464 -0
  191. package/examples/dashboard/src/SystemTypeMarty/RICServoFaultDetector.ts +146 -0
  192. package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +105 -0
  193. package/examples/dashboard/src/SystemTypeMarty/RICSystemUtils.ts +371 -0
  194. package/examples/dashboard/src/SystemTypeMarty/RICTypes.ts +20 -0
  195. package/examples/dashboard/src/SystemTypeMarty/SystemTypeMarty.ts +119 -0
  196. package/examples/dashboard/src/index.html +15 -0
  197. package/examples/dashboard/src/index.tsx +13 -0
  198. package/examples/dashboard/src/styles.css +570 -0
  199. package/examples/dashboard/tsconfig.json +18 -0
  200. package/jest.config.js +11 -0
  201. package/package.json +49 -52
  202. package/src/RaftAttributeHandler.ts +450 -0
  203. package/src/RaftChannel.ts +32 -0
  204. package/src/RaftChannelBLE.native.ts +617 -0
  205. package/src/RaftChannelBLE.web.ts +374 -0
  206. package/src/RaftChannelBLEFactory.ts +13 -0
  207. package/src/RaftChannelBLEScanner.native.ts +184 -0
  208. package/src/RaftChannelSimulated.ts +1177 -0
  209. package/src/RaftChannelWebSerial.ts +420 -0
  210. package/src/RaftChannelWebSocket.ts +272 -0
  211. package/src/RaftCommsStats.ts +142 -0
  212. package/src/RaftConnEvents.ts +58 -0
  213. package/src/RaftConnector.ts +806 -0
  214. package/src/RaftCustomAttrHandler.ts +117 -0
  215. package/src/RaftDeviceInfo.ts +125 -0
  216. package/src/RaftDeviceManager.ts +1014 -0
  217. package/src/RaftDeviceMgrIF.ts +37 -0
  218. package/src/RaftDeviceMsg.ts +20 -0
  219. package/src/RaftDeviceStates.ts +122 -0
  220. package/src/RaftFileHandler.ts +668 -0
  221. package/src/RaftLog.ts +70 -0
  222. package/src/RaftMiniHDLC.ts +396 -0
  223. package/src/RaftMsgHandler.ts +812 -0
  224. package/src/RaftMsgTrackInfo.ts +51 -0
  225. package/src/RaftProtocolDefs.ts +46 -0
  226. package/src/RaftPublish.ts +92 -0
  227. package/src/RaftStreamHandler.ts +412 -0
  228. package/src/RaftStruct.ts +282 -0
  229. package/src/RaftSysTypeManager.ts +87 -0
  230. package/src/RaftSystemType.ts +34 -0
  231. package/src/RaftSystemUtils.ts +548 -0
  232. package/src/RaftTypes.ts +306 -0
  233. package/src/RaftUpdateEvents.ts +48 -0
  234. package/src/RaftUpdateManager.ts +781 -0
  235. package/src/RaftUtils.ts +514 -0
  236. package/src/RaftWifiTypes.ts +36 -0
  237. package/src/main.ts +40 -0
  238. package/testdata/TestDeviceTypeRecs.json +492 -0
  239. package/tsconfig.json +30 -0
  240. package/tsconfig.react-native.json +29 -0
@@ -0,0 +1,51 @@
1
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2
+ //
3
+ // RaftMsgTrackInfo
4
+ // Part of RaftJS
5
+ //
6
+ // Rob Dobson & Chris Greening 2020-2024
7
+ // (C) 2020-2024 All rights reserved
8
+ //
9
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
10
+
11
+ export class RaftMsgTrackInfo {
12
+ static readonly MAX_MSG_NUM = 255;
13
+ static readonly MSG_RESPONSE_TIMEOUT_MS = 2000;
14
+ static readonly MSG_RETRY_COUNT = 5;
15
+ msgOutstanding = false;
16
+ msgFrame: Uint8Array = new Uint8Array();
17
+ msgSentMs = 0;
18
+ retryCount = 0;
19
+ withResponse = false;
20
+ bridgeID: number | undefined = undefined;
21
+ msgHandle = 0;
22
+ msgTimeoutMs: number | undefined = undefined;
23
+ resolve: unknown;
24
+ reject: unknown;
25
+
26
+ constructor() {
27
+ this.msgOutstanding = false;
28
+ }
29
+
30
+ set(
31
+ msgOutstanding: boolean,
32
+ msgFrame: Uint8Array,
33
+ withResponse: boolean,
34
+ bridgeID: number | undefined = undefined,
35
+ msgHandle: number,
36
+ msgTimeoutMs: number | undefined,
37
+ resolve: unknown,
38
+ reject: unknown,
39
+ ) {
40
+ this.msgOutstanding = msgOutstanding;
41
+ this.msgFrame = msgFrame;
42
+ this.retryCount = 0;
43
+ this.msgSentMs = Date.now();
44
+ this.withResponse = withResponse;
45
+ this.bridgeID = bridgeID;
46
+ this.msgHandle = msgHandle;
47
+ this.msgTimeoutMs = msgTimeoutMs;
48
+ this.resolve = resolve;
49
+ this.reject = reject;
50
+ }
51
+ }
@@ -0,0 +1,46 @@
1
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2
+ //
3
+ // Raft Protocol Definitions
4
+ // Part of RaftJS
5
+ //
6
+ // Rob Dobson & Chris Greening 2020-2024
7
+ // (C) 2020-2024 All rights reserved
8
+ //
9
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
10
+
11
+ // RIC Protocols
12
+ export const RICSERIAL_MSG_NUM_POS = 0;
13
+ export const RICSERIAL_PROTOCOL_POS = 1;
14
+ export const RICSERIAL_PAYLOAD_POS = 2;
15
+ export const RICREST_REST_ELEM_CODE_POS = 0;
16
+ export const RICREST_HEADER_PAYLOAD_POS = 1;
17
+ export const RICREST_FILEBLOCK_CHANNEL_POS = 0;
18
+ export const RICREST_FILEBLOCK_FILEPOS_POS = 0;
19
+ export const RICREST_FILEBLOCK_FILEPOS_POS_BYTES = 4;
20
+ export const RICREST_FILEBLOCK_PAYLOAD_POS = 4;
21
+ export const RICREST_BRIDGE_ID_POS = 2;
22
+ export const RICREST_BRIDGE_PAYLOAD_POS = 3;
23
+
24
+ // Protocol enums
25
+ export enum RICRESTElemCode {
26
+ RICREST_ELEM_CODE_URL,
27
+ RICREST_ELEM_CODE_CMDRESPJSON,
28
+ RICREST_ELEM_CODE_BODY,
29
+ RICREST_ELEM_CODE_COMMAND_FRAME,
30
+ RICREST_ELEM_CODE_FILEBLOCK,
31
+ }
32
+
33
+ export enum RaftCommsMsgTypeCode {
34
+ MSG_TYPE_COMMAND,
35
+ MSG_TYPE_RESPONSE,
36
+ MSG_TYPE_PUBLISH,
37
+ MSG_TYPE_REPORT,
38
+ }
39
+
40
+ export enum RaftCommsMsgProtocol {
41
+ MSG_PROTOCOL_ROSSERIAL = 0,
42
+ MSG_PROTOCOL_RESERVED_1 = 1,
43
+ MSG_PROTOCOL_RICREST = 2,
44
+ MSG_PROTOCOL_BRIDGE_RICREST = 3,
45
+ }
46
+
@@ -0,0 +1,92 @@
1
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2
+ //
3
+ // RaftSystem
4
+ // Part of RaftJS
5
+ //
6
+ // Rob Dobson 2025
7
+ // (C) 2020-2025 All rights reserved
8
+ //
9
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
10
+
11
+ import { RaftPublishFrameMeta } from "./RaftTypes";
12
+
13
+ const RAFT_PUBLISH_PREFIX_LEN = 2;
14
+ const DEVBIN_MAGIC_MIN = 0xDB;
15
+ const DEVBIN_MAGIC_MAX = 0xDF;
16
+ const DEVBIN_VERSION_BASE = 0xDA;
17
+
18
+ export function inspectPublishFrame(
19
+ payload: Uint8Array,
20
+ topicLookup?: (topicIndex: number) => string | undefined,
21
+ ): RaftPublishFrameMeta {
22
+
23
+ if (payload.length < RAFT_PUBLISH_PREFIX_LEN) {
24
+ return { frameType: "unknown" };
25
+ }
26
+
27
+ const payloadStartPos = RAFT_PUBLISH_PREFIX_LEN;
28
+ if (payload.length <= payloadStartPos) {
29
+ return { frameType: "unknown" };
30
+ }
31
+
32
+ // Try JSON first
33
+ if (payload[payloadStartPos] === 0x7B) {
34
+ try {
35
+ const jsonString = new TextDecoder("utf-8").decode(payload.slice(payloadStartPos));
36
+ const jsonObj = JSON.parse(jsonString) as Record<string, unknown>;
37
+
38
+ let topicIndex: number | undefined = undefined;
39
+ let topicName: string | undefined = undefined;
40
+ if (typeof jsonObj._t === "number") {
41
+ topicIndex = jsonObj._t;
42
+ topicName = topicLookup ? topicLookup(topicIndex) : undefined;
43
+ } else if (typeof jsonObj._t === "string") {
44
+ topicName = jsonObj._t;
45
+ }
46
+
47
+ const version = typeof jsonObj._v === "number" ? jsonObj._v : undefined;
48
+
49
+ return {
50
+ frameType: "json",
51
+ topicIndex,
52
+ topicName,
53
+ version,
54
+ jsonString,
55
+ };
56
+ } catch {
57
+ return { frameType: "unknown" };
58
+ }
59
+ }
60
+
61
+ // Binary (devbin legacy or enveloped)
62
+ const firstBinaryByte = payload[payloadStartPos];
63
+ if ((firstBinaryByte & 0xF0) === 0xD0) {
64
+ if (firstBinaryByte < DEVBIN_MAGIC_MIN || firstBinaryByte > DEVBIN_MAGIC_MAX) {
65
+ return {
66
+ frameType: "binary",
67
+ binaryHasEnvelope: true,
68
+ };
69
+ }
70
+
71
+ const topicIndex = payload.length > payloadStartPos + 1 ? payload[payloadStartPos + 1] : undefined;
72
+ const topicName = (topicIndex !== undefined && topicIndex !== 0xFF && topicLookup)
73
+ ? topicLookup(topicIndex)
74
+ : undefined;
75
+
76
+ return {
77
+ frameType: "binary",
78
+ topicIndex,
79
+ topicName,
80
+ version: firstBinaryByte - DEVBIN_VERSION_BASE,
81
+ binaryHasEnvelope: true,
82
+ binaryPayloadOffset: payloadStartPos + 2,
83
+ };
84
+ }
85
+
86
+ // Legacy binary format (no envelope)
87
+ return {
88
+ frameType: "binary",
89
+ binaryHasEnvelope: false,
90
+ binaryPayloadOffset: payloadStartPos,
91
+ };
92
+ }
@@ -0,0 +1,412 @@
1
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2
+ //
3
+ // RafStreamHandler
4
+ // Part of RaftJS
5
+ //
6
+ // Rob Dobson & Chris Greening 2020-2024
7
+ // (C) 2020-2024 All rights reserved
8
+ //
9
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
10
+
11
+ import RaftLog from './RaftLog'
12
+ import RaftMsgHandler from './RaftMsgHandler';
13
+ import RaftCommsStats from './RaftCommsStats';
14
+ import { RaftOKFail, RaftStreamStartResp, RaftStreamType, RaftStreamDataProgressCBType } from './RaftTypes';
15
+ import RaftConnector from './RaftConnector';
16
+ import { RaftConnEvent } from './RaftConnEvents';
17
+ import { RICRESTElemCode } from './RaftProtocolDefs'
18
+ import RaftUtils from './RaftUtils';
19
+
20
+ export default class RaftStreamHandler {
21
+
22
+ // Queue of audio stream requests
23
+ /*
24
+ private _streamAudioQueue: {
25
+ streamContents: Uint8Array;
26
+ audioDuration: number;
27
+ }[] = [];
28
+ */
29
+
30
+ // Stream state
31
+ private _streamID: number | null = null;
32
+ DEFAULT_MAX_BLOCK_SIZE = 475;
33
+ private _maxBlockSize: number = this.DEFAULT_MAX_BLOCK_SIZE;
34
+
35
+ // Handler of messages
36
+ private _msgHandler: RaftMsgHandler;
37
+
38
+ // RaftCommsStats
39
+ private _commsStats: RaftCommsStats;
40
+
41
+ // RaftConnector
42
+ private _raftConnector: RaftConnector;
43
+
44
+ // Flow control
45
+ private _soktoReceived = false;
46
+ private _soktoPos = 0;
47
+
48
+ private _streamIsStarting = false;
49
+ private _lastStreamStartTime = 0;
50
+
51
+ private _isStreaming = false;
52
+ private _isPaused = false;
53
+ private _streamBuffer: Uint8Array<ArrayBuffer> = new Uint8Array();
54
+ private _audioDuration = 0;
55
+ private _audioByteRate = 0;
56
+ private _streamPos = 0;
57
+ private _numBlocksWithoutPause = 15;
58
+ private _legacySoktoMode = false;
59
+
60
+
61
+ // soundFinishPoint timer
62
+ private soundFinishPoint: NodeJS.Timeout | null = null;
63
+
64
+ constructor(msgHandler: RaftMsgHandler, commsStats: RaftCommsStats, raftConnector: RaftConnector) {
65
+ this._raftConnector = raftConnector;
66
+ this._msgHandler = msgHandler;
67
+ this._commsStats = commsStats;
68
+ this.onSoktoMsg = this.onSoktoMsg.bind(this);
69
+ }
70
+
71
+ setNumBlocksWithoutPause(numBlocks: number) {
72
+ this._numBlocksWithoutPause = numBlocks;
73
+ }
74
+
75
+ setLegacySoktoMode(legacyMode: boolean) {
76
+ RaftLog.debug(`Setting legacy sokto mode to ${legacyMode}`);
77
+ this._legacySoktoMode = legacyMode;
78
+ }
79
+
80
+ /**
81
+ * streamData - stream arbitrary data to a named firmware endpoint using the RT_STREAM protocol.
82
+ * Returns a promise that resolves when the stream is complete or rejects on failure.
83
+ * @param streamContents data to stream
84
+ * @param fileName logical filename sent in ufStart (used by firmware to detect format, e.g. "pattern.thr")
85
+ * @param targetEndpoint REST API endpoint name registered on the firmware (e.g. "streampattern")
86
+ * @param progressCallback optional callback reporting (bytesSent, totalBytes, progressFraction)
87
+ * @returns Promise<boolean> true if stream completed successfully
88
+ */
89
+ async streamData(
90
+ streamContents: Uint8Array,
91
+ fileName: string,
92
+ targetEndpoint: string,
93
+ progressCallback?: RaftStreamDataProgressCBType,
94
+ ): Promise<boolean> {
95
+ // Reject if another stream is starting
96
+ if (this._streamIsStarting || this._lastStreamStartTime > (Date.now() - 500)) {
97
+ RaftLog.warn(`streamData: unable to start, too soon since last request`);
98
+ return false;
99
+ }
100
+
101
+ this._streamIsStarting = true;
102
+ this._lastStreamStartTime = Date.now();
103
+ this._soktoReceived = false;
104
+ this._soktoPos = 0;
105
+ this._streamPos = 0;
106
+ this._streamBuffer = RaftUtils.toArrayBufferView(streamContents);
107
+
108
+ // Send ufStart
109
+ const startOk = await this._sendStreamStartMsg(
110
+ fileName, targetEndpoint, RaftStreamType.REAL_TIME_STREAM, streamContents,
111
+ );
112
+ this._streamIsStarting = false;
113
+
114
+ if (!startOk) {
115
+ RaftLog.warn(`streamData: ufStart failed`);
116
+ return false;
117
+ }
118
+
119
+ // Send blocks
120
+ if (this._streamID === null) {
121
+ return false;
122
+ }
123
+
124
+ let pos = 0;
125
+ while (pos < this._streamBuffer.length) {
126
+ // Respect SOKTO feedback — if firmware reported a position mismatch, rewind
127
+ if (this._soktoReceived) {
128
+ RaftLog.verbose(`streamData: sokto received, pos was ${pos}, soktoPos ${this._soktoPos}`);
129
+ this._soktoReceived = false;
130
+ // Slow down on backpressure
131
+ await new Promise(r => setTimeout(r, 50));
132
+ }
133
+
134
+ const blockSize = Math.min(this._streamBuffer.length - pos, this._maxBlockSize);
135
+ const block = this._streamBuffer.slice(pos, pos + blockSize);
136
+ if (block.length > 0) {
137
+ const sentOk = await this._msgHandler.sendStreamBlock(block, pos, this._streamID);
138
+ this._commsStats.recordStreamBytes(block.length);
139
+ if (!sentOk) {
140
+ RaftLog.warn(`streamData: sendStreamBlock failed at pos ${pos}`);
141
+ return false;
142
+ }
143
+ pos += blockSize;
144
+ this._streamPos = pos;
145
+
146
+ // Progress callback
147
+ if (progressCallback) {
148
+ const progress = this._streamBuffer.length > 0 ? pos / this._streamBuffer.length : 1;
149
+ progressCallback(pos, this._streamBuffer.length, progress);
150
+ }
151
+ }
152
+
153
+ // Yield to avoid hogging the event loop
154
+ await new Promise(r => setTimeout(r, 1));
155
+ }
156
+
157
+ // Send ufEnd
158
+ const endOk = await this._sendStreamEndMsg(this._streamID);
159
+ RaftLog.debug(`streamData: complete, endOk=${endOk}`);
160
+ return endOk;
161
+ }
162
+
163
+ // Start streaming audio
164
+ streamAudio(streamContents: Uint8Array, clearExisting: boolean, audioDuration: number): void {
165
+ if (!clearExisting)
166
+ RaftLog.debug(`only clearExisting = true is supported right now.`);
167
+
168
+ // TODO - if clearExisting is not set, form a queue
169
+ if (this._streamIsStarting || this._lastStreamStartTime > (Date.now() - 500)) {
170
+ RaftLog.warn(`Unable to start sound, too soon since last request`);
171
+ return;
172
+ }
173
+
174
+ this._isPaused = true;
175
+ this._streamIsStarting = true;
176
+ this._lastStreamStartTime = Date.now();
177
+
178
+ this._soktoReceived = false;
179
+ this._soktoPos = 0;
180
+ this._streamPos = 0;
181
+ this._streamBuffer = RaftUtils.toArrayBufferView(streamContents);
182
+ this._audioDuration = audioDuration;
183
+ this._audioByteRate = (streamContents.length / audioDuration) * 1000;
184
+
185
+ this.clearFinishPointTimeout();
186
+
187
+ this._sendStreamStartMsg("audio.mp3", "streamaudio", RaftStreamType.REAL_TIME_STREAM, streamContents).then(
188
+ (result: boolean) => {
189
+ this._isPaused = false;
190
+ this._streamIsStarting = false;
191
+ if (!result) {
192
+ RaftLog.warn(`Unable to start stream. ufStart message send failed`);
193
+ return;
194
+ }
195
+ //this.streamingPerformanceChecker();
196
+ if (!this._isStreaming) {
197
+ this._isStreaming = true;
198
+ this._sendStreamBuffer();
199
+ }
200
+
201
+ }
202
+ );
203
+ }
204
+
205
+ async streamCancel(): Promise<void> {
206
+ this._streamBuffer = new Uint8Array();
207
+ this.clearFinishPointTimeout();
208
+ }
209
+
210
+ public isStreamStarting() {
211
+ return this._streamIsStarting;
212
+ }
213
+
214
+
215
+ clearFinishPointTimeout() {
216
+ if (this.soundFinishPoint) {
217
+ clearTimeout(this.soundFinishPoint);
218
+ this.soundFinishPoint = null;
219
+ }
220
+ }
221
+
222
+ streamingPerformanceChecker() {
223
+ if (this._audioDuration) {
224
+ this.clearFinishPointTimeout();
225
+ this.soundFinishPoint = setTimeout(() => {
226
+ // if the streaming hasn't finished before the end of the audio
227
+ // we can assume we are having streaming issues
228
+
229
+ // publish event in case we are having issues
230
+ this._raftConnector.onConnEvent(RaftConnEvent.CONN_STREAMING_ISSUE);
231
+
232
+ this.clearFinishPointTimeout();
233
+ }, this._audioDuration + 500);
234
+ }
235
+ }
236
+
237
+ // Send the start message
238
+ private async _sendStreamStartMsg(
239
+ streamName: string,
240
+ targetEndpoint: string,
241
+ streamTypeEnum: RaftStreamType,
242
+ streamContents: Uint8Array,
243
+ ): Promise<boolean> {
244
+ // Stream start command message
245
+ const streamType = 'rtstream';
246
+ const cmdMsg = `{"cmdName":"ufStart","reqStr":"ufStart","fileType":"${streamType}","fileName":"${streamName}","endpoint":"${targetEndpoint}","fileLen":${streamContents.length}}`;
247
+
248
+ // Debug
249
+ RaftLog.debug(`sendStreamStartMsg ${cmdMsg}`);
250
+
251
+ // Send
252
+ let streamStartResp = null;
253
+ try {
254
+ streamStartResp = await this._msgHandler.sendRICREST<RaftStreamStartResp>(
255
+ cmdMsg,
256
+ RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
257
+ );
258
+ } catch (err) {
259
+ RaftLog.warn(`sendStreamStartMsg error ${err}`);
260
+ return false;
261
+ }
262
+
263
+ // Extract params
264
+ if (streamStartResp && (streamStartResp.rslt === 'ok')) {
265
+ this._streamID = streamStartResp.streamID;
266
+ this._maxBlockSize = streamStartResp.maxBlockSize || this.DEFAULT_MAX_BLOCK_SIZE;
267
+ this.streamingPerformanceChecker();
268
+ RaftLog.verbose(
269
+ `sendStreamStartMsg streamID ${this._streamID} maxBlockSize ${this._maxBlockSize} streamType ${streamTypeEnum}`,
270
+ );
271
+ } else {
272
+ RaftLog.warn(`sendStreamStartMsg failed ${streamStartResp ? streamStartResp.rslt : 'no response'}`);
273
+ return false;
274
+ }
275
+ return true;
276
+ }
277
+
278
+ get maxBlockSize() {
279
+ return this._maxBlockSize;
280
+ }
281
+
282
+ set maxBlockSize(maxBlockSize: number) {
283
+ this._maxBlockSize = maxBlockSize;
284
+ this.DEFAULT_MAX_BLOCK_SIZE = maxBlockSize;
285
+ }
286
+
287
+ private async _sendStreamEndMsg(
288
+ streamID: number | null,
289
+ ): Promise<boolean> {
290
+ if (streamID === null) {
291
+ return false;
292
+ }
293
+ // Stram end command message
294
+ const cmdMsg = `{"cmdName":"ufEnd","reqStr":"ufEnd","streamID":${streamID}}`;
295
+
296
+ // Debug
297
+ RaftLog.debug(`sendStreamEndMsg ${cmdMsg}`);
298
+
299
+ // Send
300
+ let streamEndResp = null;
301
+ try {
302
+ streamEndResp = await this._msgHandler.sendRICREST<RaftOKFail>(
303
+ cmdMsg,
304
+ RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
305
+ );
306
+ } catch (err) {
307
+ RaftLog.warn(`sendStreamEndMsg error ${err}`);
308
+ return false;
309
+ }
310
+ return streamEndResp.rslt === 'ok';
311
+ }
312
+
313
+ /*
314
+ private async _sendAudioStopMsg(): Promise<RaftOKFail> {
315
+ const cmdMsg = `{"cmdName":"audio/stop"}`;
316
+
317
+ // Debug
318
+ RaftLog.debug(`sendAudioStopMsg ${cmdMsg}`);
319
+
320
+ // Send
321
+ return this._msgHandler.sendRICREST<RaftOKFail>(
322
+ cmdMsg,
323
+ RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
324
+ );
325
+ }
326
+
327
+
328
+ private async _sendStreamCancelMsg(): Promise<RaftOKFail> {
329
+ // File cancel command message
330
+ const cmdMsg = `{"cmdName":"ufCancel","reqStr":"ufCancel","streamID":${this._streamID}}`;
331
+
332
+ // Debug
333
+ RaftLog.debug(`sendStreamCancelMsg ${cmdMsg}`);
334
+
335
+ // Send
336
+ return this._msgHandler.sendRICREST<RaftOKFail>(
337
+ cmdMsg,
338
+ RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
339
+ );
340
+ }
341
+ */
342
+
343
+ private async _sendStreamBuffer(): Promise<boolean> {
344
+ const streamStartTime = Date.now();
345
+
346
+ // Check streamID is valid
347
+ if (this._streamID === null) {
348
+ return false;
349
+ }
350
+
351
+ let blockNum = 0;
352
+ // Send stream blocks
353
+ while (this._soktoPos < this._streamBuffer.length || this._isPaused) {
354
+ if (this._isPaused) {
355
+ await new Promise((resolve) => setTimeout(resolve, 5));
356
+ continue;
357
+ }
358
+
359
+ // Check for new sokto
360
+ if (this._soktoReceived) {
361
+ if (this._legacySoktoMode)
362
+ this._streamPos = this._soktoPos;
363
+ // apart from when in legacy mode, the sokto message is now informational only,
364
+ // to allow the central to slow down sending of data if it is swamping the peripheral
365
+ RaftLog.verbose(`sendStreamContents ${Date.now() - streamStartTime}ms soktoReceived for ${this._streamPos}`);
366
+ this._soktoReceived = false;
367
+
368
+ // receiving an sokto message before the completion of the stream means that the streaming is not keeping up
369
+ this._raftConnector.onConnEvent(RaftConnEvent.CONN_STREAMING_ISSUE);
370
+ }
371
+
372
+ // Send stream block
373
+ const blockSize = Math.min(this._streamBuffer.length - this._streamPos, this._maxBlockSize);
374
+ const block = this._streamBuffer.slice(this._streamPos, this._streamPos + blockSize);
375
+ if (block.length > 0) {
376
+ const sentOk = await this._msgHandler.sendStreamBlock(block, this._streamPos, this._streamID);
377
+ this._commsStats.recordStreamBytes(block.length);
378
+
379
+ RaftLog.verbose(
380
+ `sendStreamContents ${sentOk ? "OK" : "FAILED"} ${Date.now() - streamStartTime}ms pos ${this._streamPos} ${blockSize} ${block.length} ${this._soktoPos}`,
381
+ );
382
+ if (!sentOk) {
383
+ return false;
384
+ }
385
+ this._streamPos += blockSize;
386
+ blockNum += 1;
387
+
388
+ if (this._audioByteRate && blockNum > this._numBlocksWithoutPause) {
389
+ const pauseTime = ((blockSize / this._audioByteRate) * 1000) - 10;
390
+ RaftLog.verbose(`Pausing for ${pauseTime} ms between audio packets. Bit rate ${this._audioByteRate * 8}`)
391
+ await new Promise((resolve) => setTimeout(resolve, pauseTime));
392
+ }
393
+ }
394
+
395
+ // Wait to ensure we don't hog the CPU
396
+ await new Promise((resolve) => setTimeout(resolve, 1));
397
+ }
398
+
399
+ this._isStreaming = false;
400
+ this.clearFinishPointTimeout();
401
+ await this._sendStreamEndMsg(this._streamID);
402
+
403
+ return true;
404
+ }
405
+
406
+ onSoktoMsg(soktoPos: number) {
407
+ // Get how far we've progressed in file
408
+ this._soktoPos = soktoPos;
409
+ this._soktoReceived = true;
410
+ RaftLog.debug(`onSoktoMsg received file up to ${this._soktoPos}`);
411
+ }
412
+ }