zigbee-herdsman-converters 14.0.519 → 14.0.520

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.
@@ -7270,59 +7270,6 @@ const converters = {
7270
7270
  };
7271
7271
  },
7272
7272
  },
7273
- ubisys_dimmer_setup: {
7274
- cluster: 'manuSpecificUbisysDimmerSetup',
7275
- type: ['attributeReport', 'readResponse'],
7276
- convert: (model, msg, publish, options, meta) => {
7277
- if (msg.data.hasOwnProperty('capabilities')) {
7278
- const capabilities = msg.data.capabilities;
7279
- const forwardPhaseControl = capabilities & 1;
7280
- const reversePhaseControl = (capabilities & 2) >>> 1;
7281
- const reactanceDiscriminator = (capabilities & 0x20) >>> 5;
7282
- const configurableCurve = (capabilities & 0x40) >>> 6;
7283
- const overloadDetection = (capabilities & 0x80) >>> 7;
7284
- return {
7285
- capabilities_forward_phase_control: forwardPhaseControl ? true : false,
7286
- capabilities_reverse_phase_control: reversePhaseControl ? true : false,
7287
- capabilities_reactance_discriminator: reactanceDiscriminator ? true : false,
7288
- capabilities_configurable_curve: configurableCurve ? true : false,
7289
- capabilities_overload_detection: overloadDetection ? true : false,
7290
- };
7291
- }
7292
- if (msg.data.hasOwnProperty('status')) {
7293
- const status = msg.data.status;
7294
- const forwardPhaseControl = status & 1;
7295
- const reversePhaseControl = (status & 2) >>> 1;
7296
- const overload = (status & 8) >>> 3;
7297
- const capacitiveLoad = (status & 0x40) >>> 6;
7298
- const inductiveLoad = (status & 0x80) >>> 7;
7299
- return {
7300
- status_forward_phase_control: forwardPhaseControl ? true : false,
7301
- status_reverse_phase_control: reversePhaseControl ? true : false,
7302
- status_overload: overload ? true : false,
7303
- status_capacitive_load: capacitiveLoad ? true : false,
7304
- status_inductive_load: inductiveLoad ? true : false,
7305
- };
7306
- }
7307
- if (msg.data.hasOwnProperty('mode')) {
7308
- const mode = msg.data.mode;
7309
- const phaseControl = mode & 3;
7310
- const phaseControlValues = {0: 'automatic', 1: 'forward', 2: 'reverse'};
7311
- return {
7312
- mode_phase_control: phaseControlValues[phaseControl],
7313
- };
7314
- }
7315
- },
7316
- },
7317
- ubisys_dimmer_setup_genLevelCtrl: {
7318
- cluster: 'genLevelCtrl',
7319
- type: ['attributeReport', 'readResponse'],
7320
- convert: (model, msg, publish, options, meta) => {
7321
- if (msg.data.hasOwnProperty('ubisysMinimumOnLevel')) {
7322
- return {minimum_on_level: msg.data.ubisysMinimumOnLevel};
7323
- }
7324
- },
7325
- },
7326
7273
  itcmdr_clicks: {
7327
7274
  cluster: 'genMultistateInput',
7328
7275
  type: ['readResponse', 'attributeReport'],
@@ -20,13 +20,6 @@ const manufacturerOptions = {
20
20
  hue: {manufacturerCode: herdsman.Zcl.ManufacturerCode.PHILIPS},
21
21
  ikea: {manufacturerCode: herdsman.Zcl.ManufacturerCode.IKEA_OF_SWEDEN},
22
22
  sinope: {manufacturerCode: herdsman.Zcl.ManufacturerCode.SINOPE_TECH},
23
- /*
24
- * Ubisys doesn't accept a manufacturerCode on some commands
25
- * This bug has been reported, but it has not been fixed:
26
- * https://github.com/Koenkk/zigbee-herdsman/issues/52
27
- */
28
- ubisys: {manufacturerCode: herdsman.Zcl.ManufacturerCode.UBISYS},
29
- ubisysNull: {manufacturerCode: null},
30
23
  tint: {manufacturerCode: herdsman.Zcl.ManufacturerCode.MUELLER_LICHT_INT},
31
24
  legrand: {manufacturerCode: herdsman.Zcl.ManufacturerCode.VANTAGE, disableDefaultResponse: true},
32
25
  viessmann: {manufacturerCode: herdsman.Zcl.ManufacturerCode.VIESSMAN_ELEKTRO},
@@ -4395,421 +4388,6 @@ const converters = {
4395
4388
  }
4396
4389
  },
4397
4390
  },
