@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.
- package/devdocs/decode-overrun-investigation.md +167 -0
- package/devdocs/message-panel-design.md +320 -0
- package/dist/react-native/RaftAttributeHandler.d.ts +10 -1
- package/dist/react-native/RaftAttributeHandler.js +60 -8
- package/dist/react-native/RaftAttributeHandler.js.map +1 -1
- package/dist/react-native/RaftChannelSimulated.d.ts +1 -0
- package/dist/react-native/RaftChannelSimulated.js +67 -0
- package/dist/react-native/RaftChannelSimulated.js.map +1 -1
- package/dist/react-native/RaftConnector.d.ts +19 -1
- package/dist/react-native/RaftConnector.js +102 -2
- package/dist/react-native/RaftConnector.js.map +1 -1
- package/dist/react-native/RaftDeviceInfo.d.ts +5 -1
- package/dist/react-native/RaftDeviceManager.js +75 -32
- package/dist/react-native/RaftDeviceManager.js.map +1 -1
- package/dist/react-native/RaftFileHandler.d.ts +1 -0
- package/dist/react-native/RaftFileHandler.js +40 -11
- package/dist/react-native/RaftFileHandler.js.map +1 -1
- package/dist/react-native/RaftMicroPythonConsoleClient.d.ts +38 -0
- package/dist/react-native/RaftMicroPythonConsoleClient.js +45 -0
- package/dist/react-native/RaftMicroPythonConsoleClient.js.map +1 -0
- package/dist/react-native/RaftMsgHandler.d.ts +1 -1
- package/dist/react-native/RaftMsgHandler.js +6 -3
- package/dist/react-native/RaftMsgHandler.js.map +1 -1
- package/dist/react-native/RaftTypes.d.ts +19 -0
- package/dist/react-native/RaftTypes.js.map +1 -1
- package/dist/react-native/main.d.ts +1 -0
- package/dist/react-native/main.js +3 -1
- package/dist/react-native/main.js.map +1 -1
- package/dist/web/RaftAttributeHandler.d.ts +10 -1
- package/dist/web/RaftAttributeHandler.js +60 -8
- package/dist/web/RaftAttributeHandler.js.map +1 -1
- package/dist/web/RaftChannelSimulated.d.ts +1 -0
- package/dist/web/RaftChannelSimulated.js +67 -0
- package/dist/web/RaftChannelSimulated.js.map +1 -1
- package/dist/web/RaftConnector.d.ts +19 -1
- package/dist/web/RaftConnector.js +102 -2
- package/dist/web/RaftConnector.js.map +1 -1
- package/dist/web/RaftDeviceInfo.d.ts +5 -1
- package/dist/web/RaftDeviceManager.js +75 -32
- package/dist/web/RaftDeviceManager.js.map +1 -1
- package/dist/web/RaftFileHandler.d.ts +1 -0
- package/dist/web/RaftFileHandler.js +40 -11
- package/dist/web/RaftFileHandler.js.map +1 -1
- package/dist/web/RaftMicroPythonConsoleClient.d.ts +38 -0
- package/dist/web/RaftMicroPythonConsoleClient.js +45 -0
- package/dist/web/RaftMicroPythonConsoleClient.js.map +1 -0
- package/dist/web/RaftMsgHandler.d.ts +1 -1
- package/dist/web/RaftMsgHandler.js +6 -3
- package/dist/web/RaftMsgHandler.js.map +1 -1
- package/dist/web/RaftTypes.d.ts +19 -0
- package/dist/web/RaftTypes.js.map +1 -1
- package/dist/web/main.d.ts +1 -0
- package/dist/web/main.js +3 -1
- package/dist/web/main.js.map +1 -1
- package/examples/dashboard/src/DeviceActionsForm.tsx +24 -11
- package/examples/dashboard/src/DeviceLineChart.tsx +5 -4
- package/examples/dashboard/src/DevicePanel.tsx +3 -0
- package/package.json +1 -1
- package/src/RaftAttributeHandler.ts +89 -9
- package/src/RaftChannelSimulated.test.ts +62 -0
- package/src/RaftChannelSimulated.ts +91 -0
- package/src/RaftConnector.ts +112 -3
- package/src/RaftDeviceInfo.ts +5 -1
- package/src/RaftDeviceManager.test.ts +35 -1
- package/src/RaftDeviceManager.ts +86 -33
- package/src/RaftFileHandler.ts +43 -11
- package/src/RaftMicroPythonConsoleClient.ts +78 -0
- package/src/RaftMsgHandler.ts +8 -4
- package/src/RaftTypes.ts +23 -0
- package/src/main.ts +1 -0
package/src/RaftFileHandler.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
234
|
+
return fileEndResp?.rslt === 'ok';
|
|
227
235
|
}
|
|
228
236
|
|
|
229
237
|
async _sendFileCancelMsg(): Promise<void> {
|
|
230
238
|
// File cancel command message
|
|
231
|
-
const
|
|
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
|
|
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
|
|
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 (
|
|
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 ${
|
|
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
|
+
}
|
package/src/RaftMsgHandler.ts
CHANGED
|
@@ -269,8 +269,11 @@ export default class RaftMsgHandler {
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
} else {
|
|
272
|
-
|
|
273
|
-
|
|
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,
|
|
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';
|