motion-master-client 0.0.367 → 0.0.369

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.
@@ -3350,6 +3350,250 @@ class MotionMasterReqResClient {
3350
3350
  (0, rxjs_1.defer)(() => this.triggerSmmRestart(deviceRef)));
3351
3351
  return (0, rxjs_1.concat)(...observables).pipe((0, operators_1.last)());
3352
3352
  }
3353
+ /**
3354
+ * Resolves whether an automated SMM software update is allowed for the specified device.
3355
+ *
3356
+ * This method evaluates the device and the provided firmware binary against a set of
3357
+ * preconditions required for automated installation. It returns a detailed
3358
+ * {@link SmmAutomatedUpdate} object describing each individual check result, which
3359
+ * can be used to give precise feedback to the user when automation is not possible.
3360
+ *
3361
+ * The automation is only permitted when **all** of the following conditions are met:
3362
+ * - The device is a recognized Somanet product (`isValidProduct`).
3363
+ * - The device belongs to the Circulo product family (`isCirculo`).
3364
+ * - The binary is encrypted (`isBinaryEncrypted`).
3365
+ * - The binary targets one of the supported SMM versions — v1.4 (260) or v2.56 (568) (`isAllowedBinaryVersion`).
3366
+ * - If targeting v2.56, the device SoC firmware version is ≥ 5.5.0 (`canInstallV256`).
3367
+ * - The device is running a legacy SMM firmware below v1.3, i.e. version < 259 (`isDeviceLw1`).
3368
+ *
3369
+ * If the device is not a recognized product, the method returns early with all flags set
3370
+ * to `false` and default zero values to avoid unnecessary device communication.
3371
+ *
3372
+ * @param deviceRef - A reference to the target device.
3373
+ * @param buffer - The SMM firmware binary to be installed, provided as a `Uint8Array`.
3374
+ * Its header is parsed to determine the target version and encryption status.
3375
+ *
3376
+ * @returns A promise that resolves to an {@link SmmAutomatedUpdate} object containing:
3377
+ * - `isValidProduct` — Whether the device maps to a known Somanet product.
3378
+ * - `isCirculo` — Whether the device belongs to the Circulo product family.
3379
+ * - `isCirculo7` — Whether the device is a Circulo 7.
3380
+ * - `isCirculo9` — Whether the device is a Circulo 9.
3381
+ * - `isDeviceLw1` — Whether the device SMM firmware is below v1.3 (version < 259).
3382
+ * - `isBinaryEncrypted` — Whether the provided firmware binary is encrypted.
3383
+ * - `isAllowedBinaryVersion` — Whether the binary targets a supported version (v1.4 or v2.56).
3384
+ * - `canInstallV256` — Whether v2.56 can be installed (requires SoC firmware ≥ 5.5.0).
3385
+ * - `isAutomationAllowed` — Whether all preconditions for automated update are satisfied.
3386
+ * - `allowedTargetSmmVersions` — The list of supported target SMM versions: `[260, 568]`.
3387
+ * - `deviceFirmwareVersion` — The SoC firmware version string read from the device (index `0x100a:0`).
3388
+ * - `deviceSmmVersion` — The current SMM firmware version as a raw integer.
3389
+ * - `binaryVersion` — The target SMM version encoded in the binary header.
3390
+ */
3391
+ resolveSmmAutomatedUpdateConditions(deviceRef, buffer) {
3392
+ var _a, _b;
3393
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
3394
+ const device = yield (0, rxjs_1.lastValueFrom)(this.resolveDevice(deviceRef));
3395
+ const product = (0, product_1.findSomanetProductById)(device === null || device === void 0 ? void 0 : device.serialNumber);
3396
+ const isValidProduct = !!product;
3397
+ // v1.4 (no safe torque), v2.56 (safe torque)
3398
+ const allowedTargetSmmVersions = [260, 568];
3399
+ if (!isValidProduct) {
3400
+ return {
3401
+ isValidProduct: false,
3402
+ isDeviceLw1: false,
3403
+ isCirculo: false,
3404
+ isCirculo7: false,
3405
+ isCirculo9: false,
3406
+ isBinaryEncrypted: false,
3407
+ isAllowedBinaryVersion: false,
3408
+ canInstallV256: false,
3409
+ isAutomationAllowed: false,
3410
+ allowedTargetSmmVersions,
3411
+ deviceFirmwareVersion: 'v0.0.0',
3412
+ deviceSmmVersion: 0,
3413
+ isV990: false,
3414
+ isV770: false,
3415
+ isV771: false,
3416
+ binaryVersion: 0,
3417
+ deviceSupportsStreaming: false,
3418
+ };
3419
+ }
3420
+ const deviceSmmVersion = yield (0, rxjs_1.lastValueFrom)(this.readSmmFirmwareVersionAsNumber(deviceRef));
3421
+ const deviceFirmwareVersion = (yield this.upload(deviceRef, 0x100a, 0));
3422
+ const binaryHeader = (0, smm_1.parseSmmBinaryHeaders)(buffer);
3423
+ const binaryInformation = (0, smm_1.getInformationFromSmmFirmwareVersion)(binaryHeader.swVersion);
3424
+ // Allow installation of smm v2.56 only if device SoC firmware version is >=5.5.0
3425
+ const canInstallV256 = binaryHeader.swVersion === 568 ? semver.gte((_a = semver.coerce(deviceFirmwareVersion)) !== null && _a !== void 0 ? _a : '0.0.0', '5.5.0') : true;
3426
+ const isCirculo = product.family === 'Circulo';
3427
+ const isDeviceLw1 = deviceSmmVersion < 259; // <v1.3
3428
+ const isV990 = deviceSmmVersion === 25344;
3429
+ const isV770 = deviceSmmVersion === 19712;
3430
+ const isV771 = deviceSmmVersion === 19713;
3431
+ const isBinaryEncrypted = binaryInformation.isEncrypted;
3432
+ const isAllowedBinaryVersion = allowedTargetSmmVersions.includes(binaryInformation.version);
3433
+ const isCirculo7 = isCirculo && product.group === 'Circulo 7';
3434
+ const isCirculo9 = isCirculo && product.group === 'Circulo 9';
3435
+ const deviceSupportsStreaming = semver.gte((_b = semver.coerce(deviceFirmwareVersion)) !== null && _b !== void 0 ? _b : '0.0.0', '5.2.0');
3436
+ const isAutomationAllowed = isCirculo &&
3437
+ isBinaryEncrypted &&
3438
+ isAllowedBinaryVersion &&
3439
+ canInstallV256 &&
3440
+ (isDeviceLw1 || isV990 || isV770 || isV771); // Allow automation for devices on legacy firmware (lw1) or already on one of the intermediate versions (v99.0, v77.0, v77.1)
3441
+ return {
3442
+ isValidProduct,
3443
+ isDeviceLw1,
3444
+ isCirculo,
3445
+ isCirculo7,
3446
+ isCirculo9,
3447
+ isBinaryEncrypted,
3448
+ isAllowedBinaryVersion,
3449
+ canInstallV256,
3450
+ isAutomationAllowed,
3451
+ allowedTargetSmmVersions,
3452
+ deviceFirmwareVersion,
3453
+ deviceSmmVersion,
3454
+ isV990,
3455
+ isV770,
3456
+ isV771,
3457
+ binaryVersion: binaryInformation.version,
3458
+ deviceSupportsStreaming,
3459
+ };
3460
+ });
3461
+ }
3462
+ /**
3463
+ * Performs an automated, multi-step SMM software update to an encrypted firmware version on a Circulo device.
3464
+ *
3465
+ * Before installing the target binary, this method evaluates the device state by calling
3466
+ * {@link resolveSmmAutomatedUpdateConditions}. Based on the result, it determines which
3467
+ * sequence of intermediate firmware versions must be installed first to transition the device
3468
+ * from unencrypted to encrypted firmware.
3469
+ *
3470
+ * The installation sequence depends on the current SMM version and device group:
3471
+ * - **Legacy (< v1.3) + Circulo 7**: v99.0 → v77.0 → target
3472
+ * - **Legacy (< v1.3) + Circulo 9**: v99.0 → v77.1 → target
3473
+ * - **v99.0 + Circulo 7**: v77.0 → target
3474
+ * - **v99.0 + Circulo 9**: v77.1 → target
3475
+ * - **v77.0 or v77.1**: target only
3476
+ *
3477
+ * If the device does not support streaming, `commandTimeout` is automatically extended to 420,000 ms.
3478
+ *
3479
+ * @param deviceRef - Reference to the target device.
3480
+ * @param username - Username used for SMM authentication.
3481
+ * @param password - Password used for SMM authentication.
3482
+ * @param buffer - The target encrypted firmware binary as a `Uint8Array`.
3483
+ * @param crc - CRC32 checksum of the target firmware binary for integrity verification.
3484
+ * @param chunkSize - (Optional) Size of each data chunk transmitted per step, in bytes. Defaults to 1000.
3485
+ * @param commandTimeout - (Optional) Timeout for each OS command in ms. Automatically overridden to 420,000 ms
3486
+ * if the device does not support streaming. Defaults to 30,000 ms.
3487
+ * @param responsePollingInterval - (Optional) Polling interval for command responses in ms. Defaults to 1000 ms.
3488
+ * @param fsBufferReadWriteTimeout - (Optional) Timeout for fs-buffer read/write operations in ms. Defaults to 120,000 ms.
3489
+ *
3490
+ * @returns An `Observable<void>` that completes when the full installation sequence finishes successfully.
3491
+ *
3492
+ * @throws {Error} With message `'Unknown product. Cannot perform SMM software update.'` if the device
3493
+ * serial number does not map to a known Somanet product.
3494
+ * @throws {Error} With a descriptive message listing unmet preconditions if automation is not allowed —
3495
+ * e.g. the device is not a Circulo, the binary is not encrypted, the target version is
3496
+ * unsupported, the SoC firmware is below v5.5.0 (for SMM v2.56), or the device is
3497
+ * already on SMM v1.3+.
3498
+ * @throws {Error} If any individual firmware installation step fails during execution.
3499
+ */
3500
+ updateSmmSoftwareToEncrypted(deviceRef, username, password, buffer, crc, chunkSize = 1000, commandTimeout = 30000, responsePollingInterval = 1000, fsBufferReadWriteTimeout = 120000) {
3501
+ return new rxjs_1.Observable((subscriber) => {
3502
+ (() => tslib_1.__awaiter(this, void 0, void 0, function* () {
3503
+ /**
3504
+ * We must wrap the whole anonymous function body in try/catch so the subscriber (in `catch`)
3505
+ * knows when the error happens. Otherwise, the application will "explode" when the error occurs.
3506
+ *
3507
+ * Observable function is synchronous. When the error occurs, it will handle it via `error: (err) => {}`
3508
+ * function. Here, that is not the case. The anonymous function is asynchronous and will update the
3509
+ * subscriber only when we tell it to do so via `subscriber.<method>`. Error handling from the outside
3510
+ * will not pick-up the errors from here as this is "fire-and-forget" type of function.
3511
+ *
3512
+ * @example
3513
+ *
3514
+ * ```typescript
3515
+ * asyncHandler(async (req, res, next) => {
3516
+ * somePromiseThatRejects().then(...) // ← no await, no .catch()
3517
+ * // or a setTimeout, event emitter, etc.
3518
+ *
3519
+ * // handler finishes immediately
3520
+ })
3521
+ */
3522
+ try {
3523
+ const automatedInstall = yield this.resolveSmmAutomatedUpdateConditions(deviceRef, buffer);
3524
+ if (!automatedInstall.isValidProduct) {
3525
+ subscriber.error(new Error('Unknown product. Cannot perform SMM software update.'));
3526
+ return;
3527
+ }
3528
+ let observables = [];
3529
+ if (automatedInstall.isAutomationAllowed) {
3530
+ const v990crc = 0xd1a3bb72;
3531
+ 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; }));
3532
+ const v770crc = 0xe290a20a;
3533
+ 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; }));
3534
+ const v771crc = 0xa4a11125;
3535
+ 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; }));
3536
+ const v990$ = (0, rxjs_1.defer)(() => this.updateSmmSoftware(deviceRef, username, password, new Uint8Array(v990Buffer), v990crc, chunkSize, commandTimeout, responsePollingInterval, fsBufferReadWriteTimeout));
3537
+ const v770$ = (0, rxjs_1.defer)(() => this.updateSmmSoftware(deviceRef, username, password, new Uint8Array(v770Buffer), v770crc, chunkSize, commandTimeout, responsePollingInterval, fsBufferReadWriteTimeout));
3538
+ const v771$ = (0, rxjs_1.defer)(() => this.updateSmmSoftware(deviceRef, username, password, new Uint8Array(v771Buffer), v771crc, chunkSize, commandTimeout, responsePollingInterval, fsBufferReadWriteTimeout));
3539
+ const userVersion$ = (0, rxjs_1.defer)(() => this.updateSmmSoftware(deviceRef, username, password, buffer, crc, chunkSize, commandTimeout, responsePollingInterval, fsBufferReadWriteTimeout));
3540
+ if (automatedInstall.isDeviceLw1) {
3541
+ if (automatedInstall.isCirculo7) {
3542
+ observables = [v990$, v770$, userVersion$];
3543
+ }
3544
+ else if (automatedInstall.isCirculo9) {
3545
+ observables = [v990$, v771$, userVersion$];
3546
+ }
3547
+ }
3548
+ else if (automatedInstall.isV990) {
3549
+ if (automatedInstall.isCirculo7) {
3550
+ observables = [v770$, userVersion$];
3551
+ }
3552
+ else if (automatedInstall.isCirculo9) {
3553
+ observables = [v771$, userVersion$];
3554
+ }
3555
+ }
3556
+ else if (automatedInstall.isV770 || automatedInstall.isV771) {
3557
+ observables = [userVersion$];
3558
+ }
3559
+ (0, rxjs_1.concat)(...observables).subscribe({
3560
+ next: () => {
3561
+ subscriber.next();
3562
+ },
3563
+ error: (err) => {
3564
+ subscriber.error(err);
3565
+ },
3566
+ complete: () => {
3567
+ subscriber.complete();
3568
+ },
3569
+ });
3570
+ }
3571
+ else {
3572
+ const reasons = [];
3573
+ if (!automatedInstall.isCirculo) {
3574
+ reasons.push('The device is not part of the Circulo family.');
3575
+ }
3576
+ if (!automatedInstall.isDeviceLw1) {
3577
+ reasons.push('The device SMM software version is not lower than 1.3.');
3578
+ }
3579
+ if (!automatedInstall.isBinaryEncrypted) {
3580
+ reasons.push('The SMM binary is not encrypted.');
3581
+ }
3582
+ if (!automatedInstall.isAllowedBinaryVersion) {
3583
+ reasons.push('The target SMM software version is not supported for encrypted update.');
3584
+ }
3585
+ if (!automatedInstall.canInstallV256) {
3586
+ reasons.push('The device firmware must be minimum v5.5.0 for installing SMM v2.56.');
3587
+ }
3588
+ subscriber.error(new Error(`The following conditions are not met for SMM software automated update: ${reasons.join(' ')}`));
3589
+ }
3590
+ }
3591
+ catch (err) {
3592
+ subscriber.error(err);
3593
+ }
3594
+ }))();
3595
+ });
3596
+ }
3353
3597
  /**
3354
3598
  * Transmits SMM parameters to the specified device.
3355
3599
  *
@@ -4317,6 +4561,7 @@ class MotionMasterReqResClient {
4317
4561
  * `OPERATION ENABLED` state before executing steps.
4318
4562
  * - Restores the brake state and transitions the device to
4319
4563
  * `SWITCH_ON_DISABLED` after completion, error, or cancellation.
4564
+ * - Certain steps may temporarily release or engage the brake as needed during execution.
4320
4565
  *
4321
4566
  * Cancellation
4322
4567
  * - Unsubscribing from the returned observable cancels further step execution.
@@ -4338,36 +4583,13 @@ class MotionMasterReqResClient {
4338
4583
  * without mutation side effects.
4339
4584
  */