4398
- ubisys_configure_j1: {
4399
- key: ['configure_j1'],
4400
- convertSet: async (entity, key, value, meta) => {
4401
- const log = (message) => {
4402
- meta.logger.warn(`ubisys: ${message}`);
4403
- };
4404
- const sleepSeconds = async (s) => {
4405
- return new Promise((resolve) => setTimeout(resolve, s * 1000));
4406
- };
4407
- const waitUntilStopped = async () => {
4408
- let operationalStatus = 0;
4409
- do {
4410
- await sleepSeconds(2);
4411
- operationalStatus = (await entity.read('closuresWindowCovering',
4412
- ['operationalStatus'])).operationalStatus;
4413
- } while (operationalStatus != 0);
4414
- await sleepSeconds(2);
4415
- };
4416
- const writeAttrFromJson = async (attr, jsonAttr = attr, converterFunc, delaySecondsAfter) => {
4417
- if (jsonAttr.startsWith('ubisys')) {
4418
- jsonAttr = jsonAttr.substring(6, 1).toLowerCase + jsonAttr.substring(7);
4419
- }
4420
- if (value.hasOwnProperty(jsonAttr)) {
4421
- let attrValue = value[jsonAttr];
4422
- if (converterFunc) {
4423
- attrValue = converterFunc(attrValue);
4424
- }
4425
- const attributes = {};
4426
- attributes[attr] = attrValue;
4427
- await entity.write('closuresWindowCovering', attributes, manufacturerOptions.ubisys);
4428
- if (delaySecondsAfter) {
4429
- await sleepSeconds(delaySecondsAfter);
4430
- }
4431
- }
4432
- };
4433
- const stepsPerSecond = value.steps_per_second || 50;
4434
- const hasCalibrate = value.hasOwnProperty('calibrate');
4435
- // cancel any running calibration
4436
- let mode = (await entity.read('closuresWindowCovering', ['windowCoveringMode'])).windowCoveringMode;
4437
- const modeCalibrationBitMask = 0x02;
4438
- if (mode & modeCalibrationBitMask) {
4439
- await entity.write('closuresWindowCovering', {windowCoveringMode: mode & ~modeCalibrationBitMask});
4440
- await sleepSeconds(2);
4441
- }
4442
- // delay a bit if reconfiguring basic configuration attributes
4443
- await writeAttrFromJson('windowCoveringType', undefined, undefined, 2);
4444
- await writeAttrFromJson('configStatus', undefined, undefined, 2);
4445
- if (await writeAttrFromJson('windowCoveringMode', undefined, undefined, 2)) {
4446
- mode = value['windowCoveringMode'];
4447
- }
4448
- if (hasCalibrate) {
4449
- log('Cover calibration starting...');
4450
- // first of all, move to top position to not confuse calibration later
4451
- log(' Moving cover to top position to get a good starting point...');
4452
- await entity.command('closuresWindowCovering', 'upOpen');
4453
- await waitUntilStopped();
4454
- log(' Settings some attributes...');
4455
- // reset attributes
4456
- await entity.write('closuresWindowCovering', {
4457
- installedOpenLimitLiftCm: 0,
4458
- installedClosedLimitLiftCm: 240,
4459
- installedOpenLimitTiltDdegree: 0,
4460
- installedClosedLimitTiltDdegree: 900,
4461
- ubisysLiftToTiltTransitionSteps: 0xffff,
4462
- ubisysTotalSteps: 0xffff,
4463
- ubisysLiftToTiltTransitionSteps2: 0xffff,
4464
- ubisysTotalSteps2: 0xffff,
4465
- }, manufacturerOptions.ubisys);
4466
- // enable calibration mode
4467
- await sleepSeconds(2);
4468
- await entity.write('closuresWindowCovering', {windowCoveringMode: mode | modeCalibrationBitMask});
4469
- await sleepSeconds(2);
4470
- // move down a bit and back up to detect upper limit
4471
- log(' Moving cover down a bit...');
4472
- await entity.command('closuresWindowCovering', 'downClose');
4473
- await sleepSeconds(5);
4474
- await entity.command('closuresWindowCovering', 'stop');
4475
- await sleepSeconds(2);
4476
- log(' Moving up again to detect upper limit...');
4477
- await entity.command('closuresWindowCovering', 'upOpen');
4478
- await waitUntilStopped();
4479
- log(' Moving down to count steps from open to closed...');
4480
- await entity.command('closuresWindowCovering', 'downClose');
4481
- await waitUntilStopped();
4482
- log(' Moving up to count steps from closed to open...');
4483
- await entity.command('closuresWindowCovering', 'upOpen');
4484
- await waitUntilStopped();
4485
- }
4486
- // now write any attribute values present in JSON
4487
- await writeAttrFromJson('installedOpenLimitLiftCm');
4488
- await writeAttrFromJson('installedClosedLimitLiftCm');
4489
- await writeAttrFromJson('installedOpenLimitTiltDdegree');
4490
- await writeAttrFromJson('installedClosedLimitTiltDdegree');
4491
- await writeAttrFromJson('ubisysTurnaroundGuardTime');
4492
- await writeAttrFromJson('ubisysLiftToTiltTransitionSteps');
4493
- await writeAttrFromJson('ubisysTotalSteps');
4494
- await writeAttrFromJson('ubisysLiftToTiltTransitionSteps2');
4495
- await writeAttrFromJson('ubisysTotalSteps2');
4496
- await writeAttrFromJson('ubisysAdditionalSteps');
4497
- await writeAttrFromJson('ubisysInactivePowerThreshold');
4498
- await writeAttrFromJson('ubisysStartupSteps');
4499
- // some convenience functions to not have to calculate
4500
- await writeAttrFromJson('ubisysTotalSteps', 'open_to_closed_s', (s) => s * stepsPerSecond);
4501
- await writeAttrFromJson('ubisysTotalSteps2', 'closed_to_open_s', (s) => s * stepsPerSecond);
4502
- await writeAttrFromJson('ubisysLiftToTiltTransitionSteps', 'lift_to_tilt_transition_ms',
4503
- (s) => s * stepsPerSecond / 1000);
4504
- await writeAttrFromJson('ubisysLiftToTiltTransitionSteps2', 'lift_to_tilt_transition_ms',
4505
- (s) => s * stepsPerSecond / 1000);
4506
- if (hasCalibrate) {
4507
- log(' Finalizing calibration...');
4508
- // disable calibration mode again
4509
- await sleepSeconds(2);
4510
- await entity.write('closuresWindowCovering', {windowCoveringMode: mode & ~modeCalibrationBitMask});
4511
- await sleepSeconds(2);
4512
- // re-read and dump all relevant attributes
4513
- log(' Done - will now read back the results.');
4514
- converters.ubisys_configure_j1.convertGet(entity, key, meta);
4515
- }
4516
- },
4517
- convertGet: async (entity, key, meta) => {
4518
- const log = (json) => {
4519
- meta.logger.warn(`ubisys: Cover configuration read: ${JSON.stringify(json)}`);
4520
- };
4521
- log(await entity.read('closuresWindowCovering', [
4522
- 'windowCoveringType',
4523
- 'physicalClosedLimitLiftCm',
4524
- 'physicalClosedLimitTiltDdegree',
4525
- 'installedOpenLimitLiftCm',
4526
- 'installedClosedLimitLiftCm',
4527
- 'installedOpenLimitTiltDdegree',
4528
- 'installedClosedLimitTiltDdegree',
4529
- ]));
4530
- log(await entity.read('closuresWindowCovering', [
4531
- 'configStatus',
4532
- 'windowCoveringMode',
4533
- 'currentPositionLiftPercentage',
4534
- 'currentPositionLiftCm',
4535
- 'currentPositionTiltPercentage',
4536
- 'currentPositionTiltDdegree',
4537
- 'operationalStatus',
4538
- ]));
4539
- log(await entity.read('closuresWindowCovering', [
4540
- 'ubisysTurnaroundGuardTime',
4541
- 'ubisysLiftToTiltTransitionSteps',
4542
- 'ubisysTotalSteps',
4543
- 'ubisysLiftToTiltTransitionSteps2',
4544
- 'ubisysTotalSteps2',
4545
- 'ubisysAdditionalSteps',
4546
- 'ubisysInactivePowerThreshold',
4547
- 'ubisysStartupSteps',
4548
- ], manufacturerOptions.ubisys));
4549
- },
4550
- },
4551
- ubisys_dimmer_setup: {
4552
- key: ['capabilities_forward_phase_control',
4553
- 'capabilities_reverse_phase_control',
4554
- 'capabilities_reactance_discriminator',
4555
- 'capabilities_configurable_curve',
4556
- 'capabilities_overload_detection',
4557
- 'status_forward_phase_control',
4558
- 'status_reverse_phase_control',
4559
- 'status_overload',
4560
- 'status_capacitive_load',
4561
- 'status_inductive_load',
4562
- 'mode_phase_control'],
4563
- convertSet: async (entity, key, value, meta) => {
4564
- if (key === 'mode_phase_control') {
4565
- const phaseControl = value.toLowerCase();
4566
- const phaseControlValues = {'automatic': 0, 'forward': 1, 'reverse': 2};
4567
- utils.validateValue(phaseControl, Object.keys(phaseControlValues));
4568
- await entity.write('manuSpecificUbisysDimmerSetup',
4569
- {'mode': phaseControlValues[phaseControl]}, manufacturerOptions.ubisysNull);
4570
- }
4571
- converters.ubisys_dimmer_setup.convertGet(entity, key, meta);
4572
- },
4573
- convertGet: async (entity, key, meta) => {
4574
- await entity.read('manuSpecificUbisysDimmerSetup', ['capabilities'], manufacturerOptions.ubisysNull);
4575
- await entity.read('manuSpecificUbisysDimmerSetup', ['status'], manufacturerOptions.ubisysNull);
4576
- await entity.read('manuSpecificUbisysDimmerSetup', ['mode'], manufacturerOptions.ubisysNull);
4577
- },
4578
- },
4579
- ubisys_dimmer_setup_genLevelCtrl: {
4580
- key: ['minimum_on_level'],
4581
- convertSet: async (entity, key, value, meta) => {
4582
- if (key === 'minimum_on_level') {
4583
- await entity.write('genLevelCtrl', {'ubisysMinimumOnLevel': value}, manufacturerOptions.ubisys);
4584
- }
4585
- converters.ubisys_dimmer_setup_genLevelCtrl.convertGet(entity, key, meta);
4586
- },
4587
- convertGet: async (entity, key, meta) => {
4588
- await entity.read('genLevelCtrl', ['ubisysMinimumOnLevel'], manufacturerOptions.ubisys);
4589
- },
4590
- },
4591
- ubisys_device_setup: {
4592
- key: ['configure_device_setup'],
4593
- convertSet: async (entity, key, value, meta) => {
4594
- const devMgmtEp = meta.device.getEndpoint(232);
4595
-
4596
- if (value.hasOwnProperty('input_configurations')) {
4597
- // example: [0, 0, 0, 0]
4598
- await devMgmtEp.write(
4599
- 'manuSpecificUbisysDeviceSetup',
4600
- {'inputConfigurations': {elementType: 'data8', elements: value.input_configurations}},
4601
- manufacturerOptions.ubisysNull,
4602
- );
4603
- }
4604
-
4605
- if (value.hasOwnProperty('input_actions')) {
4606
- // example (default for C4): [[0,13,1,6,0,2], [1,13,2,6,0,2], [2,13,3,6,0,2], [3,13,4,6,0,2]]
4607
- await devMgmtEp.write(
4608
- 'manuSpecificUbisysDeviceSetup',
4609
- {'inputActions': {elementType: 'octetStr', elements: value.input_actions}},
4610
- manufacturerOptions.ubisysNull,
4611
- );
4612
- }
4613
-
4614
- if (value.hasOwnProperty('input_action_templates')) {
4615
- const templateTypes = {
4616
- // source: "ZigBee Device Physical Input Configurations Integrator’s Guide"
4617
- // (can be obtained directly from ubisys upon request)
4618
- 'toggle': {
4619
- getInputActions: (input, endpoint) => [
4620
- [input, 0x0D, endpoint, 0x06, 0x00, 0x02],
4621
- ],
4622
- },
4623
- 'toggle_switch': {
4624
- getInputActions: (input, endpoint) => [
4625
- [input, 0x0D, endpoint, 0x06, 0x00, 0x02],
4626
- [input, 0x03, endpoint, 0x06, 0x00, 0x02],
4627
- ],
4628
- },
4629
- 'on_off_switch': {
4630
- getInputActions: (input, endpoint) => [
4631
- [input, 0x0D, endpoint, 0x06, 0x00, 0x01],
4632
- [input, 0x03, endpoint, 0x06, 0x00, 0x00],
4633
- ],
4634
- },
4635
- 'on': {
4636
- getInputActions: (input, endpoint) => [
4637
- [input, 0x0D, endpoint, 0x06, 0x00, 0x01],
4638
- ],
4639
- },
4640
- 'off': {
4641
- getInputActions: (input, endpoint) => [
4642
- [input, 0x0D, endpoint, 0x06, 0x00, 0x00],
4643
- ],
4644
- },
4645
- 'dimmer_single': {
4646
- getInputActions: (input, endpoint, template) => {
4647
- const moveUpCmd = template.no_onoff || template.no_onoff_up ? 0x01 : 0x05;
4648
- const moveDownCmd = template.no_onoff || template.no_onoff_down ? 0x01 : 0x05;
4649
- const moveRate = template.rate || 50;
4650
- return [
4651
- [input, 0x07, endpoint, 0x06, 0x00, 0x02],
4652
- [input, 0x86, endpoint, 0x08, 0x00, moveUpCmd, 0x00, moveRate],
4653
- [input, 0xC6, endpoint, 0x08, 0x00, moveDownCmd, 0x01, moveRate],
4654
- [input, 0x0B, endpoint, 0x08, 0x00, 0x03],
4655
- ];
4656
- },
4657
- },
4658
- 'dimmer_double': {
4659
- doubleInputs: true,
4660
- getInputActions: (inputs, endpoint, template) => {
4661
- const moveUpCmd = template.no_onoff || template.no_onoff_up ? 0x01 : 0x05;
4662
- const moveDownCmd = template.no_onoff || template.no_onoff_down ? 0x01 : 0x05;
4663
- const moveRate = template.rate || 50;
4664
- return [
4665
- [inputs[0], 0x07, endpoint, 0x06, 0x00, 0x01],
4666
- [inputs[0], 0x06, endpoint, 0x08, 0x00, moveUpCmd, 0x00, moveRate],
4667
- [inputs[0], 0x0B, endpoint, 0x08, 0x00, 0x03],
4668
- [inputs[1], 0x07, endpoint, 0x06, 0x00, 0x00],
4669
- [inputs[1], 0x06, endpoint, 0x08, 0x00, moveDownCmd, 0x01, moveRate],
4670
- [inputs[1], 0x0B, endpoint, 0x08, 0x00, 0x03],
4671
- ];
4672
- },
4673
- },
4674
- 'cover': {
4675
- cover: true,
4676
- doubleInputs: true,
4677
- getInputActions: (inputs, endpoint) => [
4678
- [inputs[0], 0x0D, endpoint, 0x02, 0x01, 0x00],
4679
- [inputs[0], 0x07, endpoint, 0x02, 0x01, 0x02],
4680
- [inputs[1], 0x0D, endpoint, 0x02, 0x01, 0x01],
4681
- [inputs[1], 0x07, endpoint, 0x02, 0x01, 0x02],
4682
- ],
4683
- },
4684
- 'cover_switch': {
4685
- cover: true,
4686
- doubleInputs: true,
4687
- getInputActions: (inputs, endpoint) => [
4688
- [inputs[0], 0x0D, endpoint, 0x02, 0x01, 0x00],
4689
- [inputs[0], 0x03, endpoint, 0x02, 0x01, 0x02],
4690
- [inputs[1], 0x0D, endpoint, 0x02, 0x01, 0x01],
4691
- [inputs[1], 0x03, endpoint, 0x02, 0x01, 0x02],
4692
- ],
4693
- },
4694
- 'cover_up': {
4695
- cover: true,
4696
- getInputActions: (input, endpoint) => [
4697
- [input, 0x0D, endpoint, 0x02, 0x01, 0x00],
4698
- ],
4699
- },
4700
- 'cover_down': {
4701
- cover: true,
4702
- getInputActions: (input, endpoint) => [
4703
- [input, 0x0D, endpoint, 0x02, 0x01, 0x01],
4704
- ],
4705
- },
4706
- 'scene': {
4707
- scene: true,
4708
- getInputActions: (input, endpoint, groupId, sceneId) => [
4709
- [input, 0x07, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
4710
- ],
4711
- getInputActions2: (input, endpoint, groupId, sceneId) => [
4712
- [input, 0x06, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
4713
- ],
4714
- },
4715
- 'scene_switch': {
4716
- scene: true,
4717
- getInputActions: (input, endpoint, groupId, sceneId) => [
4718
- [input, 0x0D, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
4719
- ],
4720
- getInputActions2: (input, endpoint, groupId, sceneId) => [
4721
- [input, 0x03, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
4722
- ],
4723
- },
4724
- };
4725
-
4726
- // first input
4727
- let input = 0;
4728
- // first client endpoint - depends on actual device
4729
- let endpoint = {'S1': 2, 'S2': 3, 'D1': 2, 'J1': 2, 'C4': 1}[meta.mapped.model];
4730
- // default group id
4731
- let groupId = 0;
4732
-
4733
- const templates = Array.isArray(value.input_action_templates) ? value.input_action_templates :
4734
- [value.input_action_templates];
4735
- let resultingInputActions = [];
4736
- for (const template of templates) {
4737
- const templateType = templateTypes[template.type];
4738
- if (!templateType) {
4739
- throw new Error(`input_action_templates: Template type '${template.type}' is not valid ` +
4740
- `(valid types: ${Object.keys(templateTypes)})`);
4741
- }
4742
-
4743
- if (template.hasOwnProperty('input')) {
4744
- input = template.input;
4745
- }
4746
- if (template.hasOwnProperty('endpoint')) {
4747
- endpoint = template.endpoint;
4748
- }
4749
- // C4 cover endpoints only start at 5
4750
- if (templateType.cover && meta.mapped.model === 'C4' && endpoint < 5) {
4751
- endpoint += 4;
4752
- }
4753
-
4754
- let inputActions;
4755
- if (!templateType.doubleInputs) {
4756
- if (!templateType.scene) {
4757
- // single input, no scene(s)
4758
- inputActions = templateType.getInputActions(input, endpoint, template);
4759
- } else {
4760
- // scene(s) (always single input)
4761
- if (!template.hasOwnProperty('scene_id')) {
4762
- throw new Error(`input_action_templates: Need an attribute 'scene_id' for '${template.type}'`);
4763
- }
4764
- if (template.hasOwnProperty('group_id')) {
4765
- groupId = template.group_id;
4766
- }
4767
- inputActions = templateType.getInputActions(input, endpoint, groupId, template.scene_id);
4768
-
4769
- if (template.hasOwnProperty('scene_id_2')) {
4770
- if (template.hasOwnProperty('group_id_2')) {
4771
- groupId = template.group_id_2;
4772
- }
4773
- inputActions = inputActions.concat(templateType.getInputActions2(input, endpoint, groupId,
4774
- template.scene_id_2));
4775
- }
4776
- }
4777
- } else {
4778
- // double inputs
4779
- input = template.hasOwnProperty('inputs') ? template.inputs : [input, input + 1];
4780
- inputActions = templateType.getInputActions(input, endpoint, template);
4781
- }
4782
- resultingInputActions = resultingInputActions.concat(inputActions);
4783
-
4784
- meta.logger.warn(`ubisys: Using input(s) ${input} and endpoint ${endpoint} for '${template.type}'.`);
4785
- // input might by now be an array (in case of double inputs)
4786
- input = (Array.isArray(input) ? Math.max(...input) : input) + 1;
4787
- endpoint += 1;
4788
- }
4789
-
4790
- meta.logger.debug(`ubisys: input_actions to be sent to '${meta.options.friendlyName}': ` +
4791
- JSON.stringify(resultingInputActions));
4792
- await devMgmtEp.write(
4793
- 'manuSpecificUbisysDeviceSetup',
4794
- {'inputActions': {elementType: 'octetStr', elements: resultingInputActions}},
4795
- manufacturerOptions.ubisysNull,
4796
- );
4797
- }
4798
-
4799
- // re-read effective settings and dump them to the log
4800
- converters.ubisys_device_setup.convertGet(entity, key, meta);
4801
- },
4802
-
4803
- convertGet: async (entity, key, meta) => {
4804
- const log = (dataRead) => {
4805
- meta.logger.warn(
4806
- `ubisys: Device setup read for '${meta.options.friendly_name}': ${JSON.stringify(utils.toSnakeCase(dataRead))}`);
4807
- };
4808
- const devMgmtEp = meta.device.getEndpoint(232);
4809
- log(await devMgmtEp.read('manuSpecificUbisysDeviceSetup', ['inputConfigurations'], manufacturerOptions.ubisysNull));
4810
- log(await devMgmtEp.read('manuSpecificUbisysDeviceSetup', ['inputActions'], manufacturerOptions.ubisysNull));
4811
- },
4812
- },
4813
4391
  tint_scene: {
4814
4392
  key: ['tint_scene'],
4815
4393
  convertSet: async (entity, key, value, meta) => {
package/devices/lidl.js CHANGED
@@ -528,6 +528,17 @@ module.exports = [
528
528
  device.getEndpoint(1).saveClusterAttributeKeyValue('lightingColorCtrl', {colorCapabilities: 29});
529
529
  },
530
530
  },
531
+ {
532
+ fingerprint: [{modelID: 'TS0502B', manufacturerName: '_TZ3210_c2iwpxf1'}],
533
+ model: '14156408L',
534
+ vendor: 'Lidl',
535
+ description: 'Livarno Lux smart LED ceiling light',
536
+ ...extend.light_onoff_brightness_colortemp_color({disableColorTempStartup: true, colorTempRange: [153, 500]}),
537
+ meta: {applyRedFix: true, enhancedHue: false},
538
+ configure: async (device, coordinatorEndpoint, logger) => {
539
+ device.getEndpoint(1).saveClusterAttributeKeyValue('lightingColorCtrl', {colorCapabilities: 16});
540
+ },
541
+ },
531
542
  {
532
543
  fingerprint: [{modelID: 'TS0505B', manufacturerName: '_TZ3210_p9ao60da'}],
533
544
  model: 'HG08008',
@@ -80,8 +80,8 @@ module.exports = [
80
80
  model: 'MCLH-08',
81
81
  vendor: 'LifeControl',
82
82
  description: 'Air sensor',
83
- fromZigbee: [fz.lifecontrolVoc],
83
+ fromZigbee: [fz.lifecontrolVoc, fz.battery],
84
84
  toZigbee: [],
85
- exposes: [e.temperature(), e.humidity(), e.voc(), e.eco2()],
85
+ exposes: [e.temperature(), e.humidity(), e.voc(), e.eco2(), e.battery()],
86
86
  },
87
87
  ];
@@ -439,10 +439,11 @@ module.exports = [
439
439
  zigbeeModel: ['1GANG/SHUTTER/1'],
440
440
  model: 'MEG5113-0300/MEG5165-0000',
441
441
  vendor: 'Schneider Electric',
442
- description: 'Merten PlusLink Shutter insert with Merten Wiser System M Push Button',
442
+ description: 'Merten MEG5165 PlusLink Shutter insert with Merten Wiser System M Push Button (1fold)',
443
443
  fromZigbee: [fz.cover_position_tilt, fz.command_cover_close, fz.command_cover_open, fz.command_cover_stop],
444
- toZigbee: [tz.cover_position_tilt, tz.cover_state],
445
- exposes: [e.cover_position()],
444
+ toZigbee: [tz.cover_position_tilt, tz.cover_state, tzLocal.lift_duration],
445
+ exposes: [e.cover_position(), exposes.numeric('lift_duration', ea.STATE_SET).withUnit('seconds')
446
+ .withValueMin(0).withValueMax(300).withDescription('Duration of lift')],
446
447
  meta: {coverInverted: true},
447
448
  configure: async (device, coordinatorEndpoint, logger) => {
448
449
  const endpoint = device.getEndpoint(1) || device.getEndpoint(5);
package/devices/ubisys.js CHANGED
@@ -2,10 +2,498 @@ const exposes = require('../lib/exposes');
2
2
  const fz = {...require('../converters/fromZigbee'), legacy: require('../lib/legacy').fromZigbee};
3
3
  const tz = require('../converters/toZigbee');
4
4
  const ota = require('../lib/ota');
5
+ const utils = require('../lib/utils');
5
6
  const reporting = require('../lib/reporting');
7
+ const herdsman = require('zigbee-herdsman');
6
8
  const e = exposes.presets;
7
9
  const ea = exposes.access;
8
10
 
11
+ const manufacturerOptions = {
12
+ /*
13
+ * Ubisys doesn't accept a manufacturerCode on some commands
14
+ * This bug has been reported, but it has not been fixed:
15
+ * https://github.com/Koenkk/zigbee-herdsman/issues/52
16
+ */
17
+ ubisys: {manufacturerCode: herdsman.Zcl.ManufacturerCode.UBISYS},
18
+ ubisysNull: {manufacturerCode: null},
19
+ tint: {manufacturerCode: herdsman.Zcl.ManufacturerCode.MUELLER_LICHT_INT},
20
+ };
21
+
22
+ const ubisys = {
23
+ fz: {
24
+ dimmer_setup: {
25
+ cluster: 'manuSpecificUbisysDimmerSetup',
26
+ type: ['attributeReport', 'readResponse'],
27
+ convert: (model, msg, publish, options, meta) => {
28
+ if (msg.data.hasOwnProperty('capabilities')) {
29
+ const capabilities = msg.data.capabilities;
30
+ const forwardPhaseControl = capabilities & 1;
31
+ const reversePhaseControl = (capabilities & 2) >>> 1;
32
+ const reactanceDiscriminator = (capabilities & 0x20) >>> 5;
33
+ const configurableCurve = (capabilities & 0x40) >>> 6;
34
+ const overloadDetection = (capabilities & 0x80) >>> 7;
35
+ return {
36
+ capabilities_forward_phase_control: forwardPhaseControl ? true : false,
37
+ capabilities_reverse_phase_control: reversePhaseControl ? true : false,
38
+ capabilities_reactance_discriminator: reactanceDiscriminator ? true : false,
39
+ capabilities_configurable_curve: configurableCurve ? true : false,
40
+ capabilities_overload_detection: overloadDetection ? true : false,
41
+ };
42
+ }
43
+ if (msg.data.hasOwnProperty('status')) {
44
+ const status = msg.data.status;
45
+ const forwardPhaseControl = status & 1;
46
+ const reversePhaseControl = (status & 2) >>> 1;
47
+ const overload = (status & 8) >>> 3;
48
+ const capacitiveLoad = (status & 0x40) >>> 6;
49
+ const inductiveLoad = (status & 0x80) >>> 7;
50
+ return {
51
+ status_forward_phase_control: forwardPhaseControl ? true : false,
52
+ status_reverse_phase_control: reversePhaseControl ? true : false,
53
+ status_overload: overload ? true : false,
54
+ status_capacitive_load: capacitiveLoad ? true : false,
55
+ status_inductive_load: inductiveLoad ? true : false,
56
+ };
57
+ }
58
+ if (msg.data.hasOwnProperty('mode')) {
59
+ const mode = msg.data.mode;
60
+ const phaseControl = mode & 3;
61
+ const phaseControlValues = {0: 'automatic', 1: 'forward', 2: 'reverse'};
62
+ return {
63
+ mode_phase_control: phaseControlValues[phaseControl],
64
+ };
65
+ }
66
+ },
67
+ },
68
+ dimmer_setup_genLevelCtrl: {
69
+ cluster: 'genLevelCtrl',
70
+ type: ['attributeReport', 'readResponse'],
71
+ convert: (model, msg, publish, options, meta) => {
72
+ if (msg.data.hasOwnProperty('ubisysMinimumOnLevel')) {
73
+ return {minimum_on_level: msg.data.ubisysMinimumOnLevel};
74
+ }
75
+ },
76
+ },
77
+ },
78
+ tz: {
79
+ configure_j1: {
80
+ key: ['configure_j1'],
81
+ convertSet: async (entity, key, value, meta) => {
82
+ const log = (message) => {
83
+ meta.logger.warn(`ubisys: ${message}`);
84
+ };
85
+ const sleepSeconds = async (s) => {
86
+ return new Promise((resolve) => setTimeout(resolve, s * 1000));
87
+ };
88
+ const waitUntilStopped = async () => {
89
+ let operationalStatus = 0;
90
+ do {
91
+ await sleepSeconds(2);
92
+ operationalStatus = (await entity.read('closuresWindowCovering',
93
+ ['operationalStatus'])).operationalStatus;
94
+ } while (operationalStatus != 0);
95
+ await sleepSeconds(2);
96
+ };
97
+ const writeAttrFromJson = async (attr, jsonAttr = attr, converterFunc, delaySecondsAfter) => {
98
+ if (jsonAttr.startsWith('ubisys')) {
99
+ jsonAttr = jsonAttr.substring(6, 1).toLowerCase + jsonAttr.substring(7);
100
+ }
101
+ if (value.hasOwnProperty(jsonAttr)) {
102
+ let attrValue = value[jsonAttr];
103
+ if (converterFunc) {
104
+ attrValue = converterFunc(attrValue);
105
+ }
106
+ const attributes = {};
107
+ attributes[attr] = attrValue;
108
+ await entity.write('closuresWindowCovering', attributes, manufacturerOptions.ubisys);
109
+ if (delaySecondsAfter) {
110
+ await sleepSeconds(delaySecondsAfter);
111
+ }
112
+ }
113
+ };
114
+ const stepsPerSecond = value.steps_per_second || 50;
115
+ const hasCalibrate = value.hasOwnProperty('calibrate');
116
+ // cancel any running calibration
117
+ let mode = (await entity.read('closuresWindowCovering', ['windowCoveringMode'])).windowCoveringMode;
118
+ const modeCalibrationBitMask = 0x02;
119
+ if (mode & modeCalibrationBitMask) {
120
+ await entity.write('closuresWindowCovering', {windowCoveringMode: mode & ~modeCalibrationBitMask});
121
+ await sleepSeconds(2);
122
+ }
123
+ // delay a bit if reconfiguring basic configuration attributes
124
+ await writeAttrFromJson('windowCoveringType', undefined, undefined, 2);
125
+ await writeAttrFromJson('configStatus', undefined, undefined, 2);
126
+ if (await writeAttrFromJson('windowCoveringMode', undefined, undefined, 2)) {
127
+ mode = value['windowCoveringMode'];
128
+ }
129
+ if (hasCalibrate) {
130
+ log('Cover calibration starting...');
131
+ // first of all, move to top position to not confuse calibration later
132
+ log(' Moving cover to top position to get a good starting point...');
133
+ await entity.command('closuresWindowCovering', 'upOpen');
134
+ await waitUntilStopped();
135
+ log(' Settings some attributes...');
136
+ // reset attributes
137
+ await entity.write('closuresWindowCovering', {
138
+ installedOpenLimitLiftCm: 0,
139
+ installedClosedLimitLiftCm: 240,
140
+ installedOpenLimitTiltDdegree: 0,
141
+ installedClosedLimitTiltDdegree: 900,
142
+ ubisysLiftToTiltTransitionSteps: 0xffff,
143
+ ubisysTotalSteps: 0xffff,
144
+ ubisysLiftToTiltTransitionSteps2: 0xffff,
145
+ ubisysTotalSteps2: 0xffff,
146
+ }, manufacturerOptions.ubisys);
147
+ // enable calibration mode
148
+ await sleepSeconds(2);
149
+ await entity.write('closuresWindowCovering', {windowCoveringMode: mode | modeCalibrationBitMask});
150
+ await sleepSeconds(2);
151
+ // move down a bit and back up to detect upper limit
152
+ log(' Moving cover down a bit...');
153
+ await entity.command('closuresWindowCovering', 'downClose');
154
+ await sleepSeconds(5);
155
+ await entity.command('closuresWindowCovering', 'stop');
156
+ await sleepSeconds(2);
157
+ log(' Moving up again to detect upper limit...');
158
+ await entity.command('closuresWindowCovering', 'upOpen');
159
+ await waitUntilStopped();
160
+ log(' Moving down to count steps from open to closed...');
161
+ await entity.command('closuresWindowCovering', 'downClose');
162
+ await waitUntilStopped();
163
+ log(' Moving up to count steps from closed to open...');
164
+ await entity.command('closuresWindowCovering', 'upOpen');
165
+ await waitUntilStopped();
166
+ }
167
+ // now write any attribute values present in JSON
168
+ await writeAttrFromJson('installedOpenLimitLiftCm');
169
+ await writeAttrFromJson('installedClosedLimitLiftCm');
170
+ await writeAttrFromJson('installedOpenLimitTiltDdegree');
171
+ await writeAttrFromJson('installedClosedLimitTiltDdegree');
172
+ await writeAttrFromJson('ubisysTurnaroundGuardTime');
173
+ await writeAttrFromJson('ubisysLiftToTiltTransitionSteps');
174
+ await writeAttrFromJson('ubisysTotalSteps');
175
+ await writeAttrFromJson('ubisysLiftToTiltTransitionSteps2');
176
+ await writeAttrFromJson('ubisysTotalSteps2');
177
+ await writeAttrFromJson('ubisysAdditionalSteps');
178
+ await writeAttrFromJson('ubisysInactivePowerThreshold');
179
+ await writeAttrFromJson('ubisysStartupSteps');
180
+ // some convenience functions to not have to calculate
181
+ await writeAttrFromJson('ubisysTotalSteps', 'open_to_closed_s', (s) => s * stepsPerSecond);
182
+ await writeAttrFromJson('ubisysTotalSteps2', 'closed_to_open_s', (s) => s * stepsPerSecond);
183
+ await writeAttrFromJson('ubisysLiftToTiltTransitionSteps', 'lift_to_tilt_transition_ms',
184
+ (s) => s * stepsPerSecond / 1000);
185
+ await writeAttrFromJson('ubisysLiftToTiltTransitionSteps2', 'lift_to_tilt_transition_ms',
186
+ (s) => s * stepsPerSecond / 1000);
187
+ if (hasCalibrate) {
188
+ log(' Finalizing calibration...');
189
+ // disable calibration mode again
190
+ await sleepSeconds(2);
191
+ await entity.write('closuresWindowCovering', {windowCoveringMode: mode & ~modeCalibrationBitMask});
192
+ await sleepSeconds(2);
193
+ // re-read and dump all relevant attributes
194
+ log(' Done - will now read back the results.');
195
+ ubisys.tz.configure_j1.convertGet(entity, key, meta);
196
+ }
197
+ },
198
+ convertGet: async (entity, key, meta) => {
199
+ const log = (json) => {
200
+ meta.logger.warn(`ubisys: Cover configuration read: ${JSON.stringify(json)}`);
201
+ };
202
+ log(await entity.read('closuresWindowCovering', [
203
+ 'windowCoveringType',
204
+ 'physicalClosedLimitLiftCm',
205
+ 'physicalClosedLimitTiltDdegree',
206
+ 'installedOpenLimitLiftCm',
207
+ 'installedClosedLimitLiftCm',
208
+ 'installedOpenLimitTiltDdegree',
209
+ 'installedClosedLimitTiltDdegree',
210
+ ]));
211
+ log(await entity.read('closuresWindowCovering', [
212
+ 'configStatus',
213
+ 'windowCoveringMode',
214
+ 'currentPositionLiftPercentage',
215
+ 'currentPositionLiftCm',
216
+ 'currentPositionTiltPercentage',
217
+ 'currentPositionTiltDdegree',
218
+ 'operationalStatus',
219
+ ]));
220
+ log(await entity.read('closuresWindowCovering', [
221
+ 'ubisysTurnaroundGuardTime',
222
+ 'ubisysLiftToTiltTransitionSteps',
223
+ 'ubisysTotalSteps',
224
+ 'ubisysLiftToTiltTransitionSteps2',
225
+ 'ubisysTotalSteps2',
226
+ 'ubisysAdditionalSteps',
227
+ 'ubisysInactivePowerThreshold',
228
+ 'ubisysStartupSteps',
229
+ ], manufacturerOptions.ubisys));
230
+ },
231
+ },
232
+ dimmer_setup: {
233
+ key: ['capabilities_forward_phase_control',
234
+ 'capabilities_reverse_phase_control',
235
+ 'capabilities_reactance_discriminator',
236
+ 'capabilities_configurable_curve',
237
+ 'capabilities_overload_detection',
238
+ 'status_forward_phase_control',
239
+ 'status_reverse_phase_control',
240
+ 'status_overload',
241
+ 'status_capacitive_load',
242
+ 'status_inductive_load',
243
+ 'mode_phase_control'],
244
+ convertSet: async (entity, key, value, meta) => {
245
+ if (key === 'mode_phase_control') {
246
+ const phaseControl = value.toLowerCase();
247
+ const phaseControlValues = {'automatic': 0, 'forward': 1, 'reverse': 2};
248
+ utils.validateValue(phaseControl, Object.keys(phaseControlValues));
249
+ await entity.write('manuSpecificUbisysDimmerSetup',
250
+ {'mode': phaseControlValues[phaseControl]}, manufacturerOptions.ubisysNull);
251
+ }
252
+ ubisys.tz.dimmer_setup.convertGet(entity, key, meta);
253
+ },
254
+ convertGet: async (entity, key, meta) => {
255
+ await entity.read('manuSpecificUbisysDimmerSetup', ['capabilities'], manufacturerOptions.ubisysNull);
256
+ await entity.read('manuSpecificUbisysDimmerSetup', ['status'], manufacturerOptions.ubisysNull);
257
+ await entity.read('manuSpecificUbisysDimmerSetup', ['mode'], manufacturerOptions.ubisysNull);
258
+ },
259
+ },
260
+ dimmer_setup_genLevelCtrl: {
261
+ key: ['minimum_on_level'],
262
+ convertSet: async (entity, key, value, meta) => {
263
+ if (key === 'minimum_on_level') {
264
+ await entity.write('genLevelCtrl', {'ubisysMinimumOnLevel': value}, manufacturerOptions.ubisys);
265
+ }
266
+ ubisys.tz.dimmer_setup_genLevelCtrl.convertGet(entity, key, meta);
267
+ },
268
+ convertGet: async (entity, key, meta) => {
269
+ await entity.read('genLevelCtrl', ['ubisysMinimumOnLevel'], manufacturerOptions.ubisys);
270
+ },
271
+ },
272
+ configure_device_setup: {
273
+ key: ['configure_device_setup'],
274
+ convertSet: async (entity, key, value, meta) => {
275
+ const devMgmtEp = meta.device.getEndpoint(232);
276
+
277
+ if (value.hasOwnProperty('input_configurations')) {
278
+ // example: [0, 0, 0, 0]
279
+ await devMgmtEp.write(
280
+ 'manuSpecificUbisysDeviceSetup',
281
+ {'inputConfigurations': {elementType: 'data8', elements: value.input_configurations}},
282
+ manufacturerOptions.ubisysNull,
283
+ );
284
+ }
285
+
286
+ if (value.hasOwnProperty('input_actions')) {
287
+ // example (default for C4): [[0,13,1,6,0,2], [1,13,2,6,0,2], [2,13,3,6,0,2], [3,13,4,6,0,2]]
288
+ await devMgmtEp.write(
289
+ 'manuSpecificUbisysDeviceSetup',
290
+ {'inputActions': {elementType: 'octetStr', elements: value.input_actions}},
291
+ manufacturerOptions.ubisysNull,
292
+ );
293
+ }
294
+
295
+ if (value.hasOwnProperty('input_action_templates')) {
296
+ const templateTypes = {
297
+ // source: "ZigBee Device Physical Input Configurations Integrator’s Guide"
298
+ // (can be obtained directly from ubisys upon request)
299
+ 'toggle': {
300
+ getInputActions: (input, endpoint) => [
301
+ [input, 0x0D, endpoint, 0x06, 0x00, 0x02],
302
+ ],
303
+ },
304
+ 'toggle_switch': {
305
+ getInputActions: (input, endpoint) => [
306
+ [input, 0x0D, endpoint, 0x06, 0x00, 0x02],
307
+ [input, 0x03, endpoint, 0x06, 0x00, 0x02],
308
+ ],
309
+ },
310
+ 'on_off_switch': {
311
+ getInputActions: (input, endpoint) => [
312
+ [input, 0x0D, endpoint, 0x06, 0x00, 0x01],
313
+ [input, 0x03, endpoint, 0x06, 0x00, 0x00],
314
+ ],
315
+ },
316
+ 'on': {
317
+ getInputActions: (input, endpoint) => [
318
+ [input, 0x0D, endpoint, 0x06, 0x00, 0x01],
319
+ ],
320
+ },
321
+ 'off': {
322
+ getInputActions: (input, endpoint) => [
323
+ [input, 0x0D, endpoint, 0x06, 0x00, 0x00],
324
+ ],
325
+ },
326
+ 'dimmer_single': {
327
+ getInputActions: (input, endpoint, template) => {
328
+ const moveUpCmd = template.no_onoff || template.no_onoff_up ? 0x01 : 0x05;
329
+ const moveDownCmd = template.no_onoff || template.no_onoff_down ? 0x01 : 0x05;
330
+ const moveRate = template.rate || 50;
331
+ return [
332
+ [input, 0x07, endpoint, 0x06, 0x00, 0x02],
333
+ [input, 0x86, endpoint, 0x08, 0x00, moveUpCmd, 0x00, moveRate],
334
+ [input, 0xC6, endpoint, 0x08, 0x00, moveDownCmd, 0x01, moveRate],
335
+ [input, 0x0B, endpoint, 0x08, 0x00, 0x03],
336
+ ];
337
+ },
338
+ },
339
+ 'dimmer_double': {
340
+ doubleInputs: true,
341
+ getInputActions: (inputs, endpoint, template) => {
342
+ const moveUpCmd = template.no_onoff || template.no_onoff_up ? 0x01 : 0x05;
343
+ const moveDownCmd = template.no_onoff || template.no_onoff_down ? 0x01 : 0x05;
344
+ const moveRate = template.rate || 50;
345
+ return [
346
+ [inputs[0], 0x07, endpoint, 0x06, 0x00, 0x01],
347
+ [inputs[0], 0x06, endpoint, 0x08, 0x00, moveUpCmd, 0x00, moveRate],
348
+ [inputs[0], 0x0B, endpoint, 0x08, 0x00, 0x03],
349
+ [inputs[1], 0x07, endpoint, 0x06, 0x00, 0x00],
350
+ [inputs[1], 0x06, endpoint, 0x08, 0x00, moveDownCmd, 0x01, moveRate],
351
+ [inputs[1], 0x0B, endpoint, 0x08, 0x00, 0x03],
352
+ ];
353
+ },
354
+ },
355
+ 'cover': {
356
+ cover: true,
357
+ doubleInputs: true,
358
+ getInputActions: (inputs, endpoint) => [
359
+ [inputs[0], 0x0D, endpoint, 0x02, 0x01, 0x00],
360
+ [inputs[0], 0x07, endpoint, 0x02, 0x01, 0x02],
361
+ [inputs[1], 0x0D, endpoint, 0x02, 0x01, 0x01],
362
+ [inputs[1], 0x07, endpoint, 0x02, 0x01, 0x02],
363
+ ],
364
+ },
365
+ 'cover_switch': {
366
+ cover: true,
367
+ doubleInputs: true,
368
+ getInputActions: (inputs, endpoint) => [
369
+ [inputs[0], 0x0D, endpoint, 0x02, 0x01, 0x00],
370
+ [inputs[0], 0x03, endpoint, 0x02, 0x01, 0x02],
371
+ [inputs[1], 0x0D, endpoint, 0x02, 0x01, 0x01],
372
+ [inputs[1], 0x03, endpoint, 0x02, 0x01, 0x02],
373
+ ],
374
+ },
375
+ 'cover_up': {
376
+ cover: true,
377
+ getInputActions: (input, endpoint) => [
378
+ [input, 0x0D, endpoint, 0x02, 0x01, 0x00],
379
+ ],
380
+ },
381
+ 'cover_down': {
382
+ cover: true,
383
+ getInputActions: (input, endpoint) => [
384
+ [input, 0x0D, endpoint, 0x02, 0x01, 0x01],
385
+ ],
386
+ },
387
+ 'scene': {
388
+ scene: true,
389
+ getInputActions: (input, endpoint, groupId, sceneId) => [
390
+ [input, 0x07, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
391
+ ],
392
+ getInputActions2: (input, endpoint, groupId, sceneId) => [
393
+ [input, 0x06, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
394
+ ],
395
+ },
396
+ 'scene_switch': {
397
+ scene: true,
398
+ getInputActions: (input, endpoint, groupId, sceneId) => [
399
+ [input, 0x0D, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
400
+ ],
401
+ getInputActions2: (input, endpoint, groupId, sceneId) => [
402
+ [input, 0x03, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
403
+ ],
404
+ },
405
+ };
406
+
407
+ // first input
408
+ let input = 0;
409
+ // first client endpoint - depends on actual device
410
+ let endpoint = {'S1': 2, 'S2': 3, 'D1': 2, 'J1': 2, 'C4': 1}[meta.mapped.model];
411
+ // default group id
412
+ let groupId = 0;
413
+
414
+ const templates = Array.isArray(value.input_action_templates) ? value.input_action_templates :
415
+ [value.input_action_templates];
416
+ let resultingInputActions = [];
417
+ for (const template of templates) {
418
+ const templateType = templateTypes[template.type];
419
+ if (!templateType) {
420
+ throw new Error(`input_action_templates: Template type '${template.type}' is not valid ` +
421
+ `(valid types: ${Object.keys(templateTypes)})`);
422
+ }
423
+
424
+ if (template.hasOwnProperty('input')) {
425
+ input = template.input;
426
+ }
427
+ if (template.hasOwnProperty('endpoint')) {
428
+ endpoint = template.endpoint;
429
+ }
430
+ // C4 cover endpoints only start at 5
431
+ if (templateType.cover && meta.mapped.model === 'C4' && endpoint < 5) {
432
+ endpoint += 4;
433
+ }
434
+
435
+ let inputActions;
436
+ if (!templateType.doubleInputs) {
437
+ if (!templateType.scene) {
438
+ // single input, no scene(s)
439
+ inputActions = templateType.getInputActions(input, endpoint, template);
440
+ } else {
441
+ // scene(s) (always single input)
442
+ if (!template.hasOwnProperty('scene_id')) {
443
+ throw new Error(`input_action_templates: Need an attribute 'scene_id' for '${template.type}'`);
444
+ }
445
+ if (template.hasOwnProperty('group_id')) {
446
+ groupId = template.group_id;
447
+ }
448
+ inputActions = templateType.getInputActions(input, endpoint, groupId, template.scene_id);
449
+
450
+ if (template.hasOwnProperty('scene_id_2')) {
451
+ if (template.hasOwnProperty('group_id_2')) {
452
+ groupId = template.group_id_2;
453
+ }
454
+ inputActions = inputActions.concat(templateType.getInputActions2(input, endpoint, groupId,
455
+ template.scene_id_2));
456
+ }
457
+ }
458
+ } else {
459
+ // double inputs
460
+ input = template.hasOwnProperty('inputs') ? template.inputs : [input, input + 1];
461
+ inputActions = templateType.getInputActions(input, endpoint, template);
462
+ }
463
+ resultingInputActions = resultingInputActions.concat(inputActions);
464
+
465
+ meta.logger.warn(`ubisys: Using input(s) ${input} and endpoint ${endpoint} for '${template.type}'.`);
466
+ // input might by now be an array (in case of double inputs)
467
+ input = (Array.isArray(input) ? Math.max(...input) : input) + 1;
468
+ endpoint += 1;
469
+ }
470
+
471
+ meta.logger.debug(`ubisys: input_actions to be sent to '${meta.options.friendlyName}': ` +
472
+ JSON.stringify(resultingInputActions));
473
+ await devMgmtEp.write(
474
+ 'manuSpecificUbisysDeviceSetup',
475
+ {'inputActions': {elementType: 'octetStr', elements: resultingInputActions}},
476
+ manufacturerOptions.ubisysNull,
477
+ );
478
+ }
479
+
480
+ // re-read effective settings and dump them to the log
481
+ ubisys.tz.configure_device_setup.convertGet(entity, key, meta);
482
+ },
483
+
484
+ convertGet: async (entity, key, meta) => {
485
+ const log = (dataRead) => {
486
+ meta.logger.warn(
487
+ `ubisys: Device setup read for '${meta.options.friendly_name}': ${JSON.stringify(utils.toSnakeCase(dataRead))}`);
488
+ };
489
+ const devMgmtEp = meta.device.getEndpoint(232);
490
+ log(await devMgmtEp.read('manuSpecificUbisysDeviceSetup', ['inputConfigurations'], manufacturerOptions.ubisysNull));
491
+ log(await devMgmtEp.read('manuSpecificUbisysDeviceSetup', ['inputActions'], manufacturerOptions.ubisysNull));
492
+ },
493
+ },
494
+ },
495
+ };
496
+
9
497
  module.exports = [
10
498
  {
11
499
  zigbeeModel: ['S1 (5501)'],
@@ -20,7 +508,7 @@ module.exports = [
20
508
  e.power_on_behavior()],
21
509
  fromZigbee: [fz.on_off, fz.metering, fz.command_toggle, fz.command_on, fz.command_off, fz.command_recall, fz.command_move,
22
510
  fz.command_stop, fz.power_on_behavior],
23
- toZigbee: [tz.on_off, tz.metering_power, tz.ubisys_device_setup, tz.power_on_behavior],
511
+ toZigbee: [tz.on_off, tz.metering_power, ubisys.tz.configure_device_setup, tz.power_on_behavior],
24
512
  endpoint: (device) => {
25
513
  return {'l1': 1, 's1': 2, 'meter': 3};
26
514
  },
@@ -62,7 +550,7 @@ module.exports = [
62
550
  e.power_on_behavior()],
63
551
  fromZigbee: [fz.on_off, fz.metering, fz.command_toggle, fz.command_on, fz.command_off, fz.command_recall, fz.command_move,
64
552
  fz.command_stop, fz.power_on_behavior],
65
- toZigbee: [tz.on_off, tz.metering_power, tz.ubisys_device_setup, tz.power_on_behavior],
553
+ toZigbee: [tz.on_off, tz.metering_power, ubisys.tz.configure_device_setup, tz.power_on_behavior],
66
554
  endpoint: (device) => {
67
555
  return {'l1': 1, 's1': 2, 'meter': 4};
68
556
  },
@@ -105,7 +593,7 @@ module.exports = [
105
593
  e.power_on_behavior().withEndpoint('l1'), e.power_on_behavior().withEndpoint('l2')],
106
594
  fromZigbee: [fz.on_off, fz.metering, fz.command_toggle, fz.command_on, fz.command_off, fz.command_recall, fz.command_move,
107
595
  fz.command_stop, fz.power_on_behavior],
108
- toZigbee: [tz.on_off, tz.metering_power, tz.ubisys_device_setup, tz.power_on_behavior],
596
+ toZigbee: [tz.on_off, tz.metering_power, ubisys.tz.configure_device_setup, tz.power_on_behavior],
109
597
  endpoint: (device) => {
110
598
  return {'l1': 1, 'l2': 2, 's1': 3, 's2': 4, 'meter': 5};
111
599
  },
@@ -149,10 +637,10 @@ module.exports = [
149
637
  vendor: 'Ubisys',
150
638
  description: 'Universal dimmer D1',
151
639
  fromZigbee: [fz.on_off, fz.brightness, fz.metering, fz.command_toggle, fz.command_on, fz.command_off, fz.command_recall,
152
- fz.command_move, fz.command_stop, fz.lighting_ballast_configuration, fz.level_config, fz.ubisys_dimmer_setup,
153
- fz.ubisys_dimmer_setup_genLevelCtrl],
154
- toZigbee: [tz.light_onoff_brightness, tz.ballast_config, tz.level_config, tz.ubisys_dimmer_setup,
155
- tz.ubisys_dimmer_setup_genLevelCtrl, tz.ubisys_device_setup, tz.ignore_transition],
640
+ fz.command_move, fz.command_stop, fz.lighting_ballast_configuration, fz.level_config, ubisys.fz.dimmer_setup,
641
+ ubisys.fz.dimmer_setup_genLevelCtrl],
642
+ toZigbee: [tz.light_onoff_brightness, tz.ballast_config, tz.level_config, ubisys.tz.dimmer_setup,
643
+ ubisys.tz.dimmer_setup_genLevelCtrl, ubisys.tz.configure_device_setup, tz.ignore_transition],
156
644
  exposes: [e.light_brightness().withLevelConfig(), e.power(),
157
645
  exposes.numeric('ballast_physical_minimum_level', ea.ALL).withValueMin(1).withValueMax(254)
158
646
  .withDescription('Specifies the minimum light output the ballast can achieve.'),
@@ -215,7 +703,7 @@ module.exports = [
215
703
  description: 'Shutter control J1',
216
704
  fromZigbee: [fz.cover_position_tilt, fz.metering],
217
705
  toZigbee: [tz.cover_state, tz.cover_position_tilt, tz.metering_power,
218
- tz.ubisys_configure_j1, tz.ubisys_device_setup],
706
+ ubisys.tz.configure_j1, ubisys.tz.configure_device_setup],
219
707
  exposes: [e.cover_position_tilt(),
220
708
  e.power().withAccess(ea.STATE_GET).withEndpoint('meter').withProperty('power')],
221
709
  configure: async (device, coordinatorEndpoint, logger) => {
@@ -251,7 +739,7 @@ module.exports = [
251
739
  vendor: 'Ubisys',
252
740
  description: 'Control unit C4',
253
741
  fromZigbee: [fz.legacy.ubisys_c4_scenes, fz.legacy.ubisys_c4_onoff, fz.legacy.ubisys_c4_level, fz.legacy.ubisys_c4_cover],
254
- toZigbee: [tz.ubisys_device_setup],
742
+ toZigbee: [ubisys.tz.configure_device_setup],
255
743
  exposes: [e.action([
256
744
  '1_scene_*', '1_on', '1_off', '1_toggle', '1_level_move_down', '1_level_move_up',
257
745
  '2_scene_*', '2_on', '2_off', '2_toggle', '2_level_move_down', '2_level_move_up',
package/devices/xiaomi.js CHANGED
@@ -1001,7 +1001,7 @@ module.exports = [
1001
1001
  meta: {battery: {voltageToPercentage: '3V_2850_3000_log'}},
1002
1002
  fromZigbee: [fz.xiaomi_basic, fz.ias_water_leak_alarm_1],
1003
1003
  toZigbee: [],
1004
- exposes: [e.battery(), e.water_leak(), e.battery_low(), e.battery_voltage(), e.temperature()],
1004
+ exposes: [e.battery(), e.water_leak(), e.battery_low(), e.battery_voltage(), e.temperature(), e.power_outage_count()],
1005
1005
  },
1006
1006
  {
1007
1007
  zigbeeModel: ['lumi.flood.agl02'],
@@ -1021,7 +1021,7 @@ module.exports = [
1021
1021
  description: 'Mi/Aqara smart home cube',
1022
1022
  meta: {battery: {voltageToPercentage: '3V_2850_3000_log'}},
1023
1023
  fromZigbee: [fz.xiaomi_basic, fz.MFKZQ01LM_action_multistate, fz.MFKZQ01LM_action_analog],
1024
- exposes: [e.battery(), e.battery_voltage(), e.angle('action_angle'),
1024
+ exposes: [e.battery(), e.battery_voltage(), e.angle('action_angle'), e.temperature(), e.power_outage_count(),
1025
1025
  e.cube_side('action_from_side'), e.cube_side('action_side'), e.cube_side('action_to_side'),
1026
1026
  e.action(['shake', 'wakeup', 'fall', 'tap', 'slide', 'flip180', 'flip90', 'rotate_left', 'rotate_right'])],
1027
1027
  toZigbee: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "14.0.519",
3
+ "version": "14.0.520",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [