@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,668 @@
1
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2
+ //
3
+ // RaftFileHandler
4
+ // Communications Library
5
+ //
6
+ // Rob Dobson & Chris Greening 2020-2022
7
+ // (C) 2020-2022
8
+ //
9
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
10
+
11
+ import RaftLog from './RaftLog'
12
+ import RaftMsgHandler from './RaftMsgHandler';
13
+ import {
14
+ RaftFileDownloadResult,
15
+ RaftFileDownloadStartResp,
16
+ RaftFileSendType,
17
+ RaftFileStartResp,
18
+ RaftOKFail,
19
+ RaftProgressCBType,
20
+ } from './RaftTypes';
21
+ import RaftCommsStats from './RaftCommsStats';
22
+ import RaftUtils from './RaftUtils';
23
+ import RaftMiniHDLC from './RaftMiniHDLC';
24
+ import { RICRESTElemCode } from './RaftProtocolDefs';
25
+
26
+ class FileBlockTrackInfo {
27
+ isDone = false;
28
+ prom: Promise<boolean>;
29
+ constructor(prom: Promise<boolean>) {
30
+ this.prom = prom;
31
+ this.prom.then(
32
+ () => {
33
+ // RaftLog.debug('send complete');
34
+ this.isDone = true;
35
+ },
36
+ rej => {
37
+ RaftLog.debug(`FileBlockTrackInfo send rejected ${rej}`);
38
+ this.isDone = true;
39
+ },
40
+ );
41
+ }
42
+ isComplete() {
43
+ return this.isDone;
44
+ }
45
+ get() {
46
+ return this.prom;
47
+ }
48
+ }
49
+
50
+ export default class RaftFileHandler {
51
+ private _msgHandler: RaftMsgHandler;
52
+
53
+ // Timeouts
54
+ private BLOCK_ACK_TIMEOUT_MS = 30000;
55
+
56
+ // Contents of file to send
57
+ private _requestedFileBlockSize = 500;
58
+ private _fileBlockSize = 0;
59
+ private _requestedBatchAckSize = 10;
60
+ private _batchAckSize = 0;
61
+
62
+ // File sending flow control
63
+ private _sendWithoutBatchAcks = false;
64
+ private _ackedFilePos = 0;
65
+ private _batchAckReceived = false;
66
+ private _isTxCancelled = false;
67
+
68
+ // File receive info
69
+ private _isRxCancelled = false;
70
+ private _fileRxActive = false;
71
+ private _fileRxBatchMsgSize = 0;
72
+ private _fileRxBatchAckSize = 0;
73
+ private _fileRxStreamID = 0;
74
+ private _fileRxFileLen = 0;
75
+ private _fileRxCrc16 = 0;
76
+ private _fileRxBuffer = new Uint8Array(0);
77
+ private _fileRxLastAckTime = 0;
78
+ private _fileRxLastBlockTime = 0;
79
+ private _fileRxLastAckPos = 0;
80
+ private OVERALL_FILE_TRANSFER_TIMEOUT_MS = 100000;
81
+ private FILE_RX_ACK_RESEND_TIMEOUT_MS = 1000;
82
+
83
+ // RaftCommsStats
84
+ private _commsStats: RaftCommsStats;
85
+
86
+ // Message await list
87
+ private _msgAwaitList: Array<FileBlockTrackInfo> = new Array<FileBlockTrackInfo>();
88
+ private MAX_OUTSTANDING_FILE_BLOCK_SEND_PROMISES = 1;
89
+
90
+ constructor(msgHandler: RaftMsgHandler, commsStats: RaftCommsStats) {
91
+ this._msgHandler = msgHandler;
92
+ this._commsStats = commsStats;
93
+ this._fileBlockSize = this._requestedFileBlockSize;
94
+ this.onOktoMsg = this.onOktoMsg.bind(this);
95
+ }
96
+
97
+ setRequestedFileBlockSize(blockSize: number){
98
+ this._requestedFileBlockSize = blockSize;
99
+ }
100
+
101
+ setRequestedBatchAckSize(batchAckSize: number){
102
+ this._requestedBatchAckSize = batchAckSize;
103
+ }
104
+
105
+ async fileSend(
106
+ fileName: string,
107
+ fileType: RaftFileSendType,
108
+ fileDest: string,
109
+ fileContents: Uint8Array,
110
+ progressCallback: ((sent: number, total: number, progress: number) => void) | undefined,
111
+ ): Promise<boolean> {
112
+ this._isTxCancelled = false;
113
+
114
+ // Send file start message
115
+ if (!await this._sendFileStartMsg(fileName, fileType, fileDest, fileContents))
116
+ return false;
117
+
118
+ // Send contents
119
+ if (!await this._sendFileContents(fileContents, progressCallback))
120
+ return false;
121
+
122
+ // Send file end
123
+ await this._sendFileEndMsg(fileName, fileType, fileDest, fileContents);
124
+
125
+ // Clean up
126
+ await this.awaitOutstandingMsgPromises(true);
127
+
128
+ // Complete
129
+ return true;
130
+ }
131
+
132
+ async fileSendCancel(): Promise<void> {
133
+ // Await outstanding promises
134
+ await this.awaitOutstandingMsgPromises(true);
135
+ this._isTxCancelled = true;
136
+ }
137
+
138
+ // Send the start message
139
+ async _sendFileStartMsg(
140
+ fileName: string,
141
+ fileType: RaftFileSendType,
142
+ fileDest: string,
143
+ fileContents: Uint8Array,
144
+ ): Promise<boolean> {
145
+ // File start command message
146
+ const reqStr =
147
+ fileType == RaftFileSendType.FIRMWARE_UPDATE
148
+ ? 'espfwupdate'
149
+ : 'fileupload';
150
+
151
+ const fileLen = fileContents.length;
152
+ const cmdMsg = `{"cmdName":"ufStart","reqStr":"${reqStr}","fileType":"${fileDest}","fileName":"${fileName}","fileLen":${fileLen},"batchMsgSize":${this._requestedFileBlockSize},"batchAckSize":${this._requestedBatchAckSize}}`;
153
+
154
+ // Debug
155
+ RaftLog.debug(`sendFileStartMsg ${cmdMsg}`);
156
+
157
+ // Send
158
+ let fileStartResp = null;
159
+ try {
160
+ fileStartResp = await this._msgHandler.sendRICREST<RaftFileStartResp>(
161
+ cmdMsg,
162
+ RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
163
+ );
164
+ } catch (err) {
165
+ RaftLog.error(`sendFileStartMsg error ${err}`);
166
+ return false;
167
+ }
168
+ if (fileStartResp.rslt !== 'ok') {
169
+ RaftLog.error(`sendFileStartMsg error ${fileStartResp.rslt}`);
170
+ return false;
171
+ }
172
+
173
+ // Extract params
174
+ if (fileStartResp.batchMsgSize) {
175
+ this._fileBlockSize = fileStartResp.batchMsgSize;
176
+ } else {
177
+ this._fileBlockSize = this._requestedFileBlockSize;
178
+ }
179
+ if (fileStartResp.batchAckSize) {
180
+ this._batchAckSize = fileStartResp.batchAckSize;
181
+ } else {
182
+ this._batchAckSize = this._requestedBatchAckSize;
183
+ }
184
+ RaftLog.debug(
185
+ `_fileSendStartMsg fileBlockSize req ${this._requestedFileBlockSize} resp ${fileStartResp.batchMsgSize} actual ${this._fileBlockSize}`,
186
+ );
187
+ RaftLog.debug(
188
+ `_fileSendStartMsg batchAckSize req ${this._requestedBatchAckSize} resp ${fileStartResp.batchAckSize} actual ${this._batchAckSize}`,
189
+ );
190
+ return true;
191
+ }
192
+
193
+ async _sendFileEndMsg(
194
+ fileName: string,
195
+ fileType: RaftFileSendType,
196
+ fileDest: string,
197
+ fileContents: Uint8Array,
198
+ ): Promise<boolean> {
199
+ // File end command message
200
+ const reqStr =
201
+ fileType == RaftFileSendType.FIRMWARE_UPDATE
202
+ ? 'espfwupdate'
203
+ : 'fileupload';
204
+ const fileLen = fileContents.length;
205
+ const cmdMsg = `{"cmdName":"ufEnd","reqStr":"${reqStr}","fileType":"${fileDest}","fileName":"${fileName}","fileLen":${fileLen}}`;
206
+
207
+ // Await outstanding promises
208
+ try {
209
+ await this.awaitOutstandingMsgPromises(true);
210
+ } catch (err) {
211
+ // Ignore
212
+ RaftLog.error(`sendFileEndMsg awaitOutstandingMsgPromises error ${err}`);
213
+ }
214
+
215
+ // Send
216
+ let fileEndResp = null;
217
+ try {
218
+ fileEndResp = await this._msgHandler.sendRICREST<RaftOKFail>(
219
+ cmdMsg,
220
+ RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
221
+ );
222
+ } catch (err) {
223
+ RaftLog.error(`sendFileEndMsg error ${err}`);
224
+ return false;
225
+ }
226
+ return fileEndResp.rslt === 'ok';
227
+ }
228
+
229
+ async _sendFileCancelMsg(): Promise<void> {
230
+ // File cancel command message
231
+ const cmdMsg = `{"cmdName":"ufCancel"}`;
232
+
233
+ // Await outstanding promises
234
+ await this.awaitOutstandingMsgPromises(true);
235
+
236
+ // Send
237
+ try {
238
+ return await this._msgHandler.sendRICREST(
239
+ cmdMsg,
240
+ RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
241
+ );
242
+ } catch (err) {
243
+ RaftLog.error(`sendFileCancelMsg error ${err}`);
244
+ }
245
+ }
246
+
247
+ async _sendFileContents(
248
+ fileContents: Uint8Array,
249
+ progressCallback: ((sent: number, total: number, progress: number) => void) | undefined,
250
+ ): Promise<boolean> {
251
+ if (progressCallback) {
252
+ progressCallback(0, fileContents.length, 0);
253
+ }
254
+
255
+ this._batchAckReceived = false;
256
+ this._ackedFilePos = 0;
257
+
258
+ // Send file blocks
259
+ let progressUpdateCtr = 0;
260
+ while (this._ackedFilePos < fileContents.length) {
261
+ // Sending with or without batches
262
+ if (this._sendWithoutBatchAcks) {
263
+ // Debug
264
+ RaftLog.verbose(
265
+ `_sendFileContents NO BATCH ACKS ${progressUpdateCtr} blocks total sent ${this._ackedFilePos} block len ${this._fileBlockSize}`,
266
+ );
267
+ if (!await this._sendFileBlock(fileContents, this._ackedFilePos))
268
+ return false;
269
+ this._ackedFilePos += this._fileBlockSize;
270
+ progressUpdateCtr++;
271
+ } else {
272
+ // NOTE: first batch MUST be of size 1 (not _batchAckSize) because Raft performs a long-running
273
+ // blocking task immediately after receiving the first message in a firmware
274
+ // update - although this could be relaxed for non-firmware update file uploads
275
+ let sendFromPos = this._ackedFilePos;
276
+ const batchSize = sendFromPos == 0 ? 1 : this._batchAckSize;
277
+ for (
278
+ let i = 0;
279
+ i < batchSize && sendFromPos < fileContents.length;
280
+ i++
281
+ ) {
282
+ // Clear old batch acks
283
+ if (i == batchSize - 1) {
284
+ this._batchAckReceived = false;
285
+ }
286
+ // Debug
287
+ RaftLog.debug(
288
+ `_sendFileContents sendblock pos ${sendFromPos} len ${this._fileBlockSize} ackedTo ${this._ackedFilePos} fileLen ${fileContents.length}`,
289
+ );
290
+ if (!await this._sendFileBlock(fileContents, sendFromPos))
291
+ return false;
292
+ sendFromPos += this._fileBlockSize;
293
+ }
294
+
295
+ // Wait for response (there is a timeout at the ESP end to ensure a response is always returned
296
+ // even if blocks are dropped on reception at ESP) - the timeout here is for these responses
297
+ // being dropped
298
+ await this.batchAck(this.BLOCK_ACK_TIMEOUT_MS);
299
+ progressUpdateCtr += this._batchAckSize;
300
+ }
301
+
302
+ // Show progress
303
+ if ((progressUpdateCtr >= 20) && progressCallback) {
304
+ // Update UI
305
+ progressCallback(
306
+ this._ackedFilePos,
307
+ fileContents.length,
308
+ this._ackedFilePos / fileContents.length,
309
+ );
310
+
311
+ // Debug
312
+ RaftLog.debug(
313
+ `_sendFileContents ${progressUpdateCtr} blocks sent OkTo ${this._ackedFilePos} block len ${this._fileBlockSize}`,
314
+ );
315
+
316
+ // Continue
317
+ progressUpdateCtr = 0;
318
+ }
319
+ }
320
+ return true;
321
+ }
322
+
323
+ async batchAck(timeout: number): Promise<void> {
324
+ // Handle acknowledgement to a batch (OkTo message)
325
+ return new Promise((resolve, reject) => {
326
+ const startTime = Date.now();
327
+ const checkForAck = async () => {
328
+ if (this._isTxCancelled) {
329
+ RaftLog.debug('checkForAck - cancelling file upload');
330
+ this._isTxCancelled = false;
331
+ // Send cancel
332
+ await this._sendFileCancelMsg();
333
+ // abort the upload process
334
+ reject(new Error('Update Cancelled'));
335
+ return;
336
+ }
337
+ if (this._batchAckReceived) {
338
+ RaftLog.debug(`checkForAck - rx OkTo ${this._ackedFilePos}`);
339
+ this._batchAckReceived = false;
340
+ resolve();
341
+ return;
342
+ } else {
343
+ const now = Date.now();
344
+ if (now - startTime > timeout) {
345
+ RaftLog.warn(`checkForAck - time-out no new ack received`);
346
+ reject(new Error('Update failed. Please try again.'));
347
+ return;
348
+ }
349
+ setTimeout(checkForAck, 100);
350
+ }
351
+ };
352
+ checkForAck();
353
+ });
354
+ }
355
+
356
+ async _sendFileBlock(
357
+ fileContents: Uint8Array,
358
+ blockStart: number,
359
+ ): Promise<boolean> {
360
+ // Calc block start and end
361
+ const blockEnd = Math.min(
362
+ fileContents.length,
363
+ blockStart + this._fileBlockSize,
364
+ );
365
+
366
+ // Check if we need to await a message send promise
367
+ await this.awaitOutstandingMsgPromises(false);
368
+
369
+ // Send
370
+ const promRslt = this._msgHandler.sendFileBlock(fileContents.subarray(blockStart, blockEnd), blockStart);
371
+ if (!promRslt) {
372
+ return false;
373
+ }
374
+
375
+ // Record
376
+ this._commsStats.recordFileBytes(blockEnd - blockStart);
377
+
378
+ // Add to list of pending messages
379
+ this._msgAwaitList.push(new FileBlockTrackInfo(promRslt));
380
+
381
+ // Debug
382
+ // RaftLog.debug(
383
+ // `sendFileBlock start ${blockStart} end ${blockEnd} len ${blockLen}`,
384
+ // );
385
+ return true;
386
+ }
387
+
388
+ onOktoMsg(fileOkTo: number) {
389
+ // Get how far we've progressed in file
390
+ this._ackedFilePos = fileOkTo;
391
+ this._batchAckReceived = true;
392
+ RaftLog.verbose(`onOktoMsg received file up to ${this._ackedFilePos}`);
393
+ }
394
+
395
+ async awaitOutstandingMsgPromises(all: boolean): Promise<void> {
396
+ // Check if all outstanding promises to be awaited
397
+ if (all) {
398
+ for (const promRslt of this._msgAwaitList) {
399
+ try {
400
+ await promRslt.get();
401
+ } catch (error: unknown) {
402
+ RaftLog.warn(`awaitAll file part send failed ${error}`);
403
+ }
404
+ }
405
+ this._msgAwaitList = [];
406
+ } else {
407
+ // RaftLog.debug('Await list len', this._msgAwaitList.length);
408
+ if (
409
+ this._msgAwaitList.length >=
410
+ this.MAX_OUTSTANDING_FILE_BLOCK_SEND_PROMISES
411
+ ) {
412
+ const fileBlockTrackInfo = this._msgAwaitList.shift();
413
+ try {
414
+ if (fileBlockTrackInfo) {
415
+ await fileBlockTrackInfo.get();
416
+ }
417
+ } catch (error: unknown) {
418
+ RaftLog.warn(`awaitSome file part send failed ${error}`);
419
+ }
420
+ }
421
+ }
422
+ }
423
+
424
+ async fileReceive(
425
+ fileName: string,
426
+ fileSource: string,
427
+ progressCallback: RaftProgressCBType | undefined,
428
+ ): Promise<RaftFileDownloadResult> {
429
+ this._isRxCancelled = false;
430
+
431
+ // Check for bridgeserial1..N as fileSource - in this case use the RICREST bridging protocol
432
+ // as attached devices using CommandSerial require bridging
433
+ let bridgeID: number | undefined = undefined;
434
+ const bridgeSerialPrefix = 'bridgeserial';
435
+ if (fileSource.startsWith(bridgeSerialPrefix)) {
436
+
437
+ // Establish a bridge
438
+ const bridgedDeviceSerialPort = "Serial" + fileSource.slice(bridgeSerialPrefix.length);
439
+ const cmdResp = await this._msgHandler.createCommsBridge(bridgedDeviceSerialPort, "fileSource");
440
+ if (cmdResp.rslt != "ok") {
441
+ RaftLog.error(`fileReceive - failed to setup bridge ${cmdResp.rslt}`);
442
+ return new RaftFileDownloadResult();
443
+ }
444
+ bridgeID = cmdResp.bridgeID;
445
+
446
+ // Debug
447
+ RaftLog.info(`fileReceive - bridge setup ${bridgeID}`);
448
+ }
449
+
450
+ // Send file start message
451
+ if (!await this._receiveFileStart(fileName, bridgeID))
452
+ return new RaftFileDownloadResult();
453
+
454
+ // Send contents
455
+ const fileContents = await this._receiveFileContents(progressCallback, bridgeID);
456
+
457
+ // Send file end
458
+ await this._receiveFileEnd(fileName, bridgeID);
459
+
460
+ // Clean up
461
+ await this.awaitOutstandingMsgPromises(true);
462
+
463
+ // Complete
464
+ return fileContents;
465
+ }
466
+
467
+ async fileReceiveCancel(): Promise<void> {
468
+ this._isRxCancelled = true;
469
+ }
470
+
471
+ async _receiveFileStart(fileName: string, bridgeID: number | undefined) : Promise<boolean> {
472
+
473
+ const blockMaxSizeRequested = 5000;
474
+ const batchAckSizeRequested = 10;
475
+ const fileSrc = "fs";
476
+
477
+ // Request file transfer
478
+ // Frames follow the approach used in the web interface start, block..., end
479
+ const cmdMsg = `{"cmdName":"dfStart","reqStr":"getFile","fileType":"${fileSrc}",` +
480
+ `"batchMsgSize":${blockMaxSizeRequested},` +
481
+ `"batchAckSize":${batchAckSizeRequested},` +
482
+ `"fileName":"${fileName}"}`
483
+
484
+ // Send
485
+ let cmdResp = null;
486
+ try {
487
+ cmdResp = await this._msgHandler.sendRICREST<RaftFileDownloadStartResp>(
488
+ cmdMsg,
489
+ RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
490
+ bridgeID,
491
+ );
492
+ } catch (err) {
493
+ RaftLog.error(`_receiveFileStartMsg error ${err}`);
494
+ return false;
495
+ }
496
+ RaftLog.info(`_receiveFileStartMsg rslt ${JSON.stringify(cmdResp)}`);
497
+ if (cmdResp.rslt === 'ok') {
498
+ this._fileRxBatchMsgSize = cmdResp.batchMsgSize;
499
+ this._fileRxBatchAckSize = cmdResp.batchAckSize;
500
+ this._fileRxStreamID = cmdResp.streamID;
501
+ this._fileRxFileLen = cmdResp.fileLen;
502
+ this._fileRxCrc16 = parseInt(cmdResp.crc16, 16);
503
+ this._fileRxBuffer = new Uint8Array(0);
504
+ this._fileRxLastAckTime = 0;
505
+ this._fileRxLastAckPos = 0;
506
+ this._fileRxLastBlockTime = Date.now();
507
+ this._fileRxActive = true;
508
+ }
509
+ return cmdResp.rslt === 'ok';
510
+ }
511
+
512
+ async _receiveFileContents(
513
+ progressCallback: RaftProgressCBType | undefined,
514
+ bridgeID: number | undefined
515
+ ): Promise<RaftFileDownloadResult> {
516
+
517
+ // Wait for file to be received
518
+ return new Promise<RaftFileDownloadResult>((resolve, reject) => {
519
+ const startTime = Date.now();
520
+ const checkForComplete = async () => {
521
+
522
+ // Check if we've received the whole file
523
+ if (this._fileRxFileLen === this._fileRxBuffer.length) {
524
+ this._fileRxActive = false;
525
+ // Progress callback
526
+ if (progressCallback) {
527
+ progressCallback(this._fileRxBuffer.length, this._fileRxFileLen);
528
+ }
529
+
530
+ // Check CRC
531
+ const crc16 = RaftMiniHDLC.crc16(this._fileRxBuffer);
532
+ if (crc16 !== this._fileRxCrc16) {
533
+ RaftLog.error(`_receiveFileContents - CRC error ${crc16} ${this._fileRxCrc16}`);
534
+ reject(new Error('fileReceive CRC error'));
535
+ return;
536
+ } else {
537
+ RaftLog.info(`_receiveFileContents - CRC OK ${crc16} ${this._fileRxCrc16}`);
538
+ }
539
+ resolve(new RaftFileDownloadResult(this._fileRxBuffer));
540
+ return;
541
+ }
542
+
543
+ // Check if file transfer cancelled
544
+ if (this._isRxCancelled) {
545
+ RaftLog.info('_receiveFileContents - cancelling file upload');
546
+ this._isRxCancelled = false;
547
+ this._fileRxActive = false;
548
+ // Send cancel message
549
+ this._sendFileRxCancelMsg(bridgeID);
550
+ // abort the upload process
551
+ reject(new Error('fileReceive Cancelled'));
552
+ return;
553
+ }
554
+
555
+ // Check for timeouts
556
+ const now = Date.now();
557
+
558
+ // Check for overall timeouts
559
+ if ((now - startTime > this.OVERALL_FILE_TRANSFER_TIMEOUT_MS) ||
560
+ (now - this._fileRxLastBlockTime > this.BLOCK_ACK_TIMEOUT_MS)) {
561
+ RaftLog.warn(`_receiveFileContents - time-out no new data received`);
562
+ this._fileRxActive = false;
563
+ reject(new Error('fileReceive failed'));
564
+ return;
565
+ }
566
+
567
+ // Check if time to send ack
568
+ let ackRequired = false;
569
+ if (Date.now() - this._fileRxLastAckTime > this.FILE_RX_ACK_RESEND_TIMEOUT_MS) {
570
+ ackRequired = true;
571
+ }
572
+
573
+ // Check if position to send ack
574
+ if (this._fileRxBuffer.length - this._fileRxLastAckPos >= this._fileRxBatchAckSize * this._fileRxBatchMsgSize) {
575
+ ackRequired = true;
576
+ }
577
+ // RaftLog.info(`_receiveFileContents ${ackRequired ? "ACK_REQUIRED" : "ACK_NOTREQUIRED"} ${this._fileRxBuffer.length} ${this._fileRxLastAckPos} ${this._fileRxBatchAckSize} ${this._fileRxBatchMsgSize}`);
578
+
579
+ // Check if ack required
580
+ if (ackRequired) {
581
+
582
+ // Ack timing
583
+ this._fileRxLastAckTime = Date.now();
584
+ this._fileRxLastAckPos = this._fileRxBuffer.length;
585
+
586
+ // Okto message
587
+ const cmdMsg = `{"cmdName":"dfAck","okto":${this._fileRxBuffer.length},` +
588
+ `"streamID":${this._fileRxStreamID},"rslt":"ok"}`;
589
+
590
+ // Send without waiting for response
591
+ this._msgHandler.sendRICRESTNoResp(
592
+ cmdMsg,
593
+ RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
594
+ bridgeID
595
+ );
596
+
597
+ // Debug
598
+ RaftLog.verbose(`_receiveFileContents ack generated at ${this._fileRxBuffer.length} msg ${cmdMsg}`);
599
+ }
600
+
601
+ // Progress callback
602
+ if (progressCallback) {
603
+ progressCallback(this._fileRxBuffer.length, this._fileRxFileLen);
604
+ }
605
+
606
+ // Set timeout for next check
607
+ setTimeout(checkForComplete, 50);
608
+ };
609
+ checkForComplete();
610
+ });
611
+ }
612
+
613
+ async _receiveFileEnd(fileName: string, bridgeID: number | undefined): Promise<boolean> {
614
+
615
+ // Send file end message
616
+ const cmdMsg = `{"cmdName":"dfAck","reqStr":"getFile","okto":${this._fileRxBuffer.length},` +
617
+ `"fileName":"${fileName}","streamID":${this._fileRxStreamID},"rslt":"ok"}`
618
+ this._msgHandler.sendRICRESTNoResp(
619
+ cmdMsg,
620
+ RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
621
+ bridgeID
622
+ );
623
+
624
+ // No longer active
625
+ this._fileRxActive = false;
626
+ return false;
627
+ }
628
+
629
+ async _sendFileRxCancelMsg(bridgeID: number | undefined): Promise<void> {
630
+ // Send file end message
631
+ const cmdMsg = `{"cmdName":"dfCancel","reqStr":"getFile","streamID":${this._fileRxStreamID}}`
632
+ this._msgHandler.sendRICRESTNoResp(
633
+ cmdMsg,
634
+ RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME,
635
+ bridgeID
636
+ );
637
+ }
638
+
639
+ onFileBlock(
640
+ filePos: number,
641
+ fileBlockData: Uint8Array
642
+ ): void {
643
+ // RaftLog.info(`onFileBlock filePos ${filePos} fileBlockData ${RaftUtils.bufferToHex(fileBlockData)}`);
644
+
645
+ // Check if this is the next block we are expecting
646
+ if (filePos === this._fileRxBuffer.length) {
647
+
648
+ // Add to buffer
649
+ const tmpArray = new Uint8Array(this._fileRxBuffer.length + fileBlockData.length);
650
+ tmpArray.set(this._fileRxBuffer, 0);
651
+ tmpArray.set(fileBlockData, this._fileRxBuffer.length);
652
+ this._fileRxBuffer = tmpArray;
653
+
654
+ // Update last block time
655
+ this._fileRxLastBlockTime = Date.now();
656
+
657
+ // Debug
658
+ // RaftLog.info(`onFileBlock filePos ${filePos} fileBlockData ${RaftUtils.bufferToHex(fileBlockData)} added to buffer`);
659
+
660
+ } else {
661
+ RaftLog.warn(`onFileBlock expected streamID ${this._fileRxStreamID} filePos ${filePos} fileBlockData ${RaftUtils.bufferToHex(fileBlockData)} out of sequence`);
662
+ }
663
+ }
664
+
665
+ isFileRxActive(): boolean {
666
+ return this._fileRxActive;
667
+ }
668
+ }