@robdobsn/raftjs 1.1.1

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 (149) hide show
  1. package/.editorconfig +14 -0
  2. package/.gitattributes +11 -0
  3. package/.nvmrc +1 -0
  4. package/LICENSE +22 -0
  5. package/README.md +11 -0
  6. package/TODO.md +1 -0
  7. package/dist/RaftAttributeHandler.d.ts +12 -0
  8. package/dist/RaftAttributeHandler.js +241 -0
  9. package/dist/RaftAttributeHandler.js.map +1 -0
  10. package/dist/RaftChannel.d.ts +18 -0
  11. package/dist/RaftChannel.js +12 -0
  12. package/dist/RaftChannel.js.map +1 -0
  13. package/dist/RaftChannelWebBLE.d.ts +38 -0
  14. package/dist/RaftChannelWebBLE.js +274 -0
  15. package/dist/RaftChannelWebBLE.js.map +1 -0
  16. package/dist/RaftChannelWebSerial.d.ts +37 -0
  17. package/dist/RaftChannelWebSerial.js +319 -0
  18. package/dist/RaftChannelWebSerial.js.map +1 -0
  19. package/dist/RaftChannelWebSocket.d.ts +28 -0
  20. package/dist/RaftChannelWebSocket.js +197 -0
  21. package/dist/RaftChannelWebSocket.js.map +1 -0
  22. package/dist/RaftCommsStats.d.ts +39 -0
  23. package/dist/RaftCommsStats.js +128 -0
  24. package/dist/RaftCommsStats.js.map +1 -0
  25. package/dist/RaftConnEvents.d.ts +31 -0
  26. package/dist/RaftConnEvents.js +44 -0
  27. package/dist/RaftConnEvents.js.map +1 -0
  28. package/dist/RaftConnector.d.ts +242 -0
  29. package/dist/RaftConnector.js +613 -0
  30. package/dist/RaftConnector.js.map +1 -0
  31. package/dist/RaftCustomAttrHandler.d.ts +4 -0
  32. package/dist/RaftCustomAttrHandler.js +50 -0
  33. package/dist/RaftCustomAttrHandler.js.map +1 -0
  34. package/dist/RaftDeviceInfo.d.ts +64 -0
  35. package/dist/RaftDeviceInfo.js +36 -0
  36. package/dist/RaftDeviceInfo.js.map +1 -0
  37. package/dist/RaftDeviceManager.d.ts +37 -0
  38. package/dist/RaftDeviceManager.js +450 -0
  39. package/dist/RaftDeviceManager.js.map +1 -0
  40. package/dist/RaftDeviceMsg.d.ts +9 -0
  41. package/dist/RaftDeviceMsg.js +11 -0
  42. package/dist/RaftDeviceMsg.js.map +1 -0
  43. package/dist/RaftDeviceStates.d.ts +33 -0
  44. package/dist/RaftDeviceStates.js +60 -0
  45. package/dist/RaftDeviceStates.js.map +1 -0
  46. package/dist/RaftFileHandler.d.ts +52 -0
  47. package/dist/RaftFileHandler.js +502 -0
  48. package/dist/RaftFileHandler.js.map +1 -0
  49. package/dist/RaftLog.d.ts +22 -0
  50. package/dist/RaftLog.js +63 -0
  51. package/dist/RaftLog.js.map +1 -0
  52. package/dist/RaftMiniHDLC.d.ts +18 -0
  53. package/dist/RaftMiniHDLC.js +383 -0
  54. package/dist/RaftMiniHDLC.js.map +1 -0
  55. package/dist/RaftMsgHandler.d.ts +57 -0
  56. package/dist/RaftMsgHandler.js +480 -0
  57. package/dist/RaftMsgHandler.js.map +1 -0
  58. package/dist/RaftMsgTrackInfo.d.ts +17 -0
  59. package/dist/RaftMsgTrackInfo.js +42 -0
  60. package/dist/RaftMsgTrackInfo.js.map +1 -0
  61. package/dist/RaftProtocolDefs.d.ts +30 -0
  62. package/dist/RaftProtocolDefs.js +48 -0
  63. package/dist/RaftProtocolDefs.js.map +1 -0
  64. package/dist/RaftStreamHandler.d.ts +38 -0
  65. package/dist/RaftStreamHandler.js +257 -0
  66. package/dist/RaftStreamHandler.js.map +1 -0
  67. package/dist/RaftSystemType.d.ts +21 -0
  68. package/dist/RaftSystemType.js +3 -0
  69. package/dist/RaftSystemType.js.map +1 -0
  70. package/dist/RaftSystemUtils.d.ts +136 -0
  71. package/dist/RaftSystemUtils.js +410 -0
  72. package/dist/RaftSystemUtils.js.map +1 -0
  73. package/dist/RaftTypes.d.ts +184 -0
  74. package/dist/RaftTypes.js +157 -0
  75. package/dist/RaftTypes.js.map +1 -0
  76. package/dist/RaftUpdateEvents.d.ts +33 -0
  77. package/dist/RaftUpdateEvents.js +46 -0
  78. package/dist/RaftUpdateEvents.js.map +1 -0
  79. package/dist/RaftUpdateManager.d.ts +61 -0
  80. package/dist/RaftUpdateManager.js +618 -0
  81. package/dist/RaftUpdateManager.js.map +1 -0
  82. package/dist/RaftUtils.d.ts +125 -0
  83. package/dist/RaftUtils.js +454 -0
  84. package/dist/RaftUtils.js.map +1 -0
  85. package/dist/RaftWifiTypes.d.ts +23 -0
  86. package/dist/RaftWifiTypes.js +43 -0
  87. package/dist/RaftWifiTypes.js.map +1 -0
  88. package/dist/TestDataGen.d.ts +7 -0
  89. package/dist/TestDataGen.js +133 -0
  90. package/dist/TestDataGen.js.map +1 -0
  91. package/dist/main.d.ts +18 -0
  92. package/dist/main.js +42 -0
  93. package/dist/main.js.map +1 -0
  94. package/eslint.config.mjs +33 -0
  95. package/examples/dashboard/package.json +39 -0
  96. package/examples/dashboard/src/ConnManager.ts +86 -0
  97. package/examples/dashboard/src/Main.tsx +100 -0
  98. package/examples/dashboard/src/StatusScreen.tsx +72 -0
  99. package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +144 -0
  100. package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +77 -0
  101. package/examples/dashboard/src/SystemTypeMarty/RICAddOn.ts +70 -0
  102. package/examples/dashboard/src/SystemTypeMarty/RICAddOnBase.ts +33 -0
  103. package/examples/dashboard/src/SystemTypeMarty/RICAddOnManager.ts +342 -0
  104. package/examples/dashboard/src/SystemTypeMarty/RICCommsStats.ts +170 -0
  105. package/examples/dashboard/src/SystemTypeMarty/RICHWElem.ts +123 -0
  106. package/examples/dashboard/src/SystemTypeMarty/RICLEDPatternChecker.ts +207 -0
  107. package/examples/dashboard/src/SystemTypeMarty/RICROSSerial.ts +464 -0
  108. package/examples/dashboard/src/SystemTypeMarty/RICServoFaultDetector.ts +146 -0
  109. package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +32 -0
  110. package/examples/dashboard/src/SystemTypeMarty/RICSystemUtils.ts +371 -0
  111. package/examples/dashboard/src/SystemTypeMarty/RICTypes.ts +20 -0
  112. package/examples/dashboard/src/SystemTypeMarty/SystemTypeMarty.ts +113 -0
  113. package/examples/dashboard/src/index.html +15 -0
  114. package/examples/dashboard/src/index.tsx +15 -0
  115. package/examples/dashboard/src/styles.css +122 -0
  116. package/examples/dashboard/tsconfig.json +18 -0
  117. package/jest.config.js +11 -0
  118. package/package.json +50 -0
  119. package/src/RaftAttributeHandler.ts +289 -0
  120. package/src/RaftChannel.ts +30 -0
  121. package/src/RaftChannelWebBLE.ts +342 -0
  122. package/src/RaftChannelWebSerial.ts +408 -0
  123. package/src/RaftChannelWebSocket.ts +245 -0
  124. package/src/RaftCommsStats.ts +142 -0
  125. package/src/RaftConnEvents.ts +46 -0
  126. package/src/RaftConnector.ts +745 -0
  127. package/src/RaftCustomAttrHandler.ts +54 -0
  128. package/src/RaftDeviceInfo.ts +104 -0
  129. package/src/RaftDeviceManager.ts +542 -0
  130. package/src/RaftDeviceMsg.ts +20 -0
  131. package/src/RaftDeviceStates.ts +89 -0
  132. package/src/RaftFileHandler.ts +668 -0
  133. package/src/RaftLog.ts +70 -0
  134. package/src/RaftMiniHDLC.ts +396 -0
  135. package/src/RaftMsgHandler.ts +778 -0
  136. package/src/RaftMsgTrackInfo.ts +51 -0
  137. package/src/RaftProtocolDefs.ts +46 -0
  138. package/src/RaftStreamHandler.ts +328 -0
  139. package/src/RaftSystemType.ts +25 -0
  140. package/src/RaftSystemUtils.ts +487 -0
  141. package/src/RaftTypes.ts +250 -0
  142. package/src/RaftUpdateEvents.ts +48 -0
  143. package/src/RaftUpdateManager.ts +778 -0
  144. package/src/RaftUtils.ts +484 -0
  145. package/src/RaftWifiTypes.ts +36 -0
  146. package/src/TestDataGen.ts +157 -0
  147. package/src/main.ts +28 -0
  148. package/testdata/TestDeviceTypeRecs.json +492 -0
  149. package/tsconfig.json +27 -0
