zigbee-herdsman-converters 14.0.632 → 14.0.634

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.
@@ -706,30 +706,6 @@ const converters = {
706
706
  return result;
707
707
  },
708
708
  },
709
- develco_metering: {
710
- cluster: 'seMetering',
711
- type: ['attributeReport', 'readResponse'],
712
- convert: (model, msg, publish, options, meta) => {
713
- const result = {};
714
- if (msg.data.hasOwnProperty('develcoPulseConfiguration')) {
715
- result[postfixWithEndpointName('pulse_configuration', msg, model, meta)] =
716
- msg.data['develcoPulseConfiguration'];
717
- }
718
-
719
- if (msg.data.hasOwnProperty('develcoInterfaceMode')) {
720
- result[postfixWithEndpointName('interface_mode', msg, model, meta)] =
721
- constants.develcoInterfaceMode.hasOwnProperty(msg.data['develcoInterfaceMode']) ?
722
- constants.develcoInterfaceMode[msg.data['develcoInterfaceMode']] :
723
- msg.data['develcoInterfaceMode'];
724
- }
725
- if (msg.data.hasOwnProperty('status')) {
726
- result['battery_low'] = (msg.data.status & 2) > 0;
727
- result['check_meter'] = (msg.data.status & 1) > 0;
728
- }
729
-
730
- return result;
731
- },
732
- },
733
709
  electrical_measurement: {
734
710
  /**
735
711
  * When using this converter also add the following to the configure method of the device:
@@ -1927,49 +1903,6 @@ const converters = {
1927
1903
  return {...payload1, ...payload2};
1928
1904
  },
1929
1905
  },
1930
- develco_voc: {
1931
- cluster: 'develcoSpecificAirQuality',
1932
- type: ['attributeReport', 'readResponse'],
1933
- options: [exposes.options.precision('voc'), exposes.options.calibration('voc')],
1934
- convert: (model, msg, publish, options, meta) => {
1935
- const voc = parseFloat(msg.data['measuredValue']);
1936
- const vocProperty = postfixWithEndpointName('voc', msg, model, meta);
1937
-
1938
- let airQuality;
1939
- const airQualityProperty = postfixWithEndpointName('air_quality', msg, model, meta);
1940
- if (voc <= 65) {
1941
- airQuality = 'excellent';
1942
- } else if (voc <= 220) {
1943
- airQuality = 'good';
1944
- } else if (voc <= 660) {
1945
- airQuality = 'moderate';
1946
- } else if (voc <= 2200) {
1947
- airQuality = 'poor';
1948
- } else if (voc <= 5500) {
1949
- airQuality = 'unhealthy';
1950
- } else if (voc > 5500) {
1951
- airQuality = 'out_of_range';
1952
- } else {
1953
- airQuality = 'unknown';
1954
- }
1955
- return {[vocProperty]: calibrateAndPrecisionRoundOptions(voc, options, 'voc'), [airQualityProperty]: airQuality};
1956
- },
1957
- },
1958
- develco_voc_battery: {
1959
- cluster: 'genPowerCfg',
1960
- type: ['attributeReport', 'readResponse'],
1961
- convert: (model, msg, publish, options, meta) => {
1962
- /*
1963
- * Per the technical documentation for AQSZB-110:
1964
- * To detect low battery the system can monitor the "BatteryVoltage" by setting up a reporting interval of every 12 hour.
1965
- * When a voltage of 2.5V is measured the battery should be replaced.
1966
- * Low batt LED indication–RED LED will blink twice every 60 second.
1967
- */
1968
- const result = converters.battery.convert(model, msg, publish, options, meta);
1969
- result.battery_low = (result.voltage <= 2500);
1970
- return result;
1971
- },
1972
- },
1973
1906
  tuya_temperature_humidity_sensor: {
1974
1907
  cluster: 'manuSpecificTuya',
1975
1908
  type: ['commandDataReport', 'commandDataResponse'],
@@ -7818,39 +7751,6 @@ const converters = {
7818
7751
  return {action: `scene_${scenes[msg.data.level]}`};
7819
7752
  },
7820
7753
  },
