zigbee-herdsman-converters 15.0.9 → 15.0.10

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.
@@ -8320,6 +8320,17 @@ const converters = {
8320
8320
  return result;
8321
8321
  },
8322
8322
  },
8323
+ led_on_motion: {
8324
+ cluster: 'ssIasZone',
8325
+ type: ['attributeReport', 'readResponse'],
8326
+ convert: (model, msg, publish, options, meta) => {
8327
+ const result = {};
8328
+ if (0x4000 in msg.data) {
8329
+ result.led_on_motion = msg.data[0x4000] == 1 ? true : false;
8330
+ }
8331
+ return result;
8332
+ },
8333
+ },
8323
8334
  // #endregion
8324
8335
 
8325
8336
  // #region Ignore converters (these message dont need parsing).
@@ -2047,127 +2047,6 @@ const converters = {
2047
2047
  return await converters.light_color_colortemp.convertGet(entity, key, meta);
2048
2048
  },
2049
2049
  },
2050
- hue_power_on_behavior: {
2051
- key: ['hue_power_on_behavior'],
2052
- convertSet: async (entity, key, value, meta) => {
2053
- if (value === 'default') {
2054
- value = 'on';
2055
- }
2056
-
2057
- let supports = {colorTemperature: false, colorXY: false};
2058
- if (entity.constructor.name === 'Endpoint' && entity.supportsInputCluster('lightingColorCtrl')) {
2059
- const readResult = await entity.read('lightingColorCtrl', ['colorCapabilities']);
2060
- supports = {
2061
- colorTemperature: (readResult.colorCapabilities & 1 << 4) > 0,
2062
- colorXY: (readResult.colorCapabilities & 1 << 3) > 0,
2063
- };
2064
- } else if (entity.constructor.name === 'Group') {
2065
- supports = {colorTemperature: true, colorXY: true};
2066
- }
2067
-
2068
- if (value === 'off') {
2069
- await entity.write('genOnOff', {0x4003: {value: 0x00, type: 0x30}});
2070
- } else if (value === 'recover') {
2071
- await entity.write('genOnOff', {0x4003: {value: 0xff, type: 0x30}});
2072
- await entity.write('genLevelCtrl', {0x4000: {value: 0xff, type: 0x20}});
2073
-
2074
- if (supports.colorTemperature) {
2075
- await entity.write('lightingColorCtrl', {0x4010: {value: 0xffff, type: 0x21}});
2076
- }
2077
-
2078
- if (supports.colorXY) {
2079
- await entity.write('lightingColorCtrl', {0x0003: {value: 0xffff, type: 0x21}}, manufacturerOptions.hue);
2080
- await entity.write('lightingColorCtrl', {0x0004: {value: 0xffff, type: 0x21}}, manufacturerOptions.hue);
2081
- }
2082
- } else if (value === 'on') {
2083
- await entity.write('genOnOff', {0x4003: {value: 0x01, type: 0x30}});
2084
-
2085
- let brightness = meta.message.hasOwnProperty('hue_power_on_brightness') ?
2086
- meta.message.hue_power_on_brightness : 0xfe;
2087
- if (brightness === 255) {
2088
- // 255 (0xFF) is the value for recover, therefore set it to 254 (0xFE)
2089
- brightness = 254;
2090
- }
2091
- await entity.write('genLevelCtrl', {0x4000: {value: brightness, type: 0x20}});
2092
-
2093
- if (entity.supportsInputCluster('lightingColorCtrl')) {
2094
- if (
2095
- meta.message.hasOwnProperty('hue_power_on_color_temperature') &&
2096
- meta.message.hasOwnProperty('hue_power_on_color')
2097
- ) {
2098
- meta.logger.error(`Provide either color temperature or color, not both`);
2099
- } else if (meta.message.hasOwnProperty('hue_power_on_color_temperature')) {
2100
- const colortemp = meta.message.hue_power_on_color_temperature;
2101
- await entity.write('lightingColorCtrl', {0x4010: {value: colortemp, type: 0x21}});
2102
- // Set color to default
2103
- if (supports.colorXY) {
2104
- await entity.write('lightingColorCtrl', {0x0003: {value: 0xFFFF, type: 0x21}}, manufacturerOptions.hue);
2105
- await entity.write('lightingColorCtrl', {0x0004: {value: 0xFFFF, type: 0x21}}, manufacturerOptions.hue);
2106
- }
2107
- } else if (meta.message.hasOwnProperty('hue_power_on_color')) {
2108
- const colorXY = libColor.ColorRGB.fromHex(meta.message.hue_power_on_color).toXY();
2109
- value = {x: utils.mapNumberRange(colorXY.x, 0, 1, 0, 65535), y: utils.mapNumberRange(colorXY.y, 0, 1, 0, 65535)};
2110
-
2111
- // Set colortemp to default
2112
- if (supports.colorTemperature) {
2113
- await entity.write('lightingColorCtrl', {0x4010: {value: 366, type: 0x21}});
2114
- }
2115
-
2116
- await entity.write('lightingColorCtrl', {0x0003: {value: value.x, type: 0x21}}, manufacturerOptions.hue);
2117
- await entity.write('lightingColorCtrl', {0x0004: {value: value.y, type: 0x21}}, manufacturerOptions.hue);
2118
- } else {
2119
- // Set defaults for colortemp and color
2120
- if (supports.colorTemperature) {
2121
- await entity.write('lightingColorCtrl', {0x4010: {value: 366, type: 0x21}});
2122
- }
2123
-
2124
- if (supports.colorXY) {
2125
- await entity.write('lightingColorCtrl', {0x0003: {value: 0xFFFF, type: 0x21}}, manufacturerOptions.hue);
2126
- await entity.write('lightingColorCtrl', {0x0004: {value: 0xFFFF, type: 0x21}}, manufacturerOptions.hue);
2127
- }
2128
- }
2129
- }
2130
- }
2131
-
2132
- return {state: {hue_power_on_behavior: value}};
2133
- },
2134
- },
2135
- hue_power_on_error: {
2136
- key: ['hue_power_on_brightness', 'hue_power_on_color_temperature', 'hue_power_on_color'],
2137
- convertSet: async (entity, key, value, meta) => {
2138
- if (!meta.message.hasOwnProperty('hue_power_on_behavior')) {
2139
- throw new Error(`Provide a value for 'hue_power_on_behavior'`);
2140
- }
2141
- },
2142
- },
2143
- hue_motion_sensitivity: {
2144
- // motion detect sensitivity, philips specific
2145
- key: ['motion_sensitivity'],
2146
- convertSet: async (entity, key, value, meta) => {
2147
- // make sure you write to second endpoint!
2148
- const lookup = {'low': 0, 'medium': 1, 'high': 2, 'very_high': 3, 'max': 4};
2149
- value = value.toLowerCase();
2150
- utils.validateValue(value, Object.keys(lookup));
2151
-
2152
- const payload = {48: {value: lookup[value], type: 32}};
2153
- await entity.write('msOccupancySensing', payload, manufacturerOptions.hue);
2154
- return {state: {motion_sensitivity: value}};
2155
- },
2156
- convertGet: async (entity, key, meta) => {
2157
- await entity.read('msOccupancySensing', [48], manufacturerOptions.hue);
2158
- },
2159
- },
2160
- hue_motion_led_indication: {
2161
- key: ['led_indication'],
2162
- convertSet: async (entity, key, value, meta) => {
2163
- const payload = {0x0033: {value, type: 0x10}};
2164
- await entity.write('genBasic', payload, manufacturerOptions.hue);
2165
- return {state: {led_indication: value}};
2166
- },
2167
- convertGet: async (entity, key, meta) => {
2168
- await entity.read('genBasic', [0x0033], manufacturerOptions.hue);
2169
- },
2170
- },
2171
2050
  aqara_motion_sensitivity: {
2172
2051
  key: ['motion_sensitivity'],
2173
2052
  convertSet: async (entity, key, value, meta) => {
@@ -7166,6 +7045,17 @@ const converters = {
7166
7045
  }
7167
7046
  },
7168
7047
  },