@@ -0,0 +1,778 @@
1
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2
+ //
3
+ // RaftMsgHandler
4
+ // Part of RaftJS
5
+ //
6
+ // Rob Dobson & Chris Greening 2020-2024
7
+ // (C) 2020-2024 All rights reserved
8
+ //
9
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
10
+
11
+ import RaftCommsStats from './RaftCommsStats';
12
+ import { RaftMsgTrackInfo } from './RaftMsgTrackInfo';
13
+ import RaftLog from './RaftLog';
14
+ import RaftUtils from './RaftUtils';
15
+ import {
16
+ RICSERIAL_MSG_NUM_POS,
17
+ RICSERIAL_PAYLOAD_POS,
18
+ RICSERIAL_PROTOCOL_POS,
19
+ RICREST_REST_ELEM_CODE_POS,
20
+ RICREST_HEADER_PAYLOAD_POS,
21
+ RICREST_FILEBLOCK_FILEPOS_POS,
22
+ RICREST_FILEBLOCK_PAYLOAD_POS,
23
+ RICREST_BRIDGE_PAYLOAD_POS,
24
+ RICREST_BRIDGE_ID_POS,
25
+ RICRESTElemCode,
26
+ RaftCommsMsgProtocol,
27
+ RaftCommsMsgTypeCode,
28
+ } from './RaftProtocolDefs';
29
+ import RaftMiniHDLC from './RaftMiniHDLC';
30
+ import { RaftBridgeSetupResp, RaftReportMsg } from './RaftTypes';
31
+
32
+ // Message results
33
+ export enum RaftMsgResultCode {
34
+ MESSAGE_RESULT_TIMEOUT,
35
+ MESSAGE_RESULT_OK,
36
+ MESSAGE_RESULT_FAIL,
37
+ MESSAGE_RESULT_UNKNOWN,
38
+ }
39
+
40
+ export interface RaftMessageResult {
41
+ onRxReply(
42
+ msgHandle: number,
43
+ msgRsltCode: RaftMsgResultCode,
44
+ msgRsltJsonObj: object | null,
45
+ ): void;
46
+ onRxUnnumberedMsg(msgRsltJsonObj: object): void;
47
+ onRxFileBlock(
48
+ filePos: number,
49
+ fileBlockData: Uint8Array
50
+ ): void;
51
+ onRxOtherMsgType(payload: Uint8Array, frameTimeMs: number): void;
52
+ }
53
+
54
+ export interface RaftMessageSender {
55
+ sendTxMsg(
56
+ msg: Uint8Array,
57
+ sendWithResponse: boolean,
58
+ ): Promise<boolean>;
59
+ sendTxMsgNoAwait(
60
+ msg: Uint8Array,
61
+ sendWithResponse: boolean,
62
+ ): Promise<boolean>;
63
+ }
64
+
65
+ export default class RaftMsgHandler {
66
+ // Message numbering and tracking
67
+ private _currentMsgNumber = 1;
68
+ private _currentMsgHandle = 1;
69
+ private _msgTrackInfos: Array<RaftMsgTrackInfo> = new Array<RaftMsgTrackInfo>(
70
+ RaftMsgTrackInfo.MAX_MSG_NUM + 1,
71
+ );
72
+ private _msgTrackTimerMs = 50;
73
+ private _msgTrackLastCheckIdx = 0;
74
+
75
+ // report message callback dictionary. Add a callback to subscribe to report messages
76
+ private _reportMsgCallbacks = new Map<string, (report: RaftReportMsg) => void>();
77
+
78
+ // Interface to inform of message results
79
+ private _msgResultHandler: RaftMessageResult | null = null;
80
+
81
+ // Interface to send messages
82
+ private _msgSender: RaftMessageSender | null = null;
83
+
84
+ // Comms stats
85
+ private _commsStats: RaftCommsStats;
86
+
87
+ // RaftMiniHDLC - handles part of RICSerial protocol
88
+ private _miniHDLC: RaftMiniHDLC;
89
+
90
+ // Constructor
91
+ constructor(commsStats: RaftCommsStats) {
92
+ this._commsStats = commsStats;
93
+ RaftLog.debug('RaftMsgHandler constructor');
94
+
95
+ // Message tracking
96
+ for (let i = 0; i < this._msgTrackInfos.length; i++) {
97
+ this._msgTrackInfos[i] = new RaftMsgTrackInfo();
98
+ }
99
+
100
+ // Timer for checking messages
101
+ setTimeout(async () => {
102
+ this._onMsgTrackTimer(true);
103
+ }, this._msgTrackTimerMs);
104
+
105
+ // HDLC used to encode/decode the RICREST protocol
106
+ this._miniHDLC = new RaftMiniHDLC();
107
+ this._miniHDLC.setOnRxFrame(this._onHDLCFrameDecode.bind(this));
108
+ }
109
+
110
+ registerForResults(msgResultHandler: RaftMessageResult) {
111
+ this._msgResultHandler = msgResultHandler;
112
+ }
113
+
114
+ registerMsgSender(RaftMessageSender: RaftMessageSender) {
115
+ this._msgSender = RaftMessageSender;
116
+ }
117
+
118
+ handleNewRxMsg(rxMsg: Uint8Array): void {
119
+ this._miniHDLC.addRxBytes(rxMsg);
120
+ // RaftLog.verbose(`handleNewRxMsg len ${rxMsg.length} ${RaftUtils.bufferToHex(rxMsg)}`)
121
+ }
122
+
123
+ reportMsgCallbacksSet(callbackName: string, callback: (report: RaftReportMsg) => void): void {
124
+ this._reportMsgCallbacks.set(callbackName, callback);
125
+ }
126
+
127
+ reportMsgCallbacksDelete(callbackName: string) {
128
+ this._reportMsgCallbacks.delete(callbackName);
129
+ }
130
+
131
+ _onHDLCFrameDecode(rxMsg: Uint8Array, frameTimeMs: number): void {
132
+ // Add to stats
133
+ this._commsStats.msgRx();
134
+
135
+ // Validity
136
+ if (rxMsg.length < RICSERIAL_PAYLOAD_POS) {
137
+ this._commsStats.msgTooShort();
138
+ return;
139
+ }
140
+
141
+ // RaftLog.verbose(`_onHDLCFrameDecode len ${rxMsg.length}`);
142
+
143
+ // Decode the RICFrame header
144
+ let rxMsgNum = rxMsg[RICSERIAL_MSG_NUM_POS] & 0xff;
145
+ let rxProtocol = rxMsg[RICSERIAL_PROTOCOL_POS] & 0x3f;
146
+ let rxMsgType = (rxMsg[RICSERIAL_PROTOCOL_POS] >> 6) & 0x03;
147
+
148
+ // Check for RICREST bridging protocol
149
+ if (rxProtocol == RaftCommsMsgProtocol.MSG_PROTOCOL_BRIDGE_RICREST) {
150
+
151
+ // Debug
152
+ const bridgeID = rxMsg.length > RICREST_BRIDGE_ID_POS ? rxMsg[RICREST_BRIDGE_ID_POS] : 0;
153
+ RaftLog.info(
154
+ `_onHDLCFrameDecode RICREST bridge rx bridgeID ${bridgeID} len ${rxMsg.length}`
155
+ );
156
+
157
+ // Simply remove the wrapper
158
+ rxMsg = rxMsg.slice(RICREST_BRIDGE_PAYLOAD_POS);
159
+
160
+ // Get the message info from the unwrapped message
161
+ rxMsgNum = rxMsg[RICSERIAL_MSG_NUM_POS] & 0xff;
162
+ rxProtocol = rxMsg[RICSERIAL_PROTOCOL_POS] & 0x3f;
163
+ rxMsgType = (rxMsg[RICSERIAL_PROTOCOL_POS] >> 6) & 0x03;
164
+ }
165
+
166
+ // Check for RICREST protocol
167
+ if (rxProtocol == RaftCommsMsgProtocol.MSG_PROTOCOL_RICREST) {
168
+ RaftLog.verbose(
169
+ `_onHDLCFrameDecode RICREST rx msgNum ${rxMsgNum} msgDirn ${rxMsgType} ${RaftUtils.bufferToHex(
170
+ rxMsg,
171
+ )}`,
172
+ );
173
+ // Extract payload
174
+ const ricRestElemCode =
175
+ rxMsg[RICSERIAL_PAYLOAD_POS + RICREST_REST_ELEM_CODE_POS] & 0xff;
176
+ if (
177
+ ricRestElemCode == RICRESTElemCode.RICREST_ELEM_CODE_URL ||
178
+ ricRestElemCode == RICRESTElemCode.RICREST_ELEM_CODE_CMDRESPJSON ||
179
+ ricRestElemCode == RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME
180
+ ) {
181
+ // These are all text-based messages
182
+ const restStr = RaftUtils.getStringFromBuffer(
183
+ rxMsg,
184
+ RICSERIAL_PAYLOAD_POS + RICREST_HEADER_PAYLOAD_POS,
185
+ rxMsg.length - RICSERIAL_PAYLOAD_POS - RICREST_HEADER_PAYLOAD_POS - 1,
186
+ );
187
+ RaftLog.verbose(
188
+ `_onHDLCFrameDecode RICREST rx elemCode ${ricRestElemCode} ${restStr}`,
189
+ );
190
+
191
+ // Check message types
192
+ if (rxMsgType == RaftCommsMsgTypeCode.MSG_TYPE_RESPONSE) {
193
+
194
+ // Handle response messages
195
+ this._handleResponseMessages(restStr, rxMsgNum);
196
+
197
+ } else if (rxMsgType == RaftCommsMsgTypeCode.MSG_TYPE_REPORT) {
198
+
199
+ // Handle report messages
200
+ this._handleReportMessages(restStr);
201
+
202
+ }
203
+
204
+ } else {
205
+ const binMsgLen = rxMsg.length - RICSERIAL_PAYLOAD_POS - RICREST_HEADER_PAYLOAD_POS;
206
+ RaftLog.verbose(
207
+ `_onHDLCFrameDecode RICREST rx binary message elemCode ${ricRestElemCode} len ${binMsgLen} data ${RaftUtils.bufferToHex(rxMsg)}`,
208
+ );
209
+ if (ricRestElemCode == RICRESTElemCode.RICREST_ELEM_CODE_FILEBLOCK) {
210
+ const filePos = RaftUtils.getBEUint32FromBuf(rxMsg, RICSERIAL_PAYLOAD_POS + RICREST_HEADER_PAYLOAD_POS + RICREST_FILEBLOCK_FILEPOS_POS);
211
+ this._msgResultHandler?.onRxFileBlock(
212
+ filePos,
213
+ rxMsg.slice(RICSERIAL_PAYLOAD_POS + RICREST_HEADER_PAYLOAD_POS + RICREST_FILEBLOCK_PAYLOAD_POS, rxMsg.length));
214
+ }
215
+ }
216
+
217
+ // Other message types
218
+ } else {
219
+ this._msgResultHandler?.onRxOtherMsgType(rxMsg, frameTimeMs);
220
+ }
221
+ }
222
+
223
+ _handleResponseMessages(restStr: string, rxMsgNum: number): void {
224
+ try {
225
+ let msgRsltCode = RaftMsgResultCode.MESSAGE_RESULT_UNKNOWN;
226
+ const msgRsltJsonObj = JSON.parse(restStr);
227
+ if ('rslt' in msgRsltJsonObj) {
228
+ const rsltStr = msgRsltJsonObj.rslt.toLowerCase();
229
+ if (rsltStr === 'ok') {
230
+ RaftLog.verbose(
231
+ `_handleResponseMessages RICREST rslt Ok ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()} resp ${msgRsltJsonObj.rslt}`,
232
+ );
233
+ msgRsltCode = RaftMsgResultCode.MESSAGE_RESULT_OK;
234
+ } else if (rsltStr === 'fail') {
235
+ msgRsltCode = RaftMsgResultCode.MESSAGE_RESULT_FAIL;
236
+ RaftLog.warn(
237
+ `_handleResponseMessages RICREST rslt fail ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()} resp ${restStr}`,
238
+ );
239
+ } else {
240
+ RaftLog.warn(
241
+ `_handleResponseMessages RICREST rslt not recognized ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()}resp ${restStr}`,
242
+ );
243
+ }
244
+
245
+ } else {
246
+ RaftLog.warn(
247
+ `_handleResponseMessages RICREST response doesn't contain rslt ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()}resp ${restStr}`,
248
+ );
249
+ }
250
+
251
+ // Handle matching of request and response
252
+ this.msgTrackingRxRespMsg(rxMsgNum, msgRsltCode, msgRsltJsonObj);
253
+
254
+ } catch (excp: unknown) {
255
+ if (excp instanceof Error) {
256
+ RaftLog.warn(
257
+ `_handleResponseMessages Failed to parse JSON ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()} JSON STR ${restStr} resp ${excp.toString()}`,
258
+ );
259
+ }
260
+ }
261
+
262
+ }
263
+
264
+ _handleReportMessages(restStr: string): void {
265
+ try {
266
+ const reportMsg: RaftReportMsg = JSON.parse(restStr);
267
+ reportMsg.timeReceived = Date.now();
268
+ RaftLog.debug(`_handleReportMessages ${JSON.stringify(reportMsg)}`);
269
+ this._reportMsgCallbacks.forEach((callback) => callback(reportMsg));
270
+ } catch (excp: unknown) {
271
+ if (excp instanceof Error) {
272
+ RaftLog.warn(
273
+ `_handleReportMessages Failed to parse JSON report ${excp.toString()}`,
274
+ );
275
+ }
276
+ }
277
+ }
278
+
279
+ async sendRICRESTURL<T>(
280
+ cmdStr: string,
281
+ bridgeID: number | undefined = undefined,
282
+ msgTimeoutMs: number | undefined = undefined,
283
+ ): Promise<T> {
284
+ // Send
285
+ return this.sendRICREST(
286
+ cmdStr,
287
+ RICRESTElemCode.RICREST_ELEM_CODE_URL,
288
+ bridgeID,
289
+ msgTimeoutMs,
290
+ );
291
+ }
292
+
293
+ async sendRICRESTCmdFrame<T>(
294
+ cmdStr: string,
295
+ bridgeID: number | undefined = undefined,
296
+ msgTimeoutMs: number | undefined = undefined,
297
+ ): Promise<T> {
298
+ // Send
299
+ return this.sendRICREST(
300
+ cmdStr,
301
+ RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
302
+ bridgeID,
303
+ msgTimeoutMs,
304
+ );
305
+ }
306
+
307
+ async sendRICREST<T>(
308
+ cmdStr: string,
309
+ ricRESTElemCode: RICRESTElemCode,
310
+ bridgeID: number | undefined = undefined,
311
+ msgTimeoutMs: number | undefined = undefined,
312
+ ): Promise<T> {
313
+ // Put cmdStr into buffer
314
+ const cmdStrTerm = new Uint8Array(cmdStr.length + 1);
315
+ RaftUtils.addStringToBuffer(cmdStrTerm, cmdStr, 0);
316
+ cmdStrTerm[cmdStrTerm.length - 1] = 0;
317
+
318
+ // Send
319
+ return this.sendRICRESTBytes(
320
+ cmdStrTerm,
321
+ ricRESTElemCode,
322
+ true,
323
+ bridgeID,
324
+ msgTimeoutMs,
325
+ );
326
+ }
327
+
328
+ async sendRICRESTNoResp(
329
+ cmdStr: string,
330
+ ricRESTElemCode: RICRESTElemCode,
331
+ bridgeID: number | undefined = undefined,
332
+ ): Promise<boolean> {
333
+
334
+ // Check there is a sender
335
+ if (!this._msgSender) {
336
+ return false;
337
+ }
338
+
339
+ // Put cmdStr into buffer
340
+ const cmdBytes = new Uint8Array(cmdStr.length + 1);
341
+ RaftUtils.addStringToBuffer(cmdBytes, cmdStr, 0);
342
+ cmdBytes[cmdBytes.length - 1] = 0;
343
+
344
+ // Form message
345
+ const cmdMsg = new Uint8Array(cmdBytes.length + RICREST_HEADER_PAYLOAD_POS);
346
+ cmdMsg[RICREST_REST_ELEM_CODE_POS] = ricRESTElemCode;
347
+ cmdMsg.set(cmdBytes, RICREST_HEADER_PAYLOAD_POS);
348
+
349
+ // Frame the message
350
+ let framedMsg = this.frameCommsMsg(cmdMsg,
351
+ RaftCommsMsgTypeCode.MSG_TYPE_COMMAND,
352
+ RaftCommsMsgProtocol.MSG_PROTOCOL_RICREST,
353
+ true);
354
+
355
+ // Wrap if bridged
356
+ if (bridgeID !== undefined) {
357
+ framedMsg = this.bridgeCommsMsg(framedMsg, bridgeID);
358
+ }
359
+
360
+ // Encode like HDLC
361
+ const encodedMsg = this._miniHDLC.encode(framedMsg);
362
+
363
+ // Send
364
+ if (!await this._msgSender.sendTxMsg(encodedMsg, false)) {
365
+ RaftLog.warn(`sendRICRESTNoResp failed to send message`);
366
+ this._commsStats.recordMsgNoConnection();
367
+ }
368
+
369
+ return true;
370
+ }
371
+
372
+ async sendRICRESTBytes<T>(
373
+ cmdBytes: Uint8Array,
374
+ ricRESTElemCode: RICRESTElemCode,
375
+ withResponse: boolean,
376
+ bridgeID: number | undefined = undefined,
377
+ msgTimeoutMs: number | undefined = undefined,
378
+ ): Promise<T> {
379
+ // Form message
380
+ const cmdMsg = new Uint8Array(cmdBytes.length + RICREST_HEADER_PAYLOAD_POS);
381
+ cmdMsg[RICREST_REST_ELEM_CODE_POS] = ricRESTElemCode;
382
+ cmdMsg.set(cmdBytes, RICREST_HEADER_PAYLOAD_POS);
383
+
384
+ // Send
385
+ return this.sendMsgAndWaitForReply<T>(
386
+ cmdMsg,
387
+ RaftCommsMsgTypeCode.MSG_TYPE_COMMAND,
388
+ RaftCommsMsgProtocol.MSG_PROTOCOL_RICREST,
389
+ withResponse,
390
+ bridgeID,
391
+ msgTimeoutMs,
392
+ );
393
+ }
394
+
395
+ async sendMsgAndWaitForReply<T>(
396
+ msgPayload: Uint8Array,
397
+ msgDirection: RaftCommsMsgTypeCode,
398
+ msgProtocol: RaftCommsMsgProtocol,
399
+ withResponse: boolean,
400
+ bridgeID: number | undefined = undefined,
401
+ msgTimeoutMs: number | undefined,
402
+ ): Promise<T> {
403
+
404
+ // Check there is a sender
405
+ if (!this._msgSender) {
406
+ throw new Error('sendMsgAndWaitForReply failed no sender');
407
+ }
408
+
409
+ // Frame the message
410
+ let framedMsg = this.frameCommsMsg(msgPayload, msgDirection, msgProtocol, true);
411
+
412
+ // Wrap if bridged
413
+ if (bridgeID !== undefined) {
414
+ framedMsg = this.bridgeCommsMsg(framedMsg, bridgeID);
415
+ // RaftLog.debug(`sendMsgAndWaitForReply - bridged idx ${bridgeID}`)
416
+ } else {
417
+ // RaftLog.debug(`sendMsgAndWaitForReply - not bridged`)
418
+ }
419
+
420
+ // Encode like HDLC
421
+ const encodedMsg = this._miniHDLC.encode(framedMsg);
422
+
423
+ // Debug
424
+ // RaftLog.debug(
425
+ // `sendMsgAndWaitForReply ${RaftUtils.bufferToHex(encodedMsg)}`,
426
+ // );
427
+
428
+ // Return a promise that will be resolved when a reply is received or timeout occurs
429
+ const promise = new Promise<T>((resolve, reject) => {
430
+
431
+ // Update message tracking
432
+ this.msgTrackingTxCmdMsg<T>(
433
+ encodedMsg,
434
+ withResponse,
435
+ bridgeID,
436
+ msgTimeoutMs,
437
+ resolve,
438
+ reject,
439
+ );
440
+ this._currentMsgHandle++;
441
+ });
442
+
443
+ return promise;
444
+ }
445
+
446
+ frameCommsMsg(
447
+ msgPayload: Uint8Array,
448
+ msgDirection: RaftCommsMsgTypeCode,
449
+ msgProtocol: RaftCommsMsgProtocol,
450
+ isNumbered: boolean,
451
+ ): Uint8Array {
452
+ // Header
453
+ const msgBuf = new Uint8Array(
454
+ msgPayload.length + RICSERIAL_PAYLOAD_POS,
455
+ );
456
+ msgBuf[RICSERIAL_MSG_NUM_POS] = isNumbered ? this._currentMsgNumber & 0xff : 0;
457
+ msgBuf[RICSERIAL_PROTOCOL_POS] = (msgDirection << 6) + msgProtocol;
458
+
459
+ // Payload
460
+ msgBuf.set(msgPayload, RICSERIAL_PAYLOAD_POS);
461
+
462
+ // Return framed message
463
+ return msgBuf;
464
+ }
465
+
466
+ bridgeCommsMsg(
467
+ msgBuf: Uint8Array,
468
+ bridgeID: number
469
+ ) {
470
+ //
471
+ const bridgedMsg = new Uint8Array(msgBuf.length + RICREST_BRIDGE_PAYLOAD_POS);
472
+
473
+ // Bridged messages are unnumbered (msgNum == 0)
474
+ bridgedMsg[RICSERIAL_MSG_NUM_POS] = 0;
475
+ bridgedMsg[RICSERIAL_PROTOCOL_POS] = (RaftCommsMsgTypeCode.MSG_TYPE_COMMAND << 6) + RaftCommsMsgProtocol.MSG_PROTOCOL_BRIDGE_RICREST;
476
+ bridgedMsg[RICREST_BRIDGE_ID_POS] = bridgeID;
477
+ bridgedMsg.set(msgBuf, RICREST_BRIDGE_PAYLOAD_POS);
478
+ return bridgedMsg;
479
+ }
480
+
481
+ msgTrackingTxCmdMsg<T>(
482
+ msgFrame: Uint8Array,
483
+ withResponse: boolean,
484
+ bridgeID: number | undefined = undefined,
485
+ msgTimeoutMs: number | undefined,
486
+ resolve: (arg: T) => void,
487
+ reject: (reason: Error) => void,
488
+ ): void {
489
+ // Record message re-use of number
490
+ if (this._msgTrackInfos[this._currentMsgNumber].msgOutstanding) {
491
+ this._commsStats.recordMsgNumCollision();
492
+ }
493
+ // Set tracking info
494
+ this._msgTrackInfos[this._currentMsgNumber].set(
495
+ true,
496
+ msgFrame,
497
+ withResponse,
498
+ bridgeID,
499
+ this._currentMsgHandle,
500
+ msgTimeoutMs,
501
+ resolve,
502
+ reject,
503
+ );
504
+
505
+ // Debug
506
+ RaftLog.debug(
507
+ `msgTrackingTxCmdMsg msgNum ${this._currentMsgNumber} bridgeID ${bridgeID} msg ${
508
+ RaftUtils.bufferToHex(msgFrame)} msgOutstanding ${this._msgTrackInfos[this._currentMsgNumber].msgOutstanding
509
+ }`,
510
+ );
511
+
512
+ // Stats
513
+ this._commsStats.msgTx();
514
+
515
+ // Bump msg number
516
+ if (this._currentMsgNumber == RaftMsgTrackInfo.MAX_MSG_NUM) {
517
+ this._currentMsgNumber = 1;
518
+ } else {
519
+ this._currentMsgNumber++;
520
+ }
521
+ }
522
+
523
+ msgTrackingRxRespMsg(
524
+ msgNum: number,
525
+ msgRsltCode: RaftMsgResultCode,
526
+ msgRsltJsonObj: object,
527
+ ) {
528
+ // Check message number
529
+ if (msgNum == 0) {
530
+ // Callback on unnumbered message
531
+ if (this._msgResultHandler !== null)
532
+ this._msgResultHandler.onRxUnnumberedMsg(msgRsltJsonObj);
533
+ return;
534
+ }
535
+ if (msgNum > RaftMsgTrackInfo.MAX_MSG_NUM) {
536
+ RaftLog.warn('msgTrackingRxRespMsg msgNum > 255');
537
+ return;
538
+ }
539
+ if (!this._msgTrackInfos[msgNum].msgOutstanding) {
540
+ RaftLog.warn(`msgTrackingRxRespMsg unmatched msgNum ${msgNum}`);
541
+ this._commsStats.recordMsgNumUnmatched();
542
+ return;
543
+ }
544
+
545
+ // Handle message
546
+ RaftLog.verbose(
547
+ `msgTrackingRxRespMsg Message response received msgNum ${msgNum}`,
548
+ );
549
+ this._commsStats.recordMsgResp(
550
+ Date.now() - this._msgTrackInfos[msgNum].msgSentMs,
551
+ );
552
+ this._msgCompleted(msgNum, msgRsltCode, msgRsltJsonObj);
553
+ }
554
+
555
+ _msgCompleted(
556
+ msgNum: number,
557
+ msgRsltCode: RaftMsgResultCode,
558
+ msgRsltObj: object | null,
559
+ ) {
560
+
561
+ // Lookup message in tracking
562
+ const msgHandle = this._msgTrackInfos[msgNum].msgHandle;
563
+ this._msgTrackInfos[msgNum].msgOutstanding = false;
564
+
565
+ // Check if message result handler should be informed
566
+ if (this._msgResultHandler !== null) {
567
+ this._msgResultHandler.onRxReply(msgHandle, msgRsltCode, msgRsltObj);
568
+ }
569
+
570
+ // Handle reply
571
+ // if (msgRsltCode === RaftMsgResultCode.MESSAGE_RESULT_OK) {
572
+ const resolve = this._msgTrackInfos[msgNum].resolve;
573
+ if (resolve) {
574
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
575
+ RaftLog.debug(`_msgCompleted msgNum ${msgNum} result ${msgRsltCode.toString()} ${JSON.stringify(msgRsltObj)}`);
576
+ (resolve as ((arg: object | null) => void))(msgRsltObj);
577
+ }
578
+ // } else {
579
+ // const reject = this._msgTrackInfos[msgNum].reject;
580
+ // if (reject) {
581
+ // // eslint-disable-next-line @typescript-eslint/no-explicit-any
582
+ // try {
583
+ // RaftLog.debug(`_msgCompleted reject rsltCode ${msgRsltCode}`);
584
+ // // (reject as any)(new Error(`Message failed msgNum ${msgNum} rslt ${msgRsltCode}`));
585
+ // } catch (excp: unknown) {
586
+ // RaftLog.warn(`_msgCompleted reject ${excp}`);
587
+ // }
588
+ // }
589
+ // }
590
+
591
+ // No longer waiting for reply
592
+ this._msgTrackInfos[msgNum].resolve = null;
593
+ this._msgTrackInfos[msgNum].reject = null;
594
+ }
595
+
596
+ // Check message timeouts
597
+ async _onMsgTrackTimer(chainRecall: boolean): Promise<void> {
598
+
599
+ if (this._msgSender !== null) {
600
+ // Handle message tracking
601
+ for (let loopIdx = 0; loopIdx < this._msgTrackInfos.length; loopIdx++) {
602
+
603
+ // Index to check
604
+ const checkIdx = this._msgTrackLastCheckIdx;
605
+ this._msgTrackLastCheckIdx = (checkIdx + 1) % this._msgTrackInfos.length;
606
+
607
+ // Check if message is outstanding
608
+ if (!this._msgTrackInfos[checkIdx].msgOutstanding) continue;
609
+
610
+ // Get message timeout and ensure valid
611
+ let msgTimeoutMs = this._msgTrackInfos[checkIdx].msgTimeoutMs;
612
+ if (msgTimeoutMs === undefined) {
613
+ msgTimeoutMs = RaftMsgTrackInfo.MSG_RESPONSE_TIMEOUT_MS;
614
+ }
615
+
616
+ // Check for timeout (or never sent)
617
+ if ((this._msgTrackInfos[checkIdx].retryCount === 0) || (Date.now() > this._msgTrackInfos[checkIdx].msgSentMs + (msgTimeoutMs * (this._msgTrackInfos[checkIdx].retryCount)))) {
618
+
619
+ // Debug
620
+ RaftLog.debug(`msgTrackTimer msgNum ${checkIdx} ${this._msgTrackInfos[checkIdx].retryCount === 0 ? 'first send' : 'timeout - retrying'} ${RaftUtils.bufferToHex(this._msgTrackInfos[checkIdx].msgFrame)}`);
621
+ // RaftLog.verbose(`msgTrackTimer msg ${RaftUtils.bufferToHex(this._msgTrackInfos[i].msgFrame)}`);
622
+
623
+ // Handle timeout (or first send)
624
+ if (this._msgTrackInfos[checkIdx].retryCount < RaftMsgTrackInfo.MSG_RETRY_COUNT) {
625
+ this._msgTrackInfos[checkIdx].retryCount++;
626
+ try {
627
+
628
+ // Send the message
629
+ if (!await this._msgSender.sendTxMsg(
630
+ this._msgTrackInfos[checkIdx].msgFrame,
631
+ this._msgTrackInfos[checkIdx].withResponse)) {
632
+ RaftLog.warn(`msgTrackTimer Message send failed msgNum ${checkIdx} ${RaftUtils.bufferToHex(this._msgTrackInfos[checkIdx].msgFrame)}`);
633
+ this._msgCompleted(checkIdx, RaftMsgResultCode.MESSAGE_RESULT_FAIL, null);
634
+ this._commsStats.recordMsgNoConnection();
635
+ }
636
+
637
+ // Message sent ok so break here
638
+ break;
639
+
640
+ } catch (error: unknown) {
641
+ RaftLog.warn(`Retry message failed ${error}`);
642
+ }
643
+ this._commsStats.recordMsgRetry();
644
+ this._msgTrackInfos[checkIdx].msgSentMs = Date.now();
645
+ } else {
646
+ RaftLog.warn(
647
+ `msgTrackTimer TIMEOUT msgNum ${checkIdx} after ${RaftMsgTrackInfo.MSG_RETRY_COUNT} retries ${RaftUtils.bufferToHex(this._msgTrackInfos[checkIdx].msgFrame)}`,
648
+ );
649
+ this._msgCompleted(checkIdx, RaftMsgResultCode.MESSAGE_RESULT_TIMEOUT, null);
650
+ this._commsStats.recordMsgTimeout();
651
+ }
652
+ }
653
+ }
654
+ }
655
+
656
+ // Call again if required
657
+ if (chainRecall) {
658
+ setTimeout(async () => {
659
+ this._onMsgTrackTimer(true);
660
+ }, this._msgTrackTimerMs);
661
+ }
662
+ }
663
+
664
+ encodeFileStreamBlock(blockContents: Uint8Array,
665
+ blockStart: number,
666
+ streamID: number): Uint8Array {
667
+ // Create entire message buffer (including protocol wrappers)
668
+ const msgBuf = new Uint8Array(
669
+ blockContents.length + 4 + RICREST_HEADER_PAYLOAD_POS + RICSERIAL_PAYLOAD_POS,
670
+ );
671
+ let msgBufPos = 0;
672
+
673
+ // RICSERIAL protocol
674
+ msgBuf[msgBufPos++] = 0; // not numbered
675
+ msgBuf[msgBufPos++] =
676
+ (RaftCommsMsgTypeCode.MSG_TYPE_COMMAND << 6) +
677
+ RaftCommsMsgProtocol.MSG_PROTOCOL_RICREST;
678
+
679
+ // RICREST protocol
680
+ msgBuf[msgBufPos++] = RICRESTElemCode.RICREST_ELEM_CODE_FILEBLOCK;
681
+
682
+ // Buffer header
683
+ msgBuf[msgBufPos++] = streamID & 0xff;
684
+ msgBuf[msgBufPos++] = (blockStart >> 16) & 0xff;
685
+ msgBuf[msgBufPos++] = (blockStart >> 8) & 0xff;
686
+ msgBuf[msgBufPos++] = blockStart & 0xff;
687
+
688
+ // Copy block info
689
+ msgBuf.set(blockContents, msgBufPos);
690
+ return msgBuf;
691
+ }
692
+
693
+ async sendFileBlock(
694
+ blockContents: Uint8Array,
695
+ blockStart: number
696
+ ): Promise<boolean> {
697
+ const msgBuf = this.encodeFileStreamBlock(blockContents, blockStart, 0);
698
+
699
+ // // Debug
700
+ // RaftLog.debug(
701
+ // `sendFileBlock frameLen ${msgBuf.length} start ${blockStart} end ${blockEnd} len ${blockLen}`,
702
+ // );
703
+
704
+ // Send
705
+ try {
706
+ // Send
707
+ if (this._msgSender) {
708
+
709
+ // Wrap into HDLC
710
+ const framedMsg = this._miniHDLC.encode(msgBuf);
711
+
712
+ // Send
713
+ return this._msgSender.sendTxMsg(
714
+ framedMsg,
715
+ true,
716
+ // Platform.OS === 'ios',
717
+ );
718
+ }
719
+ } catch (error: unknown) {
720
+ RaftLog.warn(`RaftMsgHandler sendFileBlock error${error}`);
721
+ }
722
+ return false;
723
+ }
724
+
725
+ async sendStreamBlock(
726
+ blockContents: Uint8Array,
727
+ blockStart: number,
728
+ streamID: number,
729
+ ): Promise<boolean> {
730
+
731
+ // Ensure any waiting messages are sent first
732
+ await this._onMsgTrackTimer(false);
733
+
734
+ // Encode message
735
+ const msgBuf = this.encodeFileStreamBlock(blockContents, blockStart, streamID);
736
+
737
+ // // Debug
738
+ // RaftLog.debug(
739
+ // `sendStreamBlock frameLen ${msgBuf.length} start ${blockStart} end ${blockEnd} len ${blockLen}`,
740
+ // );
741
+
742
+ // Send
743
+ try {
744
+ // Send
745
+ if (this._msgSender) {
746
+
747
+ // Wrap into HDLC
748
+ const framedMsg = this._miniHDLC.encode(msgBuf);
749
+
750
+ // Send
751
+ return await this._msgSender.sendTxMsg(
752
+ framedMsg,
753
+ true,
754
+ // Platform.OS === 'ios',
755
+ );
756
+ }
757
+ } catch (error: unknown) {
758
+ RaftLog.warn(`RaftMsgHandler sendStreamBlock error${error}`);
759
+ }
760
+ return false;
761
+ }
762
+
763
+ async createCommsBridge(bridgeSource: string, bridgeName: string, idleCloseSecs = 0): Promise<RaftBridgeSetupResp> {
764
+
765
+ // Establish a bridge
766
+ return await this.sendRICRESTURL<RaftBridgeSetupResp>(
767
+ `commandserial/bridge/setup?port=${bridgeSource}&name=${bridgeName}&idleCloseSecs=${idleCloseSecs}`,
768
+ )
769
+ }
770
+
771
+ async removeCommsBridge(bridgeID: number): Promise<boolean> {
772
+
773
+ // Remove a bridge
774
+ return await this.sendRICRESTURL<boolean>(
775
+ `commandserial/bridge/remove?id=${bridgeID}`,
776
+ )
777
+ }
778
+ }