motion-master-client 0.0.421 → 0.0.423
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/package.json +1 -1
- package/src/api.js +47 -40
- package/src/api.js.map +1 -1
- package/src/lib/motion-master-req-res-client.d.ts +121 -28
- package/src/lib/motion-master-req-res-client.js +313 -195
- package/src/lib/motion-master-req-res-client.js.map +1 -1
- package/src/lib/os-command.d.ts +20 -0
- package/src/lib/os-command.js +62 -1
- package/src/lib/os-command.js.map +1 -1
- package/src/lib/smm.d.ts +34 -1
- package/src/lib/smm.js +50 -1
- package/src/lib/smm.js.map +1 -1
- package/src/lib/types.d.ts +40 -0
|
@@ -310,47 +310,61 @@ class MotionMasterReqResClient {
|
|
|
310
310
|
*
|
|
311
311
|
* If the requested file content is empty, Motion Master will return a not found error. This is subject to change now that Motion Master reads the file error codes.
|
|
312
312
|
*
|
|
313
|
-
* SOMANET_CiA_402.xml
|
|
313
|
+
* The ESI file ("SOMANET_CiA_402.xml" or "SOMANET_CiA_402.xml.zip") is never read from the device, see {@link getEsiFromIndexedDB}.
|
|
314
314
|
*/
|
|
315
315
|
getDeviceFile(props, requestTimeout, messageId) {
|
|
316
316
|
return this.resolveDeviceAddress(props).pipe((0, operators_1.mergeMap)((deviceAddress) => {
|
|
317
|
-
if (
|
|
318
|
-
|
|
319
|
-
typeof window !== 'undefined' &&
|
|
320
|
-
// @ts-ignore
|
|
321
|
-
window.indexedDB) {
|
|
322
|
-
return this.getParameterValue(deviceAddress, 0x100a, 0x00).pipe((0, operators_1.mergeMap)((firmwareVersion) => {
|
|
323
|
-
return (0, rxjs_1.from)(this.mmcidb.open()).pipe((0, operators_1.mergeMap)(() => this.mmcidb.getEsi(firmwareVersion)), (0, operators_1.mergeMap)((data) => {
|
|
324
|
-
if (data) {
|
|
325
|
-
const status = {
|
|
326
|
-
request: 'succeeded',
|
|
327
|
-
name: props.name,
|
|
328
|
-
deviceAddress,
|
|
329
|
-
content: data,
|
|
330
|
-
success: {
|
|
331
|
-
code: types_1.MotionMasterMessage.Status.DeviceFile.Success.Code.UNSPECIFIED,
|
|
332
|
-
message: 'ESI file successfully retrieved from IndexedDB.',
|
|
333
|
-
},
|
|
334
|
-
messageId,
|
|
335
|
-
};
|
|
336
|
-
return (0, rxjs_1.of)(status);
|
|
337
|
-
}
|
|
338
|
-
else {
|
|
339
|
-
return this.getDeviceFileUsingResolvedDeviceAddress(deviceAddress, props, requestTimeout, messageId).pipe((0, rxjs_1.tap)((status) => {
|
|
340
|
-
if (status.content) {
|
|
341
|
-
this.mmcidb.putEsi(firmwareVersion, new Uint8Array(status.content));
|
|
342
|
-
}
|
|
343
|
-
}));
|
|
344
|
-
}
|
|
345
|
-
}));
|
|
346
|
-
}), (0, operators_1.catchError)((err) => {
|
|
347
|
-
console.error('Failed to retrieve and cache ESI file from IndexedDB:', err);
|
|
348
|
-
return this.getDeviceFileUsingResolvedDeviceAddress(deviceAddress, props, requestTimeout, messageId);
|
|
349
|
-
}));
|
|
317
|
+
if (props.name === firmware_esi_1.esiFilename || props.name === firmware_esi_1.esiZipFilename) {
|
|
318
|
+
return this.getEsiFromIndexedDB(deviceAddress, props.name, messageId);
|
|
350
319
|
}
|
|
351
|
-
|
|
352
|
-
|
|
320
|
+
return this.getDeviceFileUsingResolvedDeviceAddress(deviceAddress, props, requestTimeout, messageId);
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Returns the ESI file cached in IndexedDB under the firmware version the device reports in 0x100A:00.
|
|
325
|
+
*
|
|
326
|
+
* The ESI file is never read from the device. Firmware installation no longer writes it to the device (see
|
|
327
|
+
* {@link installFirmwareAndCacheEsi}), and Motion Master does not remove a skipped file, so the file on the device may
|
|
328
|
+
* belong to an older firmware. Neither the file nor its ESI revision tells which firmware it belongs to.
|
|
329
|
+
* Caching it under the installed version would hide the correct ESI file for good.
|
|
330
|
+
*
|
|
331
|
+
* The ESI file gets into IndexedDB only from a firmware package: at firmware installation, see
|
|
332
|
+
* {@link cacheEsiFromFirmwarePackage}, or when a user loads it from a package in OBLAC Drives.
|
|
333
|
+
*
|
|
334
|
+
* Returns a failed status with the NOT_FOUND error code when the ESI file is not cached for the installed firmware,
|
|
335
|
+
* or when there is no IndexedDB, as outside the browser. Returns a failed status with the FAILED error code when the
|
|
336
|
+
* firmware version or IndexedDB can't be read.
|
|
337
|
+
*/
|
|
338
|
+
getEsiFromIndexedDB(deviceAddress, name, messageId) {
|
|
339
|
+
const failed = (code, message) => ({
|
|
340
|
+
request: 'failed',
|
|
341
|
+
name,
|
|
342
|
+
deviceAddress,
|
|
343
|
+
error: { code, message },
|
|
344
|
+
messageId,
|
|
345
|
+
});
|
|
346
|
+
if (!globalThis.indexedDB) {
|
|
347
|
+
return (0, rxjs_1.of)(failed(types_1.MotionMasterMessage.Status.DeviceFile.Error.Code.NOT_FOUND, 'The ESI file is not read from the device, and there is no IndexedDB to read it from.'));
|
|
348
|
+
}
|
|
349
|
+
return this.getParameterValue(deviceAddress, 0x100a, 0x00).pipe((0, operators_1.mergeMap)((firmwareVersion) => (0, rxjs_1.from)(this.mmcidb.open().then(() => this.mmcidb.getEsi(firmwareVersion))).pipe((0, operators_1.map)((data) => {
|
|
350
|
+
if (data) {
|
|
351
|
+
return {
|
|
352
|
+
request: 'succeeded',
|
|
353
|
+
name,
|
|
354
|
+
deviceAddress,
|
|
355
|
+
content: data,
|
|
356
|
+
success: {
|
|
357
|
+
code: types_1.MotionMasterMessage.Status.DeviceFile.Success.Code.UNSPECIFIED,
|
|
358
|
+
message: 'ESI file successfully retrieved from IndexedDB.',
|
|
359
|
+
},
|
|
360
|
+
messageId,
|
|
361
|
+
};
|
|
353
362
|
}
|
|
363
|
+
return failed(types_1.MotionMasterMessage.Status.DeviceFile.Error.Code.NOT_FOUND, `The ESI file for firmware ${firmwareVersion} is not cached. The ESI file is not read from the device.`);
|
|
364
|
+
}))), (0, operators_1.catchError)((err) => {
|
|
365
|
+
console.error('Failed to retrieve the ESI file from IndexedDB:', err);
|
|
366
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
367
|
+
return (0, rxjs_1.of)(failed(types_1.MotionMasterMessage.Status.DeviceFile.Error.Code.FAILED, `Failed to retrieve the ESI file from IndexedDB: ${reason}`));
|
|
354
368
|
}));
|
|
355
369
|
}
|
|
356
370
|
getDeviceFileUsingResolvedDeviceAddress(deviceAddress, props, requestTimeout, messageId) {
|
|
@@ -507,8 +521,7 @@ class MotionMasterReqResClient {
|
|
|
507
521
|
* Extracts the ESI file (SOMANET_CiA_402.xml) from a firmware package and stores its unzipped content in
|
|
508
522
|
* IndexedDB, keyed by the given firmware version.
|
|
509
523
|
*
|
|
510
|
-
*
|
|
511
|
-
* cached here, OBLAC can resolve it from IndexedDB without reading it back from the drive. This is what
|
|
524
|
+
* IndexedDB is the only source of the ESI file: {@link getDeviceFile} never reads it from the drive. This is what
|
|
512
525
|
* allows the ESI file (SOMANET_CiA_402.xml.zip) to be skipped during firmware installation, which avoids
|
|
513
526
|
* slow and unreliable FoE transfers and works around devices (e.g. NetX) that reject writing that file.
|
|
514
527
|
*
|
|
@@ -542,17 +555,18 @@ class MotionMasterReqResClient {
|
|
|
542
555
|
* user-overridable: when `props.skipFiles` is provided it is passed through unchanged, so a caller can choose to
|
|
543
556
|
* still write the ESI by omitting it from that list.
|
|
544
557
|
* - Extracts the ESI from the package and stores it in IndexedDB keyed by firmware version (see
|
|
545
|
-
* {@link cacheEsiFromFirmwarePackage}) *before* the installation starts, so
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
558
|
+
* {@link cacheEsiFromFirmwarePackage}) *before* the installation starts, so {@link getDeviceFile} can return it
|
|
559
|
+
* as soon as the installation ends. IndexedDB is the only source of the ESI file: {@link getDeviceFile} never
|
|
560
|
+
* reads it from the drive, even when the ESI is also written there. A caching failure is logged but does not
|
|
561
|
+
* prevent installation; the ESI file is then not found until a user loads it from the package (Load ESI in
|
|
562
|
+
* OBLAC Drives).
|
|
549
563
|
*
|
|
550
564
|
* @param firmwareVersion - The firmware version to key the cached ESI by; this must match the value reported by
|
|
551
565
|
* the device parameter 0x100a:00 after installation, which {@link getDeviceFile} uses.
|
|
552
566
|
* For OBLAC firmware packages this is the version encoded in the package filename, see
|
|
553
567
|
* {@link extractFirmwareVersionFromFirmwarePackageFilename}. When it cannot be determined
|
|
554
568
|
* (e.g. an arbitrary package with an unrecognized filename), pass an empty value to skip
|
|
555
|
-
* caching; the
|
|
569
|
+
* caching; the ESI file is then not found unless it was cached before.
|
|
556
570
|
*/
|
|
557
571
|
installFirmwareAndCacheEsi(firmwareVersion, props, requestTimeout, messageId) {
|
|
558
572
|
var _a;
|
|
@@ -3367,7 +3381,7 @@ class MotionMasterReqResClient {
|
|
|
3367
3381
|
// @ts-ignore
|
|
3368
3382
|
return this.runOsCommandAndWriteFsBuffer(deviceRef, command, content, commandTimeout, responsePollingInterval).pipe((0, operators_1.last)(), (0, operators_1.map)((response) => {
|
|
3369
3383
|
if (response.request !== 'succeeded') {
|
|
3370
|
-
throw new Error(
|
|
3384
|
+
throw new Error(`Login to SMM for parameter download failed: ${(0, os_command_1.describeOsCommandFailure)(response)}.`);
|
|
3371
3385
|
}
|
|
3372
3386
|
}));
|
|
3373
3387
|
}
|
|
@@ -3395,7 +3409,7 @@ class MotionMasterReqResClient {
|
|
|
3395
3409
|
// @ts-ignore
|
|
3396
3410
|
return this.runOsCommandAndWriteFsBuffer(deviceRef, command, content, commandTimeout, responsePollingInterval).pipe((0, operators_1.last)(), (0, operators_1.map)((response) => {
|
|
3397
3411
|
if (response.request !== 'succeeded') {
|
|
3398
|
-
throw new Error(
|
|
3412
|
+
throw new Error(`Login to SMM for software update failed: ${(0, os_command_1.describeOsCommandFailure)(response)}.`);
|
|
3399
3413
|
}
|
|
3400
3414
|
}));
|
|
3401
3415
|
}
|
|
@@ -3415,7 +3429,7 @@ class MotionMasterReqResClient {
|
|
|
3415
3429
|
const command = (0, os_command_1.createSmmAcyclicHandlerOsCommand)(os_command_1.SmmAcyclicHandlerSubcommandId.LOGOUT);
|
|
3416
3430
|
return this.runOsCommand(deviceRef, command, commandTimeout, responsePollingInterval).pipe((0, operators_1.last)(), (0, operators_1.map)((response) => {
|
|
3417
3431
|
if (response.request !== 'succeeded') {
|
|
3418
|
-
throw new Error(
|
|
3432
|
+
throw new Error(`Logout from SMM failed: ${(0, os_command_1.describeOsCommandFailure)(response)}.`);
|
|
3419
3433
|
}
|
|
3420
3434
|
}));
|
|
3421
3435
|
}
|
|
@@ -3440,25 +3454,84 @@ class MotionMasterReqResClient {
|
|
|
3440
3454
|
/**
|
|
3441
3455
|
* Changes the SMM password for the specified device.
|
|
3442
3456
|
*
|
|
3457
|
+
* The sequence is: write `<newPassword><oldPassword>` to the fs-buffer and run CHANGE_PASSWORD. If, and only if, the
|
|
3458
|
+
* SMM accepts the change, log out from the SMM, trigger an SMM restart (unless `triggerRestart` is false) and then
|
|
3459
|
+
* wait `rebootDelay` milliseconds before completing, so the next SMM command a caller issues does not land in the
|
|
3460
|
+
* reboot.
|
|
3461
|
+
*
|
|
3462
|
+
* The logout has to come before the restart. When the SMM is restarted while the session is still open, it stores
|
|
3463
|
+
* neither the new nor the old password, although it accepted the change, and only the master password gets in again.
|
|
3464
|
+
* After an accepted change the session is therefore closed; log in again with the new password to continue.
|
|
3465
|
+
*
|
|
3443
3466
|
* @param deviceRef - A reference to the target device.
|
|
3444
3467
|
* @param oldPassword - The current SMM password.
|
|
3445
|
-
* @param newPassword - The new password to set for the SMM.
|
|
3446
|
-
* @param commandTimeout - The timeout in milliseconds for the command
|
|
3468
|
+
* @param newPassword - The new password to set for the SMM, 4 to 24 printable ASCII characters.
|
|
3469
|
+
* @param commandTimeout - The timeout in milliseconds for the CHANGE_PASSWORD command. On firmware below 5.2.0 it also
|
|
3470
|
+
* covers the FoE write of the fs-buffer, because the buffer is written before the command is issued. If it runs out
|
|
3471
|
+
* while the SMM is still storing the password, the client gives up, the SMM is not restarted, and the result of the
|
|
3472
|
+
* change is unknown. Defaults to 12000: the drive gives up on the SMM after 10 s, so do not set it lower, and do not
|
|
3473
|
+
* retry after a timeout, the SMM may still be running the change.
|
|
3447
3474
|
* @param responsePollingInterval - The interval in milliseconds between polling for the response.
|
|
3448
|
-
*
|
|
3449
|
-
*
|
|
3475
|
+
* @param triggerRestart - Whether to restart the SMM after an accepted change and the logout. Defaults to true. The
|
|
3476
|
+
* firmware requires a restart after a password change, so pass false only if you restart the SMM yourself (with
|
|
3477
|
+
* {@link triggerSmmRestart}) before issuing any other SMM command. The logout happens either way, so your restart
|
|
3478
|
+
* comes after it.
|
|
3479
|
+
* @param rebootDelay - How long to wait after the restart command completes, in milliseconds, before the observable
|
|
3480
|
+
* completes. Defaults to 3000, the `SMM_REBOOT_DELAY` the firmware test reference uses. Ignored when
|
|
3481
|
+
* `triggerRestart` is false.
|
|
3482
|
+
*
|
|
3483
|
+
* @throws {Error} If a password is not 1 (old) or 4 (new) to 24 printable ASCII characters, if the SMM rejects the
|
|
3484
|
+
* change (the SMM is then not restarted and the session stays open), if the logout after an accepted change fails
|
|
3485
|
+
* (the SMM is then not restarted), or if the restart fails.
|
|
3450
3486
|
*
|
|
3451
3487
|
* @remarks
|
|
3452
|
-
*
|
|
3453
|
-
*
|
|
3488
|
+
* Completion means the SMM accepted the change. It does not prove that the SMM stored the intended password; only a
|
|
3489
|
+
* login with the new password does.
|
|
3454
3490
|
*
|
|
3455
|
-
*
|
|
3491
|
+
* Rules a caller has to follow, because the SMM and the drive do not enforce them for you:
|
|
3492
|
+
*
|
|
3493
|
+
* - A login for parameter download must be open. A login fails while a session is already open, so log out first:
|
|
3494
|
+
* use {@link reloginToSmmForParameterDownload}. The SMM closes a session after 5 minutes.
|
|
3495
|
+
* - The OS command object (0x1023) and the fs-buffer are a single slot per device. Nothing else may issue OS commands
|
|
3496
|
+
* to the same device during the sequence: no second script, no Motion Master Console on the OS Commands page, no
|
|
3497
|
+
* other tool. Issue SMM functions one at a time and wait for each to complete.
|
|
3498
|
+
* - When the SMM rejects the change, the response does not say why: a wrong old password, a missing or expired
|
|
3499
|
+
* session and an SMM that is still booting all produce the same "the SMM rejected the request (status 2)".
|
|
3456
3500
|
*/
|
|
3457
|
-
changeSmmPassword(deviceRef, oldPassword, newPassword, commandTimeout =
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3501
|
+
changeSmmPassword(deviceRef, oldPassword, newPassword, commandTimeout = 12000, responsePollingInterval = 500, triggerRestart = true, rebootDelay = 3000) {
|
|
3502
|
+
return (0, rxjs_1.defer)(() => {
|
|
3503
|
+
(0, smm_1.assertValidSmmPassword)(oldPassword, 'Old password');
|
|
3504
|
+
(0, smm_1.assertValidSmmPassword)(newPassword, 'New password', smm_1.SMM_PASSWORD_MIN_LENGTH);
|
|
3505
|
+
const command = (0, os_command_1.createSmmAcyclicHandlerOsCommand)(os_command_1.SmmAcyclicHandlerSubcommandId.CHANGE_PASSWORD);
|
|
3506
|
+
const content = (0, smm_1.packSmmChangePassword)(newPassword, oldPassword);
|
|
3507
|
+
// @ts-ignore
|
|
3508
|
+
return this.runOsCommandAndWriteFsBuffer(deviceRef, command, content, commandTimeout, responsePollingInterval);
|
|
3509
|
+
}).pipe((0, operators_1.last)(), (0, operators_1.concatMap)((response) => {
|
|
3510
|
+
if (response.request !== 'succeeded') {
|
|
3511
|
+
throw new Error(`Changing SMM password failed: ${(0, os_command_1.describeOsCommandFailure)(response)}. The password was not changed and the SMM was not restarted.`);
|
|
3512
|
+
}
|
|
3513
|
+
// Log out before the restart. A restart while the session is still open makes the SMM store neither the new
|
|
3514
|
+
// nor the old password, although it accepted the change (reproduced on firmware v5.6.6, issue #185).
|
|
3515
|
+
const logout$ = this.logoutFromSmm(deviceRef).pipe((0, operators_1.catchError)((err) => (0, rxjs_1.throwError)(() => {
|
|
3516
|
+
var _a;
|
|
3517
|
+
return new Error(`The SMM accepted the password change, but the logout failed, so the SMM was not restarted. Log out and restart the SMM before using the new password. ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
|
|
3518
|
+
})), (0, operators_1.last)(null, undefined));
|
|
3519
|
+
if (!triggerRestart) {
|
|
3520
|
+
return logout$;
|
|
3521
|
+
}
|
|
3522
|
+
return logout$.pipe((0, operators_1.concatMap)(() => this.triggerSmmRestart(deviceRef).pipe((0, operators_1.catchError)((err) => (0, rxjs_1.throwError)(() => { var _a; return new Error(`The SMM accepted the password change, but the restart failed. ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`); })), (0, operators_1.last)(null, undefined))), (0, operators_1.concatMap)(() => (0, rxjs_1.timer)(rebootDelay)));
|
|
3523
|
+
}), (0, operators_1.last)(null, undefined), (0, operators_1.map)(() => undefined));
|
|
3524
|
+
}
|
|
3525
|
+
/**
|
|
3526
|
+
* Runs an SMM sequence and then logs out from the SMM, on completion and on error alike.
|
|
3527
|
+
*
|
|
3528
|
+
* Unlike a `finalize(() => logout.subscribe())`, the logout is part of the returned observable: the result is
|
|
3529
|
+
* delivered only after the logout has finished, so a caller's next SMM command cannot run alongside it on the
|
|
3530
|
+
* device's single OS command slot. A failing logout does not replace the sequence's own result or error.
|
|
3531
|
+
*/
|
|
3532
|
+
logoutFromSmmAfter(deviceRef, sequence$) {
|
|
3533
|
+
const logout$ = (0, rxjs_1.defer)(() => this.logoutFromSmm(deviceRef)).pipe((0, operators_1.catchError)(() => rxjs_1.EMPTY), (0, operators_1.filter)(() => false));
|
|
3534
|
+
return (0, rxjs_1.concat)(sequence$.pipe((0, operators_1.catchError)((err) => (0, rxjs_1.concat)(logout$, (0, rxjs_1.throwError)(() => err)))), logout$);
|
|
3462
3535
|
}
|
|
3463
3536
|
/**
|
|
3464
3537
|
* Triggers an SMM restart for the specified device.
|
|
@@ -3479,7 +3552,7 @@ class MotionMasterReqResClient {
|
|
|
3479
3552
|
const command = (0, os_command_1.createSmmAcyclicHandlerOsCommand)(os_command_1.SmmAcyclicHandlerSubcommandId.TRIGGER_RESTART);
|
|
3480
3553
|
return this.runOsCommand(deviceRef, command, commandTimeout, responsePollingInterval).pipe((0, operators_1.last)(), (0, operators_1.map)((response) => {
|
|
3481
3554
|
if (response.request !== 'succeeded') {
|
|
3482
|
-
throw new Error(
|
|
3555
|
+
throw new Error(`Triggering SMM restart failed: ${(0, os_command_1.describeOsCommandFailure)(response)}.`);
|
|
3483
3556
|
}
|
|
3484
3557
|
}));
|
|
3485
3558
|
}
|
|
@@ -3499,7 +3572,7 @@ class MotionMasterReqResClient {
|
|
|
3499
3572
|
const command = (0, os_command_1.createSmmAcyclicHandlerOsCommand)(os_command_1.SmmAcyclicHandlerSubcommandId.IO_FAILURE_ACKNOWLEDGE);
|
|
3500
3573
|
return this.runOsCommand(deviceRef, command, commandTimeout, responsePollingInterval).pipe((0, operators_1.last)(), (0, operators_1.map)((response) => {
|
|
3501
3574
|
if (response.request !== 'succeeded') {
|
|
3502
|
-
throw new Error(
|
|
3575
|
+
throw new Error(`Acknowledging I/O failure on SMM failed: ${(0, os_command_1.describeOsCommandFailure)(response)}.`);
|
|
3503
3576
|
}
|
|
3504
3577
|
}));
|
|
3505
3578
|
}
|
|
@@ -3516,7 +3589,7 @@ class MotionMasterReqResClient {
|
|
|
3516
3589
|
const command = (0, os_command_1.createSmmAcyclicHandlerConfigureSoftwareUpdateOsCommand)(fileSize);
|
|
3517
3590
|
return this.runOsCommand(deviceRef, command, 10000, 100).pipe((0, operators_1.last)(), (0, operators_1.map)((response) => {
|
|
3518
3591
|
if (response.request !== 'succeeded') {
|
|
3519
|
-
throw new Error(
|
|
3592
|
+
throw new Error(`Configuring SMM software update failed: ${(0, os_command_1.describeOsCommandFailure)(response)}.`);
|
|
3520
3593
|
}
|
|
3521
3594
|
}));
|
|
3522
3595
|
}
|
|
@@ -3551,45 +3624,94 @@ class MotionMasterReqResClient {
|
|
|
3551
3624
|
* The commands execute only when subscribed to, ensuring a strict sequence.
|
|
3552
3625
|
*/
|
|
3553
3626
|
updateSmmSoftware(deviceRef, username, password, buffer, crc, chunkSize = 1000, commandTimeout = 30000, responsePollingInterval = 1000, fsBufferReadWriteTimeout = 120000) {
|
|
3627
|
+
return this.updateSmmSoftwareWithProgress(deviceRef, username, password, buffer, crc, chunkSize, commandTimeout, responsePollingInterval, fsBufferReadWriteTimeout).pipe((0, operators_1.last)(), (0, operators_1.map)(() => undefined));
|
|
3628
|
+
}
|
|
3629
|
+
/**
|
|
3630
|
+
* Performs a complete SMM software update like {@link updateSmmSoftware}, reporting progress as it goes.
|
|
3631
|
+
*
|
|
3632
|
+
* A {@link SmmSoftwareUpdateProgress} value is emitted when each stage starts, once per transmitted chunk,
|
|
3633
|
+
* and a final `done` value after the SMM restart has been triggered. Use it to show the user what the
|
|
3634
|
+
* update is doing; transmitting a large software file can take many minutes.
|
|
3635
|
+
*
|
|
3636
|
+
* Parameters are the same as for {@link updateSmmSoftware}.
|
|
3637
|
+
*
|
|
3638
|
+
* @throws {Error} If `buffer` is empty or `chunkSize` is not a positive integer. Nothing is sent to the device then.
|
|
3639
|
+
* @throws {Error} If any step in the software update process fails.
|
|
3640
|
+
*/
|
|
3641
|
+
updateSmmSoftwareWithProgress(deviceRef, username, password, buffer, crc, chunkSize = 1000, commandTimeout = 30000, responsePollingInterval = 1000, fsBufferReadWriteTimeout = 120000) {
|
|
3554
3642
|
const fileSize = buffer.byteLength;
|
|
3555
|
-
//
|
|
3643
|
+
// A NaN chunk size (e.g. from a disabled form control) would otherwise skip every chunk
|
|
3644
|
+
// and finalize an update with no software transmitted.
|
|
3645
|
+
if (!Number.isInteger(chunkSize) || chunkSize <= 0) {
|
|
3646
|
+
return (0, rxjs_1.throwError)(() => new Error(`Invalid SMM software update chunk size: ${chunkSize}. It must be a positive integer.`));
|
|
3647
|
+
}
|
|
3648
|
+
if (fileSize === 0) {
|
|
3649
|
+
return (0, rxjs_1.throwError)(() => new Error('The SMM software file is empty.'));
|
|
3650
|
+
}
|
|
3651
|
+
const chunks = Math.ceil(fileSize / chunkSize); // Total number of chunks to send.
|
|
3652
|
+
// Reports the stage, then runs its work. Deferred, so each step starts only after the previous one completes.
|
|
3653
|
+
const step = (stage, work, chunk = 0) => (0, rxjs_1.concat)((0, rxjs_1.of)({ stage, chunk, chunks }), (0, rxjs_1.defer)(work).pipe((0, operators_1.ignoreElements)()));
|
|
3556
3654
|
const observables = [
|
|
3557
3655
|
// Step 1: Ensure any existing session is logged out before starting the update.
|
|
3558
|
-
(
|
|
3656
|
+
step('logout', () => this.logoutFromSmm(deviceRef)),
|
|
3559
3657
|
// Step 2: Log in to the device specifically for software update permissions.
|
|
3560
|
-
(
|
|
3658
|
+
step('login', () => this.loginToSmmForSoftwareUpdate(deviceRef, username, password)),
|
|
3561
3659
|
// Step 3: Inform the device about the incoming file size for proper setup.
|
|
3562
|
-
(
|
|
3660
|
+
step('configure', () => this.configureSmmSoftwareUpdate(deviceRef, fileSize)),
|
|
3563
3661
|
];
|
|
3564
|
-
const n = Math.ceil(fileSize / chunkSize); // Total number of chunks to send.
|
|
3565
3662
|
// Step 4: Transmit the update file chunk-by-chunk.
|
|
3566
3663
|
const transmitSoftwareCommand = (0, os_command_1.createSmmAcyclicHandlerOsCommand)(os_command_1.SmmAcyclicHandlerSubcommandId.TRANSMIT_SOFTWARE);
|
|
3567
|
-
for (let i = 0; i <
|
|
3664
|
+
for (let i = 0; i < chunks; i++) {
|
|
3568
3665
|
const start = i * chunkSize;
|
|
3569
|
-
const
|
|
3570
|
-
|
|
3571
|
-
observables.push((0, rxjs_1.defer)(() => this.runOsCommandAndWriteFsBuffer(deviceRef, transmitSoftwareCommand, fsBufferContent, commandTimeout, responsePollingInterval, false, fsBufferReadWriteTimeout).pipe((0, operators_1.last)(), // Only consider the final response of the command.
|
|
3666
|
+
const fsBufferContent = buffer.slice(start, start + chunkSize);
|
|
3667
|
+
observables.push(step('transmit', () => this.runOsCommandAndWriteFsBuffer(deviceRef, transmitSoftwareCommand, fsBufferContent, commandTimeout, responsePollingInterval, false, fsBufferReadWriteTimeout).pipe((0, operators_1.last)(), // Only consider the final response of the command.
|
|
3572
3668
|
(0, operators_1.map)((response) => {
|
|
3573
3669
|
if (response.request !== 'succeeded') {
|
|
3574
|
-
throw new Error(`Transmitting SMM software chunk ${i + 1}/${
|
|
3670
|
+
throw new Error(`Transmitting SMM software chunk ${i + 1}/${chunks} failed: ${(0, os_command_1.describeOsCommandFailure)(response)}.`);
|
|
3575
3671
|
}
|
|
3576
|
-
})
|
|
3577
|
-
)));
|
|
3672
|
+
})), i + 1));
|
|
3578
3673
|
}
|
|
3579
|
-
// Step 5:
|
|
3674
|
+
// Step 5: Send the CRC to validate data integrity and finalize the update.
|
|
3580
3675
|
const packedCrc = (0, smm_1.packSmmSoftwareUpdateCrc)(crc);
|
|
3581
3676
|
const finalizeSoftwareUpdateCommand = (0, os_command_1.createSmmAcyclicHandlerOsCommand)(os_command_1.SmmAcyclicHandlerSubcommandId.FINALIZE_SOFTWARE_UPDATE);
|
|
3582
|
-
|
|
3583
|
-
observables.push((0, rxjs_1.defer)(() =>
|
|
3677
|
+
observables.push(step('finalize', () =>
|
|
3584
3678
|
// @ts-ignore
|
|
3585
3679
|
this.runOsCommandAndWriteFsBuffer(deviceRef, finalizeSoftwareUpdateCommand, packedCrc).pipe((0, operators_1.last)(), (0, operators_1.map)((response) => {
|
|
3586
3680
|
if (response.request !== 'succeeded') {
|
|
3587
|
-
throw new Error(
|
|
3681
|
+
throw new Error(`Finalizing SMM software update failed: ${(0, os_command_1.describeOsCommandFailure)(response)}. Check that the CRC matches the software file.`);
|
|
3588
3682
|
}
|
|
3589
|
-
}))),
|
|
3590
|
-
// Step
|
|
3591
|
-
(
|
|
3592
|
-
return (0, rxjs_1.concat)(...observables)
|
|
3683
|
+
})), chunks),
|
|
3684
|
+
// Step 6: Trigger a restart so the new software takes effect.
|
|
3685
|
+
step('restart', () => this.triggerSmmRestart(deviceRef), chunks), (0, rxjs_1.of)({ stage: 'done', chunk: chunks, chunks }));
|
|
3686
|
+
return (0, rxjs_1.concat)(...observables);
|
|
3687
|
+
}
|
|
3688
|
+
/**
|
|
3689
|
+
* Waits until the SMM reports the expected software version.
|
|
3690
|
+
*
|
|
3691
|
+
* After an SMM software update the SMM restarts and boots the new software, which can take around 30 s.
|
|
3692
|
+
* Reads may fail or return the previous version during that time, so the version is polled until it
|
|
3693
|
+
* matches or the timeout expires.
|
|
3694
|
+
*
|
|
3695
|
+
* @param deviceRef - Reference to the target device.
|
|
3696
|
+
* @param expectedVersion - The expected SMM software version, bitwise encoded (e.g. 25344 for v99.0).
|
|
3697
|
+
* @param timeout - (Optional) How long to wait, in ms. Defaults to 90,000 ms.
|
|
3698
|
+
* @param pollingInterval - (Optional) Delay between reads, in ms. Defaults to 2000 ms.
|
|
3699
|
+
*
|
|
3700
|
+
* @throws {Error} If the SMM does not report the expected version within `timeout`.
|
|
3701
|
+
*/
|
|
3702
|
+
waitForSmmFirmwareVersion(deviceRef, expectedVersion, timeout = 90000, pollingInterval = 2000) {
|
|
3703
|
+
let lastVersion;
|
|
3704
|
+
let lastError;
|
|
3705
|
+
const read$ = (0, rxjs_1.defer)(() => this.readSmmFirmwareVersionAsNumber(deviceRef)).pipe((0, rxjs_1.tap)((version) => (lastVersion = version)), (0, operators_1.catchError)((err) => {
|
|
3706
|
+
lastError = err;
|
|
3707
|
+
return rxjs_1.EMPTY;
|
|
3708
|
+
}));
|
|
3709
|
+
return read$.pipe((0, operators_1.repeat)({ delay: () => (lastVersion === expectedVersion ? rxjs_1.EMPTY : (0, rxjs_1.timer)(pollingInterval)) }), (0, operators_1.filter)((version) => version === expectedVersion), (0, operators_1.take)(1), (0, operators_1.map)(() => undefined), (0, operators_1.takeUntil)((0, rxjs_1.timer)(timeout).pipe((0, operators_1.mergeMap)(() => {
|
|
3710
|
+
const reported = lastVersion !== undefined
|
|
3711
|
+
? `it reports v${(0, smm_1.bitwiseDecodeNumberVersion)(lastVersion)}`
|
|
3712
|
+
: `reading the version failed: ${lastError instanceof Error ? lastError.message : String(lastError)}`;
|
|
3713
|
+
return (0, rxjs_1.throwError)(() => new Error(`SMM did not report software v${(0, smm_1.bitwiseDecodeNumberVersion)(expectedVersion)} within ${timeout / 1000} s after the update; ${reported}.`));
|
|
3714
|
+
}))));
|
|
3593
3715
|
}
|
|
3594
3716
|
/**
|
|
3595
3717
|
* Resolves whether an automated SMM software update is allowed for the specified device.
|
|
@@ -3601,7 +3723,8 @@ class MotionMasterReqResClient {
|
|
|
3601
3723
|
*
|
|
3602
3724
|
* The automation is only permitted when **all** of the following conditions are met:
|
|
3603
3725
|
* - The device is a recognized Somanet product (`isValidProduct`).
|
|
3604
|
-
* - The device belongs to the Circulo product family (`isCirculo`).
|
|
3726
|
+
* - The device belongs to the Circulo product family (`isCirculo`). ACTILINK-JD / JD Duo devices are judged
|
|
3727
|
+
* by the underlying Circulo from their hardware description, see {@link resolveEncoderProductId}.
|
|
3605
3728
|
* - The binary is encrypted (`isBinaryEncrypted`).
|
|
3606
3729
|
* - The binary targets one of the supported SMM versions — v1.4 (260) or v2.56 (568) (`isAllowedBinaryVersion`).
|
|
3607
3730
|
* - If targeting v2.56, the device SoC firmware version is ≥ 5.5.0 (`canInstallV256`).
|
|
@@ -3616,7 +3739,7 @@ class MotionMasterReqResClient {
|
|
|
3616
3739
|
*
|
|
3617
3740
|
* @returns A promise that resolves to an {@link SmmAutomatedUpdate} object containing:
|
|
3618
3741
|
* - `isValidProduct` — Whether the device maps to a known Somanet product.
|
|
3619
|
-
* - `isCirculo` — Whether the device
|
|
3742
|
+
* - `isCirculo` — Whether the device is a Circulo or a Circulo-based ACTILINK-JD / JD Duo.
|
|
3620
3743
|
* - `isCirculo7` — Whether the device is a Circulo 7.
|
|
3621
3744
|
* - `isCirculo9` — Whether the device is a Circulo 9.
|
|
3622
3745
|
* - `isDeviceLw1` — Whether the device SMM firmware is below v1.3 (version < 259).
|
|
@@ -3630,10 +3753,11 @@ class MotionMasterReqResClient {
|
|
|
3630
3753
|
* - `binaryVersion` — The target SMM version encoded in the binary header.
|
|
3631
3754
|
*/
|
|
3632
3755
|
resolveSmmAutomatedUpdateConditions(deviceRef, buffer) {
|
|
3633
|
-
var _a, _b;
|
|
3756
|
+
var _a, _b, _c;
|
|
3634
3757
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
3635
3758
|
const device = yield (0, rxjs_1.lastValueFrom)(this.resolveDevice(deviceRef));
|
|
3636
|
-
|
|
3759
|
+
// ACTILINK-JD / JD Duo wrap a Circulo; judge the product by the underlying Circulo device.
|
|
3760
|
+
const product = (0, product_1.findSomanetProductById)((0, product_1.resolveEncoderProductId)((_a = device === null || device === void 0 ? void 0 : device.hardwareDescription) !== null && _a !== void 0 ? _a : device === null || device === void 0 ? void 0 : device.serialNumber));
|
|
3637
3761
|
const isValidProduct = !!product;
|
|
3638
3762
|
// v1.4 (no safe torque), v2.56 (safe torque)
|
|
3639
3763
|
const allowedTargetSmmVersions = [260, 568];
|
|
@@ -3663,7 +3787,7 @@ class MotionMasterReqResClient {
|
|
|
3663
3787
|
const binaryHeader = (0, smm_1.parseSmmBinaryHeaders)(buffer);
|
|
3664
3788
|
const binaryInformation = (0, smm_1.getInformationFromSmmFirmwareVersion)(binaryHeader.swVersion);
|
|
3665
3789
|
// Allow installation of smm v2.56 only if device SoC firmware version is >=5.5.0
|
|
3666
|
-
const canInstallV256 = binaryHeader.swVersion === 568 ? semver.gte((
|
|
3790
|
+
const canInstallV256 = binaryHeader.swVersion === 568 ? semver.gte((_b = semver.coerce(deviceFirmwareVersion)) !== null && _b !== void 0 ? _b : '0.0.0', '5.5.0') : true;
|
|
3667
3791
|
const isCirculo = product.family === 'Circulo';
|
|
3668
3792
|
const isDeviceLw1 = deviceSmmVersion < 259; // <v1.3
|
|
3669
3793
|
const isV990 = deviceSmmVersion === 25344;
|
|
@@ -3673,7 +3797,7 @@ class MotionMasterReqResClient {
|
|
|
3673
3797
|
const isAllowedBinaryVersion = allowedTargetSmmVersions.includes(binaryInformation.version);
|
|
3674
3798
|
const isCirculo7 = isCirculo && product.group === 'Circulo 7';
|
|
3675
3799
|
const isCirculo9 = isCirculo && product.group === 'Circulo 9';
|
|
3676
|
-
const deviceSupportsStreaming = semver.gte((
|
|
3800
|
+
const deviceSupportsStreaming = semver.gte((_c = semver.coerce(deviceFirmwareVersion)) !== null && _c !== void 0 ? _c : '0.0.0', '5.2.0');
|
|
3677
3801
|
const isAutomationAllowed = isCirculo &&
|
|
3678
3802
|
isBinaryEncrypted &&
|
|
3679
3803
|
isAllowedBinaryVersion &&
|
|
@@ -3701,7 +3825,8 @@ class MotionMasterReqResClient {
|
|
|
3701
3825
|
});
|
|
3702
3826
|
}
|
|
3703
3827
|
/**
|
|
3704
|
-
* Performs an automated, multi-step SMM software update to an encrypted firmware version on a Circulo device
|
|
3828
|
+
* Performs an automated, multi-step SMM software update to an encrypted firmware version on a Circulo device,
|
|
3829
|
+
* or a Circulo-based ACTILINK-JD / JD Duo (judged by the underlying Circulo).
|
|
3705
3830
|
*
|
|
3706
3831
|
* Before installing the target binary, this method evaluates the device state by calling
|
|
3707
3832
|
* {@link resolveSmmAutomatedUpdateConditions}. Based on the result, it determines which
|
|
@@ -3715,20 +3840,29 @@ class MotionMasterReqResClient {
|
|
|
3715
3840
|
* - **v99.0 + Circulo 9**: v77.1 → target
|
|
3716
3841
|
* - **v77.0 or v77.1**: target only
|
|
3717
3842
|
*
|
|
3718
|
-
*
|
|
3843
|
+
* After each installation the SMM restarts. The next step starts only once the SMM reports the
|
|
3844
|
+
* version that was just installed (see {@link waitForSmmFirmwareVersion}), so a failed or
|
|
3845
|
+
* unfinished installation stops the sequence with an error instead of continuing blindly.
|
|
3846
|
+
*
|
|
3847
|
+
* Transfer settings that are not given are resolved from the device firmware with
|
|
3848
|
+
* {@link resolveSmmSoftwareUpdateTimings}. On firmware without fs-buffer streaming (< v5.2.0),
|
|
3849
|
+
* `chunkSize` is always 1000 and `commandTimeout` is at least 420,000 ms.
|
|
3719
3850
|
*
|
|
3720
3851
|
* @param deviceRef - Reference to the target device.
|
|
3721
3852
|
* @param username - Username used for SMM authentication.
|
|
3722
3853
|
* @param password - Password used for SMM authentication.
|
|
3723
3854
|
* @param buffer - The target encrypted firmware binary as a `Uint8Array`.
|
|
3724
3855
|
* @param crc - CRC32 checksum of the target firmware binary for integrity verification.
|
|
3725
|
-
* @param chunkSize - (Optional) Size of each data chunk transmitted per step, in bytes.
|
|
3726
|
-
*
|
|
3727
|
-
*
|
|
3856
|
+
* @param chunkSize - (Optional) Size of each data chunk transmitted per step, in bytes.
|
|
3857
|
+
* Defaults to 1,000,000 with streaming (one command per file) and 1000 without.
|
|
3858
|
+
* @param commandTimeout - (Optional) Timeout for each OS command in ms.
|
|
3859
|
+
* Defaults to 180,000 ms with streaming and 420,000 ms without.
|
|
3728
3860
|
* @param responsePollingInterval - (Optional) Polling interval for command responses in ms. Defaults to 1000 ms.
|
|
3729
|
-
* @param fsBufferReadWriteTimeout - (Optional) Timeout for fs-buffer read/write operations in ms.
|
|
3861
|
+
* @param fsBufferReadWriteTimeout - (Optional) Timeout for fs-buffer read/write operations in ms.
|
|
3862
|
+
* Defaults to 150,000 ms with streaming and 5000 ms without.
|
|
3730
3863
|
*
|
|
3731
|
-
* @returns An `Observable
|
|
3864
|
+
* @returns An `Observable` of {@link SmmAutomatedUpdateProgress} that reports each step and stage,
|
|
3865
|
+
* and completes when the full installation sequence finishes successfully.
|
|
3732
3866
|
*
|
|
3733
3867
|
* @throws {Error} With message `'Unknown product. Cannot perform SMM software update.'` if the device
|
|
3734
3868
|
* serial number does not map to a known Somanet product.
|
|
@@ -3736,104 +3870,88 @@ class MotionMasterReqResClient {
|
|
|
3736
3870
|
* e.g. the device is not a Circulo, the binary is not encrypted, the target version is
|
|
3737
3871
|
* unsupported, the SoC firmware is below v5.5.0 (for SMM v2.56), or the device is
|
|
3738
3872
|
* already on SMM v1.3+.
|
|
3739
|
-
* @throws {Error} If any
|
|
3873
|
+
* @throws {Error} If any installation step fails, or the SMM does not report the installed version afterwards.
|
|
3740
3874
|
*/
|
|
3741
|
-
updateSmmSoftwareToEncrypted(deviceRef, username, password, buffer, crc, chunkSize
|
|
3742
|
-
return
|
|
3743
|
-
(
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
* subscriber only when we tell it to do so via `subscriber.<method>`. Error handling from the outside
|
|
3751
|
-
* will not pick-up the errors from here as this is "fire-and-forget" type of function.
|
|
3752
|
-
*
|
|
3753
|
-
* @example
|
|
3754
|
-
*
|
|
3755
|
-
* ```typescript
|
|
3756
|
-
* asyncHandler(async (req, res, next) => {
|
|
3757
|
-
* somePromiseThatRejects().then(...) // ← no await, no .catch()
|
|
3758
|
-
* // or a setTimeout, event emitter, etc.
|
|
3759
|
-
*
|
|
3760
|
-
* // handler finishes immediately
|
|
3761
|
-
})
|
|
3762
|
-
*/
|
|
3763
|
-
try {
|
|
3764
|
-
const automatedInstall = yield this.resolveSmmAutomatedUpdateConditions(deviceRef, buffer);
|
|
3765
|
-
if (!automatedInstall.isValidProduct) {
|
|
3766
|
-
subscriber.error(new Error('Unknown product. Cannot perform SMM software update.'));
|
|
3767
|
-
return;
|
|
3768
|
-
}
|
|
3769
|
-
let observables = [];
|
|
3770
|
-
if (automatedInstall.isAutomationAllowed) {
|
|
3771
|
-
const v990crc = 0xd1a3bb72;
|
|
3772
|
-
const v990Buffer = yield Promise.resolve().then(() => tslib_1.__importStar(require('./smm/40701_DEBUG_Update_V99.0'))).then((m) => Uint8Array.from(atob(m.smmBinaryV990), (c, _) => { var _a; return (_a = c.codePointAt(0)) !== null && _a !== void 0 ? _a : 0; }));
|
|
3773
|
-
const v770crc = 0xe290a20a;
|
|
3774
|
-
const v770Buffer = yield Promise.resolve().then(() => tslib_1.__importStar(require('./smm/40701_DEBUG_Update_V77.0'))).then((m) => Uint8Array.from(atob(m.smmBinaryV770), (c, _) => { var _a; return (_a = c.codePointAt(0)) !== null && _a !== void 0 ? _a : 0; }));
|
|
3775
|
-
const v771crc = 0xa4a11125;
|
|
3776
|
-
const v771Buffer = yield Promise.resolve().then(() => tslib_1.__importStar(require('./smm/40701_DEBUG_Update_V77.1'))).then((m) => Uint8Array.from(atob(m.smmBinaryV771), (c, _) => { var _a; return (_a = c.codePointAt(0)) !== null && _a !== void 0 ? _a : 0; }));
|
|
3777
|
-
const v990$ = (0, rxjs_1.defer)(() => this.updateSmmSoftware(deviceRef, username, password, new Uint8Array(v990Buffer), v990crc, chunkSize, commandTimeout, responsePollingInterval, fsBufferReadWriteTimeout));
|
|
3778
|
-
const v770$ = (0, rxjs_1.defer)(() => this.updateSmmSoftware(deviceRef, username, password, new Uint8Array(v770Buffer), v770crc, chunkSize, commandTimeout, responsePollingInterval, fsBufferReadWriteTimeout));
|
|
3779
|
-
const v771$ = (0, rxjs_1.defer)(() => this.updateSmmSoftware(deviceRef, username, password, new Uint8Array(v771Buffer), v771crc, chunkSize, commandTimeout, responsePollingInterval, fsBufferReadWriteTimeout));
|
|
3780
|
-
const userVersion$ = (0, rxjs_1.defer)(() => this.updateSmmSoftware(deviceRef, username, password, buffer, crc, chunkSize, commandTimeout, responsePollingInterval, fsBufferReadWriteTimeout));
|
|
3781
|
-
if (automatedInstall.isDeviceLw1) {
|
|
3782
|
-
if (automatedInstall.isCirculo7) {
|
|
3783
|
-
observables = [v990$, v770$, userVersion$];
|
|
3784
|
-
}
|
|
3785
|
-
else if (automatedInstall.isCirculo9) {
|
|
3786
|
-
observables = [v990$, v771$, userVersion$];
|
|
3787
|
-
}
|
|
3788
|
-
}
|
|
3789
|
-
else if (automatedInstall.isV990) {
|
|
3790
|
-
if (automatedInstall.isCirculo7) {
|
|
3791
|
-
observables = [v770$, userVersion$];
|
|
3792
|
-
}
|
|
3793
|
-
else if (automatedInstall.isCirculo9) {
|
|
3794
|
-
observables = [v771$, userVersion$];
|
|
3795
|
-
}
|
|
3796
|
-
}
|
|
3797
|
-
else if (automatedInstall.isV770 || automatedInstall.isV771) {
|
|
3798
|
-
observables = [userVersion$];
|
|
3799
|
-
}
|
|
3800
|
-
(0, rxjs_1.concat)(...observables).subscribe({
|
|
3801
|
-
next: () => {
|
|
3802
|
-
subscriber.next();
|
|
3803
|
-
},
|
|
3804
|
-
error: (err) => {
|
|
3805
|
-
subscriber.error(err);
|
|
3806
|
-
},
|
|
3807
|
-
complete: () => {
|
|
3808
|
-
subscriber.complete();
|
|
3809
|
-
},
|
|
3810
|
-
});
|
|
3811
|
-
}
|
|
3812
|
-
else {
|
|
3813
|
-
const reasons = [];
|
|
3814
|
-
if (!automatedInstall.isCirculo) {
|
|
3815
|
-
reasons.push('The device is not part of the Circulo family.');
|
|
3816
|
-
}
|
|
3817
|
-
if (!automatedInstall.isDeviceLw1) {
|
|
3818
|
-
reasons.push('The device SMM software version is not lower than 1.3.');
|
|
3819
|
-
}
|
|
3820
|
-
if (!automatedInstall.isBinaryEncrypted) {
|
|
3821
|
-
reasons.push('The SMM binary is not encrypted.');
|
|
3822
|
-
}
|
|
3823
|
-
if (!automatedInstall.isAllowedBinaryVersion) {
|
|
3824
|
-
reasons.push('The target SMM software version is not supported for encrypted update.');
|
|
3825
|
-
}
|
|
3826
|
-
if (!automatedInstall.canInstallV256) {
|
|
3827
|
-
reasons.push('The device firmware must be minimum v5.5.0 for installing SMM v2.56.');
|
|
3828
|
-
}
|
|
3829
|
-
subscriber.error(new Error(`The following conditions are not met for SMM software automated update: ${reasons.join(' ')}`));
|
|
3830
|
-
}
|
|
3875
|
+
updateSmmSoftwareToEncrypted(deviceRef, username, password, buffer, crc, chunkSize, commandTimeout, responsePollingInterval = 1000, fsBufferReadWriteTimeout) {
|
|
3876
|
+
return (0, rxjs_1.defer)(() => (0, rxjs_1.from)(this.resolveSmmAutomatedUpdateConditions(deviceRef, buffer))).pipe((0, operators_1.mergeMap)((conditions) => {
|
|
3877
|
+
if (!conditions.isValidProduct) {
|
|
3878
|
+
throw new Error('Unknown product. Cannot perform SMM software update.');
|
|
3879
|
+
}
|
|
3880
|
+
if (!conditions.isAutomationAllowed) {
|
|
3881
|
+
const reasons = [];
|
|
3882
|
+
if (!conditions.isCirculo) {
|
|
3883
|
+
reasons.push('The device is not a Circulo or a Circulo-based ACTILINK-JD.');
|
|
3831
3884
|
}
|
|
3832
|
-
|
|
3833
|
-
|
|
3885
|
+
if (!conditions.isDeviceLw1 && !conditions.isV990 && !conditions.isV770 && !conditions.isV771) {
|
|
3886
|
+
reasons.push('The device SMM software version is not lower than 1.3.');
|
|
3834
3887
|
}
|
|
3835
|
-
|
|
3836
|
-
|
|
3888
|
+
if (!conditions.isBinaryEncrypted) {
|
|
3889
|
+
reasons.push('The SMM binary is not encrypted.');
|
|
3890
|
+
}
|
|
3891
|
+
if (!conditions.isAllowedBinaryVersion) {
|
|
3892
|
+
reasons.push('The target SMM software version is not supported for encrypted update.');
|
|
3893
|
+
}
|
|
3894
|
+
if (!conditions.canInstallV256) {
|
|
3895
|
+
reasons.push('The device firmware must be minimum v5.5.0 for installing SMM v2.56.');
|
|
3896
|
+
}
|
|
3897
|
+
throw new Error(`The following conditions are not met for SMM software automated update: ${reasons.join(' ')}`);
|
|
3898
|
+
}
|
|
3899
|
+
const timings = (0, smm_1.resolveSmmSoftwareUpdateTimings)(conditions.deviceSupportsStreaming);
|
|
3900
|
+
const transfer = conditions.deviceSupportsStreaming
|
|
3901
|
+
? {
|
|
3902
|
+
chunkSize: chunkSize !== null && chunkSize !== void 0 ? chunkSize : timings.chunkSize,
|
|
3903
|
+
commandTimeout: commandTimeout !== null && commandTimeout !== void 0 ? commandTimeout : timings.commandTimeout,
|
|
3904
|
+
fsBufferReadWriteTimeout: fsBufferReadWriteTimeout !== null && fsBufferReadWriteTimeout !== void 0 ? fsBufferReadWriteTimeout : timings.fsBufferReadWriteTimeout,
|
|
3905
|
+
}
|
|
3906
|
+
: {
|
|
3907
|
+
// Without streaming the fs-buffer is written before each command, which only works with 1000-byte chunks.
|
|
3908
|
+
chunkSize: timings.chunkSize,
|
|
3909
|
+
commandTimeout: Math.max(commandTimeout !== null && commandTimeout !== void 0 ? commandTimeout : 0, timings.commandTimeout),
|
|
3910
|
+
fsBufferReadWriteTimeout: fsBufferReadWriteTimeout !== null && fsBufferReadWriteTimeout !== void 0 ? fsBufferReadWriteTimeout : timings.fsBufferReadWriteTimeout,
|
|
3911
|
+
};
|
|
3912
|
+
const decode = (base64) => Uint8Array.from(atob(base64), (c) => { var _a; return (_a = c.codePointAt(0)) !== null && _a !== void 0 ? _a : 0; });
|
|
3913
|
+
const v990 = {
|
|
3914
|
+
version: 25344,
|
|
3915
|
+
load: () => Promise.resolve().then(() => tslib_1.__importStar(require('./smm/40701_DEBUG_Update_V99.0'))).then((m) => ({
|
|
3916
|
+
buffer: decode(m.smmBinaryV990),
|
|
3917
|
+
crc: 0xd1a3bb72,
|
|
3918
|
+
})),
|
|
3919
|
+
};
|
|
3920
|
+
const v770 = {
|
|
3921
|
+
version: 19712,
|
|
3922
|
+
load: () => Promise.resolve().then(() => tslib_1.__importStar(require('./smm/40701_DEBUG_Update_V77.0'))).then((m) => ({
|
|
3923
|
+
buffer: decode(m.smmBinaryV770),
|
|
3924
|
+
crc: 0xe290a20a,
|
|
3925
|
+
})),
|
|
3926
|
+
};
|
|
3927
|
+
const v771 = {
|
|
3928
|
+
version: 19713,
|
|
3929
|
+
load: () => Promise.resolve().then(() => tslib_1.__importStar(require('./smm/40701_DEBUG_Update_V77.1'))).then((m) => ({
|
|
3930
|
+
buffer: decode(m.smmBinaryV771),
|
|
3931
|
+
crc: 0xa4a11125,
|
|
3932
|
+
})),
|
|
3933
|
+
};
|
|
3934
|
+
const target = { version: conditions.binaryVersion, load: () => Promise.resolve({ buffer, crc }) };
|
|
3935
|
+
const intermediate = conditions.isCirculo7 ? v770 : conditions.isCirculo9 ? v771 : undefined;
|
|
3936
|
+
let plan = [];
|
|
3937
|
+
if (conditions.isV770 || conditions.isV771) {
|
|
3938
|
+
plan = [target];
|
|
3939
|
+
}
|
|
3940
|
+
else if (intermediate && conditions.isV990) {
|
|
3941
|
+
plan = [intermediate, target];
|
|
3942
|
+
}
|
|
3943
|
+
else if (intermediate && conditions.isDeviceLw1) {
|
|
3944
|
+
plan = [v990, intermediate, target];
|
|
3945
|
+
}
|
|
3946
|
+
if (plan.length === 0) {
|
|
3947
|
+
throw new Error('Cannot determine the SMM update path: the device is neither a Circulo 7 nor a Circulo 9.');
|
|
3948
|
+
}
|
|
3949
|
+
const steps = plan.length;
|
|
3950
|
+
return (0, rxjs_1.concat)(...plan.map(({ version, load }, i) => {
|
|
3951
|
+
const progress = { step: i + 1, steps, version };
|
|
3952
|
+
return (0, rxjs_1.concat)((0, rxjs_1.defer)(() => (0, rxjs_1.from)(load())).pipe((0, operators_1.mergeMap)((software) => this.updateSmmSoftwareWithProgress(deviceRef, username, password, software.buffer, software.crc, transfer.chunkSize, transfer.commandTimeout, responsePollingInterval, transfer.fsBufferReadWriteTimeout)), (0, operators_1.map)((install) => (Object.assign(Object.assign({}, progress), { stage: 'install', install })))), (0, rxjs_1.of)(Object.assign(Object.assign({}, progress), { stage: 'verify' })), (0, rxjs_1.defer)(() => this.waitForSmmFirmwareVersion(deviceRef, version)).pipe((0, operators_1.ignoreElements)()), (0, rxjs_1.of)(Object.assign(Object.assign({}, progress), { stage: 'installed' })));
|
|
3953
|
+
}));
|
|
3954
|
+
}));
|
|
3837
3955
|
}
|
|
3838
3956
|
/**
|
|
3839
3957
|
* Transmits SMM parameters to the specified device.
|
|
@@ -3862,7 +3980,7 @@ class MotionMasterReqResClient {
|
|
|
3862
3980
|
// @ts-ignore
|
|
3863
3981
|
content, commandTimeout, responsePollingInterval).pipe((0, operators_1.last)(), (0, operators_1.map)((response) => {
|
|
3864
3982
|
if (response.request !== 'succeeded') {
|
|
3865
|
-
throw new Error(
|
|
3983
|
+
throw new Error(`Transmitting SMM parameters failed: ${(0, os_command_1.describeOsCommandFailure)(response)}.`);
|
|
3866
3984
|
}
|
|
3867
3985
|
}));
|
|
3868
3986
|
}));
|
|
@@ -3925,7 +4043,7 @@ class MotionMasterReqResClient {
|
|
|
3925
4043
|
const command = (0, os_command_1.createSmmAcyclicHandlerOsCommand)(os_command_1.SmmAcyclicHandlerSubcommandId.VERIFY_PARAMETERS);
|
|
3926
4044
|
return this.runOsCommandAndWriteFsBuffer(deviceRef, command, content, commandTimeout, responsePollingInterval).pipe((0, operators_1.last)(), (0, operators_1.map)((response) => {
|
|
3927
4045
|
if (response.request !== 'succeeded') {
|
|
3928
|
-
throw new Error(
|
|
4046
|
+
throw new Error(`Verifying SMM parameters failed: ${(0, os_command_1.describeOsCommandFailure)(response)}.`);
|
|
3929
4047
|
}
|
|
3930
4048
|
}));
|
|
3931
4049
|
}));
|
|
@@ -3990,7 +4108,7 @@ class MotionMasterReqResClient {
|
|
|
3990
4108
|
const command = (0, os_command_1.createSmmAcyclicHandlerOsCommand)(os_command_1.SmmAcyclicHandlerSubcommandId.VALIDATE_CONFIGURATION);
|
|
3991
4109
|
return this.runOsCommandAndWriteFsBuffer(deviceRef, command, content, commandTimeout, responsePollingInterval).pipe((0, operators_1.last)(), (0, operators_1.map)((response) => {
|
|
3992
4110
|
if (response.request !== 'succeeded') {
|
|
3993
|
-
throw new Error(
|
|
4111
|
+
throw new Error(`Validating SMM configuration failed: ${(0, os_command_1.describeOsCommandFailure)(response)}.`);
|
|
3994
4112
|
}
|
|
3995
4113
|
}));
|
|
3996
4114
|
}
|
|
@@ -4454,7 +4572,7 @@ class MotionMasterReqResClient {
|
|
|
4454
4572
|
// Fail lambda to handle errors in a consistent way.
|
|
4455
4573
|
const fail = (message) => (0, rxjs_1.throwError)(() => new Error(message));
|
|
4456
4574
|
// Resolve the SMM parameter structure version for getting the correct SMM OD.
|
|
4457
|
-
|
|
4575
|
+
const sequence$ = this.resolveSmmParameterStructureVersion(deviceRef).pipe((0, operators_1.concatMap)((parameterStructureVersion) => {
|
|
4458
4576
|
const smmOd = (0, smm_1.resolveSmmOd)(parameterStructureVersion);
|
|
4459
4577
|
// Group SMM safety parameters by their 'group' property,
|
|
4460
4578
|
// then for each group, map the SMM OD parameters to the matching
|
|
@@ -4509,9 +4627,9 @@ class MotionMasterReqResClient {
|
|
|
4509
4627
|
}));
|
|
4510
4628
|
}));
|
|
4511
4629
|
}));
|
|
4512
|
-
}), (0, operators_1.finalize)(() => {
|
|
4513
|
-
this.logoutFromSmm(deviceRef).subscribe();
|
|
4514
4630
|
}));
|
|
4631
|
+
// Log out at the end, on success and on failure, before the result is delivered.
|
|
4632
|
+
return this.logoutFromSmmAfter(deviceRef, sequence$);
|
|
4515
4633
|
}
|
|
4516
4634
|
/**
|
|
4517
4635
|
* Executes a forward call by sending the provided data, then parses the JSON response.
|