7048
+ led_on_motion: {
7049
+ key: ['led_on_motion'],
7050
+ convertSet: async (entity, key, value, meta) => {
7051
+ await entity.write('ssIasZone', {0x4000: {value: value === true ? 1 : 0, type: 0x10}},
7052
+ {manufacturerCode: 4919});
7053
+ return {state: {led_on_motion: value}};
7054
+ },
7055
+ convertGet: async (entity, key, meta) => {
7056
+ await entity.read('ssIasZone', [0x4000], {manufacturerCode: 4919});
7057
+ },
7058
+ },
7169
7059
  // #endregion
7170
7060
 
7171
7061
  // #region Ignore converters
package/devices/datek.js CHANGED
@@ -88,10 +88,29 @@ module.exports = [
88
88
  fingerprint: [{modelID: 'Motion Sensor', manufacturerName: 'Eva'}],
89
89
  model: 'HSE2927E',
90
90
  vendor: 'Datek',
91
- description: 'Eva Zigbee motion sensor',
92
- fromZigbee: [fz.ias_occupancy_alarm_1, fz.ias_occupancy_alarm_1_with_timeout, fz.illuminance, fz.temperature],
93
- toZigbee: [],
94
- exposes: [e.occupancy(), e.battery_low(), e.tamper(), e.illuminance(), e.illuminance_lux(), e.temperature()],
91
+ description: 'Eva motion sensor',
92
+ fromZigbee: [fz.battery, fz.occupancy, fz.occupancy_timeout, fz.illuminance, fz.temperature,
93
+ fz.ias_enroll, fz.ias_occupancy_alarm_1, fz.ias_occupancy_alarm_1_report, fz.led_on_motion],
94
+ toZigbee: [tz.occupancy_timeout, tz.led_on_motion],
95
+ configure: async (device, coordinatorEndpoint, logger) => {
96
+ const options = {manufacturerCode: 4919};
97
+ const endpoint = device.getEndpoint(1);
98
+ const binds = ['msIlluminanceMeasurement', 'msTemperatureMeasurement', 'msOccupancySensing', 'ssIasZone'];
99
+ await reporting.bind(endpoint, coordinatorEndpoint, binds);
100
+ await reporting.occupancy(endpoint);
101
+ await reporting.temperature(endpoint);
102
+ await reporting.illuminance(endpoint);
103
+ const payload = [{
104
+ attribute: {ID: 0x4000, type: 0x10},
105
+ }];
106
+ await endpoint.configureReporting('ssIasZone', payload, options);
107
+ await endpoint.read('ssIasZone', ['iasCieAddr', 'zoneState', 'zoneId']);
108
+ await endpoint.read('msOccupancySensing', ['pirOToUDelay']);
109
+ await endpoint.read('ssIasZone', [0x4000], options);
110
+ },
111
+ exposes: [e.temperature(), e.occupancy(), e.battery_low(), e.illuminance_lux(), e.illuminance(),
112
+ exposes.binary('led_on_motion', ea.ALL, true, false).withDescription('Enable/disable LED on motion'),
113
+ exposes.numeric('occupancy_timeout', ea.ALL).withUnit('seconds').withValueMin(0).withValueMax(65535)],
95
114
  },
96
115
  {
97
116
  zigbeeModel: ['ID Lock 150'],
@@ -208,4 +227,19 @@ module.exports = [
208
227
  e.action(['recall_1', 'recall_2', 'recall_3', 'recall_4', 'on', 'off',
209
228
  'brightness_move_down', 'brightness_move_up', 'brightness_stop'])],
210
229
  },
230
+ {
231
+ zigbeeModel: ['Door/Window Sensor'],
232
+ model: 'HSE2920E',
233
+ vendor: 'Datek',
234
+ description: 'Door/window sensor',
235
+ fromZigbee: [fz.ias_contact_alarm_1, fz.ias_contact_alarm_1_report, fz.temperature, fz.ias_enroll],
236
+ toZigbee: [],
237
+ configure: async (device, coordinatorEndpoint, logger) => {
238
+ const endpoint = device.getEndpoint(1);
239
+ await reporting.bind(endpoint, coordinatorEndpoint, ['ssIasZone', 'msTemperatureMeasurement']);
240
+ await reporting.temperature(endpoint);
241
+ await endpoint.read('ssIasZone', ['iasCieAddr', 'zoneState', 'zoneId']);
242
+ },
243
+ exposes: [e.contact(), e.battery_low(), e.tamper(), e.temperature()],
244
+ },
211
245
  ];
@@ -54,7 +54,7 @@ module.exports = [
54
54
  zigbeeModel: [' Contactor\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'+
55
55
  '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'],
56
56
  model: '412171',
57
- description: 'DIN contactor module ( Bticino FC80CC )',
57
+ description: 'DIN contactor module (BTicino FC80CC )',
58
58
  vendor: 'Legrand',
59
59
  extend: extend.switch(),
60
60
  fromZigbee: [fz.identify, fz.on_off, fz.electrical_measurement, fz.legrand_cluster_fc01, fz.ignore_basic_report, fz.ignore_genOta],
@@ -78,7 +78,7 @@ module.exports = [
78
78
  zigbeeModel: [' Teleruptor\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'+
79
79
  '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'],
80
80
  model: '412170',
81
- description: 'DIN smart relay for light control ( Bticino FC80RC ) ',
81
+ description: 'DIN smart relay for light control (BTicino FC80RC ) ',
82
82
  vendor: 'Legrand',
83
83
  extend: extend.switch(),
84
84
  fromZigbee: [fz.identify, fz.on_off, fz.electrical_measurement, fz.legrand_cluster_fc01, fz.ignore_basic_report, fz.ignore_genOta],
package/devices/moes.js CHANGED
@@ -310,7 +310,8 @@ module.exports = [
310
310
  exposes.numeric('boost_heating_countdown', ea.STATE).withUnit('Min').withDescription('Countdown in minutes')
311
311
  .withValueMin(0).withValueMax(15),
312
312
  exposes.numeric('boost_heating_countdown_time_set', ea.STATE_SET).withUnit('second')
313
- .withDescription('Boost Time Setting 100 sec - 900 sec, (default = 300 sec)').withValueMin(100).withValueMax(900)],
313
+ .withDescription('Boost Time Setting 100 sec - 900 sec, (default = 300 sec)').withValueMin(100)
314
+ .withValueMax(900).withValueStep(100)],
314
315
  },
315
316
  {
316
317
  fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_e3oitdyu'}],
package/devices/netvox.js CHANGED
@@ -23,4 +23,13 @@ module.exports = [
23
23
  },
24
24
  exposes: [e.switch(), e.power(), e.current(), e.voltage()],
25
25
  },
26
+ {
27
+ zigbeeModel: ['ZB02AE0ED'],
28
+ model: 'ZB02A',
29
+ vendor: 'Netvox',
30
+ description: 'Wireless wall switch',
31
+ fromZigbee: [fz.command_toggle],
32
+ toZigbee: [],
33
+ exposes: [e.action(['toggle'])],
34
+ },
26
35
  ];
@@ -6,6 +6,8 @@ const reporting = require('../lib/reporting');
6
6
  const globalStore = require('../lib/store');
7
7
  const philips = require('../lib/philips');
8
8
  const utils = require('../lib/utils');
9
+ const libColor = require('../lib/color');
10
+ const herdsman = require('zigbee-herdsman');
9
11
  const e = exposes.presets;
10
12
  const ea = exposes.access;
11
13
 
@@ -13,115 +15,96 @@ const ea = exposes.access;
13
15
  const extendDontUse = require('../lib/extend');
14
16
  const extend = {switch: extendDontUse.switch};
15
17
 
