@robotical/raftjs 2.1.2 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/devdocs/decode-overrun-investigation.md +167 -0
  2. package/devdocs/message-panel-design.md +320 -0
  3. package/dist/react-native/RaftAttributeHandler.d.ts +10 -1
  4. package/dist/react-native/RaftAttributeHandler.js +60 -8
  5. package/dist/react-native/RaftAttributeHandler.js.map +1 -1
  6. package/dist/react-native/RaftChannelSimulated.d.ts +1 -0
  7. package/dist/react-native/RaftChannelSimulated.js +67 -0
  8. package/dist/react-native/RaftChannelSimulated.js.map +1 -1
  9. package/dist/react-native/RaftConnector.d.ts +19 -1
  10. package/dist/react-native/RaftConnector.js +102 -2
  11. package/dist/react-native/RaftConnector.js.map +1 -1
  12. package/dist/react-native/RaftDeviceInfo.d.ts +5 -1
  13. package/dist/react-native/RaftDeviceManager.js +75 -32
  14. package/dist/react-native/RaftDeviceManager.js.map +1 -1
  15. package/dist/react-native/RaftFileHandler.d.ts +1 -0
  16. package/dist/react-native/RaftFileHandler.js +40 -11
  17. package/dist/react-native/RaftFileHandler.js.map +1 -1
  18. package/dist/react-native/RaftMicroPythonConsoleClient.d.ts +38 -0
  19. package/dist/react-native/RaftMicroPythonConsoleClient.js +45 -0
  20. package/dist/react-native/RaftMicroPythonConsoleClient.js.map +1 -0
  21. package/dist/react-native/RaftMsgHandler.d.ts +1 -1
  22. package/dist/react-native/RaftMsgHandler.js +6 -3
  23. package/dist/react-native/RaftMsgHandler.js.map +1 -1
  24. package/dist/react-native/RaftTypes.d.ts +19 -0
  25. package/dist/react-native/RaftTypes.js.map +1 -1
  26. package/dist/react-native/main.d.ts +1 -0
  27. package/dist/react-native/main.js +3 -1
  28. package/dist/react-native/main.js.map +1 -1
  29. package/dist/web/RaftAttributeHandler.d.ts +10 -1
  30. package/dist/web/RaftAttributeHandler.js +60 -8
  31. package/dist/web/RaftAttributeHandler.js.map +1 -1
  32. package/dist/web/RaftChannelSimulated.d.ts +1 -0
  33. package/dist/web/RaftChannelSimulated.js +67 -0
  34. package/dist/web/RaftChannelSimulated.js.map +1 -1
  35. package/dist/web/RaftConnector.d.ts +19 -1
  36. package/dist/web/RaftConnector.js +102 -2
  37. package/dist/web/RaftConnector.js.map +1 -1
  38. package/dist/web/RaftDeviceInfo.d.ts +5 -1
  39. package/dist/web/RaftDeviceManager.js +75 -32
  40. package/dist/web/RaftDeviceManager.js.map +1 -1
  41. package/dist/web/RaftFileHandler.d.ts +1 -0
  42. package/dist/web/RaftFileHandler.js +40 -11
  43. package/dist/web/RaftFileHandler.js.map +1 -1
  44. package/dist/web/RaftMicroPythonConsoleClient.d.ts +38 -0
  45. package/dist/web/RaftMicroPythonConsoleClient.js +45 -0
  46. package/dist/web/RaftMicroPythonConsoleClient.js.map +1 -0
  47. package/dist/web/RaftMsgHandler.d.ts +1 -1
  48. package/dist/web/RaftMsgHandler.js +6 -3
  49. package/dist/web/RaftMsgHandler.js.map +1 -1
  50. package/dist/web/RaftTypes.d.ts +19 -0
  51. package/dist/web/RaftTypes.js.map +1 -1
  52. package/dist/web/main.d.ts +1 -0
  53. package/dist/web/main.js +3 -1
  54. package/dist/web/main.js.map +1 -1
  55. package/examples/dashboard/src/DeviceActionsForm.tsx +24 -11
  56. package/examples/dashboard/src/DeviceLineChart.tsx +5 -4
  57. package/examples/dashboard/src/DevicePanel.tsx +3 -0
  58. package/package.json +1 -1
  59. package/src/RaftAttributeHandler.ts +89 -9
  60. package/src/RaftChannelSimulated.test.ts +62 -0
  61. package/src/RaftChannelSimulated.ts +91 -0
  62. package/src/RaftConnector.ts +112 -3
  63. package/src/RaftDeviceInfo.ts +5 -1
  64. package/src/RaftDeviceManager.test.ts +35 -1
  65. package/src/RaftDeviceManager.ts +86 -33
  66. package/src/RaftFileHandler.ts +43 -11
  67. package/src/RaftMicroPythonConsoleClient.ts +78 -0
  68. package/src/RaftMsgHandler.ts +8 -4
  69. package/src/RaftTypes.ts +23 -0
  70. package/src/main.ts +1 -0
