motion-master-client 0.0.423 → 0.0.425

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.
@@ -3365,21 +3365,27 @@ class MotionMasterReqResClient {
3365
3365
  * attempt will fail unless the user logs out beforehand.
3366
3366
  *
3367
3367
  * @param deviceRef - The reference to the device to which the login command will be sent.
3368
- * @param username - The optional username used for logging in to the SMM. If not provided, it will be treated as an empty string.
3369
- * @param password - The password used for logging in to the SMM.
3368
+ * @param username - The name of the user who logs in, 4 to 24 printable ASCII characters. It tells who configured
3369
+ * the SMM, so pass the real user's name.
3370
+ * @param password - The password used for logging in to the SMM, 1 to 24 printable ASCII characters.
3370
3371
  * @param commandTimeout - The timeout in milliseconds for the command to complete.
3371
3372
  * @param responsePollingInterval - The interval in milliseconds between polling for the response.
3372
3373
  *
3373
- * @throws {Error} If the login attempt fails.
3374
+ * @throws {Error} If the username or the password is not valid, in which case nothing is sent, or if the login
3375
+ * attempt fails.
3374
3376
  *
3375
3377
  * @remarks
3376
3378
  * If the device is already logged in, the next login attempt will fail unless the user logs out first.
3377
3379
  */
3378
3380
  loginToSmmForParameterDownload(deviceRef, username, password, commandTimeout = 5000, responsePollingInterval = 500) {
3379
- const command = (0, os_command_1.createSmmAcyclicHandlerOsCommand)(os_command_1.SmmAcyclicHandlerSubcommandId.PARAMETER_DOWNLOAD_LOGIN);
3380
- const content = (0, smm_1.packSmmCredentials)(username, password);
3381
- // @ts-ignore
3382
- return this.runOsCommandAndWriteFsBuffer(deviceRef, command, content, commandTimeout, responsePollingInterval).pipe((0, operators_1.last)(), (0, operators_1.map)((response) => {
3381
+ return (0, rxjs_1.defer)(() => {
3382
+ (0, smm_1.assertValidSmmCredential)(username, 'Username', smm_1.SMM_USERNAME_MIN_LENGTH);
3383
+ (0, smm_1.assertValidSmmCredential)(password, 'Password');
3384
+ const command = (0, os_command_1.createSmmAcyclicHandlerOsCommand)(os_command_1.SmmAcyclicHandlerSubcommandId.PARAMETER_DOWNLOAD_LOGIN);
3385
+ const content = (0, smm_1.packSmmCredentials)(username, password);
3386
+ // @ts-ignore
3387
+ return this.runOsCommandAndWriteFsBuffer(deviceRef, command, content, commandTimeout, responsePollingInterval);
3388
+ }).pipe((0, operators_1.last)(), (0, operators_1.map)((response) => {
3383
3389
  if (response.request !== 'succeeded') {
3384
3390
  throw new Error(`Login to SMM for parameter download failed: ${(0, os_command_1.describeOsCommandFailure)(response)}.`);
3385
3391
  }
@@ -3393,21 +3399,27 @@ class MotionMasterReqResClient {
3393
3399
  * attempt will fail unless the user logs out beforehand.
3394
3400
  *
3395
3401
  * @param deviceRef - The reference to the device to which the login command will be sent.
3396
- * @param username - The optional username used for logging in to the SMM. If not provided, it will be treated as an empty string.
3397
- * @param password - The password used for logging in to the SMM.
3402
+ * @param username - The name of the user who logs in, 4 to 24 printable ASCII characters. It tells who configured
3403
+ * the SMM, so pass the real user's name.
3404
+ * @param password - The password used for logging in to the SMM, 1 to 24 printable ASCII characters.
3398
3405
  * @param commandTimeout - The timeout in milliseconds for the command to complete.
3399
3406
  * @param responsePollingInterval - The interval in milliseconds between polling for the response.
3400
3407
  *
3401
- * @throws {Error} If the login attempt fails.
3408
+ * @throws {Error} If the username or the password is not valid, in which case nothing is sent, or if the login
3409
+ * attempt fails.
3402
3410
  *
3403
3411
  * @remarks
3404
3412
  * If the device is already logged in, the next login attempt will fail unless the user logs out first.
3405
3413
  */
3406
3414
  loginToSmmForSoftwareUpdate(deviceRef, username, password, commandTimeout = 10000, responsePollingInterval = 500) {
3407
- const command = (0, os_command_1.createSmmAcyclicHandlerOsCommand)(os_command_1.SmmAcyclicHandlerSubcommandId.SOFTWARE_UPDATE_LOGIN);
3408
- const content = (0, smm_1.packSmmCredentials)(username, password);
3409
- // @ts-ignore
3410
- return this.runOsCommandAndWriteFsBuffer(deviceRef, command, content, commandTimeout, responsePollingInterval).pipe((0, operators_1.last)(), (0, operators_1.map)((response) => {
3415
+ return (0, rxjs_1.defer)(() => {
3416
+ (0, smm_1.assertValidSmmCredential)(username, 'Username', smm_1.SMM_USERNAME_MIN_LENGTH);
3417
+ (0, smm_1.assertValidSmmCredential)(password, 'Password');
3418
+ const command = (0, os_command_1.createSmmAcyclicHandlerOsCommand)(os_command_1.SmmAcyclicHandlerSubcommandId.SOFTWARE_UPDATE_LOGIN);
3419
+ const content = (0, smm_1.packSmmCredentials)(username, password);
3420
+ // @ts-ignore
3421
+ return this.runOsCommandAndWriteFsBuffer(deviceRef, command, content, commandTimeout, responsePollingInterval);
3422
+ }).pipe((0, operators_1.last)(), (0, operators_1.map)((response) => {
3411
3423
  if (response.request !== 'succeeded') {
3412
3424
  throw new Error(`Login to SMM for software update failed: ${(0, os_command_1.describeOsCommandFailure)(response)}.`);
3413
3425
  }
@@ -3455,38 +3467,46 @@ class MotionMasterReqResClient {
3455
3467
  * Changes the SMM password for the specified device.
3456
3468
  *
3457
3469
  * 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.
3470
+ * SMM accepts the change, log in for parameter download with the new password until the SMM accepts the login. The
3471
+ * SMM is not restarted, because it does not need a restart to use a new password.
3461
3472
  *
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.
3473
+ * The SMM acknowledges CHANGE_PASSWORD before it stores the new password. It writes the password to its EEPROM in the
3474
+ * background, refuses every login while the write runs, and closes the session itself when the write is done. A
3475
+ * restart during the write leaves a stored password that the SMM discards at boot, so neither the new nor the old
3476
+ * password logs in and only the master password gets in again. A logout does not wait for the write either. The
3477
+ * first login that the SMM accepts with the new password is therefore the proof that the write is done and that the
3478
+ * SMM stored the intended password.
3479
+ *
3480
+ * After the call, a session for parameter download is open, logged in with the new password, so a caller can go on
3481
+ * with SMM commands without a new login. Log out with {@link logoutFromSmm} when done.
3465
3482
  *
3466
3483
  * @param deviceRef - A reference to the target device.
3484
+ * @param username - The name of the logged-in user, 4 to 24 printable ASCII characters, for the login with the new
3485
+ * password. Pass the same name the user logged in with: it tells who configured the SMM.
3467
3486
  * @param oldPassword - The current SMM password.
3468
3487
  * @param newPassword - The new password to set for the SMM, 4 to 24 printable ASCII characters.
3469
3488
  * @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.
3489
+ * covers the FoE write of the fs-buffer, because the buffer is written before the command is issued. Defaults to
3490
+ * 12000: the drive gives up on the SMM after 10 s, so do not set it lower. If it runs out, the result of the change
3491
+ * is unknown. Do not retry after a timeout, because the SMM may still be running the change.
3474
3492
  * @param responsePollingInterval - The interval in milliseconds between polling for the response.
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.
3493
+ * @param verifyTimeout - How long to keep trying the login with the new password, in milliseconds, counted from the
3494
+ * accepted change. Defaults to 10000.
3495
+ * @param verifyInterval - The pause in milliseconds between two login attempts with the new password. Defaults to
3496
+ * 500.
3497
+ *
3498
+ * @throws {Error} If the username is not 4, or a password not 1 (old) or 4 (new), to 24 printable ASCII characters
3499
+ * (nothing is sent then), if the SMM rejects the change (the session then stays open), or if the SMM does not
3500
+ * accept a login with the new password within `verifyTimeout`.
3486
3501
  *
3487
3502
  * @remarks
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.
3503
+ * Completion means the SMM accepted a login with the new password, so the new password is stored.
3504
+ *
3505
+ * When the SMM does not accept the new password within `verifyTimeout`, the stored password is unknown. Do not run
3506
+ * the change again. Try to log in with the new password, then with the old one.
3507
+ *
3508
+ * Do not restart the SMM, and do not power the device off, until the call completes. Either one during the write
3509
+ * leaves a stored password that the SMM discards.
3490
3510
  *
3491
3511
  * Rules a caller has to follow, because the SMM and the drive do not enforce them for you:
3492
3512
  *
@@ -3498,28 +3518,32 @@ class MotionMasterReqResClient {
3498
3518
  * - When the SMM rejects the change, the response does not say why: a wrong old password, a missing or expired
3499
3519
  * session and an SMM that is still booting all produce the same "the SMM rejected the request (status 2)".
3500
3520
  */
3501
- changeSmmPassword(deviceRef, oldPassword, newPassword, commandTimeout = 12000, responsePollingInterval = 500, triggerRestart = true, rebootDelay = 3000) {
3521
+ changeSmmPassword(deviceRef, username, oldPassword, newPassword, commandTimeout = 12000, responsePollingInterval = 500, verifyTimeout = 10000, verifyInterval = 500) {
3502
3522
  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);
3523
+ (0, smm_1.assertValidSmmCredential)(username, 'Username', smm_1.SMM_USERNAME_MIN_LENGTH);
3524
+ (0, smm_1.assertValidSmmCredential)(oldPassword, 'Old password');
3525
+ (0, smm_1.assertValidSmmCredential)(newPassword, 'New password', smm_1.SMM_PASSWORD_MIN_LENGTH);
3505
3526
  const command = (0, os_command_1.createSmmAcyclicHandlerOsCommand)(os_command_1.SmmAcyclicHandlerSubcommandId.CHANGE_PASSWORD);
3506
3527
  const content = (0, smm_1.packSmmChangePassword)(newPassword, oldPassword);
3507
3528
  // @ts-ignore
3508
3529
  return this.runOsCommandAndWriteFsBuffer(deviceRef, command, content, commandTimeout, responsePollingInterval);
3509
3530
  }).pipe((0, operators_1.last)(), (0, operators_1.concatMap)((response) => {
3510
3531
  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.`);
3532
+ throw new Error(`Changing SMM password failed: ${(0, os_command_1.describeOsCommandFailure)(response)}. The password was not changed.`);
3512
3533
  }
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)));
3534
+ // The SMM acknowledges the change before it writes the password to its EEPROM, and refuses logins until the
3535
+ // write is done. A restart during the write makes the SMM discard the stored password (issue #185), so the
3536
+ // first accepted login with the new password is what ends the write, not a logout or a delay. That login's
3537
+ // session stays open for the caller.
3538
+ const deadline = Date.now() + verifyTimeout;
3539
+ return (0, rxjs_1.defer)(() => this.loginToSmmForParameterDownload(deviceRef, username, newPassword, undefined, responsePollingInterval)).pipe((0, operators_1.retry)({
3540
+ delay: (err) => Date.now() + verifyInterval < deadline
3541
+ ? (0, rxjs_1.timer)(verifyInterval)
3542
+ : (0, rxjs_1.throwError)(() => {
3543
+ var _a;
3544
+ return new Error(`The SMM accepted the password change, but did not accept a login with the new password within ${verifyTimeout} ms, so the stored password is unknown. Do not change the password again; try to log in with the new password, then with the old one. ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
3545
+ }),
3546
+ }), (0, operators_1.last)(null, undefined));
3523
3547
  }), (0, operators_1.last)(null, undefined), (0, operators_1.map)(() => undefined));
3524
3548
  }
3525
3549
  /**
@@ -4361,15 +4385,85 @@ class MotionMasterReqResClient {
4361
4385
  * It sends a series of commands to the device to read the registers and returns the responses as an array.
4362
4386
  * It was added as part of a fix for the EEPROM issue in Circulo integrated encoders.
4363
4387
  *
4388
+ * A register read completes on the drive within a few tens of milliseconds, so the response is polled every
4389
+ * 50 ms rather than at the 1000 ms default for OS commands. The first poll usually comes before the read has
4390
+ * completed; at 1000 ms each of those reads then waits a full second, and the 33 registers take about 30 s
4391
+ * instead of 1 to 2 s (measured on a Circulo 7 Safe Motion).
4392
+ *
4393
+ * The registers are read through the encoder configured in 0x2110 (encoder 1) or 0x2112 (encoder 2). When its
4394
+ * sensor port (subindex 1) is 0, no encoder is configured: no OS command is sent, and the observable fails at
4395
+ * once. Otherwise the firmware times out each read after 5 s and then refuses further OS commands.
4396
+ *
4364
4397
  * @param deviceRef - The reference to the device to which the command will be sent.
4365
4398
  * @param encoderOrdinal - The ordinal number of the encoder.
4366
4399
  * @param commandTimeout - The timeout in milliseconds for the command to complete.
4400
+ * @param responsePollingInterval - How often, in milliseconds, the response of each register read is polled.
4367
4401
  * @returns An observable that emits an array of `EncoderRegisterCommunicationOsCommandResponse` objects.
4402
+ * @throws Error when the encoder is not configured.
4368
4403
  */
4369
- readCirculoIntegratedEncoderKeyConfigurationRegisters(deviceRef, encoderOrdinal, commandTimeout = 5000) {
4404
+ readCirculoIntegratedEncoderKeyConfigurationRegisters(deviceRef, encoderOrdinal, commandTimeout = 5000, responsePollingInterval = 50) {
4370
4405
  const registerAddresses = circulo_1.circuloIntegratedEncoderKeyConfigurationRegisterAddresses.map((addr) => addr.ram);
4371
- const defferedOsCommands = registerAddresses.map((registerAddress) => (0, rxjs_1.defer)(() => this.runEncoderRegisterCommunicationOsCommand(deviceRef, encoderOrdinal, 0, 0, registerAddress, undefined, commandTimeout).pipe((0, operators_1.last)())));
4372
- return (0, rxjs_1.concat)(...defferedOsCommands).pipe((0, operators_1.toArray)());
4406
+ const defferedOsCommands = registerAddresses.map((registerAddress) => (0, rxjs_1.defer)(() => this.runEncoderRegisterCommunicationOsCommand(deviceRef, encoderOrdinal, 0, 0, registerAddress, undefined, commandTimeout, responsePollingInterval).pipe((0, operators_1.last)())));
4407
+ const configurationIndex = { 1: 0x2110, 2: 0x2112 }[encoderOrdinal];
4408
+ if (configurationIndex === undefined) {
4409
+ return (0, rxjs_1.concat)(...defferedOsCommands).pipe((0, operators_1.toArray)());
4410
+ }
4411
+ return (0, rxjs_1.defer)(() => this.upload(deviceRef, configurationIndex, 1)).pipe((0, operators_1.mergeMap)((sensorPort) => {
4412
+ if (sensorPort === 0) {
4413
+ const index = `0x${configurationIndex.toString(16).toUpperCase()}`;
4414
+ return (0, rxjs_1.throwError)(() => new Error(`Encoder ${encoderOrdinal} is not configured: its sensor port (${index}:01) is 0. Configure the ` +
4415
+ 'integrated encoder before reading its registers.'));
4416
+ }
4417
+ return (0, rxjs_1.concat)(...defferedOsCommands).pipe((0, operators_1.toArray)());
4418
+ }));
4419
+ }
4420
+ /**
4421
+ * Reads the key configuration registers of a Circulo integrated encoder, `HARD_REV` (register 0x74) and the
4422
+ * amplitude control gain (registers 0x2B and 0x2F), and builds its report: the chip revision, health monitoring,
4423
+ * the Nonius track offset table and every register with the raw values of its fields. See
4424
+ * {@link buildCirculoIntegratedEncoderReport}.
4425
+ *
4426
+ * The device serial number is resolved alongside; when it can't be, the report says it is unknown.
4427
+ *
4428
+ * @param deviceRef - The reference to the device.
4429
+ * @param encoderOrdinal - The ordinal number of the encoder, 1 or 2.
4430
+ * @param commandTimeout - The timeout in milliseconds for each register read.
4431
+ * @param responsePollingInterval - How often, in milliseconds, the response of each register read is polled.
4432
+ * @returns An observable that emits the report and completes.
4433
+ * @throws Error when a register read returns no value, so the report never shows a value it didn't read.
4434
+ */
4435
+ getCirculoIntegratedEncoderReport(deviceRef, encoderOrdinal, commandTimeout = 5000, responsePollingInterval = 50) {
4436
+ const valueOf = (response, ram) => {
4437
+ if (response.registerValue === undefined) {
4438
+ const reason = [response.request, response.errorCode, response.errorName, response.errorDescription]
4439
+ .filter((part) => part !== undefined && part !== '')
4440
+ .join(', ');
4441
+ throw new Error(`Register 0x${ram.toString(16).padStart(2, '0')} returned no value (${reason}).`);
4442
+ }
4443
+ return response.registerValue;
4444
+ };
4445
+ const readRegister = (ram) => (0, rxjs_1.defer)(() => this.runEncoderRegisterCommunicationOsCommand(deviceRef, encoderOrdinal, 0, 0, ram, undefined, commandTimeout, responsePollingInterval).pipe((0, operators_1.last)()));
4446
+ const extraRegisters = [
4447
+ circulo_1.icMuHardRevRegister,
4448
+ ...circulo_1.circuloIntegratedEncoderAmplitudeControlRegisters.map((r) => r.ram),
4449
+ ];
4450
+ const deviceSerialNumber$ = this.resolveDevice(deviceRef).pipe((0, operators_1.map)((device) => device.id), (0, operators_1.catchError)(() => (0, rxjs_1.of)(undefined)));
4451
+ // The OS command channel runs one command at a time, so HARD_REV and the amplitude control registers are read
4452
+ // after the key configuration registers, not alongside.
4453
+ return (0, rxjs_1.forkJoin)([
4454
+ (0, rxjs_1.concat)(this.readCirculoIntegratedEncoderKeyConfigurationRegisters(deviceRef, encoderOrdinal, commandTimeout, responsePollingInterval), (0, rxjs_1.concat)(...extraRegisters.map(readRegister)).pipe((0, operators_1.toArray)())).pipe((0, operators_1.toArray)()),
4455
+ deviceSerialNumber$,
4456
+ ]).pipe((0, operators_1.map)(([[responses, extraResponses], deviceSerialNumber]) => {
4457
+ const values = responses.map((response, i) => valueOf(response, circulo_1.circuloIntegratedEncoderKeyConfigurationRegisterAddresses[i].ram));
4458
+ const [hardRev, ...amplitudeControlValues] = extraResponses.map((response, i) => valueOf(response, extraRegisters[i]));
4459
+ return (0, circulo_1.buildCirculoIntegratedEncoderReport)(values, {
4460
+ encoderOrdinal,
4461
+ deviceSerialNumber,
4462
+ readAt: new Date(),
4463
+ hardRev,
4464
+ amplitudeControlValues,
4465
+ });
4466
+ }));
4373
4467
  }
4374
4468
  /**
4375
4469
  * Writes the integrated encoder configuration to a binary file on the device.
@@ -5019,7 +5113,9 @@ class MotionMasterReqResClient {
5019
5113
  },
5020
5114
  commutationOffsetMeasurement: {
5021
5115
  execute: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
5022
- measurementMethod === 2 ? yield this.engageBrake(deviceRef, brakeEngageWaitMs) : yield this.releaseBrake(deviceRef, brakeReleaseWaitMs);
5116
+ measurementMethod === 2
5117
+ ? yield this.engageBrake(deviceRef, brakeEngageWaitMs)
5118
+ : yield this.releaseBrake(deviceRef, brakeReleaseWaitMs);
5023
5119
  return yield (0, rxjs_1.lastValueFrom)(this.runCommutationOffsetMeasurementOsCommand(deviceRef));
5024
5120
  }),
5025
5121
  valueKey: 'commutationAngleOffset',