4340
4585
  runOffsetDetection(deviceRef, stepIds = []) {
4341
- // Mapping of step IDs to their corresponding execution functions and result value keys
4342
- const stepCommand = {
4343
- openPhaseDetection: { execute: () => this.runOpenPhaseDetectionOsCommand(deviceRef), valueKey: 'phaseOpened' },
4344
- phaseResistanceMeasurement: {
4345
- execute: () => this.runPhaseResistanceMeasurementOsCommand(deviceRef),
4346
- valueKey: 'phaseResistance',
4347
- },
4348
- phaseInductanceMeasurement: {
4349
- execute: () => this.runPhaseInductanceMeasurementOsCommand(deviceRef),
4350
- valueKey: 'phaseInductance',
4351
- },
4352
- polePairDetection: {
4353
- execute: () => this.runPolePairDetectionOsCommand(deviceRef),
4354
- valueKey: 'numberOfPolePairs',
4355
- },
4356
- motorPhaseOrderDetection: {
4357
- execute: () => this.runMotorPhaseOrderDetectionOsCommand(deviceRef),
4358
- valueKey: 'motorPhaseOrder',
4359
- },
4360
- commutationOffsetMeasurement: {
4361
- execute: () => this.runCommutationOffsetMeasurementOsCommand(deviceRef),
4362
- valueKey: 'commutationAngleOffset',
4363
- },
4364
- };
4365
4586
  return new rxjs_1.Observable((subscriber) => {
4366
4587
  let isCancelled = false;
4367
4588
  // Brake-related state restored in finally block
4368
4589
  let brakeReleaseStrategy = 0;
4369
4590
  let brakeStatusInit = 0;
4370
4591
  let brakePullTime = 0;
4592
+ let measurementMethod = 0;
4371
4593
  // Throws if cancellation was requested
4372
4594
  const checkCancelled = () => {
4373
4595
  if (isCancelled) {
@@ -4391,17 +4613,80 @@ class MotionMasterReqResClient {
4391
4613
  }
4392
4614
  try {
4393
4615
  // Read brake configuration from device
4394
- [brakeReleaseStrategy, brakeStatusInit, brakePullTime] = (yield this.uploadMany([
4616
+ [brakeReleaseStrategy, brakeStatusInit, brakePullTime, measurementMethod] = (yield this.uploadMany([
4395
4617
  [deviceRef, 0x2004, 4],
4396
4618
  [deviceRef, 0x2004, 7],
4397
4619
  [deviceRef, 0x2004, 3],
4620
+ [deviceRef, 0x2009, 3],
4398
4621
  ]));
4399
4622
  checkCancelled();
4400
- // Temporarily release brake if required
4401
- if (brakeReleaseStrategy > 0) {
4402
- yield this.download(deviceRef, 0x2004, 7, 2);
4403
- yield (0, util_1.resolveAfter)(brakePullTime + 500);
4404
- }
4623
+ const engageBrake = () => tslib_1.__awaiter(this, void 0, void 0, function* () {
4624
+ if (brakeReleaseStrategy > 0) {
4625
+ yield this.download(deviceRef, 0x2004, 7, 1);
4626
+ yield (0, util_1.resolveAfter)(brakePullTime + 250);
4627
+ }
4628
+ });
4629
+ const releaseBrake = () => tslib_1.__awaiter(this, void 0, void 0, function* () {
4630
+ if (brakeReleaseStrategy > 0) {
4631
+ yield this.download(deviceRef, 0x2004, 7, 2);
4632
+ yield (0, util_1.resolveAfter)(brakePullTime + 250);
4633
+ }
4634
+ });
4635
+ // Mapping of step IDs to their corresponding execution functions and result value keys
4636
+ const stepCommand = {
4637
+ openPhaseDetection: {
4638
+ execute: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
4639
+ const response = yield (0, rxjs_1.lastValueFrom)(this.runOpenPhaseDetectionOsCommand(deviceRef));
4640
+ return response;
4641
+ }),
4642
+ valueKey: 'phaseOpened',
4643
+ },
4644
+ phaseResistanceMeasurement: {
4645
+ execute: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
4646
+ const response = yield (0, rxjs_1.lastValueFrom)(this.runPhaseResistanceMeasurementOsCommand(deviceRef));
4647
+ return response;
4648
+ }),
4649
+ valueKey: 'phaseResistance',
4650
+ },
4651
+ phaseInductanceMeasurement: {
4652
+ execute: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
4653
+ const response = yield (0, rxjs_1.lastValueFrom)(this.runPhaseInductanceMeasurementOsCommand(deviceRef));
4654
+ return response;
4655
+ }),
4656
+ valueKey: 'phaseInductance',
4657
+ },
4658
+ polePairDetection: {
4659
+ execute: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
4660
+ yield releaseBrake();
4661
+ const response = yield (0, rxjs_1.lastValueFrom)(this.runPolePairDetectionOsCommand(deviceRef));
4662
+ yield engageBrake();
4663
+ return response;
4664
+ }),
4665
+ valueKey: 'numberOfPolePairs',
4666
+ },
4667
+ motorPhaseOrderDetection: {
4668
+ execute: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
4669
+ yield releaseBrake();
4670
+ const response = yield (0, rxjs_1.lastValueFrom)(this.runMotorPhaseOrderDetectionOsCommand(deviceRef));
4671
+ yield engageBrake();
4672
+ return response;
4673
+ }),
4674
+ valueKey: 'motorPhaseOrder',
4675
+ },
4676
+ commutationOffsetMeasurement: {
4677
+ execute: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
4678
+ if (measurementMethod != 2) {
4679
+ yield releaseBrake();
4680
+ }
4681
+ const response = yield (0, rxjs_1.lastValueFrom)(this.runCommutationOffsetMeasurementOsCommand(deviceRef));
4682
+ if (measurementMethod !== 2) {
4683
+ yield engageBrake();
4684
+ }
4685
+ return response;
4686
+ }),
4687
+ valueKey: 'commutationAngleOffset',
4688
+ },
4689
+ };
4405
4690
  checkCancelled();
4406
4691
  // Switch device into diagnostics mode and ensure OPERATION ENABLED state
4407
4692
  yield (0, rxjs_1.lastValueFrom)(this.setModesOfOperationAndTransitionToCia402State(deviceRef, cia402_1.ModesOfOperation.DIAGNOSTICS_MODE, cia402_1.Cia402State.OPERATION_ENABLED));
@@ -4425,7 +4710,7 @@ class MotionMasterReqResClient {
4425
4710
  step.status = 'running';
4426
4711
  subscriber.next(structuredClone(steps));
4427
4712
  // Execute diagnostic command
4428
- const response = yield (0, rxjs_1.lastValueFrom)(command.execute());
4713
+ const response = yield command.execute();
4429
4714
  checkCancelled();
4430
4715
  if (response.request === 'succeeded') {
4431
4716
  // Mark success and store result value