@@ -65,6 +65,7 @@ export default class RaftFileHandler {
65
65
  private _ackedFilePos = 0;
66
66
  private _batchAckReceived = false;
67
67
  private _isTxCancelled = false;
68
+ private _fileTxStreamID = 0;
68
69
 
69
70
  // File receive info
70
71
  private _isRxCancelled = false;
@@ -110,6 +111,7 @@ export default class RaftFileHandler {
110
111
  progressCallback: ((sent: number, total: number, progress: number) => void) | undefined,
111
112
  ): Promise<boolean> {
112
113
  this._isTxCancelled = false;
114
+ this._fileTxStreamID = 0;
113
115
 
114
116
  // Send file start message
115
117
  if (!await this._sendFileStartMsg(fileName, fileType, fileDest, fileContents))
@@ -165,8 +167,8 @@ export default class RaftFileHandler {
165
167
  RaftLog.warn(`sendFileStartMsg error ${err}`);
166
168
  return false;
167
169
  }
168
- if (fileStartResp.rslt !== 'ok') {
169
- RaftLog.warn(`sendFileStartMsg error ${fileStartResp.rslt}`);
170
+ if (!fileStartResp || fileStartResp.rslt !== 'ok') {
171
+ RaftLog.warn(`sendFileStartMsg error ${fileStartResp?.rslt ?? 'no response'}`);
170
172
  return false;
171
173
  }
172
174
 
@@ -181,12 +183,17 @@ export default class RaftFileHandler {
181
183
  } else {
182
184
  this._batchAckSize = this._requestedBatchAckSize;
183
185
  }
186
+ const streamID = fileStartResp.streamID;
187
+ this._fileTxStreamID = streamID !== undefined && streamID > 0 && streamID <= 0xff ? streamID : 0;
184
188
  RaftLog.debug(
185
189
  `_fileSendStartMsg fileBlockSize req ${this._requestedFileBlockSize} resp ${fileStartResp.batchMsgSize} actual ${this._fileBlockSize}`,
186
190
  );
187
191
  RaftLog.debug(
188
192
  `_fileSendStartMsg batchAckSize req ${this._requestedBatchAckSize} resp ${fileStartResp.batchAckSize} actual ${this._batchAckSize}`,
189
193
  );
194
+ RaftLog.debug(
195
+ `_fileSendStartMsg streamID ${this._fileTxStreamID}`,
196
+ );
190
197
  return true;
191
198
  }
192
199
 
@@ -202,7 +209,8 @@ export default class RaftFileHandler {
202
209
  ? 'espfwupdate'
203
210
  : 'fileupload';
204
211
  const fileLen = fileContents.length;
205
- const cmdMsg = `{"cmdName":"ufEnd","reqStr":"${reqStr}","fileType":"${fileDest}","fileName":"${fileName}","fileLen":${fileLen}}`;
212
+ const streamIDJson = this._fileTxStreamID !== 0 ? `,"streamID":${this._fileTxStreamID}` : '';
213
+ const cmdMsg = `{"cmdName":"ufEnd","reqStr":"${reqStr}","fileType":"${fileDest}","fileName":"${fileName}","fileLen":${fileLen}${streamIDJson}}`;
206
214
 
207
215
  // Await outstanding promises
208
216
  try {
@@ -223,12 +231,13 @@ export default class RaftFileHandler {
223
231
  RaftLog.warn(`sendFileEndMsg error ${err}`);
224
232
  return false;
225
233
  }
226
- return fileEndResp.rslt === 'ok';
234
+ return fileEndResp?.rslt === 'ok';
227
235
  }
228
236
 
229
237
  async _sendFileCancelMsg(): Promise<void> {
230
238
  // File cancel command message
231
- const cmdMsg = `{"cmdName":"ufCancel"}`;
239
+ const streamIDJson = this._fileTxStreamID !== 0 ? `,"streamID":${this._fileTxStreamID}` : '';
240
+ const cmdMsg = `{"cmdName":"ufCancel"${streamIDJson}}`;
232
241
 
233
242
  // Await outstanding promises
234
243
  await this.awaitOutstandingMsgPromises(true);
@@ -367,7 +376,7 @@ export default class RaftFileHandler {
367
376
  await this.awaitOutstandingMsgPromises(false);
368
377
 
369
378
  // Send
370
- const promRslt = this._msgHandler.sendFileBlock(fileContents.subarray(blockStart, blockEnd), blockStart);
379
+ const promRslt = this._msgHandler.sendFileBlock(fileContents.subarray(blockStart, blockEnd), blockStart, this._fileTxStreamID);
371
380
  if (!promRslt) {
372
381
  return false;
373
382
  }
@@ -437,8 +446,8 @@ export default class RaftFileHandler {
437
446
  // Establish a bridge
438
447
  const bridgedDeviceSerialPort = "Serial" + fileSource.slice(bridgeSerialPrefix.length);
439
448
  const cmdResp = await this._msgHandler.createCommsBridge(bridgedDeviceSerialPort, "fileSource");
440
- if (cmdResp.rslt != "ok") {
441
- RaftLog.warn(`fileReceive - failed to setup bridge ${cmdResp.rslt}`);
449
+ if (!cmdResp || cmdResp.rslt != "ok") {
450
+ RaftLog.warn(`fileReceive - failed to setup bridge ${cmdResp?.rslt ?? 'no response'}`);
442
451
  return new RaftFileDownloadResult();
443
452
  }
444
453
  bridgeID = cmdResp.bridgeID;
@@ -497,7 +506,7 @@ export default class RaftFileHandler {
497
506
  return false;
498
507
  }
499
508
  RaftLog.info(`_receiveFileStartMsg rslt ${JSON.stringify(cmdResp)}`);
500
- if (cmdResp.rslt === 'ok') {
509
+ if (cmdResp?.rslt === 'ok') {
501
510
  this._fileRxBatchMsgSize = cmdResp.batchMsgSize;
502
511
  this._fileRxBatchAckSize = cmdResp.batchAckSize;
503
512
  this._fileRxStreamID = cmdResp.streamID;
@@ -510,6 +519,10 @@ export default class RaftFileHandler {
510
519
  this._fileRxLastBlockTime = Date.now();
511
520
  this._fileRxActive = true;
512
521
  }
522
+ if (!cmdResp) {
523
+ RaftLog.warn(`_receiveFileStartMsg failed no response`);
524
+ return false;
525
+ }
513
526
  return cmdResp.rslt === 'ok';
514
527
  }
515
528
 
@@ -580,6 +593,7 @@ export default class RaftFileHandler {
580
593
  `elapsed ${now - startTime}ms overallTimeout ${overallTimeoutMs}ms ` +
581
594
  `blockGap ${now - this._fileRxLastBlockTime}ms blockTimeout ${blockTimeoutMs}ms`);
582
595
  this._fileRxActive = false;
596
+ this._sendFileRxCancelMsg(bridgeID);
583
597
  reject(new Error('fileReceive failed'));
584
598
  return;
585
599
  }
@@ -651,6 +665,12 @@ export default class RaftFileHandler {
651
665
  }
652
666
 
653
667
  // Check deferred CRC if start response didn't include one
668
+ if (!cmdResp) {
669
+ RaftLog.warn(`_receiveFileEnd failed no response`);
670
+ this._fileRxActive = false;
671
+ return false;
672
+ }
673
+
654
674
  if (this._fileRxCrc16 < 0 && cmdResp.crc16) {
655
675
  const expectedCrc = parseInt(cmdResp.crc16, 16);
656
676
  const actualCrc = RaftMiniHDLC.crc16(this._fileRxBuffer);
@@ -683,8 +703,20 @@ export default class RaftFileHandler {
683
703
  ): void {
684
704
  // RaftLog.info(`onFileBlock filePos ${filePos} fileBlockData ${RaftUtils.bufferToHex(fileBlockData)}`);
685
705
 
706
+ if (!this._fileRxActive) {
707
+ RaftLog.verbose(`onFileBlock ignored inactive transfer filePos ${filePos} len ${fileBlockData.length}`);
708
+ return;
709
+ }
710
+
711
+ const streamID = (filePos >>> 24) & 0xff;
712
+ const streamFilePos = filePos & 0x00ffffff;
713
+ if (streamID !== 0 && streamID !== this._fileRxStreamID) {
714
+ RaftLog.verbose(`onFileBlock ignored stale streamID ${streamID} active ${this._fileRxStreamID}`);
715
+ return;
716
+ }
717
+
686
718
  // Check if this is the next block we are expecting
687
- if (filePos === this._fileRxBuffer.length) {
719
+ if (streamFilePos === this._fileRxBuffer.length) {
688
720
 
689
721
  // Add to buffer
690
722
  const tmpArray = new Uint8Array(this._fileRxBuffer.length + fileBlockData.length);
@@ -699,7 +731,7 @@ export default class RaftFileHandler {
699
731
  // RaftLog.info(`onFileBlock filePos ${filePos} fileBlockData ${RaftUtils.bufferToHex(fileBlockData)} added to buffer`);
700
732
 
701
733
  } else {
702
- RaftLog.warn(`onFileBlock expected streamID ${this._fileRxStreamID} filePos ${filePos} fileBlockData ${RaftUtils.bufferToHex(fileBlockData)} out of sequence`);
734
+ RaftLog.warn(`onFileBlock expected streamID ${this._fileRxStreamID} filePos ${streamFilePos} fileBlockData ${RaftUtils.bufferToHex(fileBlockData)} out of sequence`);
703
735
  }
704
736
  }
705
737
 
@@ -0,0 +1,78 @@
1
+ import RaftConnector from "./RaftConnector";
2
+ import { RaftRtStreamDataCBType, RaftRtStreamHandle } from "./RaftTypes";
3
+
4
+ const REPL_NEWLINE = "\r";
5
+
6
+ export type RaftMicroPythonConsoleStatus = {
7
+ rslt?: string;
8
+ running?: string;
9
+ replEnabled?: boolean;
10
+ replRunning?: boolean;
11
+ mode?: string;
12
+ streamAttached?: boolean;
13
+ mirrorToSerial?: boolean;
14
+ inputBuffered?: number;
15
+ outputBuffered?: number;
16
+ outputSeq?: number;
17
+ inputDropped?: number;
18
+ outputDropped?: number;
19
+ };
20
+
21
+ export type RaftMicroPythonConsoleOutput = {
22
+ rslt?: string;
23
+ seq?: number;
24
+ missed?: number;
25
+ dropped?: number;
26
+ data?: string;
27
+ };
28
+
29
+ export default class RaftMicroPythonConsoleClient {
30
+ constructor(private _connector: RaftConnector) {}
31
+ private readonly _streamDrainMs = 1200;
32
+
33
+ async status(): Promise<RaftMicroPythonConsoleStatus> {
34
+ return this._connector.sendRICRESTMsg("upy/repl/status", {}) as Promise<RaftMicroPythonConsoleStatus>;
35
+ }
36
+
37
+ async output(since: number): Promise<RaftMicroPythonConsoleOutput> {
38
+ return this._connector.sendRICRESTMsg("upy/repl/output", { since }) as Promise<RaftMicroPythonConsoleOutput>;
39
+ }
40
+
41
+ async input(data: string, newline = true): Promise<{ rslt?: string }> {
42
+ const payload = newline ? `${data}${REPL_NEWLINE}` : data;
43
+ return this._connector.sendRICRESTMsg(
44
+ `upy/repl/input?data=${payload}&newline=0`,
45
+ {}
46
+ ) as Promise<{ rslt?: string }>;
47
+ }
48
+
49
+ async interrupt(): Promise<{ rslt?: string }> {
50
+ return this._connector.sendRICRESTMsg("upy/repl/interrupt", {}) as Promise<{ rslt?: string }>;
51
+ }
52
+
53
+ async openStream(onData: RaftRtStreamDataCBType): Promise<RaftRtStreamHandle> {
54
+ return this._connector.openRtStream({
55
+ fileName: "upyconsole",
56
+ endpoint: "upyconsole",
57
+ onData,
58
+ });
59
+ }
60
+
61
+ async streamInput(
62
+ data: string,
63
+ newline = true,
64
+ onData: RaftRtStreamDataCBType = () => {}
65
+ ): Promise<boolean> {
66
+ const payload = newline ? `${data}${REPL_NEWLINE}` : data;
67
+ const streamHandle = await this.openStream(onData);
68
+ try {
69
+ return await streamHandle.sendText(payload);
70
+ } finally {
71
+ setTimeout(() => {
72
+ streamHandle.close().catch(() => {
73
+ // The firmware may close short-lived console sessions first.
74
+ });
75
+ }, this._streamDrainMs);
76
+ }
77
+ }
78
+ }
@@ -269,8 +269,11 @@ export default class RaftMsgHandler {
269
269
  }
270
270
 
271
271
  } else {
272
- RaftLog.warn(
273
- `_handleResponseMessages RICREST response doesn't contain rslt ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()}resp ${restStr}`,
272
+ // Some REST APIs return data-only JSON, including an empty object, with no rslt field.
273
+ // Treat parseable JSON as a successful transport response and let callers interpret it.
274
+ msgRsltCode = RaftMsgResultCode.MESSAGE_RESULT_OK;
275
+ RaftLog.verbose(
276
+ `_handleResponseMessages RICREST response without rslt ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()} resp ${restStr}`,
274
277
  );
275
278
  }
276
279
 
@@ -726,9 +729,10 @@ export default class RaftMsgHandler {
726
729
 
727
730
  async sendFileBlock(
728
731
  blockContents: Uint8Array,
729
- blockStart: number
732
+ blockStart: number,
733
+ streamID = 0,
730
734
  ): Promise<boolean> {
731
- const msgBuf = this.encodeFileStreamBlock(blockContents, blockStart, 0);
735
+ const msgBuf = this.encodeFileStreamBlock(blockContents, blockStart, streamID);
732
736
 
733
737
  // // Debug
734
738
  // RaftLog.debug(
package/src/RaftTypes.ts CHANGED
@@ -152,6 +152,7 @@ export type RaftFileStartResp = {
152
152
  rslt: string;
153
153
  batchMsgSize: number;
154
154
  batchAckSize: number;
155
+ streamID?: number;
155
156
  };
156
157
 
157
158
  export type RaftStreamStartResp = {
@@ -208,6 +209,28 @@ export type RaftProgressCBType = (received: number, total: number) => void;
208
209
 
209
210
  export type RaftStreamDataProgressCBType = (sent: number, total: number, progress: number) => void;
210
211
 
212
+ export type RaftRtStreamDataCBType = (data: Uint8Array, filePos: number, streamID: number) => void;
213
+
214
+ export type RaftRtStreamOptions = {
215
+ fileName: string;
216
+ endpoint: string;
217
+ onData: RaftRtStreamDataCBType;
218
+ sendInitialEmptyBlock?: boolean;
219
+ };
220
+
221
+ export type RaftRtStreamHandle = {
222
+ streamID: number;
223
+ maxBlockSize: number;
224
+ sendBytes: (bytes: Uint8Array) => Promise<boolean>;
225
+ sendText: (text: string) => Promise<boolean>;
226
+ close: () => Promise<boolean>;
227
+ };
228
+
229
+ export type RaftRtStreamStartResp = RaftOKFail & {
230
+ streamID?: number;
231
+ maxBlockSize?: number;
232
+ };
233
+
211
234
  export class RaftFileDownloadResult {
212
235
  fileData: Uint8Array | null = null;
213
236
  downloadedOk = false;
package/src/main.ts CHANGED
@@ -21,6 +21,7 @@ export { default as RaftFileHandler } from './RaftFileHandler';
21
21
  export { default as RaftLog } from './RaftLog';
22
22
  export { default as RaftMiniHDLC } from './RaftMiniHDLC';
23
23
  export { default as RaftMsgHandler } from './RaftMsgHandler'
24
+ export { default as RaftMicroPythonConsoleClient } from './RaftMicroPythonConsoleClient';
24
25
  export { default as RaftStreamHandler } from './RaftStreamHandler';
25
26
  export { default as RaftSystemUtils } from './RaftSystemUtils';
26
27
  export { default as RaftUtils } from './RaftUtils';