18
+ const manufacturerOptions = {manufacturerCode: herdsman.Zcl.ManufacturerCode.PHILIPS};
19
+
16
20
  const hueExtend = {
17
21
  light_onoff_brightness: (options={}) => ({
18
22
  ...extendDontUse.light_onoff_brightness(options),
19
23
  ota: ota.zigbeeOTA,
20
24
  meta: {turnsOffAtBrightness1: true},
21
- toZigbee: extendDontUse.light_onoff_brightness(options).toZigbee.concat([tz.hue_power_on_behavior, tz.hue_power_on_error]),
22
- }),
23
- light_onoff_brightness_colortemp: (options={}) => ({
24
- ...extendDontUse.light_onoff_brightness_colortemp(options),
25
- ota: ota.zigbeeOTA,
26
- meta: {turnsOffAtBrightness1: true},
27
- toZigbee: extendDontUse.light_onoff_brightness_colortemp(options).toZigbee
28
- .concat([tz.hue_power_on_behavior, tz.hue_power_on_error]),
29
- }),
30
- light_onoff_brightness_color: (options={}) => ({
31
- ...extendDontUse.light_onoff_brightness_color({supportsHS: true, ...options}),
32
- ota: ota.zigbeeOTA,
33
- meta: {turnsOffAtBrightness1: true},
34
- toZigbee: extendDontUse.light_onoff_brightness_color({supportsHS: true, ...options}).toZigbee
35
- .concat([tz.hue_power_on_behavior, tz.hue_power_on_error]),
36
- }),
37
- light_onoff_brightness_colortemp_color: (options={}) => ({
38
- ...extendDontUse.light_onoff_brightness_colortemp_color({supportsHS: true, ...options}),
39
- ota: ota.zigbeeOTA,
40
- meta: {turnsOffAtBrightness1: true},
41
- toZigbee: extendDontUse.light_onoff_brightness_colortemp_color({supportsHS: true, ...options})
42
- .toZigbee.concat([tz.hue_power_on_behavior, tz.hue_power_on_error]),
25
+ toZigbee: extendDontUse.light_onoff_brightness(options).toZigbee.concat([
26
+ tzLocal.hue_power_on_behavior, tzLocal.hue_power_on_error,
27
+ ]),
43
28
  }),
44
- light_onoff_brightness_colortemp_color_gradient: (options={}) => ({
45
- ...extendDontUse.light_onoff_brightness_colortemp_color({supportsHS: true, noConfigure: true, ...options}),
46
- ota: ota.zigbeeOTA,
47
- meta: {turnsOffAtBrightness1: true},
48
- configure: async (device, coordinatorEndpoint, logger) => {
49
- await extendDontUse.light_onoff_brightness_colortemp_color({supportsHS: true, ...options})
29
+ light_onoff_brightness_colortemp: (options={}) => {
30
+ options = {disableHueEffects: true, ...options};
31
+ if (!options.disableHueEffects) options.disableEffect = true;
32
+ const result = extendDontUse.light_onoff_brightness_colortemp(options);
33
+ result['ota'] = ota.zigbeeOTA;
34
+ result['meta'] = {turnsOffAtBrightness1: true};
35
+ result['toZigbee'] = result['toZigbee'].concat([tzLocal.hue_power_on_behavior, tzLocal.hue_power_on_error]);
36
+ if (!options.disableHueEffects) {
37
+ result['toZigbee'] = result['toZigbee'].concat([tzLocal.effect]);
38
+ result['exposes'] = result['exposes'].concat([exposes.enum('effect', ea.SET,
39
+ ['blink', 'breathe', 'okay', 'channel_change', 'candle', 'finish_effect', 'stop_effect', 'stop_hue_effect'])]);
40
+ }
41
+ return result;
42
+ },
43
+ light_onoff_brightness_color: (options={}) => {
44
+ options = {disableHueEffects: true, ...options};
45
+ if (!options.disableHueEffects) options.disableEffect = true;
46
+ const result = extendDontUse.light_onoff_brightness_color({supportsHS: true, ...options});
47
+ result['ota'] = ota.zigbeeOTA;
48
+ result['meta'] = {turnsOffAtBrightness1: true};
49
+ result['toZigbee'] = result['toZigbee'].concat([tzLocal.hue_power_on_behavior, tzLocal.hue_power_on_error]);
50
+ if (!options.disableHueEffects) {
51
+ result['toZigbee'] = result['toZigbee'].concat([tzLocal.effect]);
52
+ result['exposes'] = result['exposes'].concat([exposes.enum('effect', ea.SET, [
53
+ 'blink', 'breathe', 'okay', 'channel_change',
54
+ 'candle', 'fireplace', 'colorloop',
55
+ 'finish_effect', 'stop_effect', 'stop_hue_effect',
56
+ ])]);
57
+ }
58
+ return result;
59
+ },
60
+ light_onoff_brightness_colortemp_color: (options={}) => {
61
+ options = {disableHueEffects: true, ...options};
62
+ if (!options.disableHueEffects) options.disableEffect = true;
63
+ const result = extendDontUse.light_onoff_brightness_colortemp_color({supportsHS: true, ...options});
64
+ result['ota'] = ota.zigbeeOTA;
65
+ result['meta'] = {turnsOffAtBrightness1: true};
66
+ result['toZigbee'] = result['toZigbee'].concat([tzLocal.hue_power_on_behavior, tzLocal.hue_power_on_error]);
67
+ if (!options.disableHueEffects) {
68
+ result['toZigbee'] = result['toZigbee'].concat([tzLocal.effect]);
69
+ result['exposes'] = result['exposes'].concat([exposes.enum('effect', ea.SET, [
70
+ 'blink', 'breathe', 'okay', 'channel_change',
71
+ 'candle', 'fireplace', 'colorloop',
72
+ 'finish_effect', 'stop_effect', 'stop_hue_effect',
73
+ ])]);
74
+ }
75
+ return result;
76
+ },
77
+ light_onoff_brightness_colortemp_color_gradient: (options={}) => {
78
+ options = {supportsHS: true, disableEffect: true, noConfigure: true, ...options};
79
+ const result = extendDontUse.light_onoff_brightness_colortemp_color({supportsHS: true, ...options});
80
+ result['ota'] = ota.zigbeeOTA;
81
+ result['meta'] = {turnsOffAtBrightness1: true};
82
+ result['toZigbee'] = result['toZigbee'].concat([
83
+ tzLocal.hue_power_on_behavior, tzLocal.hue_power_on_error, tzLocal.effect,
84
+ tzLocal.gradient_scene, tzLocal.gradient({reverse: true}),
85
+ ]);
86
+ result['fromZigbee'] = result['fromZigbee'].concat([fzLocal.gradient({reverse: true})]);
87
+ result['configure'] = async (device, coordinatorEndpoint, logger) => {
88
+ await extendDontUse.light_onoff_brightness_colortemp_color(options)
50
89
  .configure(device, coordinatorEndpoint, logger);
51
90
  for (const ep of device.endpoints) {
52
91
  await ep.bind('manuSpecificPhilips2', coordinatorEndpoint);
53
92
  }
54
- },
55
- exposes: extendDontUse.light_onoff_brightness_colortemp_color({supportsHS: true, ...options}).exposes.concat([
93
+ };
94
+ result['exposes'] = result['exposes'].concat([
56
95
  // gradient_scene is deprecated, use gradient instead
57
96
  exposes.enum('gradient_scene', ea.SET, Object.keys(gradientScenes)),
58
97
  exposes.list('gradient', ea.ALL, exposes.text('hex', 'Color in RGB HEX format (eg #663399)'))
59
98
  .withLengthMin(1)
60
99
  .withLengthMax(9)
61
100
  .withDescription('List of RGB HEX colors'),
62
- ]),
63
- fromZigbee: extendDontUse.light_onoff_brightness_colortemp_color({supportsHS: true, ...options}).fromZigbee.concat(
64
- [fzLocal.gradient({reverse: true})]),
65
- toZigbee: extendDontUse.light_onoff_brightness_colortemp_color({supportsHS: true, ...options}).toZigbee.concat(
66
- [tz.hue_power_on_behavior, tz.hue_power_on_error, tzLocal.gradient_scene, tzLocal.gradient({reverse: true})]),
67
- }),
68
- };
69
-
70
- const fzLocal = {
71
- hue_tap_dial: {
72
- cluster: 'manuSpecificPhilips',
73
- type: 'commandHueNotification',
74
- options: [exposes.options.simulated_brightness()],
75
- convert: (model, msg, publish, options, meta) => {
76
- const buttonLookup = {1: 'button_1', 2: 'button_2', 3: 'button_3', 4: 'button_4', 20: 'dial'};
77
- const button = buttonLookup[msg.data['button']];
78
- const typeLookup = {0: 'press', 1: 'hold', 2: 'press_release', 3: 'hold_release'};
79
- const type = typeLookup[msg.data['type']];
80
- const direction = msg.data['unknown2'] <127 ? 'right' : 'left';
81
- const time = msg.data['time'];
82
- const payload = {};
83
-
84
- if (button === 'dial') {
85
- const adjustedTime = direction === 'right' ? time : 256 - time;
86
- const dialType = 'rotate';
87
- const speed = adjustedTime <= 25 ? 'step' : adjustedTime <= 75 ? 'slow' : 'fast';
88
- payload.action = `${button}_${dialType}_${direction}_${speed}`;
89
-
90
- // simulated brightness
91
- if (options.simulated_brightness) {
92
- const opts = options.simulated_brightness;
93
- const deltaOpts = typeof opts === 'object' && opts.hasOwnProperty('delta') ? opts.delta : 35;
94
- const delta = direction === 'right' ? deltaOpts : deltaOpts * -1;
95
- const brightness = globalStore.getValue(msg.endpoint, 'brightness', 255) + delta;
96
- payload.brightness = utils.numberWithinRange(brightness, 0, 255);
97
- globalStore.putValue(msg.endpoint, 'brightness', payload.brightness);
98
- }
99
- } else {
100
- payload.action = `${button}_${type}`;
101
- // duration
102
- if (type === 'press') globalStore.putValue(msg.endpoint, 'press_start', Date.now());
103
- else if (type === 'hold' || type === 'hold_release') {
104
- payload.action_duration = (Date.now() - globalStore.getValue(msg.endpoint, 'press_start')) / 1000;
105
- }
106
- }
107
- return payload;
108
- },
109
- },
110
- gradient: (opts = {reverse: false}) => {
111
- return {
112
- cluster: 'manuSpecificPhilips2',
113
- type: ['attributeReport', 'readResponse'],
114
- convert: (model, msg, publish, options, meta) => {
115
- if (msg.data && msg.data.hasOwnProperty('state')) {
116
- const input = msg.data['state'].toString('hex');
117
- const decoded = philips.decodeGradientColors(input, opts);
118
- if (decoded.color_mode === 'gradient') {
119
- return {gradient: decoded.colors};
120
- }
121
- }
122
- return {};
123
- },
124
- };
101
+ exposes.enum('effect', ea.SET, [
102
+ 'blink', 'breathe', 'okay', 'channel_change',
103
+ 'candle', 'fireplace', 'colorloop',
104
+ 'finish_effect', 'stop_effect', 'stop_hue_effect',
105
+ ]),
106
+ ]);
107
+ return result;
125
108
  },
126
109
  };
127
110
 
@@ -203,6 +186,71 @@ const gradientScenes = {
203
186
  'crystalline': '5001040013500000006ea96a92a85e58074e18543d9cf3332800',
204
187
  };
205
188
 
189
+ const hueEffects = {
190
+ 'candle': '21000101',
191
+ 'fireplace': '21000102',
192
+ 'colorloop': '21000103',
193
+ 'stop_hue_effect': '200000',
194
+ };
195
+
196
+ const fzLocal = {
197
+ hue_tap_dial: {
198
+ cluster: 'manuSpecificPhilips',
199
+ type: 'commandHueNotification',
200
+ options: [exposes.options.simulated_brightness()],
201
+ convert: (model, msg, publish, options, meta) => {
202
+ const buttonLookup = {1: 'button_1', 2: 'button_2', 3: 'button_3', 4: 'button_4', 20: 'dial'};
203
+ const button = buttonLookup[msg.data['button']];
204
+ const typeLookup = {0: 'press', 1: 'hold', 2: 'press_release', 3: 'hold_release'};
205
+ const type = typeLookup[msg.data['type']];
206
+ const direction = msg.data['unknown2'] <127 ? 'right' : 'left';
207
+ const time = msg.data['time'];
208
+ const payload = {};
209
+
210
+ if (button === 'dial') {
211
+ const adjustedTime = direction === 'right' ? time : 256 - time;
212
+ const dialType = 'rotate';
213
+ const speed = adjustedTime <= 25 ? 'step' : adjustedTime <= 75 ? 'slow' : 'fast';
214
+ payload.action = `${button}_${dialType}_${direction}_${speed}`;
215
+
216
+ // simulated brightness
217
+ if (options.simulated_brightness) {
218
+ const opts = options.simulated_brightness;
219
+ const deltaOpts = typeof opts === 'object' && opts.hasOwnProperty('delta') ? opts.delta : 35;
220
+ const delta = direction === 'right' ? deltaOpts : deltaOpts * -1;
221
+ const brightness = globalStore.getValue(msg.endpoint, 'brightness', 255) + delta;
222
+ payload.brightness = utils.numberWithinRange(brightness, 0, 255);
223
+ globalStore.putValue(msg.endpoint, 'brightness', payload.brightness);
224
+ }
225
+ } else {
226
+ payload.action = `${button}_${type}`;
227
+ // duration
228
+ if (type === 'press') globalStore.putValue(msg.endpoint, 'press_start', Date.now());
229
+ else if (type === 'hold' || type === 'hold_release') {
230
+ payload.action_duration = (Date.now() - globalStore.getValue(msg.endpoint, 'press_start')) / 1000;
231
+ }
232
+ }
233
+ return payload;
234
+ },
235
+ },
236
+ gradient: (opts = {reverse: false}) => {
237
+ return {
238
+ cluster: 'manuSpecificPhilips2',
239
+ type: ['attributeReport', 'readResponse'],
240
+ convert: (model, msg, publish, options, meta) => {
241
+ if (msg.data && msg.data.hasOwnProperty('state')) {
242
+ const input = msg.data['state'].toString('hex');
243
+ const decoded = philips.decodeGradientColors(input, opts);
244
+ if (decoded.color_mode === 'gradient') {
245
+ return {gradient: decoded.colors};
246
+ }
247
+ }
248
+ return {};
249
+ },
250
+ };
251
+ },
252
+ };
253
+
206
254
  const tzLocal = {
207
255
  gradient_scene: {
208
256
  key: ['gradient_scene'],
@@ -226,6 +274,137 @@ const tzLocal = {
226
274
  },
227
275
  };
228
276
  },
277
+ effect: {
278
+ key: ['effect'],
279
+ convertSet: async (entity, key, value, meta) => {
280
+ if (Object.keys(hueEffects).includes(value.toLowerCase())) {
281
+ await entity.command('manuSpecificPhilips2', 'multiColor', {data: Buffer.from(hueEffects[value.toLowerCase()], 'hex')});
282
+ } else {
283
+ return await tz.effect.convertSet(entity, key, value, meta);
284
+ }
285
+ },
286
+ },
287
+ hue_power_on_behavior: {
288
+ key: ['hue_power_on_behavior'],
289
+ convertSet: async (entity, key, value, meta) => {
290
+ if (value === 'default') {
291
+ value = 'on';
292
+ }
293
+
294
+ let supports = {colorTemperature: false, colorXY: false};
295
+ if (entity.constructor.name === 'Endpoint' && entity.supportsInputCluster('lightingColorCtrl')) {
296
+ const readResult = await entity.read('lightingColorCtrl', ['colorCapabilities']);
297
+ supports = {
298
+ colorTemperature: (readResult.colorCapabilities & 1 << 4) > 0,
299
+ colorXY: (readResult.colorCapabilities & 1 << 3) > 0,
300
+ };
301
+ } else if (entity.constructor.name === 'Group') {
302
+ supports = {colorTemperature: true, colorXY: true};
303
+ }
304
+
305
+ if (value === 'off') {
306
+ await entity.write('genOnOff', {0x4003: {value: 0x00, type: 0x30}});
307
+ } else if (value === 'recover') {
308
+ await entity.write('genOnOff', {0x4003: {value: 0xff, type: 0x30}});
309
+ await entity.write('genLevelCtrl', {0x4000: {value: 0xff, type: 0x20}});
310
+
311
+ if (supports.colorTemperature) {
312
+ await entity.write('lightingColorCtrl', {0x4010: {value: 0xffff, type: 0x21}});
313
+ }
314
+
315
+ if (supports.colorXY) {
316
+ await entity.write('lightingColorCtrl', {0x0003: {value: 0xffff, type: 0x21}}, manufacturerOptions);
317
+ await entity.write('lightingColorCtrl', {0x0004: {value: 0xffff, type: 0x21}}, manufacturerOptions);
318
+ }
319
+ } else if (value === 'on') {
320
+ await entity.write('genOnOff', {0x4003: {value: 0x01, type: 0x30}});
321
+
322
+ let brightness = meta.message.hasOwnProperty('hue_power_on_brightness') ?
323
+ meta.message.hue_power_on_brightness : 0xfe;
324
+ if (brightness === 255) {
325
+ // 255 (0xFF) is the value for recover, therefore set it to 254 (0xFE)
326
+ brightness = 254;
327
+ }
328
+ await entity.write('genLevelCtrl', {0x4000: {value: brightness, type: 0x20}});
329
+
330
+ if (entity.supportsInputCluster('lightingColorCtrl')) {
331
+ if (
332
+ meta.message.hasOwnProperty('hue_power_on_color_temperature') &&
333
+ meta.message.hasOwnProperty('hue_power_on_color')
334
+ ) {
335
+ meta.logger.error(`Provide either color temperature or color, not both`);
336
+ } else if (meta.message.hasOwnProperty('hue_power_on_color_temperature')) {
337
+ const colortemp = meta.message.hue_power_on_color_temperature;
338
+ await entity.write('lightingColorCtrl', {0x4010: {value: colortemp, type: 0x21}});
339
+ // Set color to default
340
+ if (supports.colorXY) {
341
+ await entity.write('lightingColorCtrl', {0x0003: {value: 0xFFFF, type: 0x21}}, manufacturerOptions);
342
+ await entity.write('lightingColorCtrl', {0x0004: {value: 0xFFFF, type: 0x21}}, manufacturerOptions);
343
+ }
344
+ } else if (meta.message.hasOwnProperty('hue_power_on_color')) {
345
+ const colorXY = libColor.ColorRGB.fromHex(meta.message.hue_power_on_color).toXY();
346
+ value = {x: utils.mapNumberRange(colorXY.x, 0, 1, 0, 65535), y: utils.mapNumberRange(colorXY.y, 0, 1, 0, 65535)};
347
+
348
+ // Set colortemp to default
349
+ if (supports.colorTemperature) {
350
+ await entity.write('lightingColorCtrl', {0x4010: {value: 366, type: 0x21}});
351
+ }
352
+
353
+ await entity.write('lightingColorCtrl', {0x0003: {value: value.x, type: 0x21}}, manufacturerOptions);
354
+ await entity.write('lightingColorCtrl', {0x0004: {value: value.y, type: 0x21}}, manufacturerOptions);
355
+ } else {
356
+ // Set defaults for colortemp and color
357
+ if (supports.colorTemperature) {
358
+ await entity.write('lightingColorCtrl', {0x4010: {value: 366, type: 0x21}});
359
+ }
360
+
361
+ if (supports.colorXY) {
362
+ await entity.write('lightingColorCtrl', {0x0003: {value: 0xFFFF, type: 0x21}}, manufacturerOptions);
363
+ await entity.write('lightingColorCtrl', {0x0004: {value: 0xFFFF, type: 0x21}}, manufacturerOptions);
364
+ }
365
+ }
366
+ }
367
+ }
368
+
369
+ return {state: {hue_power_on_behavior: value}};
370
+ },
371
+ },
372
+ hue_power_on_error: {
373
+ key: ['hue_power_on_brightness', 'hue_power_on_color_temperature', 'hue_power_on_color'],
374
+ convertSet: async (entity, key, value, meta) => {
375
+ if (!meta.message.hasOwnProperty('hue_power_on_behavior')) {
376
+ throw new Error(`Provide a value for 'hue_power_on_behavior'`);
377
+ }
378
+ },
379
+ },
380
+ hue_motion_sensitivity: {
381
+ // motion detect sensitivity, philips specific
382
+ key: ['motion_sensitivity'],
383
+ convertSet: async (entity, key, value, meta) => {
384
+ // make sure you write to second endpoint!
385
+ const lookup = {'low': 0, 'medium': 1, 'high': 2, 'very_high': 3, 'max': 4};
386
+ value = value.toLowerCase();
387
+ utils.validateValue(value, Object.keys(lookup));
388
+
389
+ const payload = {48: {value: lookup[value], type: 32}};
390
+ await entity.write('msOccupancySensing', payload, manufacturerOptions);
391
+ return {state: {motion_sensitivity: value}};
392
+ },
393
+ convertGet: async (entity, key, meta) => {
394
+ await entity.read('msOccupancySensing', [48], manufacturerOptions);
395
+ },
396
+ },
397
+ hue_motion_led_indication: {
398
+ key: ['led_indication'],
399
+ convertSet: async (entity, key, value, meta) => {
400
+ const payload = {0x0033: {value, type: 0x10}};
401
+ await entity.write('genBasic', payload, manufacturerOptions);
402
+ return {state: {led_indication: value}};
403
+ },
404
+ convertGet: async (entity, key, meta) => {
405
+ await entity.read('genBasic', [0x0033], manufacturerOptions);
406
+ },
407
+ },
229
408
  };
230
409
 
231
410
  module.exports = [
@@ -787,7 +966,9 @@ module.exports = [
787
966
  model: '929001953101',
788
967
  vendor: 'Philips',
789
968
  description: 'Hue White and Color Ambiance GU10',
790
- extend: hueExtend.light_onoff_brightness_colortemp_color(),
969
+ extend: hueExtend.light_onoff_brightness_colortemp_color({
970
+ colorTempRange: [153, 500], disableHueEffects: false,
971
+ }),
791
972
  },
792
973
  {
793
974
  zigbeeModel: ['LWA003', 'LWW002'],
@@ -1060,7 +1241,9 @@ module.exports = [
1060
1241
  model: '929002471601',
1061
1242
  vendor: 'Philips',
1062
1243
  description: 'Hue white and color ambiance E26/E27 1600lm',
1063
- extend: hueExtend.light_onoff_brightness_colortemp_color({colorTempRange: [153, 500]}),
1244
+ extend: hueExtend.light_onoff_brightness_colortemp_color({
1245
+ colorTempRange: [153, 500], disableHueEffects: false,
1246
+ }),
1064
1247
  },
1065
1248
  {
1066
1249
  zigbeeModel: ['LCA009'],
@@ -1179,7 +1362,7 @@ module.exports = [
1179
1362
  model: '929001953301',
1180
1363
  vendor: 'Philips',
1181
1364
  description: 'Hue white ambiance GU10 with Bluetooth',
1182
- extend: hueExtend.light_onoff_brightness_colortemp({colorTempRange: [153, 454]}),
1365
+ extend: hueExtend.light_onoff_brightness_colortemp({colorTempRange: [153, 500], disableHueEffects: false}),
1183
1366
  },
1184
1367
  {
1185
1368
  zigbeeModel: ['LTD005'],
@@ -2039,7 +2222,7 @@ module.exports = [
2039
2222
  exposes.enum('motion_sensitivity', ea.ALL, ['low', 'medium', 'high']),
2040
2223
  exposes.binary('led_indication', ea.ALL, true, false).withDescription('Blink green LED on motion detection'),
2041
2224
  exposes.numeric('occupancy_timeout', ea.ALL).withUnit('second').withValueMin(0).withValueMax(65535)],
2042
- toZigbee: [tz.occupancy_timeout, tz.hue_motion_sensitivity, tz.hue_motion_led_indication],
2225
+ toZigbee: [tz.occupancy_timeout, tzLocal.hue_motion_sensitivity, tzLocal.hue_motion_led_indication],
2043
2226
  endpoint: (device) => {
2044
2227
  return {'default': 2, 'ep1': 1, 'ep2': 2};
2045
2228
  },
@@ -2068,7 +2251,7 @@ module.exports = [
2068
2251
  exposes.enum('motion_sensitivity', ea.ALL, ['low', 'medium', 'high']),
2069
2252
  exposes.binary('led_indication', ea.ALL, true, false).withDescription('Blink green LED on motion detection'),
2070
2253
  exposes.numeric('occupancy_timeout', ea.ALL).withUnit('second').withValueMin(0).withValueMax(65535)],
2071
- toZigbee: [tz.occupancy_timeout, tz.hue_motion_sensitivity, tz.hue_motion_led_indication],
2254
+ toZigbee: [tz.occupancy_timeout, tzLocal.hue_motion_sensitivity, tzLocal.hue_motion_led_indication],
2072
2255
  endpoint: (device) => {
2073
2256
  return {'default': 2, 'ep1': 1, 'ep2': 2};
2074
2257
  },
@@ -2097,7 +2280,7 @@ module.exports = [
2097
2280
  exposes.enum('motion_sensitivity', ea.ALL, ['low', 'medium', 'high', 'very_high', 'max']),
2098
2281
  exposes.binary('led_indication', ea.ALL, true, false).withDescription('Blink green LED on motion detection'),
2099
2282
  exposes.numeric('occupancy_timeout', ea.ALL).withUnit('second').withValueMin(0).withValueMax(65535)],
2100
- toZigbee: [tz.occupancy_timeout, tz.hue_motion_sensitivity, tz.hue_motion_led_indication],
2283
+ toZigbee: [tz.occupancy_timeout, tzLocal.hue_motion_sensitivity, tzLocal.hue_motion_led_indication],
2101
2284
  configure: async (device, coordinatorEndpoint, logger) => {
2102
2285
  const endpoint = device.getEndpoint(2);
2103
2286
  const binds = ['genPowerCfg', 'msIlluminanceMeasurement', 'msTemperatureMeasurement', 'msOccupancySensing'];
@@ -2123,7 +2306,7 @@ module.exports = [
2123
2306
  exposes.enum('motion_sensitivity', ea.ALL, ['low', 'medium', 'high', 'very_high', 'max']),
2124
2307
  exposes.binary('led_indication', ea.ALL, true, false).withDescription('Blink green LED on motion detection'),
2125
2308
  exposes.numeric('occupancy_timeout', ea.ALL).withUnit('second').withValueMin(0).withValueMax(65535)],
2126
- toZigbee: [tz.occupancy_timeout, tz.hue_motion_sensitivity, tz.hue_motion_led_indication],
2309
+ toZigbee: [tz.occupancy_timeout, tzLocal.hue_motion_sensitivity, tzLocal.hue_motion_led_indication],
2127
2310
  configure: async (device, coordinatorEndpoint, logger) => {
2128
2311
  const endpoint = device.getEndpoint(2);
2129
2312
  const binds = ['genPowerCfg', 'msIlluminanceMeasurement', 'msTemperatureMeasurement', 'msOccupancySensing'];
@@ -2144,7 +2327,7 @@ module.exports = [
2144
2327
  vendor: 'Philips',
2145
2328
  description: 'Hue smart plug - EU',
2146
2329
  extend: extend.switch(),
2147
- toZigbee: [tz.on_off].concat([tz.hue_power_on_behavior, tz.hue_power_on_error]),
2330
+ toZigbee: [tz.on_off].concat([tzLocal.hue_power_on_behavior, tzLocal.hue_power_on_error]),
2148
2331
  configure: async (device, coordinatorEndpoint, logger) => {
2149
2332
  const endpoint = device.getEndpoint(11);
2150
2333
  await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
@@ -2158,7 +2341,7 @@ module.exports = [
2158
2341
  vendor: 'Philips',
2159
2342
  description: 'Hue smart plug bluetooth',
2160
2343
  extend: extend.switch(),
2161
- toZigbee: [tz.on_off].concat([tz.hue_power_on_behavior, tz.hue_power_on_error]),
2344
+ toZigbee: [tz.on_off].concat([tzLocal.hue_power_on_behavior, tzLocal.hue_power_on_error]),
2162
2345
  configure: async (device, coordinatorEndpoint, logger) => {
2163
2346
  const endpoint = device.getEndpoint(11);
2164
2347
  await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
@@ -2172,7 +2355,7 @@ module.exports = [
2172
2355
  vendor: 'Philips',
2173
2356
  description: 'Hue smart plug - UK',
2174
2357
  extend: extend.switch(),
2175
- toZigbee: [tz.on_off].concat([tz.hue_power_on_behavior, tz.hue_power_on_error]),
2358
+ toZigbee: [tz.on_off].concat([tzLocal.hue_power_on_behavior, tzLocal.hue_power_on_error]),
2176
2359
  configure: async (device, coordinatorEndpoint, logger) => {
2177
2360
  const endpoint = device.getEndpoint(11);
2178
2361
  await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
@@ -2186,7 +2369,7 @@ module.exports = [
2186
2369
  vendor: 'Philips',
2187
2370
  description: 'Hue smart plug - AU',
2188
2371
  extend: extend.switch(),
2189
- toZigbee: [tz.on_off].concat([tz.hue_power_on_behavior, tz.hue_power_on_error]),
2372
+ toZigbee: [tz.on_off].concat([tzLocal.hue_power_on_behavior, tzLocal.hue_power_on_error]),
2190
2373
  configure: async (device, coordinatorEndpoint, logger) => {
2191
2374
  const endpoint = device.getEndpoint(11);
2192
2375
  await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
@@ -2200,7 +2383,7 @@ module.exports = [
2200
2383
  vendor: 'Philips',
2201
2384
  description: 'Hue smart plug - AU',
2202
2385
  extend: extend.switch(),
2203
- toZigbee: [tz.on_off].concat([tz.hue_power_on_behavior, tz.hue_power_on_error]),
2386
+ toZigbee: [tz.on_off].concat([tzLocal.hue_power_on_behavior, tzLocal.hue_power_on_error]),
2204
2387
  configure: async (device, coordinatorEndpoint, logger) => {
2205
2388
  const endpoint = device.getEndpoint(11);
2206
2389
  await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
@@ -2214,7 +2397,7 @@ module.exports = [
2214
2397
  vendor: 'Philips',
2215
2398
  description: 'Hue smart plug - CH',
2216
2399
  extend: extend.switch(),
2217
- toZigbee: [tz.on_off].concat([tz.hue_power_on_behavior, tz.hue_power_on_error]),
2400
+ toZigbee: [tz.on_off].concat([tzLocal.hue_power_on_behavior, tzLocal.hue_power_on_error]),
2218
2401
  configure: async (device, coordinatorEndpoint, logger) => {
2219
2402
  const endpoint = device.getEndpoint(11);
2220
2403
  await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
@@ -2228,7 +2411,7 @@ module.exports = [
2228
2411
  vendor: 'Philips',
2229
2412
  description: 'Hue smart plug',
2230
2413
  extend: extend.switch(),
2231
- toZigbee: [tz.on_off].concat([tz.hue_power_on_behavior, tz.hue_power_on_error]),
2414
+ toZigbee: [tz.on_off].concat([tzLocal.hue_power_on_behavior, tzLocal.hue_power_on_error]),
2232
2415
  configure: async (device, coordinatorEndpoint, logger) => {
2233
2416
  const endpoint = device.getEndpoint(11);
2234
2417
  await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
@@ -2242,7 +2425,7 @@ module.exports = [
2242
2425
  vendor: 'Philips',
2243
2426
  description: 'Hue smart plug - EU',
2244
2427
  extend: extend.switch(),
2245
- toZigbee: [tz.on_off].concat([tz.hue_power_on_behavior, tz.hue_power_on_error]),
2428
+ toZigbee: [tz.on_off].concat([tzLocal.hue_power_on_behavior, tzLocal.hue_power_on_error]),
2246
2429
  configure: async (device, coordinatorEndpoint, logger) => {
2247
2430
  const endpoint = device.getEndpoint(11);
2248
2431
  await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff']);
package/devices/tuya.js CHANGED
@@ -1345,6 +1345,30 @@ module.exports = [
1345
1345
  {vendor: 'Moes', model: 'ZS-EUD_3gang'},
1346
1346
  ],
1347
1347
  },
1348
+ {
1349
+ fingerprint: tuya.fingerprint('TS0601', ['_TZE200_p0gzbqct']),
1350
+ model: 'TS0601_dimmer_knob',
1351
+ vendor: 'TuYa',
1352
+ description: 'Zigbee smart knob dimmer',
1353
+ fromZigbee: [tuya.fz.datapoints],
1354
+ toZigbee: [tuya.tz.datapoints],
1355
+ configure: tuya.configureMagicPacket,
1356
+ exposes: [tuya.exposes.lightBrightness().withMinBrightness().setAccess('min_brightness', ea.STATE_SET), tuya.exposes.lightType(),
1357
+ tuya.exposes.indicatorModeNoneRelayPos()],
1358
+ meta: {
1359
+ tuyaDatapoints: [
1360
+ [1, 'state', tuya.valueConverter.onOff, {skip: tuya.skip.stateOnAndBrightnessPresent}],
1361
+ [2, 'brightness', tuya.valueConverter.scale0_254to0_1000],
1362
+ [3, 'min_brightness', tuya.valueConverter.scale0_254to0_1000],
1363
+ [4, 'light_type', tuya.valueConverter.lightType],
1364
+ [21, 'indicator_mode', tuya.valueConverterBasic.lookup({0: 'none', 1: 'relay', 2: 'pos'})],
1365
+ ],
1366
+ },
1367
+ whiteLabel: [
1368
+ {vendor: 'Moes', model: 'WS-SY-EURD'},
1369
+ {vendor: 'Moes', model: 'WS-SY-EURD-WH-MS'},
1370
+ ],
1371
+ },
1348
1372
  {
1349
1373
  fingerprint: [{modelID: 'TS011F', manufacturerName: '_TZ3000_oiymh3qu'}],
1350
1374
  model: 'TS011F_socket_module',
@@ -2515,6 +2539,30 @@ module.exports = [
2515
2539
  ],
2516
2540
  },
2517
2541
  },
2542
+ {
2543
+ fingerprint: tuya.fingerprint('TS0601', ['_TZE200_m9skfctm']),
2544
+ model: 'TS0601_smoke_2',
2545
+ vendor: 'TuYa',
2546
+ description: 'Photoelectric smoke detector',
2547
+ fromZigbee: [tuya.fz.datapoints],
2548
+ toZigbee: [tuya.tz.datapoints],
2549
+ onEvent: tuya.onEventSetTime,
2550
+ configure: tuya.configureMagicPacket,
2551
+ exposes: [
2552
+ e.smoke(), e.battery(), e.test(),
2553
+ exposes.numeric('smoke_concentration', ea.STATE).withUnit('ppm').withDescription('Parts per million of smoke detected'),
2554
+ exposes.binary('device_fault', ea.STATE, true, false).withDescription('Indicates a fault with the device'),
2555
+ ],
2556
+ meta: {
2557
+ tuyaDatapoints: [
2558
+ [1, 'smoke', tuya.valueConverter.true0ElseFalse],
2559
+ [2, 'smoke_concentration', tuya.valueConverter.divideBy10],
2560
+ [11, 'device_fault', tuya.valueConverter.raw],
2561
+ [15, 'battery', tuya.valueConverter.raw],
2562
+ [101, 'test', tuya.valueConverter.raw],
2563
+ ],
2564
+ },
2565
+ },
2518
2566
  {
2519
2567
  zigbeeModel: ['5p1vj8r'],
2520
2568
  fingerprint: tuya.fingerprint('TS0601', ['_TZE200_t5p1vj8r', '_TZE200_uebojraa', '_TZE200_vzekyi4c', '_TZE200_yh7aoahi',
@@ -2548,6 +2596,24 @@ module.exports = [
2548
2596
  exposes.binary('silence_siren', ea.STATE_SET, true, false).withDescription('Silence the siren')],
2549
2597
  onEvent: tuya.onEventsetTime,
2550
2598
  },
2599
+ {
2600
+ fingerprint: tuya.fingerprint('TS0601', ['_TZE204_cjbofhxw']),
2601
+ model: 'TS0601_clamp_meter',
2602
+ vendor: 'TuYa',
2603
+ description: 'Clamp meter',
2604
+ fromZigbee: [tuya.fz.datapoints, tuya.fz.gateway_connection_status],
2605
+ toZigbee: [tuya.tz.datapoints],
2606
+ configure: tuya.configureMagicPacket,
2607
+ exposes: [e.current(), e.power(), e.voltage(), e.energy()],
2608
+ meta: {
2609
+ tuyaDatapoints: [
2610
+ [18, 'current', tuya.valueConverter.divideBy1000],
2611
+ [19, 'power', tuya.valueConverter.divideBy10],
2612
+ [20, 'voltage', tuya.valueConverter.divideBy10],
2613
+ [101, 'energy', tuya.valueConverter.divideBy1000],
2614
+ ],
2615
+ },
2616
+ },
2551
2617
  {
2552
2618
  fingerprint: tuya.fingerprint('TS0601', ['_TZE200_bkkmqmyo', '_TZE200_eaac7dkw']),
2553
2619
  model: 'TS0601_din_1',
package/devices/ubisys.js CHANGED
@@ -17,7 +17,6 @@ const manufacturerOptions = {
17
17
  */
18
18
  ubisys: {manufacturerCode: herdsman.Zcl.ManufacturerCode.UBISYS},
19
19
  ubisysNull: {manufacturerCode: null},
20
- tint: {manufacturerCode: herdsman.Zcl.ManufacturerCode.MUELLER_LICHT_INT},
21
20
  };
22
21
 
23
22
  const ubisysOnEventReadCurrentSummDelivered = async function(type, data, devic) {
package/lib/ota/common.js CHANGED
@@ -27,7 +27,6 @@ const eblImageSignature = 0xe350;
27
27
 
28
28
  const gblTagHeader = 0xeb17a603;
29
29
  const gblTagEnd = 0xfc0404fc;
30
- const gblPadding = 0x0;
31
30
 
32
31
  function getOTAEndpoint(device) {
33
32
  return device.endpoints.find((e) => e.supportsOutputCluster('genOta'));
@@ -145,10 +144,6 @@ function validateSilabsGbl(data) {
145
144
  continue;
146
145
  }
147
146
 
148
- for (let position2 = position; position2 < dataLength; position2++) {
149
- assert(data.readUInt8(position2) === gblPadding, `Image padding contains invalid bytes`);
150
- }
151
-
152
147
  const calculatedCrc32 = crc32.unsigned(data.slice(0, position));
153
148
 
154
149
  assert(calculatedCrc32 === validSilabsCrc, `Image CRC-32 is invalid`);
package/lib/tuya.js CHANGED
@@ -1163,6 +1163,8 @@ const tuyaExposes = {
1163
1163
  backlightModeOffNormalInverted: () => exposes.enum('backlight_mode', ea.ALL, ['off', 'normal', 'inverted'])
1164
1164
  .withDescription('Mode of the backlight'),
1165
1165
  indicatorMode: () => exposes.enum('indicator_mode', ea.ALL, ['off', 'off/on', 'on/off', 'on']).withDescription('LED indicator mode'),
1166
+ indicatorModeNoneRelayPos: () => exposes.enum('indicator_mode', ea.ALL, ['none', 'relay', 'pos'])
1167
+ .withDescription('Mode of the indicator light'),
1166
1168
  powerOutageMemory: () => exposes.enum('power_outage_memory', ea.ALL, ['on', 'off', 'restore'])
1167
1169
  .withDescription('Recover state after power outage'),
1168
1170
  batteryState: () => exposes.enum('battery_state', ea.STATE, ['low', 'medium', 'high']).withDescription('State of the battery'),
@@ -1616,6 +1618,18 @@ const tuyaTz = {
1616
1618
  };
1617
1619
 
1618
1620
  const tuyaFz = {
1621
+ gateway_connection_status: {
1622
+ cluster: 'manuSpecificTuya',
1623
+ type: ['commandMcuGatewayConnectionStatus'],
1624
+ convert: async (model, msg, publish, options, meta) => {
1625
+ // "payload" can have the following values:
1626
+ // 0x00: The gateway is not connected to the internet.
1627
+ // 0x01: The gateway is connected to the internet.
1628
+ // 0x02: The request timed out after three seconds.
1629
+ const payload = {payloadSize: 1, payload: 1};
1630
+ await msg.endpoint.command('manuSpecificTuya', 'mcuGatewayConnectionStatus', payload, {});
1631
+ },
1632
+ },
1619
1633
  power_on_behavior: {
1620
1634
  cluster: 'genOnOff',
1621
1635
  type: ['attributeReport', 'readResponse'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zigbee-herdsman-converters",
3
- "version": "15.0.9",
3
+ "version": "15.0.10",
4
4
  "description": "Collection of device converters to be used with zigbee-herdsman",
5
5
  "main": "index.js",
6
6
  "files": [