7821
- develco_fw: {
7822
- cluster: 'genBasic',
7823
- type: ['attributeReport', 'readResponse'],
7824
- convert: (model, msg, publish, options, meta) => {
7825
- const result = {};
7826
- if (0x8000 in msg.data) {
7827
- const firmware = msg.data[0x8000].join('.');
7828
- result.current_firmware = firmware;
7829
- meta.device.softwareBuildID = firmware;
7830
- }
7831
-
7832
- if (0x8020 in msg.data) {
7833
- meta.device.hardwareVersion = msg.data[0x8020].join('.');
7834
- }
7835
-
7836
- return result;
7837
- },
7838
- },
7839
- develco_genbinaryinput: {
7840
- cluster: 'genBinaryInput',
7841
- type: ['attributeReport', 'readResponse'],
7842
- convert: (model, msg, publish, options, meta) => {
7843
- const result = {};
7844
- if (msg.data.hasOwnProperty('reliability')) {
7845
- const lookup = {0: 'no_fault_detected', 7: 'unreliable_other', 8: 'process_error'};
7846
- result.reliability = lookup[msg.data['reliability']];
7847
- }
7848
- if (msg.data.hasOwnProperty('statusFlags')) {
7849
- result.fault = (msg.data['statusFlags']===1);
7850
- }
7851
- return result;
7852
- },
7853
- },
7854
7754
  xiaomi_tvoc: {
7855
7755
  cluster: 'genAnalogInput',
7856
7756
  type: ['attributeReport', 'readResponse'],
@@ -16,7 +16,6 @@ const manufacturerOptions = {
16
16
  osram: {manufacturerCode: herdsman.Zcl.ManufacturerCode.OSRAM},
17
17
  eurotronic: {manufacturerCode: herdsman.Zcl.ManufacturerCode.JENNIC},
18
18
  danfoss: {manufacturerCode: herdsman.Zcl.ManufacturerCode.DANFOSS},
19
- develco: {manufacturerCode: herdsman.Zcl.ManufacturerCode.DEVELCO},
20
19
  hue: {manufacturerCode: herdsman.Zcl.ManufacturerCode.PHILIPS},
21
20
  ikea: {manufacturerCode: herdsman.Zcl.ManufacturerCode.IKEA_OF_SWEDEN},
22
21
  sinope: {manufacturerCode: herdsman.Zcl.ManufacturerCode.SINOPE_TECH},
@@ -3081,34 +3080,6 @@ const converters = {
3081
3080
  await entity.read('haDiagnostic', ['danfossMultimasterRole'], manufacturerOptions.danfoss);
3082
3081
  },
3083
3082
  },
3084
- develco_pulse_configuration: {
3085
- key: ['pulse_configuration'],
3086
- convertSet: async (entity, key, value, meta) => {
3087
- await entity.write('seMetering', {'develcoPulseConfiguration': value}, manufacturerOptions.develco);
3088
- return {readAfterWriteTime: 200, state: {'pulse_configuration': value}};
3089
- },
3090
- convertGet: async (entity, key, meta) => {
3091
- await entity.read('seMetering', ['develcoPulseConfiguration'], manufacturerOptions.develco);
3092
- },
3093
- },
3094
- develco_interface_mode: {
3095
- key: ['interface_mode'],
3096
- convertSet: async (entity, key, value, meta) => {
3097
- const payload = {'develcoInterfaceMode': utils.getKey(constants.develcoInterfaceMode, value, undefined, Number)};
3098
- await entity.write('seMetering', payload, manufacturerOptions.develco);
3099
- return {readAfterWriteTime: 200, state: {'interface_mode': value}};
3100
- },
3101
- convertGet: async (entity, key, meta) => {
3102
- await entity.read('seMetering', ['develcoInterfaceMode'], manufacturerOptions.develco);
3103
- },
3104
- },
3105
- develco_current_summation: {
3106
- key: ['current_summation'],
3107
- convertSet: async (entity, key, value, meta) => {
3108
- await entity.write('seMetering', {'develcoCurrentSummation': value}, manufacturerOptions.develco);
3109
- return {state: {'current_summation': value}};
3110
- },
3111
- },
3112
3083
  ZMCSW032D_cover_position: {
3113
3084
  key: ['position', 'tilt'],
3114
3085
  convertSet: async (entity, key, value, meta) => {
@@ -4,35 +4,247 @@ const tz = require('../converters/toZigbee');
4
4
  const constants = require('../lib/constants');
5
5
  const reporting = require('../lib/reporting');
6
6
  const globalStore = require('../lib/store');
7
+ const utils = require('../lib/utils');
7
8
  const ota = require('../lib/ota');
8
9
  const e = exposes.presets;
9
10
  const ea = exposes.access;
10
11
 
11
- const fzLocal = {
12
- // SPLZB-134 and SPLZB-131 reports strange values sometimes
13
- // https://github.com/Koenkk/zigbee2mqtt/issues/13329
14
- develco_electrical_measurement: {
15
- ...fz.electrical_measurement,
16
- convert: (model, msg, publish, options, meta) => {
17
- if (msg.data.rmsVoltage !== 0xFFFF && msg.data.rmsCurrent !== 0xFFFF && msg.data.activePower !== -0x8000) {
18
- return fz.electrical_measurement.convert(model, msg, publish, options, meta);
19
- }
12
+ // develco specific cosntants
13
+ const manufacturerOptions = {manufacturerCode: 0x1015};
14
+
15
+ /* MOSZB-1xx - ledControl - bitmap8 - r/w
16
+ * 0x00 Disable LED when movement is detected.
17
+ * 0x01 Enables periodic fault flashes. These flashes are used to indicate e.g. low battery level.
18
+ * 0x02 Enables green application defined LED. This is e.g. used to indicate motion detection.
19
+ * Default value 0xFF ( seems to be fault + motion)
20
+ */
21
+ const develcoLedControlMap = {
22
+ 0x00: 'off',
23
+ 0x01: 'fault_only',
24
+ 0x02: 'motion_only',
25
+ 0xFF: 'both',
26
+ };
27
+
28
+ // develco specific convertors
29
+ const develco = {
30
+ fz: {
31
+ // SPLZB-134 and SPLZB-131 reports strange values sometimes
32
+ // https://github.com/Koenkk/zigbee2mqtt/issues/13329
33
+ electrical_measurement: {
34
+ ...fz.electrical_measurement,
35
+ convert: (model, msg, publish, options, meta) => {
36
+ if (msg.data.rmsVoltage !== 0xFFFF && msg.data.rmsCurrent !== 0xFFFF && msg.data.activePower !== -0x8000) {
37
+ return fz.electrical_measurement.convert(model, msg, publish, options, meta);
38
+ }
39
+ },
20
40
  },
21
- },
22
- develco_device_temperature: {
23
- ...fz.device_temperature,
24
- convert: (model, msg, publish, options, meta) => {
25
- if (msg.data.currentTemperature !== -0x8000) {
26
- return fz.device_temperature.convert(model, msg, publish, options, meta);
27
- }
41
+ device_temperature: {
42
+ ...fz.device_temperature,
43
+ convert: (model, msg, publish, options, meta) => {
44
+ if (msg.data.currentTemperature !== -0x8000) {
45
+ return fz.device_temperature.convert(model, msg, publish, options, meta);
46
+ }
47
+ },
48
+ },
49
+ metering: {
50
+ ...fz.metering,
51
+ convert: (model, msg, publish, options, meta) => {
52
+ if (msg.data.instantaneousDemand !== -0x800000) {
53
+ return fz.metering.convert(model, msg, publish, options, meta);
54
+ }
55
+ },
56
+ },
57
+ pulse_configuration: {
58
+ cluster: 'seMetering',
59
+ type: ['attributeReport', 'readResponse'],
60
+ convert: (model, msg, publish, options, meta) => {
61
+ const result = {};
62
+ if (msg.data.hasOwnProperty('develcoPulseConfiguration')) {
63
+ result[utils.postfixWithEndpointName('pulse_configuration', msg, model, meta)] =
64
+ msg.data['develcoPulseConfiguration'];
65
+ }
66
+
67
+ return result;
68
+ },
69
+ },
70
+ interface_mode: {
71
+ cluster: 'seMetering',
72
+ type: ['attributeReport', 'readResponse'],
73
+ convert: (model, msg, publish, options, meta) => {
74
+ const result = {};
75
+ if (msg.data.hasOwnProperty('develcoInterfaceMode')) {
76
+ result[utils.postfixWithEndpointName('interface_mode', msg, model, meta)] =
77
+ constants.develcoInterfaceMode.hasOwnProperty(msg.data['develcoInterfaceMode']) ?
78
+ constants.develcoInterfaceMode[msg.data['develcoInterfaceMode']] :
79
+ msg.data['develcoInterfaceMode'];
80
+ }
81
+ if (msg.data.hasOwnProperty('status')) {
82
+ result['battery_low'] = (msg.data.status & 2) > 0;
83
+ result['check_meter'] = (msg.data.status & 1) > 0;
84
+ }
85
+
86
+ return result;
87
+ },
88
+ },
89
+ firmware_version: {
90
+ cluster: 'genBasic',
91
+ type: ['attributeReport', 'readResponse'],
92
+ convert: (model, msg, publish, options, meta) => {
93
+ const result = {};
94
+ if (0x8000 in msg.data) {
95
+ const firmware = msg.data[0x8000].join('.');
96
+ result.current_firmware = firmware;
97
+ meta.device.softwareBuildID = firmware;
98
+ }
99
+
100
+ if (0x8020 in msg.data) {
101
+ meta.device.hardwareVersion = msg.data[0x8020].join('.');
102
+ }
103
+
104
+ return result;
105
+ },
106
+ },
107
+ fault_status: {
108
+ cluster: 'genBinaryInput',
109
+ type: ['attributeReport', 'readResponse'],
110
+ convert: (model, msg, publish, options, meta) => {
111
+ const result = {};
112
+ if (msg.data.hasOwnProperty('reliability')) {
113
+ const lookup = {0: 'no_fault_detected', 7: 'unreliable_other', 8: 'process_error'};
114
+ result.reliability = lookup[msg.data['reliability']];
115
+ }
116
+ if (msg.data.hasOwnProperty('statusFlags')) {
117
+ result.fault = (msg.data['statusFlags']===1);
118
+ }
119
+ return result;
120
+ },
121
+ },
122
+ voc: {
123
+ cluster: 'develcoSpecificAirQuality',
124
+ type: ['attributeReport', 'readResponse'],
125
+ options: [exposes.options.precision('voc'), exposes.options.calibration('voc')],
126
+ convert: (model, msg, publish, options, meta) => {
127
+ const voc = parseFloat(msg.data['measuredValue']);
128
+ const vocProperty = utils.postfixWithEndpointName('voc', msg, model, meta);
129
+
130
+ let airQuality;
131
+ const airQualityProperty = utils.postfixWithEndpointName('air_quality', msg, model, meta);
132
+ if (voc <= 65) {
133
+ airQuality = 'excellent';
134
+ } else if (voc <= 220) {
135
+ airQuality = 'good';
136
+ } else if (voc <= 660) {
137
+ airQuality = 'moderate';
138
+ } else if (voc <= 2200) {
139
+ airQuality = 'poor';
140
+ } else if (voc <= 5500) {
141
+ airQuality = 'unhealthy';
142
+ } else if (voc > 5500) {
143
+ airQuality = 'out_of_range';
144
+ } else {
145
+ airQuality = 'unknown';
146
+ }
147
+ return {[vocProperty]: utils.calibrateAndPrecisionRoundOptions(voc, options, 'voc'), [airQualityProperty]: airQuality};
148
+ },
149
+ },
150
+ voc_battery: {
151
+ cluster: 'genPowerCfg',
152
+ type: ['attributeReport', 'readResponse'],
153
+ convert: (model, msg, publish, options, meta) => {
154
+ /*
155
+ * Per the technical documentation for AQSZB-110:
156
+ * To detect low battery the system can monitor the "BatteryVoltage" by setting up a reporting interval of every 12 hour.
157
+ * When a voltage of 2.5V is measured the battery should be replaced.
158
+ * Low batt LED indication–RED LED will blink twice every 60 second.
159
+ */
160
+ const result = fz.battery.convert(model, msg, publish, options, meta);
161
+ result.battery_low = (result.voltage <= 2500);
162
+ return result;
163
+ },
164
+ },
165
+ led_control: {
166
+ cluster: 'genBasic',
167
+ type: ['attributeReport', 'readResponse'],
168
+ options: [],
169
+ convert: (model, msg, publish, options, meta) => {
170
+ const state = {};
171
+
172
+ if (msg.data.hasOwnProperty('develcoLedControl')) {
173
+ state['led_control'] = develcoLedControlMap[msg.data['develcoLedControl']];
174
+ }
175
+
176
+ return state;
177
+ },
178
+ },
179
+ ias_occupancy_timeout: {
180
+ cluster: 'ssIasZone',
181
+ type: ['attributeReport', 'readResponse'],
182
+ options: [],
183
+ convert: (model, msg, publish, options, meta) => {
184
+ const state = {};
185
+
186
+ if (msg.data.hasOwnProperty('develcoAlarmOffDelay')) {
187
+ state['occupancy_timeout'] = msg.data['develcoAlarmOffDelay'];
188
+ }
189
+
190
+ return state;
191
+ },
28
192
  },
29
193
  },
30
- develco_metering: {
31
- ...fz.metering,
32
- convert: (model, msg, publish, options, meta) => {
33
- if (msg.data.instantaneousDemand !== -0x800000) {
34
- return fz.metering.convert(model, msg, publish, options, meta);
35
- }
194
+ tz: {
195
+ pulse_configuration: {
196
+ key: ['pulse_configuration'],
197
+ convertSet: async (entity, key, value, meta) => {
198
+ await entity.write('seMetering', {'develcoPulseConfiguration': value}, manufacturerOptions);
199
+ return {readAfterWriteTime: 200, state: {'pulse_configuration': value}};
200
+ },
201
+ convertGet: async (entity, key, meta) => {
202
+ await entity.read('seMetering', ['develcoPulseConfiguration'], manufacturerOptions);
203
+ },
204
+ },
205
+ interface_mode: {
206
+ key: ['interface_mode'],
207
+ convertSet: async (entity, key, value, meta) => {
208
+ const payload = {'develcoInterfaceMode': utils.getKey(constants.develcoInterfaceMode, value, undefined, Number)};
209
+ await entity.write('seMetering', payload, manufacturerOptions.develco);
210
+ return {readAfterWriteTime: 200, state: {'interface_mode': value}};
211
+ },
212
+ convertGet: async (entity, key, meta) => {
213
+ await entity.read('seMetering', ['develcoInterfaceMode'], manufacturerOptions);
214
+ },
215
+ },
216
+ current_summation: {
217
+ key: ['current_summation'],
218
+ convertSet: async (entity, key, value, meta) => {
219
+ await entity.write('seMetering', {'develcoCurrentSummation': value}, manufacturerOptions);
220
+ return {state: {'current_summation': value}};
221
+ },
222
+ },
223
+ led_control: {
224
+ key: ['led_control'],
225
+ convertSet: async (entity, key, value, meta) => {
226
+ const ledControl = utils.getKey(develcoLedControlMap, value, value, Number);
227
+ await entity.write('genBasic', {'develcoLedControl': ledControl}, manufacturerOptions);
228
+ return {state: {led_control: value}};
229
+ },
230
+ convertGet: async (entity, key, meta) => {
231
+ await entity.read('genBasic', ['develcoLedControl'], manufacturerOptions);
232
+ },
233
+ },
234
+ ias_occupancy_timeout: {
235
+ key: ['occupancy_timeout'],
236
+ convertSet: async (entity, key, value, meta) => {
237
+ let timeoutValue = value;
238
+ if (timeoutValue < 20) {
239
+ meta.logger.warn(`Minimum occupancy_timeout is 20, using 20 instead of ${timeoutValue}!`);
240
+ timeoutValue = 20;
241
+ }
242
+ await entity.write('ssIasZone', {'develcoAlarmOffDelay': timeoutValue}, manufacturerOptions);
243
+ return {state: {occupancy_timeout: timeoutValue}};
244
+ },
245
+ convertGet: async (entity, key, meta) => {
246
+ await entity.read('ssIasZone', ['develcoAlarmOffDelay'], manufacturerOptions);
247
+ },
36
248
  },
37
249
  },
38
250
  };
@@ -43,7 +255,7 @@ module.exports = [
43
255
  model: 'SPLZB-131',
44
256
  vendor: 'Develco',
45
257
  description: 'Power plug',
46
- fromZigbee: [fz.on_off, fzLocal.develco_electrical_measurement, fzLocal.develco_metering],
258
+ fromZigbee: [fz.on_off, develco.fz.electrical_measurement, develco.fz.metering],
47
259
  toZigbee: [tz.on_off],
48
260
  exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy()],
49
261
  configure: async (device, coordinatorEndpoint, logger) => {
@@ -94,7 +306,7 @@ module.exports = [
94
306
  model: 'SPLZB-134',
95
307
  vendor: 'Develco',
96
308
  description: 'Power plug (type G)',
97
- fromZigbee: [fz.on_off, fzLocal.develco_electrical_measurement, fzLocal.develco_metering, fzLocal.develco_device_temperature],
309
+ fromZigbee: [fz.on_off, develco.fz.electrical_measurement, develco.fz.metering, develco.fz.device_temperature],
98
310
  toZigbee: [tz.on_off],
99
311
  exposes: [e.switch(), e.power(), e.current(), e.voltage(), e.energy(), e.device_temperature()],
100
312
  configure: async (device, coordinatorEndpoint, logger) => {
@@ -144,13 +356,12 @@ module.exports = [
144
356
  model: 'EMIZB-132',
145
357
  vendor: 'Develco',
146
358
  description: 'Wattle AMS HAN power-meter sensor',
147
- fromZigbee: [fzLocal.develco_metering, fzLocal.develco_electrical_measurement, fz.develco_fw],
359
+ fromZigbee: [develco.fz.metering, develco.fz.electrical_measurement, develco.fz.firmware_version],
148
360
  toZigbee: [tz.EMIZB_132_mode],
149
361
  ota: ota.zigbeeOTA,
150
362
  configure: async (device, coordinatorEndpoint, logger) => {
151
363
  const endpoint = device.getEndpoint(2);
152
- const options = {manufacturerCode: 4117};
153
- await endpoint.read('genBasic', [0x8000, 0x8010, 0x8020], options);
364
+ await endpoint.read('genBasic', [0x8000, 0x8010, 0x8020], manufacturerOptions);
154
365
  await reporting.bind(endpoint, coordinatorEndpoint, ['haElectricalMeasurement', 'seMetering']);
155
366
 
156
367
  try {
@@ -186,17 +397,16 @@ module.exports = [
186
397
  vendor: 'Develco',
187
398
  description: 'Smoke detector with siren',
188
399
  fromZigbee: [fz.temperature, fz.battery, fz.ias_smoke_alarm_1_develco, fz.ignore_basic_report,
189
- fz.develco_fw, fz.ias_enroll, fz.ias_wd, fz.develco_genbinaryinput],
400
+ develco.fz.firmware_version, fz.ias_enroll, fz.ias_wd, develco.fz.fault_status],
190
401
  toZigbee: [tz.warning, tz.ias_max_duration, tz.warning_simple],
191
402
  ota: ota.zigbeeOTA,
192
403
  meta: {battery: {voltageToPercentage: '3V_2500'}},
193
404
  configure: async (device, coordinatorEndpoint, logger) => {
194
- const options = {manufacturerCode: 4117};
195
405
  const endpoint = device.getEndpoint(35);
196
406
 
197
407
  await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'ssIasZone', 'ssIasWd', 'genBasic', 'genBinaryInput']);
198
408
  await reporting.batteryVoltage(endpoint);
199
- await endpoint.read('genBasic', [0x8000, 0x8010, 0x8020], options);
409
+ await endpoint.read('genBasic', [0x8000, 0x8010, 0x8020], manufacturerOptions);
200
410
  await endpoint.read('ssIasZone', ['iasCieAddr', 'zoneState', 'zoneId']);
201
411
  await endpoint.read('genBinaryInput', ['reliability', 'statusFlags']);
202
412
  await endpoint.read('ssIasWd', ['maxDuration']);
@@ -221,16 +431,15 @@ module.exports = [
221
431
  vendor: 'Develco',
222
432
  description: 'Fire detector with siren',
223
433
  fromZigbee: [fz.temperature, fz.battery, fz.ias_smoke_alarm_1_develco, fz.ignore_basic_report,
224
- fz.develco_fw, fz.ias_enroll, fz.ias_wd, fz.develco_genbinaryinput],
434
+ develco.fz.firmware_version, fz.ias_enroll, fz.ias_wd, develco.fz.fault_status],
225
435
  toZigbee: [tz.warning, tz.ias_max_duration, tz.warning_simple],
226
436
  meta: {battery: {voltageToPercentage: '3V_2500'}},
227
437
  configure: async (device, coordinatorEndpoint, logger) => {
228
- const options = {manufacturerCode: 4117};
229
438
  const endpoint = device.getEndpoint(35);
230
439
 
231
440
  await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'ssIasZone', 'ssIasWd', 'genBasic', 'genBinaryInput']);
232
441
  await reporting.batteryVoltage(endpoint);
233
- await endpoint.read('genBasic', [0x8000], options);
442
+ await endpoint.read('genBasic', [0x8000], manufacturerOptions);
234
443
  await endpoint.read('ssIasZone', ['iasCieAddr', 'zoneState', 'zoneId']);
235
444
  await endpoint.read('genBinaryInput', ['reliability', 'statusFlags']);
236
445
  await endpoint.read('ssIasWd', ['maxDuration']);
@@ -249,15 +458,6 @@ module.exports = [
249
458
  .withDescription('Indicates reason if any fault'),
250
459
  exposes.binary('fault', ea.STATE, true, false).withDescription('Indicates whether the device are in fault state')],
251
460
  },
252
- {
253
- zigbeeModel: ['MOSZB-130'],
254
- model: 'MOSZB-130',
255
- vendor: 'Develco',
256
- description: 'Motion sensor',
257
- fromZigbee: [fz.ias_occupancy_alarm_1],
258
- toZigbee: [],
259
- exposes: [e.occupancy(), e.battery_low(), e.tamper()],
260
- },
261
461
  {
262
462
  zigbeeModel: ['WISZB-120'],
263
463
  model: 'WISZB-120',
@@ -285,22 +485,48 @@ module.exports = [
285
485
  toZigbee: [],
286
486
  exposes: [e.contact(), e.battery_low()],
287
487
  },
488
+ {
489
+ zigbeeModel: ['MOSZB-130'],
490
+ model: 'MOSZB-130',
491
+ vendor: 'Develco',
492
+ description: 'Motion sensor',
493
+ fromZigbee: [fz.ias_occupancy_alarm_1],
494
+ toZigbee: [],
495
+ exposes: [e.occupancy(), e.battery_low(), e.tamper()],
496
+ },
288
497
  {
289
498
  zigbeeModel: ['MOSZB-140'],
290
499
  model: 'MOSZB-140',
291
500
  vendor: 'Develco',
292
501
  description: 'Motion sensor',
293
- fromZigbee: [fz.temperature, fz.illuminance, fz.ias_occupancy_alarm_1, fz.battery],
294
- toZigbee: [],
295
- exposes: [e.occupancy(), e.battery(), e.battery_low(), e.tamper(), e.temperature(), e.illuminance_lux()],
502
+ fromZigbee: [
503
+ fz.temperature, fz.illuminance, fz.ias_occupancy_alarm_1, fz.battery,
504
+ develco.fz.led_control, develco.fz.ias_occupancy_timeout,
505
+ ],
506
+ toZigbee: [develco.tz.led_control, develco.tz.ias_occupancy_timeout],
507
+ exposes: [
508
+ e.occupancy(), e.battery(), e.battery_low(),
509
+ e.tamper(), e.temperature(), e.illuminance_lux(),
510
+ exposes.enum('led_control', ea.ALL, ['off', 'fault_only', 'motion_only', 'both']).
511
+ withDescription('Control LED indicator usage.'),
512
+ exposes.numeric('occupancy_timeout', ea.ALL).withUnit('second').withValueMin(20).withValueMax(65535),
513
+ ],
296
514
  meta: {battery: {voltageToPercentage: '3V_2500'}},
515
+ endpoint: (device) => {
516
+ return {default: 35};
517
+ },
297
518
  configure: async (device, coordinatorEndpoint, logger) => {
298
519
  const endpoint1 = device.getEndpoint(35);
299
520
  await reporting.bind(endpoint1, coordinatorEndpoint, ['genPowerCfg']);
300
521
  await reporting.batteryVoltage(endpoint1, {min: constants.repInterval.HOUR, max: 43200, change: 100});
522
+ await endpoint1.read('genPowerCfg', ['batteryVoltage']);
523
+ await endpoint1.read('genBasic', ['develcoLedControl'], manufacturerOptions);
524
+ await endpoint1.read('ssIasZone', ['develcoAlarmOffDelay'], manufacturerOptions);
525
+
301
526
  const endpoint2 = device.getEndpoint(38);
302
527
  await reporting.bind(endpoint2, coordinatorEndpoint, ['msTemperatureMeasurement']);
303
528
  await reporting.temperature(endpoint2);
529
+
304
530
  const endpoint3 = device.getEndpoint(39);
305
531
  await reporting.bind(endpoint3, coordinatorEndpoint, ['msIlluminanceMeasurement']);
306
532
  await reporting.illuminance(endpoint3);
@@ -337,8 +563,8 @@ module.exports = [
337
563
  model: 'ZHEMI101',
338
564
  vendor: 'Develco',
339
565
  description: 'Energy meter',
340
- fromZigbee: [fz.metering, fz.develco_metering],
341
- toZigbee: [tz.develco_pulse_configuration, tz.develco_interface_mode, tz.develco_current_summation],
566
+ fromZigbee: [fz.metering, develco.fz.pulse_configuration, develco.fz.interface_mode],
567
+ toZigbee: [develco.tz.pulse_configuration, develco.tz.interface_mode, develco.tz.current_summation],
342
568
  endpoint: (device) => {
343
569
  return {'default': 2};
344
570
  },
@@ -401,7 +627,7 @@ module.exports = [
401
627
  model: 'AQSZB-110',
402
628
  vendor: 'Develco',
403
629
  description: 'Air quality sensor',
404
- fromZigbee: [fz.develco_voc_battery, fz.develco_voc, fz.temperature, fz.humidity],
630
+ fromZigbee: [develco.fz.voc, develco.fz.voc_battery, fz.temperature, fz.humidity],
405
631
  toZigbee: [],
406
632
  exposes: [
407
633
  e.voc(), e.temperature(), e.humidity(),
@@ -414,11 +640,10 @@ module.exports = [
414
640
  meta: {battery: {voltageToPercentage: '3V_2500'}},
415
641
  configure: async (device, coordinatorEndpoint, logger) => {
416
642
  const endpoint = device.getEndpoint(38);
417
- const options = {manufacturerCode: 0x1015};
418
643
  await reporting.bind(endpoint, coordinatorEndpoint,
419
644
  ['develcoSpecificAirQuality', 'msTemperatureMeasurement', 'msRelativeHumidity', 'genPowerCfg']);
420
645
  await endpoint.configureReporting('develcoSpecificAirQuality', [{attribute: 'measuredValue', minimumReportInterval: 60,
421
- maximumReportInterval: 3600, reportableChange: 10}], options);
646
+ maximumReportInterval: 3600, reportableChange: 10}], manufacturerOptions);
422
647
  await reporting.temperature(endpoint, {min: constants.repInterval.MINUTE, max: constants.repInterval.MINUTES_10, change: 10});
423
648
  await reporting.humidity(endpoint, {min: constants.repInterval.MINUTE, max: constants.repInterval.MINUTES_10, change: 300});
424
649
  await reporting.batteryVoltage(endpoint, {min: constants.repInterval.HOUR, max: 43200, change: 100});
@@ -429,15 +654,14 @@ module.exports = [
429
654
  model: 'SIRZB-110',
430
655
  vendor: 'Develco Products A/S',
431
656
  description: 'Customizable siren',
432
- fromZigbee: [fz.temperature, fz.battery, fz.ias_enroll, fz.ias_wd, fz.develco_fw, fz.ias_siren],
657
+ fromZigbee: [fz.temperature, fz.battery, fz.ias_enroll, fz.ias_wd, develco.fz.firmware_version, fz.ias_siren],
433
658
  toZigbee: [tz.warning, tz.warning_simple, tz.ias_max_duration, tz.squawk],
434
659
  meta: {battery: {voltageToPercentage: '3V_2500'}},
435
660
  configure: async (device, coordinatorEndpoint, logger) => {
436
- const options = {manufacturerCode: 4117};
437
661
  const endpoint = device.getEndpoint(43);
438
662
  await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'ssIasZone', 'ssIasWd', 'genBasic']);
439
663
  await reporting.batteryVoltage(endpoint);
440
- await endpoint.read('genBasic', [0x8000], options);
664
+ await endpoint.read('genBasic', [0x8000], manufacturerOptions);
441
665
  await endpoint.read('ssIasZone', ['iasCieAddr', 'zoneState', 'zoneId']);
442
666
  await endpoint.read('ssIasWd', ['maxDuration']);
443
667
 
package/devices/hfh.js ADDED
@@ -0,0 +1,11 @@
1
+ const extend = require('../lib/extend');
2
+
3
+ module.exports = [
4
+ {
5
+ zigbeeModel: ['On-Air Combi CTW,303-0136'],
6
+ model: '303-0136',
7
+ vendor: 'HFH Solutions',
8
+ description: 'LED controller',
9
+ extend: extend.light_onoff_brightness_colortemp({colorTempRange: [155, 495]}),
10
+ },
11
+ ];
@@ -700,6 +700,13 @@ module.exports = [
700
700
  description: 'Hue Flourish white and color ambiance table light with Bluetooth',
701
701
  extend: hueExtend.light_onoff_brightness_colortemp_color({colorTempRange: [153, 500]}),
702
702
  },
703
+ {
704
+ zigbeeModel: ['929003052601'],
705
+ model: '929003052601',
706
+ vendor: 'Philips',
707
+ description: 'Hue Flourish white and color ambiance table light with Bluetooth',
708
+ extend: hueExtend.light_onoff_brightness_colortemp_color({colorTempRange: [153, 500]}),
709
+ },
703
710
  {
704
711
  zigbeeModel: ['LCG002'],
705
712
  model: '929001953101',
@@ -2803,7 +2810,7 @@ module.exports = [
2803
2810
  zigbeeModel: ['915005998101'],
2804
2811
  model: '915005998101',
2805
2812
  vendor: 'Philips',
2806
- description: 'Hue white ambiance ceiling black Enrave',
2813
+ description: 'Hue white ambiance pendant black Enrave',
2807
2814
  extend: hueExtend.light_onoff_brightness_colortemp({colorTempRange: [153, 454]}),
2808
2815
  },
2809
2816
  {
@@ -2813,4 +2820,11 @@ module.exports = [
2813
2820
  description: 'Hue Tuar outdoor wall light',
2814
2821
  extend: hueExtend.light_onoff_brightness(),
2815
2822
  },
2823
+ {
2824
+ zigbeeModel: ['915005998001'],
2825
+ model: '915005998001',
2826
+ vendor: 'Philips',
2827
+ description: 'Hue white ambiance pendant white Enrave',
2828
+ extend: hueExtend.light_onoff_brightness_colortemp({colorTempRange: [153, 454]}),
2829
+ },
2816
2830
  ];
package/devices/tuya.js CHANGED
@@ -897,6 +897,7 @@ module.exports = [
897
897
  {modelID: 'TS0505B', manufacturerName: '_TZ3210_mzdax7ha'},
898
898
  {modelID: 'TS0505B', manufacturerName: '_TZB210_tmi0rihb'},
899
899
  {modelID: 'TS0505B', manufacturerName: '_TZ3210_a4s41wm4'},
900
+ {modelID: 'TS0505B', manufacturerName: '_TZ3210_awrucboq'},
900
901
  {modelID: 'TS0505B', manufacturerName: '_TZ3210_ijczzg9h'},
901
902
  {modelID: 'TS0505B', manufacturerName: '_TZ3210_qxenlrin'},
902
903
  {modelID: 'TS0505B', manufacturerName: '_TZ3210_iwbaamgh'},
@@ -907,7 +908,8 @@ module.exports = [
907
908
  {modelID: 'TS0505B', manufacturerName: '_TZ3210_6amjviba'},
908
909
  {modelID: 'TS0505B', manufacturerName: '_TZ3000_xr5m6kfg'},
909
910
  {modelID: 'TS0505B', manufacturerName: '_TZ3210_xr5m6kfg'},
910
- {modelID: 'TS0505B', manufacturerName: '_TZ3210_bf175wi4'}],
911
+ {modelID: 'TS0505B', manufacturerName: '_TZ3210_bf175wi4'},
912
+ {modelID: 'TS0505B', manufacturerName: '_TZB210_3zfp8mki'}],
911
913
  model: 'TS0505B',
912
914
  vendor: 'TuYa',
913
915
  description: 'Zigbee RGB+CCT light',
@@ -1137,8 +1139,6 @@ module.exports = [
1137
1139
  {modelID: 'TS0601', manufacturerName: '_TZE200_ojzhk75b'},
1138
1140
  {modelID: 'TS0601', manufacturerName: '_TZE200_swaamsoy'},
1139
1141
  {modelID: 'TS0601', manufacturerName: '_TZE200_3p5ydos3'},
1140
- {modelID: 'TS0601', manufacturerName: '_TZE200_1agwnems'},
1141
- {modelID: 'TS0601', manufacturerName: '_TZE200_ip2akl4w'},
1142
1142
  ],
1143
1143
  model: 'TS0601_dimmer',
1144
1144
  vendor: 'TuYa',
@@ -1162,6 +1162,27 @@ module.exports = [
1162
1162
  {vendor: 'Moes', model: 'EDM-1ZBB-EU'},
1163
1163
  ],
1164
1164
  },
1165
+ {
1166
+ fingerprint: tuya.fingerprint('TS0601', ['_TZE200_ip2akl4w', '_TZE200_1agwnems']),
1167
+ model: 'TS0601_dimmer_1',
1168
+ vendor: 'TuYa',
1169
+ description: 'Zigbee smart dimmer',
1170
+ fromZigbee: [tuya.fzDataPoints],
1171
+ toZigbee: [tuya.tzDataPoints],
1172
+ configure: tuya.configureMagicPacket,
1173
+ exposes: [tuya.exposes.lightBrightnessWithMinMax, tuya.exposes.powerOnBehavior, tuya.exposes.countdown, tuya.exposes.lightType],
1174
+ meta: {
1175
+ tuyaDatapoints: [
1176
+ [1, 'state', tuya.valueConverter.onOff, {skip: tuya.skip.stateOnAndBrightnessPresent}],
1177
+ [2, 'brightness', tuya.valueConverter.scale0_254to0_1000],
1178
+ [3, 'min_brightness', tuya.valueConverter.scale0_254to0_1000],
1179
+ [4, 'light_type', tuya.valueConverter.lightType],
1180
+ [5, 'max_brightness', tuya.valueConverter.scale0_254to0_1000],
1181
+ [6, 'countdown', tuya.valueConverter.countdown],
1182
+ [14, 'power_on_behavior', tuya.valueConverter.powerOnBehavior],
1183
+ ],
1184
+ },
1185
+ },
1165
1186
  {
1166
1187
  fingerprint: [{modelID: 'TS011F', manufacturerName: '_TZ3000_oiymh3qu'}],
1167
1188
  model: 'TS011F_socket_module',
package/lib/ota/common.js CHANGED
@@ -182,12 +182,12 @@ function sendQueryNextImageResponse(endpoint, image, logger) {
182
182
  }
183
183
 
184
184
  function imageNotify(endpoint) {
185
- return endpoint.commandResponse('genOta', 'imageNotify', {payloadType: 0, queryJitter: 100});
185
+ return endpoint.commandResponse('genOta', 'imageNotify', {payloadType: 0, queryJitter: 100}, {sendWhen: 'immediate'});
186
186
  }
187
187
 
188
188
  async function requestOTA(endpoint) {
189
189
  // Some devices (e.g. Insta) take very long trying to discover the correct coordinator EP for OTA.
190
- const queryNextImageRequest = endpoint.waitForCommand('genOta', 'queryNextImageRequest', null, 30000);
190
+ const queryNextImageRequest = endpoint.waitForCommand('genOta', 'queryNextImageRequest', null, 60000);
191
191
  try {
192
192
  await imageNotify(endpoint);
193
193
  return await queryNextImageRequest.promise;
package/lib/tuya.js CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  const constants = require('./constants');
4
4
  const globalStore = require('./store');
5
+ const exposes = require('./exposes');
6
+ const utils = require('./utils');
7
+ const e = exposes.presets;
8
+ const ea = exposes.access;
5
9
 
6
10
  const dataTypes = {
7
11
  raw: 0, // [ bytes ]
@@ -1131,7 +1135,132 @@ async function sendDataPointStringBuffer(entity, dp, value, cmd, seq=undefined)
1131
1135
  );
1132
1136
  }
1133
1137
 
1138
+ const tuyaExposes = {
1139
+ powerOnBehavior: exposes.enum('power_on_behavior', ea.STATE_SET, ['off', 'on', 'previous'])
1140
+ .withDescription('Controls the behavior when the device is powered on'),
1141
+ lightType: exposes.enum('light_type', ea.STATE_SET, ['led', 'incandescent', 'halogen'])
1142
+ .withDescription('Type of light attached to the device'),
1143
+ lightBrightnessWithMinMax: e.light_brightness().withMinBrightness().withMaxBrightness().setAccess(
1144
+ 'state', ea.STATE_SET).setAccess('brightness', ea.STATE_SET).setAccess(
1145
+ 'min_brightness', ea.STATE_SET).setAccess('max_brightness', ea.STATE_SET),
1146
+ countdown: exposes.numeric('countdown', ea.STATE_SET).withValueMin(0).withValueMax(43200).withValueStep(1).withUnit('s')
1147
+ .withDescription('Countdown to turn device off after a certain time'),
1148
+ };
1149
+
1150
+ const skip = {
1151
+ // Prevent state from being published when already ON and brightness is also published.
1152
+ // This prevents 100% -> X% brightness jumps when the switch is already on
1153
+ // https://github.com/Koenkk/zigbee2mqtt/issues/13800#issuecomment-1263592783
1154
+ stateOnAndBrightnessPresent: (meta) => meta.message.hasOwnProperty('brightness') && meta.state.state === 'ON',
1155
+ };
1156
+
1157
+ const configureMagicPacket = async (device, coordinatorEndpoint, logger) => {
1158
+ const endpoint = device.endpoints[0];
1159
+ await endpoint.read('genBasic', ['manufacturerName', 'zclVersion', 'appVersion', 'modelId', 'powerSource', 0xfffe]);
1160
+ };
1161
+
1162
+ const fingerprint = (modelID, manufacturerNames) => {
1163
+ return manufacturerNames.map((manufacturerName) => {
1164
+ return {modelID, manufacturerName};
1165
+ });
1166
+ };
1167
+
1168
+ const valueConverterBasic = {
1169
+ lookup: (map) => {
1170
+ return {
1171
+ to: (v) => {
1172
+ if (map[v] === undefined) throw new Error(`Value '${v}' is not allowed, expected one of ${Object.keys(map)}`);
1173
+ return map[v];
1174
+ },
1175
+ from: (v) => {
1176
+ const value = Object.entries(map).find((i) => i[1] === v);
1177
+ if (!value) throw new Error(`Value '${v}' is not allowed, expected one of ${Object.values(map)}`);
1178
+ return value[0];
1179
+ },
1180
+ };
1181
+ },
1182
+ scale: (min1, max1, min2, max2) => {
1183
+ return {to: (v) => utils.mapNumberRange(v, min1, max1, min2, max2), from: (v) => utils.mapNumberRange(v, min2, max2, min1, max1)};
1184
+ },
1185
+ raw: () => {
1186
+ return {to: (v) => v, from: (v) => v};
1187
+ },
1188
+ divideBy: (value) => {
1189
+ return {to: (v) => v * value, from: (v) => v / value};
1190
+ },
1191
+ };
1192
+
1193
+ const valueConverter = {
1194
+ trueFalse: valueConverterBasic.lookup({1: true, 0: false}),
1195
+ onOff: valueConverterBasic.lookup({'ON': true, 'OFF': false}),
1196
+ powerOnBehavior: valueConverterBasic.lookup({'off': 0, 'on': 1, 'previous': 2}),
1197
+ lightType: valueConverterBasic.lookup({'led': 0, 'incandescent': 1, 'halogen': 2}),
1198
+ countdown: valueConverterBasic.raw(),
1199
+ scale0_254to0_1000: valueConverterBasic.scale(0, 254, 0, 1000),
1200
+ scale0_1to0_1000: valueConverterBasic.scale(0, 1, 0, 1000),
1201
+ divideBy100: valueConverterBasic.divideBy(100),
1202
+ };
1203
+
1204
+ const tzDataPoints = {
1205
+ key: ['state', 'brightness', 'min_brightness', 'max_brightness', 'power_on_behavior', 'countdown', 'light_type'],
1206
+ convertSet: async (entity, key, value, meta) => {
1207
+ // A set converter is only called once; therefore we need to loop
1208
+ const state = {};
1209
+ if (!meta.mapped.meta || !meta.mapped.meta.tuyaDatapoints) throw new Error('No datapoints map defined');
1210
+ const datapoints = meta.mapped.meta.tuyaDatapoints;
1211
+ for (const [key, value] of Object.entries(meta.message)) {
1212
+ const convertedKey = meta.mapped.meta.multiEndpoint ? `${key}_${meta.endpoint_name}` : key;
1213
+ const dpEntry = datapoints.find((d) => d[1] === convertedKey);
1214
+ if (!dpEntry || !dpEntry[1]) {
1215
+ throw new Error(`No datapoint defined for '${key}'`);
1216
+ }
1217
+ if (dpEntry[3] && dpEntry[3].skip && dpEntry[3].skip(meta)) continue;
1218
+ const dpId = dpEntry[0];
1219
+ const convertedValue = dpEntry[2].to(value);
1220
+ if (typeof convertedValue === 'boolean') {
1221
+ await sendDataPointBool(entity, dpId, convertedValue, 'dataRequest', 1);
1222
+ } else if (typeof convertedValue === 'number') {
1223
+ await sendDataPointValue(entity, dpId, convertedValue, 'dataRequest', 1);
1224
+ } else {
1225
+ throw new Error(`Don't know how to send type '${typeof convertedValue}'`);
1226
+ }
1227
+ state[convertedKey] = value;
1228
+ }
1229
+ return {state};
1230
+ },
1231
+ };
1232
+
1233
+ const fzDataPoints = {
1234
+ cluster: 'manuSpecificTuya',
1235
+ type: ['commandDataResponse', 'commandDataReport'],
1236
+ convert: (model, msg, publish, options, meta) => {
1237
+ const result = {};
1238
+ if (!model.meta || !model.meta.tuyaDatapoints) throw new Error('No datapoints map defined');
1239
+ const datapoints = model.meta.tuyaDatapoints;
1240
+ for (const dpValue of msg.data.dpValues) {
1241
+ const dpId = dpValue.dp;
1242
+ const dpEntry = datapoints.find((d) => d[0] === dpId);
1243
+ if (dpEntry) {
1244
+ const value = getDataValue(dpValue);
1245
+ result[dpEntry[1]] = dpEntry[2].from(value);
1246
+ } else {
1247
+ meta.logger.warn(`Datapoint ${dpId} not defined for '${meta.device.manufacturerName}' ` +
1248
+ `with data ${JSON.stringify(dpValue)}`);
1249
+ }
1250
+ }
1251
+ return result;
1252
+ },
1253
+ };
1254
+
1134
1255
  module.exports = {
1256
+ exposes: tuyaExposes,
1257
+ skip,
1258
+ configureMagicPacket,
1259
+ fingerprint,
1260
+ valueConverterBasic,
1261
+ valueConverter,
1262
+ tzDataPoints,
1263
+ fzDataPoints,
1135
1264
  sendDataPoint,
1136
1265
  sendDataPoints,
1137
1266
  sendDataPointValue,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "14.0.632",
3
+ "version": "14.0.634",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [