@robotical/raftjs 2.1.2 → 2.1.3
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/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/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/RaftConnector.d.ts +19 -1
- package/dist/web/RaftConnector.js +102 -2
- package/dist/web/RaftConnector.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 +21 -11
- package/package.json +1 -1
- package/src/RaftConnector.ts +112 -3
- 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
|
@@ -15,6 +15,20 @@ function findClosest(arr: number[], target: number): number {
|
|
|
15
15
|
);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function getDefaultActionValue(action: DeviceTypeAction): number {
|
|
19
|
+
if (action.d !== undefined) {
|
|
20
|
+
return action.d;
|
|
21
|
+
}
|
|
22
|
+
if (action.map) {
|
|
23
|
+
const firstMapKey = Object.keys(action.map).sort((a, b) => parseFloat(a) - parseFloat(b))[0];
|
|
24
|
+
return firstMapKey !== undefined ? parseFloat(firstMapKey) : 0;
|
|
25
|
+
}
|
|
26
|
+
if (action.r && action.r.length > 1) {
|
|
27
|
+
return (action.r[1] + action.r[0]) / 2;
|
|
28
|
+
}
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
18
32
|
type DeviceActionsTableProps = {
|
|
19
33
|
deviceKey: string;
|
|
20
34
|
};
|
|
@@ -51,13 +65,7 @@ const DeviceActionsForm: React.FC<DeviceActionsTableProps> = ({ deviceKey }: Dev
|
|
|
51
65
|
setHasConfRate(!!confRateAction);
|
|
52
66
|
// Initialize input values with defaults
|
|
53
67
|
const initialValues: InputValues = actions.reduce((acc, action) => {
|
|
54
|
-
acc[action.n] =
|
|
55
|
-
action.d ??
|
|
56
|
-
(action.r
|
|
57
|
-
? action.r.length > 1
|
|
58
|
-
? (action.r[1] + action.r[0]) / 2
|
|
59
|
-
: 0
|
|
60
|
-
: 0);
|
|
68
|
+
acc[action.n] = getDefaultActionValue(action);
|
|
61
69
|
return acc;
|
|
62
70
|
}, {} as InputValues);
|
|
63
71
|
|
|
@@ -183,12 +191,13 @@ const DeviceActionsForm: React.FC<DeviceActionsTableProps> = ({ deviceKey }: Dev
|
|
|
183
191
|
const mapKeys = Object.keys(action.map).sort((a, b) => parseFloat(a) - parseFloat(b));
|
|
184
192
|
// Use "Rate Hz" label for _conf.rate actions
|
|
185
193
|
const actionLabel = action.n === '_conf.rate' ? 'Rate Hz' : (action.desc ?? action.n);
|
|
194
|
+
const actionValue = inputValues[action.n] ?? getDefaultActionValue(action);
|
|
186
195
|
return (
|
|
187
196
|
<tr key={action.n}>
|
|
188
197
|
<td>{actionLabel}</td>
|
|
189
198
|
<td>
|
|
190
199
|
<select
|
|
191
|
-
value={
|
|
200
|
+
value={actionValue}
|
|
192
201
|
onChange={(e) =>
|
|
193
202
|
handleInputChange(
|
|
194
203
|
action.n,
|
|
@@ -208,7 +217,7 @@ const DeviceActionsForm: React.FC<DeviceActionsTableProps> = ({ deviceKey }: Dev
|
|
|
208
217
|
onClick={() =>
|
|
209
218
|
handleSendAction(
|
|
210
219
|
action,
|
|
211
|
-
|
|
220
|
+
actionValue
|
|
212
221
|
)
|
|
213
222
|
}
|
|
214
223
|
>
|
|
@@ -218,6 +227,7 @@ const DeviceActionsForm: React.FC<DeviceActionsTableProps> = ({ deviceKey }: Dev
|
|
|
218
227
|
</tr>
|
|
219
228
|
);
|
|
220
229
|
} else {
|
|
230
|
+
const actionValue = inputValues[action.n] ?? getDefaultActionValue(action);
|
|
221
231
|
return (
|
|
222
232
|
<tr key={action.n}>
|
|
223
233
|
<td>{action.n}</td>
|
|
@@ -227,7 +237,7 @@ const DeviceActionsForm: React.FC<DeviceActionsTableProps> = ({ deviceKey }: Dev
|
|
|
227
237
|
type="number"
|
|
228
238
|
min={action.r?.[0] ?? 0}
|
|
229
239
|
max={action.r?.[1] ?? 100}
|
|
230
|
-
value={
|
|
240
|
+
value={actionValue}
|
|
231
241
|
onChange={(e) =>
|
|
232
242
|
handleInputChange(
|
|
233
243
|
action.n,
|
|
@@ -242,7 +252,7 @@ const DeviceActionsForm: React.FC<DeviceActionsTableProps> = ({ deviceKey }: Dev
|
|
|
242
252
|
onClick={() =>
|
|
243
253
|
handleSendAction(
|
|
244
254
|
action,
|
|
245
|
-
|
|
255
|
+
actionValue
|
|
246
256
|
)
|
|
247
257
|
}
|
|
248
258
|
>
|
package/package.json
CHANGED
package/src/RaftConnector.ts
CHANGED
|
@@ -14,7 +14,7 @@ import RaftChannelWebSocket from "./RaftChannelWebSocket";
|
|
|
14
14
|
import RaftChannelWebSerial from "./RaftChannelWebSerial";
|
|
15
15
|
import RaftChannelSimulated from "./RaftChannelSimulated";
|
|
16
16
|
import RaftCommsStats from "./RaftCommsStats";
|
|
17
|
-
import { RaftEventFn, RaftOKFail, RaftFileSendType, RaftFileDownloadResult, RaftProgressCBType, RaftStreamDataProgressCBType, RaftBridgeSetupResp, RaftFileDownloadFn, RaftReportMsg } from "./RaftTypes";
|
|
17
|
+
import { RaftEventFn, RaftOKFail, RaftFileSendType, RaftFileDownloadResult, RaftProgressCBType, RaftStreamDataProgressCBType, RaftBridgeSetupResp, RaftFileDownloadFn, RaftReportMsg, RaftRtStreamDataCBType, RaftRtStreamHandle, RaftRtStreamOptions, RaftRtStreamStartResp } from "./RaftTypes";
|
|
18
18
|
import RaftSystemUtils from "./RaftSystemUtils";
|
|
19
19
|
import RaftFileHandler from "./RaftFileHandler";
|
|
20
20
|
import RaftStreamHandler from "./RaftStreamHandler";
|
|
@@ -87,6 +87,10 @@ export default class RaftConnector {
|
|
|
87
87
|
// Update manager
|
|
88
88
|
private _raftUpdateManager: RaftUpdateManager | null = null;
|
|
89
89
|
|
|
90
|
+
// Open-ended RT stream callbacks keyed by streamID
|
|
91
|
+
private _rtStreamCallbacks = new Map<number, RaftRtStreamDataCBType>();
|
|
92
|
+
private _fallbackRtStreamCallback: { streamID: number, callback: RaftRtStreamDataCBType } | null = null;
|
|
93
|
+
|
|
90
94
|
/**
|
|
91
95
|
* RaftConnector constructor
|
|
92
96
|
* @param getSystemTypeCB - callback to get system type
|
|
@@ -193,6 +197,22 @@ export default class RaftConnector {
|
|
|
193
197
|
return this._commsStats;
|
|
194
198
|
}
|
|
195
199
|
|
|
200
|
+
/**
|
|
201
|
+
* getOperationQueueDepth
|
|
202
|
+
* @returns number of high-level device operations queued or running
|
|
203
|
+
*/
|
|
204
|
+
getOperationQueueDepth(): number {
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* isOperationBusy
|
|
210
|
+
* @returns true when a high-level device operation is queued or running
|
|
211
|
+
*/
|
|
212
|
+
isOperationBusy(): boolean {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
196
216
|
/**
|
|
197
217
|
* Get Raft message handler (to allow message sending and receiving)
|
|
198
218
|
* @returns RaftMsgHandler - Raft message handler
|
|
@@ -408,6 +428,11 @@ export default class RaftConnector {
|
|
|
408
428
|
*
|
|
409
429
|
*/
|
|
410
430
|
async sendRICRESTMsg(commandName: string, params: object,
|
|
431
|
+
bridgeID: number | undefined = undefined): Promise<RaftOKFail> {
|
|
432
|
+
return this._sendRICRESTMsg(commandName, params, bridgeID);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private async _sendRICRESTMsg(commandName: string, params: object,
|
|
411
436
|
bridgeID: number | undefined = undefined): Promise<RaftOKFail> {
|
|
412
437
|
try {
|
|
413
438
|
// Format the paramList as query string
|
|
@@ -415,11 +440,12 @@ export default class RaftConnector {
|
|
|
415
440
|
let paramQueryStr = '';
|
|
416
441
|
for (const param of paramEntries) {
|
|
417
442
|
if (paramQueryStr.length > 0) paramQueryStr += '&';
|
|
418
|
-
paramQueryStr += param[0]
|
|
443
|
+
paramQueryStr += `${encodeURIComponent(param[0])}=${encodeURIComponent(String(param[1]))}`;
|
|
419
444
|
}
|
|
420
445
|
// Format the url to send
|
|
421
446
|
if (paramQueryStr.length > 0) commandName += '?' + paramQueryStr;
|
|
422
|
-
|
|
447
|
+
const response = await this._raftMsgHandler.sendRICRESTURL<RaftOKFail | null>(commandName, bridgeID);
|
|
448
|
+
return response ?? { rslt: 'fail' };
|
|
423
449
|
} catch (error) {
|
|
424
450
|
RaftLog.warn(`sendRICRESTMsg failed ${error}`);
|
|
425
451
|
return { rslt: 'fail' };
|
|
@@ -473,6 +499,13 @@ export default class RaftConnector {
|
|
|
473
499
|
fileBlockData: Uint8Array
|
|
474
500
|
): void {
|
|
475
501
|
// RaftLog.info(`onRxFileBlock filePos ${filePos} fileBlockData ${RaftUtils.bufferToHex(fileBlockData)}`);
|
|
502
|
+
const streamID = (filePos >>> 24) & 0xff;
|
|
503
|
+
const streamFilePos = filePos & 0x00ffffff;
|
|
504
|
+
const streamCallback = this._rtStreamCallbacks.get(streamID);
|
|
505
|
+
if (streamID !== 0 && streamCallback) {
|
|
506
|
+
streamCallback(fileBlockData, streamFilePos, streamID);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
476
509
|
this._raftFileHandler.onFileBlock(filePos, fileBlockData);
|
|
477
510
|
}
|
|
478
511
|
|
|
@@ -534,6 +567,82 @@ export default class RaftConnector {
|
|
|
534
567
|
return false;
|
|
535
568
|
}
|
|
536
569
|
|
|
570
|
+
/**
|
|
571
|
+
* openRtStream - open an indefinite bidirectional RT stream.
|
|
572
|
+
* The returned handle can send byte blocks and closes with ufEnd.
|
|
573
|
+
*/
|
|
574
|
+
async openRtStream(options: RaftRtStreamOptions): Promise<RaftRtStreamHandle> {
|
|
575
|
+
const cmdMsg = JSON.stringify({
|
|
576
|
+
cmdName: "ufStart",
|
|
577
|
+
reqStr: "ufStart",
|
|
578
|
+
fileType: "rtstream",
|
|
579
|
+
fileName: options.fileName,
|
|
580
|
+
endpoint: options.endpoint,
|
|
581
|
+
fileLen: 0,
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
const startResp = await this._raftMsgHandler.sendRICRESTCmdFrame<RaftRtStreamStartResp>(cmdMsg);
|
|
585
|
+
if (!startResp || startResp.rslt !== "ok" || startResp.streamID === undefined) {
|
|
586
|
+
throw new Error(`openRtStream failed ${startResp?.rslt ?? "no response"}`);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const streamID = startResp.streamID;
|
|
590
|
+
const maxBlockSize = startResp.maxBlockSize || this._raftStreamHandler.maxBlockSize;
|
|
591
|
+
let txFilePos = 0;
|
|
592
|
+
let sendQueue = Promise.resolve();
|
|
593
|
+
this._rtStreamCallbacks.set(streamID, options.onData);
|
|
594
|
+
this._fallbackRtStreamCallback = { streamID, callback: options.onData };
|
|
595
|
+
|
|
596
|
+
const sendBytes = async (bytes: Uint8Array): Promise<boolean> => {
|
|
597
|
+
let sentOk = false;
|
|
598
|
+
sendQueue = sendQueue
|
|
599
|
+
.catch(() => {
|
|
600
|
+
// Keep later terminal input flowing even if an earlier block failed.
|
|
601
|
+
})
|
|
602
|
+
.then(async () => {
|
|
603
|
+
sentOk = await this._raftMsgHandler.sendStreamBlock(bytes, txFilePos, streamID);
|
|
604
|
+
if (sentOk) {
|
|
605
|
+
txFilePos = (txFilePos + bytes.length) & 0x00ffffff;
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
await sendQueue;
|
|
609
|
+
return sentOk;
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
const close = async (): Promise<boolean> => {
|
|
613
|
+
this._rtStreamCallbacks.delete(streamID);
|
|
614
|
+
if (this._fallbackRtStreamCallback?.streamID === streamID) {
|
|
615
|
+
this._fallbackRtStreamCallback = null;
|
|
616
|
+
}
|
|
617
|
+
const endMsg = JSON.stringify({
|
|
618
|
+
cmdName: "ufEnd",
|
|
619
|
+
reqStr: "ufEnd",
|
|
620
|
+
streamID,
|
|
621
|
+
});
|
|
622
|
+
try {
|
|
623
|
+
const endResp = await this._raftMsgHandler.sendRICRESTCmdFrame<RaftOKFail>(endMsg);
|
|
624
|
+
return endResp?.rslt === "ok";
|
|
625
|
+
} catch (error) {
|
|
626
|
+
RaftLog.warn(`closeRtStream failed ${streamID}: ${error}`);
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
// Some endpoints use an initial empty block as an attach signal.
|
|
632
|
+
// Keep this opt-in because older endpoints reject zero-length ufBlock frames.
|
|
633
|
+
if (options.sendInitialEmptyBlock) {
|
|
634
|
+
await sendBytes(new Uint8Array());
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return {
|
|
638
|
+
streamID,
|
|
639
|
+
maxBlockSize,
|
|
640
|
+
sendBytes,
|
|
641
|
+
sendText: (text: string) => sendBytes(new TextEncoder().encode(text)),
|
|
642
|
+
close,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
|
|
537
646
|
/**
|
|
538
647
|
* streamAudio - stream audio
|
|
539
648
|
* @param streamContents audio data
|
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';
|