@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.
- package/.editorconfig +14 -0
- package/.gitattributes +11 -0
- package/.nvmrc +1 -0
- package/LICENSE +22 -0
- package/README.md +11 -0
- package/TODO.md +1 -0
- package/dist/RaftAttributeHandler.d.ts +12 -0
- package/dist/RaftAttributeHandler.js +241 -0
- package/dist/RaftAttributeHandler.js.map +1 -0
- package/dist/RaftChannel.d.ts +18 -0
- package/dist/RaftChannel.js +12 -0
- package/dist/RaftChannel.js.map +1 -0
- package/dist/RaftChannelWebBLE.d.ts +38 -0
- package/dist/RaftChannelWebBLE.js +274 -0
- package/dist/RaftChannelWebBLE.js.map +1 -0
- package/dist/RaftChannelWebSerial.d.ts +37 -0
- package/dist/RaftChannelWebSerial.js +319 -0
- package/dist/RaftChannelWebSerial.js.map +1 -0
- package/dist/RaftChannelWebSocket.d.ts +28 -0
- package/dist/RaftChannelWebSocket.js +197 -0
- package/dist/RaftChannelWebSocket.js.map +1 -0
- package/dist/RaftCommsStats.d.ts +39 -0
- package/dist/RaftCommsStats.js +128 -0
- package/dist/RaftCommsStats.js.map +1 -0
- package/dist/RaftConnEvents.d.ts +31 -0
- package/dist/RaftConnEvents.js +44 -0
- package/dist/RaftConnEvents.js.map +1 -0
- package/dist/RaftConnector.d.ts +242 -0
- package/dist/RaftConnector.js +613 -0
- package/dist/RaftConnector.js.map +1 -0
- package/dist/RaftCustomAttrHandler.d.ts +4 -0
- package/dist/RaftCustomAttrHandler.js +50 -0
- package/dist/RaftCustomAttrHandler.js.map +1 -0
- package/dist/RaftDeviceInfo.d.ts +64 -0
- package/dist/RaftDeviceInfo.js +36 -0
- package/dist/RaftDeviceInfo.js.map +1 -0
- package/dist/RaftDeviceManager.d.ts +37 -0
- package/dist/RaftDeviceManager.js +450 -0
- package/dist/RaftDeviceManager.js.map +1 -0
- package/dist/RaftDeviceMsg.d.ts +9 -0
- package/dist/RaftDeviceMsg.js +11 -0
- package/dist/RaftDeviceMsg.js.map +1 -0
- package/dist/RaftDeviceStates.d.ts +33 -0
- package/dist/RaftDeviceStates.js +60 -0
- package/dist/RaftDeviceStates.js.map +1 -0
- package/dist/RaftFileHandler.d.ts +52 -0
- package/dist/RaftFileHandler.js +502 -0
- package/dist/RaftFileHandler.js.map +1 -0
- package/dist/RaftLog.d.ts +22 -0
- package/dist/RaftLog.js +63 -0
- package/dist/RaftLog.js.map +1 -0
- package/dist/RaftMiniHDLC.d.ts +18 -0
- package/dist/RaftMiniHDLC.js +383 -0
- package/dist/RaftMiniHDLC.js.map +1 -0
- package/dist/RaftMsgHandler.d.ts +57 -0
- package/dist/RaftMsgHandler.js +480 -0
- package/dist/RaftMsgHandler.js.map +1 -0
- package/dist/RaftMsgTrackInfo.d.ts +17 -0
- package/dist/RaftMsgTrackInfo.js +42 -0
- package/dist/RaftMsgTrackInfo.js.map +1 -0
- package/dist/RaftProtocolDefs.d.ts +30 -0
- package/dist/RaftProtocolDefs.js +48 -0
- package/dist/RaftProtocolDefs.js.map +1 -0
- package/dist/RaftStreamHandler.d.ts +38 -0
- package/dist/RaftStreamHandler.js +257 -0
- package/dist/RaftStreamHandler.js.map +1 -0
- package/dist/RaftSystemType.d.ts +21 -0
- package/dist/RaftSystemType.js +3 -0
- package/dist/RaftSystemType.js.map +1 -0
- package/dist/RaftSystemUtils.d.ts +136 -0
- package/dist/RaftSystemUtils.js +410 -0
- package/dist/RaftSystemUtils.js.map +1 -0
- package/dist/RaftTypes.d.ts +184 -0
- package/dist/RaftTypes.js +157 -0
- package/dist/RaftTypes.js.map +1 -0
- package/dist/RaftUpdateEvents.d.ts +33 -0
- package/dist/RaftUpdateEvents.js +46 -0
- package/dist/RaftUpdateEvents.js.map +1 -0
- package/dist/RaftUpdateManager.d.ts +61 -0
- package/dist/RaftUpdateManager.js +618 -0
- package/dist/RaftUpdateManager.js.map +1 -0
- package/dist/RaftUtils.d.ts +125 -0
- package/dist/RaftUtils.js +454 -0
- package/dist/RaftUtils.js.map +1 -0
- package/dist/RaftWifiTypes.d.ts +23 -0
- package/dist/RaftWifiTypes.js +43 -0
- package/dist/RaftWifiTypes.js.map +1 -0
- package/dist/TestDataGen.d.ts +7 -0
- package/dist/TestDataGen.js +133 -0
- package/dist/TestDataGen.js.map +1 -0
- package/dist/main.d.ts +18 -0
- package/dist/main.js +42 -0
- package/dist/main.js.map +1 -0
- package/eslint.config.mjs +33 -0
- package/examples/dashboard/package.json +39 -0
- package/examples/dashboard/src/ConnManager.ts +86 -0
- package/examples/dashboard/src/Main.tsx +100 -0
- package/examples/dashboard/src/StatusScreen.tsx +72 -0
- package/examples/dashboard/src/SystemTypeCog/CogStateInfo.ts +144 -0
- package/examples/dashboard/src/SystemTypeCog/SystemTypeCog.ts +77 -0
- package/examples/dashboard/src/SystemTypeMarty/RICAddOn.ts +70 -0
- package/examples/dashboard/src/SystemTypeMarty/RICAddOnBase.ts +33 -0
- package/examples/dashboard/src/SystemTypeMarty/RICAddOnManager.ts +342 -0
- package/examples/dashboard/src/SystemTypeMarty/RICCommsStats.ts +170 -0
- package/examples/dashboard/src/SystemTypeMarty/RICHWElem.ts +123 -0
- package/examples/dashboard/src/SystemTypeMarty/RICLEDPatternChecker.ts +207 -0
- package/examples/dashboard/src/SystemTypeMarty/RICROSSerial.ts +464 -0
- package/examples/dashboard/src/SystemTypeMarty/RICServoFaultDetector.ts +146 -0
- package/examples/dashboard/src/SystemTypeMarty/RICStateInfo.ts +32 -0
- package/examples/dashboard/src/SystemTypeMarty/RICSystemUtils.ts +371 -0
- package/examples/dashboard/src/SystemTypeMarty/RICTypes.ts +20 -0
- package/examples/dashboard/src/SystemTypeMarty/SystemTypeMarty.ts +113 -0
- package/examples/dashboard/src/index.html +15 -0
- package/examples/dashboard/src/index.tsx +15 -0
- package/examples/dashboard/src/styles.css +122 -0
- package/examples/dashboard/tsconfig.json +18 -0
- package/jest.config.js +11 -0
- package/package.json +50 -0
- package/src/RaftAttributeHandler.ts +289 -0
- package/src/RaftChannel.ts +30 -0
- package/src/RaftChannelWebBLE.ts +342 -0
- package/src/RaftChannelWebSerial.ts +408 -0
- package/src/RaftChannelWebSocket.ts +245 -0
- package/src/RaftCommsStats.ts +142 -0
- package/src/RaftConnEvents.ts +46 -0
- package/src/RaftConnector.ts +745 -0
- package/src/RaftCustomAttrHandler.ts +54 -0
- package/src/RaftDeviceInfo.ts +104 -0
- package/src/RaftDeviceManager.ts +542 -0
- package/src/RaftDeviceMsg.ts +20 -0
- package/src/RaftDeviceStates.ts +89 -0
- package/src/RaftFileHandler.ts +668 -0
- package/src/RaftLog.ts +70 -0
- package/src/RaftMiniHDLC.ts +396 -0
- package/src/RaftMsgHandler.ts +778 -0
- package/src/RaftMsgTrackInfo.ts +51 -0
- package/src/RaftProtocolDefs.ts +46 -0
- package/src/RaftStreamHandler.ts +328 -0
- package/src/RaftSystemType.ts +25 -0
- package/src/RaftSystemUtils.ts +487 -0
- package/src/RaftTypes.ts +250 -0
- package/src/RaftUpdateEvents.ts +48 -0
- package/src/RaftUpdateManager.ts +778 -0
- package/src/RaftUtils.ts +484 -0
- package/src/RaftWifiTypes.ts +36 -0
- package/src/TestDataGen.ts +157 -0
- package/src/main.ts +28 -0
- package/testdata/TestDeviceTypeRecs.json +492 -0
- 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
|
+
}
|