zigbee-herdsman-converters 15.0.25 → 15.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/devices/diyruz.js CHANGED
@@ -63,7 +63,8 @@ module.exports = [
63
63
  vendor: 'DIYRuZ',
64
64
  description: '[DiY 8/12/20 button keypad](http://modkam.ru/?p=1114)',
65
65
  fromZigbee: [fz.diyruz_freepad_clicks, fz.diyruz_freepad_config, fz.battery],
66
- exposes: [e.battery(), e.action(['*_single', '*_double', '*_triple', '*_quadruple', '*_release'])].concat(((enpoinsCount) => {
66
+ exposes: [e.battery(),
67
+ e.action(['*_single', '*_double', '*_triple', '*_quadruple', '*_release', '*_hold'])].concat(((enpoinsCount) => {
67
68
  const features = [];
68
69
  for (let i = 1; i <= enpoinsCount; i++) {
69
70
  const epName = `button_${i}`;
@@ -1575,6 +1575,13 @@ module.exports = [
1575
1575
  description: 'Hue Gradient Signe floor lamp (wood)',
1576
1576
  extend: philips.extend.light_onoff_brightness_colortemp_color_gradient({colorTempRange: [153, 500]}),
1577
1577
  },
1578
+ {
1579
+ zigbeeModel: ['929003516101'],
1580
+ model: '929003516101',
1581
+ vendor: 'Philips',
1582
+ description: 'Hue Gradient Signe floor lamp (wood)',
1583
+ extend: philips.extend.light_onoff_brightness_colortemp_color_gradient({colorTempRange: [153, 500]}),
1584
+ },
1578
1585
  {
1579
1586
  zigbeeModel: ['LCT020'],
1580
1587
  model: '4080148P7',
@@ -2224,6 +2231,13 @@ module.exports = [
2224
2231
  description: 'Hue white filament Edison ST19 E26 LED warm-to-cool',
2225
2232
  extend: philips.extend.light_onoff_brightness_colortemp({colorTempRange: [222, 454]}),
2226
2233
  },
2234
+ {
2235
+ zigbeeModel: ['LTV005'],
2236
+ model: '9290029808',
2237
+ vendor: 'Philips',
2238
+ description: 'Hue white filament Edison ST23 E26 LED warm-to-cool',
2239
+ extend: philips.extend.light_onoff_brightness_colortemp({colorTempRange: [222, 454]}),
2240
+ },
2227
2241
  {
2228
2242
  zigbeeModel: ['LTO004'],
2229
2243
  model: '9290024785',
package/devices/xiaomi.js CHANGED
@@ -10,6 +10,8 @@ const ea = exposes.access;
10
10
  const globalStore = require('../lib/store');
11
11
  const xiaomi = require('../lib/xiaomi');
12
12
  const utils = require('../lib/utils');
13
+ const {printNumberAsHex, printNumbersAsHexSequence} = utils;
14
+ const {fp1, manufacturerCode} = xiaomi;
13
15
 
14
16
  const xiaomiExtend = {
15
17
  light_onoff_brightness_colortemp: (options={disableColorTempStartup: true}) => ({
@@ -211,6 +213,73 @@ const fzLocal = {
211
213
  return result;
212
214
  },
213
215
  },
216
+ aqara_fp1_region_events: {
217
+ cluster: 'aqaraOpple',
218
+ type: ['attributeReport', 'readResponse'],
219
+ convert: (model, msg, publish, options, meta) => {
220
+ /**
221
+ * @type {{ region_event?: string; }}
222
+ */
223
+ const payload = {};
224
+ const log = utils.createLogger(meta.logger, 'xiaomi', 'aqara_fp1');
225
+
226
+ Object.entries(msg.data).forEach(([key, value]) => {
227
+ const eventKey = parseInt(key);
228
+ const eventKeyHex = printNumberAsHex(eventKey, 4);
229
+
230
+ switch (eventKey) {
231
+ case fp1.constants.region_event_key: {
232
+ if (
233
+ !Buffer.isBuffer(value) ||
234
+ !(typeof value[0] === 'string' || typeof value[0] === 'number') ||
235
+ !(typeof value[1] === 'string' || typeof value[1] === 'number')
236
+ ) {
237
+ log('warn', `region_event: Unrecognized payload structure '${JSON.stringify(value)}'`);
238
+ break;
239
+ }
240
+
241
+ /**
242
+ * @type {[ regionId: number | string, eventTypeCode: number | string ]}
243
+ */
244
+ const [regionIdRaw, eventTypeCodeRaw] = value;
245
+ const regionId = parseInt(regionIdRaw, 10);
246
+ const eventTypeCode = parseInt(eventTypeCodeRaw, 10);
247
+
248
+ if (Number.isNaN(regionId)) {
249
+ log('warn', `region_event: Invalid regionId "${regionIdRaw}"`);
250
+ break;
251
+ }
252
+ if (!Object.values(fp1.constants.region_event_types).includes(eventTypeCode)) {
253
+ log('warn', `region_event: Unknown region event type "${eventTypeCode}"`);
254
+ break;
255
+ }
256
+
257
+ const eventTypeName = fp1.mappers.aqara_fp1.region_event_type_names[eventTypeCode];
258
+ log('debug', `region_event: Triggered event (region "${regionId}", type "${eventTypeName}")`);
259
+ payload.region_event = `region_${regionId}_${eventTypeName}`;
260
+ break;
261
+ }
262
+ case 0xf7: {
263
+ const valueHexSequence = printNumbersAsHexSequence(value, 2);
264
+ log('debug', `Unhandled key ${eventKeyHex} = ${valueHexSequence}`);
265
+ break;
266
+ }
267
+ case 0x0142:
268
+ case 0x0143:
269
+ case 0x0144:
270
+ case 0x0146: {
271
+ log('debug', `Unhandled key ${eventKeyHex} = ${value}`);
272
+ break;
273
+ }
274
+ default: {
275
+ log('warn', `Unknown key ${eventKeyHex} = ${value}`);
276
+ }
277
+ }
278
+ });
279
+
280
+ return payload;
281
+ },
282
+ },
214
283
  };
215
284
 
216
285
  const tzLocal = {
@@ -415,6 +484,113 @@ const tzLocal = {
415
484
  return {state: {[key]: value}};
416
485
  },
417
486
  },
487
+ aqara_fp1_region_upsert: {
488
+ key: ['region_upsert'],
489
+ convertSet: async (entity, key, value, meta) => {
490
+ const log = utils.createLogger(meta.logger, 'xiaomi', 'aqara_fp1:region_upsert');
491
+ const commandWrapper = fp1.parseAqaraFp1RegionUpsertInput(value);
492
+
493
+ if (!commandWrapper.isSuccess) {
494
+ log('warn',
495
+ `encountered an error (${commandWrapper.error.reason}) ` +
496
+ `while parsing configuration commands (input: ${JSON.stringify(value)})`,
497
+ );
498
+
499
+ return;
500
+ }
501
+
502
+ const command = commandWrapper.payload.command;
503
+
504
+ log('debug', `trying to create region ${command.region_id}`);
505
+
506
+ /** @type {Record<string, Set<number>>} */
507
+ const sortedZonesAccumulator = {};
508
+ const sortedZones = command.zones
509
+ .reduce(
510
+ (accumulator, zone) => {
511
+ if (!accumulator[zone.y]) {
512
+ accumulator[zone.y] = new Set();
513
+ }
514
+
515
+ accumulator[zone.y].add(zone.x);
516
+
517
+ return accumulator;
518
+ },
519
+ sortedZonesAccumulator,
520
+ );
521
+
522
+ const deviceConfig = new Uint8Array(7);
523
+
524
+ // Command parameters
525
+ deviceConfig[0] = fp1.constants.region_config_cmds.create;
526
+ deviceConfig[1] = command.region_id;
527
+ deviceConfig[6] = fp1.constants.region_config_cmd_suffix_upsert;
528
+ // Zones definition
529
+ deviceConfig[2] |= fp1.encodeXCellsDefinition(sortedZones['1']);
530
+ deviceConfig[2] |= fp1.encodeXCellsDefinition(sortedZones['2']) << 4;
531
+ deviceConfig[3] |= fp1.encodeXCellsDefinition(sortedZones['3']);
532
+ deviceConfig[3] |= fp1.encodeXCellsDefinition(sortedZones['4']) << 4;
533
+ deviceConfig[4] |= fp1.encodeXCellsDefinition(sortedZones['5']);
534
+ deviceConfig[4] |= fp1.encodeXCellsDefinition(sortedZones['6']) << 4;
535
+ deviceConfig[5] |= fp1.encodeXCellsDefinition(sortedZones['7']);
536
+
537
+ log('info', `create region ${command.region_id} ${printNumbersAsHexSequence([...deviceConfig], 2)}`);
538
+
539
+ const payload = {
540
+ [fp1.constants.region_config_write_attribute]: {
541
+ value: deviceConfig,
542
+ type: fp1.constants.region_config_write_attribute_type,
543
+ },
544
+ };
545
+
546
+ await entity.write('aqaraOpple', payload, {manufacturerCode});
547
+ },
548
+ },
549
+ aqara_fp1_region_delete: {
550
+ key: ['region_delete'],
551
+ convertSet: async (entity, key, value, meta) => {
552
+ const log = utils.createLogger(meta.logger, 'xiaomi', 'aqara_fp1:region_delete');
553
+ const commandWrapper = fp1.parseAqaraFp1RegionDeleteInput(value);
554
+
555
+ if (!commandWrapper.isSuccess) {
556
+ log('warn',
557
+ `encountered an error (${commandWrapper.error.reason}) ` +
558
+ `while parsing configuration commands (input: ${JSON.stringify(value)})`,
559
+ );
560
+ return;
561
+ }
562
+
563
+ const command = commandWrapper.payload.command;
564
+
565
+ log('debug', `trying to delete region ${command.region_id}`);
566
+
567
+ const deviceConfig = new Uint8Array(7);
568
+
569
+ // Command parameters
570
+ deviceConfig[0] = fp1.constants.region_config_cmds.delete;
571
+ deviceConfig[1] = command.region_id;
572
+ deviceConfig[6] = fp1.constants.region_config_cmd_suffix_delete;
573
+ // Zones definition
574
+ deviceConfig[2] = 0;
575
+ deviceConfig[3] = 0;
576
+ deviceConfig[4] = 0;
577
+ deviceConfig[5] = 0;
578
+
579
+ log('info',
580
+ `delete region ${command.region_id} ` +
581
+ `(${printNumbersAsHexSequence([...deviceConfig], 2)})`,
582
+ );
583
+
584
+ const payload = {
585
+ [fp1.constants.region_config_write_attribute]: {
586
+ value: deviceConfig,
587
+ type: fp1.constants.region_config_write_attribute_type,
588
+ },
589
+ };
590
+
591
+ await entity.write('aqaraOpple', payload, {manufacturerCode});
592
+ },
593
+ },
418
594
  };
419
595
 
420
596
  module.exports = [
@@ -1471,11 +1647,14 @@ module.exports = [
1471
1647
  zigbeeModel: ['lumi.motion.ac01'],
1472
1648
  model: 'RTCZCGQ11LM',
1473
1649
  vendor: 'Xiaomi',
1474
- description: 'Aqara presence detector FP1 (regions not supported for now)',
1475
- fromZigbee: [fz.aqara_opple],
1476
- toZigbee: [tz.RTCZCGQ11LM_presence, tz.RTCZCGQ11LM_monitoring_mode, tz.RTCZCGQ11LM_approach_distance,
1477
- tz.aqara_motion_sensitivity, tz.RTCZCGQ11LM_reset_nopresence_status],
1478
- exposes: [e.presence().withAccess(ea.STATE_GET),
1650
+ description: 'Aqara presence detector FP1',
1651
+ fromZigbee: [fz.aqara_opple, fzLocal.aqara_fp1_region_events],
1652
+ toZigbee: [
1653
+ tz.RTCZCGQ11LM_presence, tz.RTCZCGQ11LM_monitoring_mode, tz.RTCZCGQ11LM_approach_distance, tz.aqara_motion_sensitivity,
1654
+ tz.RTCZCGQ11LM_reset_nopresence_status, tzLocal.aqara_fp1_region_upsert, tzLocal.aqara_fp1_region_delete,
1655
+ ],
1656
+ exposes: [
1657
+ e.presence().withAccess(ea.STATE_GET), e.device_temperature(), e.power_outage_count(),
1479
1658
  exposes.enum('presence_event', ea.STATE, ['enter', 'leave', 'left_enter', 'right_leave', 'right_enter', 'left_leave',
1480
1659
  'approach', 'away']).withDescription('Presence events: "enter", "leave", "left_enter", "right_leave", ' +
1481
1660
  '"right_enter", "left_leave", "approach", "away"'),
@@ -1486,7 +1665,41 @@ module.exports = [
1486
1665
  exposes.enum('motion_sensitivity', ea.ALL, ['low', 'medium', 'high']).withDescription('Different sensitivities ' +
1487
1666
  'means different static human body recognition rate and response speed of occupied'),
1488
1667
  exposes.enum('reset_nopresence_status', ea.SET, ['']).withDescription('Reset the status of no presence'),
1489
- e.device_temperature(), e.power_outage_count()],
1668
+ exposes.enum('action', ea.STATE, ['region_*_enter', 'region_*_leave', 'region_*_occupied',
1669
+ 'region_*_unoccupied']).withDescription('Most recent region event. Event template is "region_<REGION_ID>_<EVENT_TYPE>", ' +
1670
+ 'where <REGION_ID> is region number (1-10), <EVENT_TYPE> is one of "enter", "leave", "occupied", "unoccupied". ' +
1671
+ '"enter" / "leave" events are usually triggered first, followed by "occupied" / "unoccupied" after a couple of seconds.'),
1672
+ exposes.composite('region_upsert', 'region_upsert', ea.SET)
1673
+ .withDescription(
1674
+ 'Definition of a new region to be added (or replace existing one). ' +
1675
+ 'Creating or modifying a region requires you to define which zones of a 7x4 detection grid ' +
1676
+ 'should be active for that zone. Regions can overlap, meaning that a zone can be defined ' +
1677
+ 'in more than one region (eg. "zone x = 1 & y = 1" can be added to region 1 & 2). ' +
1678
+ '"Zone x = 1 & y = 1" is the nearest zone on the right (from sensor\'s perspective, along the detection path).',
1679
+ )
1680
+ .withFeature(
1681
+ exposes.numeric('region_id', ea.SET)
1682
+ .withValueMin(fp1.constants.region_config_regionId_min)
1683
+ .withValueMax(fp1.constants.region_config_regionId_max),
1684
+ )
1685
+ .withFeature(
1686
+ exposes.list('zones', ea.SET,
1687
+ exposes.composite('zone_position', ea.SET)
1688
+ .withFeature(exposes.numeric('x', ea.SET)
1689
+ .withValueMin(fp1.constants.region_config_zoneX_min)
1690
+ .withValueMax(fp1.constants.region_config_zoneX_max))
1691
+ .withFeature(exposes.numeric('y', ea.SET)
1692
+ .withValueMin(fp1.constants.region_config_zoneY_min)
1693
+ .withValueMax(fp1.constants.region_config_zoneY_max)),
1694
+ ),
1695
+ ),
1696
+ exposes.composite('region_delete', 'region_delete', ea.SET)
1697
+ .withDescription('Region definition to be deleted from the device.')
1698
+ .withFeature(exposes.numeric('region_id', ea.SET)
1699
+ .withValueMin(fp1.constants.region_config_regionId_min)
1700
+ .withValueMax(fp1.constants.region_config_regionId_max),
1701
+ ),
1702
+ ],
1490
1703
  configure: async (device, coordinatorEndpoint, logger) => {
1491
1704
  const endpoint = device.getEndpoint(1);
1492
1705
  await endpoint.read('aqaraOpple', [0x010c], {manufacturerCode: 0x115f});
@@ -2710,7 +2923,7 @@ module.exports = [
2710
2923
  fromZigbee: [fzLocal.aqara_feeder],
2711
2924
  toZigbee: [tzLocal.aqara_feeder],
2712
2925
  exposes: [
2713
- exposes.enum('feed', ea.STATE_SET, ['START']).withDescription('Start feeding'),
2926
+ exposes.enum('feed', ea.STATE_SET, ['', 'START']).withDescription('Start feeding'),
2714
2927
  exposes.enum('feeding_source', ea.STATE, ['schedule', 'manual', 'remote']).withDescription('Feeding source'),
2715
2928
  exposes.numeric('feeding_size', ea.STATE).withDescription('Feeding size').withUnit('portion'),
2716
2929
  exposes.numeric('portions_per_day', ea.STATE).withDescription('Portions per day'),
@@ -2734,6 +2947,10 @@ module.exports = [
2734
2947
  .withUnit('g'),
2735
2948
  ],
2736
2949
  ota: ota.zigbeeOTA,
2950
+ configure: async (device, coordinatorEndpoint, logger) => {
2951
+ const endpoint = device.getEndpoint(1);
2952
+ await endpoint.read('aqaraOpple', [0xfff1], {manufacturerCode: 0x115f});
2953
+ },
2737
2954
  },
2738
2955
  {
2739
2956
  zigbeeModel: ['lumi.remote.acn007'],
package/index.js CHANGED
@@ -228,5 +228,15 @@ module.exports = {
228
228
  const payload = {0xf00: {value: 23, type: 35}};
229
229
  await endpoint.readResponse('genBasic', data.meta.zclTransactionSequenceNumber, payload, options);
230
230
  }
231
+ // Aqara feeder C1 polls the time during the interview, need to send back the local time instead of the UTC.
232
+ // The device.definition has not yet been set - therefore the device.definition.onEvent method does not work.
233
+ if (type === 'message' && data.type === 'read' && data.cluster === 'genTime' &&
234
+ device.modelID === 'aqara.feeder.acn001') {
235
+ device.skipTimeResponse = true;
236
+ const oneJanuary2000 = new Date('January 01, 2000 00:00:00 UTC+00:00').getTime();
237
+ const secondsUTC = Math.round(((new Date()).getTime() - oneJanuary2000) / 1000);
238
+ const secondsLocal = secondsUTC - (new Date()).getTimezoneOffset() * 60;
239
+ await device.getEndpoint(1).readResponse('genTime', data.meta.zclTransactionSequenceNumber, {time: secondsLocal});
240
+ }
231
241
  },
232
242
  };
package/lib/utils.js CHANGED
@@ -454,6 +454,37 @@ function attachOutputCluster(device, clusterKey) {
454
454
  }
455
455
  }
456
456
 
457
+ /**
458
+ * @param {number} value
459
+ * @param {number} hexLength
460
+ * @return {string}
461
+ */
462
+ function printNumberAsHex(value, hexLength) {
463
+ const hexValue = value.toString(16).padStart(hexLength, '0');
464
+ return `0x${hexValue}`;
465
+ }
466
+
467
+ /**
468
+ * @param {number[]} numbers
469
+ * @param {number} hexLength
470
+ * @return {string}
471
+ */
472
+ function printNumbersAsHexSequence(numbers, hexLength) {
473
+ return numbers.map((v) => v.toString(16).padStart(hexLength, '0')).join(':');
474
+ }
475
+
476
+ // Note: this is valid typescript-flavored JSDoc
477
+ // eslint-disable-next-line valid-jsdoc
478
+ /**
479
+ * @param {logger} logger
480
+ * @param {vendor} vendor
481
+ * @param {key} key
482
+ * @returns {(level: string, message: string) => void}
483
+ */
484
+ const createLogger = (logger, vendor, key) => (level, message) => {
485
+ logger[level](`zigbee-herdsman-converters:${vendor}:${key}: ${message}`);
486
+ };
487
+
457
488
  module.exports = {
458
489
  noOccupancySince,
459
490
  getOptions,
@@ -490,4 +521,7 @@ module.exports = {
490
521
  getSceneState,
491
522
  extendDevice,
492
523
  attachOutputCluster,
524
+ printNumberAsHex,
525
+ printNumbersAsHexSequence,
526
+ createLogger,
493
527
  };
package/lib/xiaomi.js CHANGED
@@ -684,9 +684,188 @@ const numericAttributes2Options = (definition) => {
684
684
  return result;
685
685
  };
686
686
 
687
+ // For RTCZCGQ11LM
688
+ /**
689
+ * @typedef {{
690
+ * x: number,
691
+ * y: number,
692
+ * }} AqaraFP1RegionZone
693
+ */
694
+ const fp1Constants = {
695
+ region_event_key: 0x0151,
696
+ region_event_types: {
697
+ Enter: 1,
698
+ Leave: 2,
699
+ Occupied: 4,
700
+ Unoccupied: 8,
701
+ },
702
+ region_config_write_attribute: 0x0150,
703
+ region_config_write_attribute_type: 0x41,
704
+ region_config_cmds: {
705
+ /**
706
+ * Creates new region (or force replaces existing one)
707
+ * with new zones definition.
708
+ */
709
+ create: 1,
710
+ /**
711
+ * Modifies existing region.
712
+ * Note: unused, as it seems to break existing regions
713
+ * (region stops reporting new detection events).
714
+ * Use "create" instead, as it replaces existing region with new one.
715
+ */
716
+ modify: 2,
717
+ /**
718
+ * Deletes existing region.
719
+ */
720
+ delete: 3,
721
+ },
722
+ region_config_regionId_min: 1,
723
+ region_config_regionId_max: 10,
724
+ region_config_zoneY_min: 1,
725
+ region_config_zoneY_max: 7,
726
+ region_config_zoneX_min: 1,
727
+ region_config_zoneX_max: 4,
728
+ region_config_cmd_suffix_upsert: 0xff,
729
+ region_config_cmd_suffix_delete: 0x00,
730
+ };
731
+ const fp1Mappers = {
732
+ aqara_fp1: {
733
+ region_event_type_names: {
734
+ [fp1Constants.region_event_types.Enter]: 'enter',
735
+ [fp1Constants.region_event_types.Leave]: 'leave',
736
+ [fp1Constants.region_event_types.Occupied]: 'occupied',
737
+ [fp1Constants.region_event_types.Unoccupied]: 'unoccupied',
738
+ },
739
+ },
740
+ };
741
+ const fp1 = {
742
+ constants: fp1Constants,
743
+ mappers: fp1Mappers,
744
+ /**
745
+ * @param {undefined | Set<number>} xCells
746
+ * @return {number}
747
+ */
748
+ encodeXCellsDefinition: (xCells) => {
749
+ if (!xCells || !xCells.size) {
750
+ return 0;
751
+ }
752
+ return [...xCells.values()].reduce((accumulator, marker) => accumulator + fp1.encodeXCellIdx(marker), 0);
753
+ },
754
+ /**
755
+ * @param {number} cellXIdx
756
+ * @return {number}
757
+ */
758
+ encodeXCellIdx: (cellXIdx) => {
759
+ return 2 ** (cellXIdx - 1);
760
+ },
761
+ // Note: let TypeScript infer the return type to enable union discrimination
762
+ // eslint-disable-next-line valid-jsdoc
763
+ /**
764
+ * @param {unknown} input
765
+ */
766
+ parseAqaraFp1RegionDeleteInput: (input) => {
767
+ if (!input || typeof input !== 'object') {
768
+ return fp1.failure({reason: 'NOT_OBJECT'});
769
+ }
770
+
771
+ if (!('region_id' in input) || !fp1.isAqaraFp1RegionId(input.region_id)) {
772
+ return fp1.failure({reason: 'INVALID_REGION_ID'});
773
+ }
774
+
775
+ return {
776
+ /** @type true */
777
+ isSuccess: true,
778
+ payload: {
779
+ command: {
780
+ region_id: input.region_id,
781
+ },
782
+ },
783
+ };
784
+ },
785
+ // Note: let TypeScript infer the return type to enable union discrimination
786
+ // eslint-disable-next-line valid-jsdoc
787
+ /**
788
+ * @param {unknown} input
789
+ */
790
+ parseAqaraFp1RegionUpsertInput: (input) => {
791
+ if (!input || typeof input !== 'object') {
792
+ return fp1.failure({reason: 'NOT_OBJECT'});
793
+ }
794
+
795
+ if (!('region_id' in input) || !fp1.isAqaraFp1RegionId(input.region_id)) {
796
+ return fp1.failure({reason: 'INVALID_REGION_ID'});
797
+ }
798
+
799
+ if (!('zones' in input) || !Array.isArray(input.zones) || !input.zones.length) {
800
+ return fp1.failure({reason: 'ZONES_LIST_EMPTY'});
801
+ }
802
+
803
+ if (!input.zones.every(fp1.isAqaraFp1RegionZoneDefinition)) {
804
+ return fp1.failure({reason: 'INVALID_ZONES'});
805
+ }
806
+
807
+ return {
808
+ /** @type true */
809
+ isSuccess: true,
810
+ payload: {
811
+ command: {
812
+ region_id: input.region_id,
813
+ zones: input.zones,
814
+ },
815
+ },
816
+ };
817
+ },
818
+ // Note: this is valid typescript JSDoc
819
+ // eslint-disable-next-line valid-jsdoc
820
+ /**
821
+ * @param {unknown} value
822
+ * @returns {value is number}
823
+ */
824
+ isAqaraFp1RegionId: (value) => {
825
+ return (
826
+ typeof value === 'number' &&
827
+ value >= fp1.constants.region_config_regionId_min &&
828
+ value <= fp1.constants.region_config_regionId_max
829
+ );
830
+ },
831
+ // Note: this is valid typescript JSDoc
832
+ // eslint-disable-next-line valid-jsdoc
833
+ /**
834
+ * @param {unknown} value
835
+ * @returns {value is AqaraFP1RegionZone}
836
+ */
837
+ isAqaraFp1RegionZoneDefinition: (value) => {
838
+ return (
839
+ value &&
840
+ typeof value === 'object' &&
841
+ 'x' in value &&
842
+ 'y' in value &&
843
+ typeof value.x === 'number' &&
844
+ typeof value.y === 'number' &&
845
+ value.x >= fp1.constants.region_config_zoneX_min &&
846
+ value.x <= fp1.constants.region_config_zoneX_max &&
847
+ value.y >= fp1.constants.region_config_zoneY_min &&
848
+ value.y <= fp1.constants.region_config_zoneY_max
849
+ );
850
+ },
851
+ /**
852
+ * @template {Record<string, unknown>} ErrorType
853
+ * @param {ErrorType} error
854
+ * @return { { isSuccess: false, error: ErrorType } }
855
+ */
856
+ failure: (error) => {
857
+ return {
858
+ isSuccess: false,
859
+ error,
860
+ };
861
+ },
862
+ };
863
+
687
864
  module.exports = {
688
865
  buffer2DataObject,
689
866
  numericAttributes2Payload,
690
867
  numericAttributes2Options,
691
868
  VOCKQJK11LMDisplayUnit,
869
+ fp1,
870
+ manufacturerCode: 0x115f,
692
871
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "15.0.25",
3
+ "version": "15.0.26",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [