zigbee-herdsman-converters 14.0.582 → 14.0.585
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/converters/fromZigbee.js +6 -4
- package/devices/iluminize.js +1 -1
- package/devices/lonsonho.js +2 -1
- package/devices/moes.js +1 -1
- package/devices/namron.js +13 -0
- package/devices/philips.js +67 -0
- package/devices/robb.js +26 -0
- package/devices/sengled.js +44 -35
- package/devices/tuya.js +275 -1
- package/devices/ubisys.js +77 -0
- package/lib/exposes.js +8 -0
- package/lib/reporting.js +8 -0
- package/lib/tuya.js +20 -0
- package/package.json +1 -1
package/converters/fromZigbee.js
CHANGED
|
@@ -1544,6 +1544,8 @@ const converters = {
|
|
|
1544
1544
|
if (msg.data.hasOwnProperty('currentPositionLiftPercentage') && msg.data['currentPositionLiftPercentage'] <= 100) {
|
|
1545
1545
|
const value = msg.data['currentPositionLiftPercentage'];
|
|
1546
1546
|
result[postfixWithEndpointName('position', msg, model, meta)] = invert ? value : 100 - value;
|
|
1547
|
+
result[postfixWithEndpointName('state', msg, model, meta)] =
|
|
1548
|
+
invert ? (value === 100 ? 'CLOSE' : 'OPEN') : (value === 0 ? 'CLOSE' : 'OPEN');
|
|
1547
1549
|
}
|
|
1548
1550
|
if (msg.data.hasOwnProperty('currentPositionTiltPercentage') && msg.data['currentPositionTiltPercentage'] <= 100) {
|
|
1549
1551
|
const value = msg.data['currentPositionTiltPercentage'];
|
|
@@ -2807,21 +2809,21 @@ const converters = {
|
|
|
2807
2809
|
if (msg.data.hasOwnProperty('tuyaMovingState')) {
|
|
2808
2810
|
const value = msg.data['tuyaMovingState'];
|
|
2809
2811
|
const movingLookup = {0: 'UP', 1: 'STOP', 2: 'DOWN'};
|
|
2810
|
-
result
|
|
2812
|
+
result[postfixWithEndpointName('moving', msg, model, meta)] = movingLookup[value];
|
|
2811
2813
|
}
|
|
2812
2814
|
if (msg.data.hasOwnProperty('tuyaCalibration')) {
|
|
2813
2815
|
const value = msg.data['tuyaCalibration'];
|
|
2814
2816
|
const calibrationLookup = {0: 'ON', 1: 'OFF'};
|
|
2815
|
-
result
|
|
2817
|
+
result[postfixWithEndpointName('calibration', msg, model, meta)] = calibrationLookup[value];
|
|
2816
2818
|
}
|
|
2817
2819
|
if (msg.data.hasOwnProperty('tuyaMotorReversal')) {
|
|
2818
2820
|
const value = msg.data['tuyaMotorReversal'];
|
|
2819
2821
|
const reversalLookup = {0: 'OFF', 1: 'ON'};
|
|
2820
|
-
result
|
|
2822
|
+
result[postfixWithEndpointName('motor_reversal', msg, model, meta)] = reversalLookup[value];
|
|
2821
2823
|
}
|
|
2822
2824
|
if (msg.data.hasOwnProperty('moesCalibrationTime')) {
|
|
2823
2825
|
const value = parseFloat(msg.data['moesCalibrationTime']) / 10.0;
|
|
2824
|
-
result
|
|
2826
|
+
result[postfixWithEndpointName('calibration_time', msg, model, meta)] = value;
|
|
2825
2827
|
}
|
|
2826
2828
|
return result;
|
|
2827
2829
|
},
|
package/devices/iluminize.js
CHANGED
|
@@ -115,7 +115,7 @@ module.exports = [
|
|
|
115
115
|
model: '5128.10',
|
|
116
116
|
vendor: 'Iluminize',
|
|
117
117
|
description: 'Zigbee 3.0 switch shutter SW with level control',
|
|
118
|
-
fromZigbee: [fz.cover_position_via_brightness, fz.cover_state_via_onoff],
|
|
118
|
+
fromZigbee: [fz.cover_position_via_brightness, fz.cover_state_via_onoff, fz.cover_position_tilt],
|
|
119
119
|
toZigbee: [tz.cover_state, tz.cover_via_brightness],
|
|
120
120
|
exposes: [e.cover_position()],
|
|
121
121
|
ota: ota.zigbeeOTA,
|
package/devices/lonsonho.js
CHANGED
|
@@ -82,7 +82,8 @@ module.exports = [
|
|
|
82
82
|
return {'left': 1, 'right': 2};
|
|
83
83
|
},
|
|
84
84
|
exposes: [
|
|
85
|
-
exposes.enum('moving', ea.STATE, ['UP', 'STOP', 'DOWN']),
|
|
85
|
+
exposes.enum('moving', ea.STATE, ['UP', 'STOP', 'DOWN']).withEndpoint('left'),
|
|
86
|
+
exposes.enum('moving', ea.STATE, ['UP', 'STOP', 'DOWN']).withEndpoint('right'),
|
|
86
87
|
exposes.numeric('calibration_time', ea.STATE).withUnit('S').withDescription('Calibration time'),
|
|
87
88
|
e.cover_position().withEndpoint('left'), exposes.binary('calibration', ea.ALL, 'ON', 'OFF')
|
|
88
89
|
.withEndpoint('left'), exposes.binary('motor_reversal', ea.ALL, 'ON', 'OFF').withEndpoint('left'),
|
package/devices/moes.js
CHANGED
|
@@ -247,7 +247,7 @@ module.exports = [
|
|
|
247
247
|
tz.moesS_thermostat_min_temperature, tz.moesS_thermostat_moesSecoMode,
|
|
248
248
|
tz.moesS_thermostat_system_mode, tz.moesS_thermostat_schedule_programming],
|
|
249
249
|
exposes: [
|
|
250
|
-
e.battery(), e.child_lock(), e.eco_mode(), e.eco_temperature(), e.max_temperature(), e.min_temperature(),
|
|
250
|
+
e.battery(), e.child_lock(), e.eco_mode(), e.eco_temperature(), e.max_temperature().withValueMax(45), e.min_temperature(),
|
|
251
251
|
e.valve_state(), e.position(), e.window_detection(),
|
|
252
252
|
exposes.binary('window', ea.STATE, 'CLOSED', 'OPEN').withDescription('Window status closed or open '),
|
|
253
253
|
exposes.climate()
|
package/devices/namron.js
CHANGED
|
@@ -28,6 +28,19 @@ module.exports = [
|
|
|
28
28
|
await reporting.onOff(endpoint);
|
|
29
29
|
},
|
|
30
30
|
},
|
|
31
|
+
{
|
|
32
|
+
zigbeeModel: ['4512708'],
|
|
33
|
+
model: '4512708',
|
|
34
|
+
vendor: 'Namron',
|
|
35
|
+
description: 'Zigbee LED dimmer',
|
|
36
|
+
extend: extend.light_onoff_brightness({noConfigure: true}),
|
|
37
|
+
configure: async (device, coordinatorEndpoint, logger) => {
|
|
38
|
+
await extend.light_onoff_brightness().configure(device, coordinatorEndpoint, logger);
|
|
39
|
+
const endpoint = device.getEndpoint(1);
|
|
40
|
+
await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl']);
|
|
41
|
+
await reporting.onOff(endpoint);
|
|
42
|
+
},
|
|
43
|
+
},
|
|
31
44
|
{
|
|
32
45
|
zigbeeModel: ['4512733'],
|
|
33
46
|
model: '4512733',
|
package/devices/philips.js
CHANGED
|
@@ -3,6 +3,8 @@ const fz = {...require('../converters/fromZigbee'), legacy: require('../lib/lega
|
|
|
3
3
|
const tz = require('../converters/toZigbee');
|
|
4
4
|
const ota = require('../lib/ota');
|
|
5
5
|
const reporting = require('../lib/reporting');
|
|
6
|
+
const globalStore = require('../lib/store');
|
|
7
|
+
const utils = require('../lib/utils');
|
|
6
8
|
const e = exposes.presets;
|
|
7
9
|
const ea = exposes.access;
|
|
8
10
|
|
|
@@ -40,6 +42,48 @@ const hueExtend = {
|
|
|
40
42
|
}),
|
|
41
43
|
};
|
|
42
44
|
|
|
45
|
+
const fzLocal = {
|
|
46
|
+
hue_tap_dial: {
|
|
47
|
+
cluster: 'manuSpecificPhilips',
|
|
48
|
+
type: 'commandHueNotification',
|
|
49
|
+
options: [exposes.options.simulated_brightness()],
|
|
50
|
+
convert: (model, msg, publish, options, meta) => {
|
|
51
|
+
const buttonLookup = {1: 'button_1', 2: 'button_2', 3: 'button_3', 4: 'button_4', 20: 'dial'};
|
|
52
|
+
const button = buttonLookup[msg.data['button']];
|
|
53
|
+
const typeLookup = {0: 'press', 1: 'hold', 2: 'press_release', 3: 'hold_release'};
|
|
54
|
+
const type = typeLookup[msg.data['type']];
|
|
55
|
+
const direction = msg.data['unknown2'] <127 ? 'right' : 'left';
|
|
56
|
+
const time = msg.data['time'];
|
|
57
|
+
const payload = {};
|
|
58
|
+
|
|
59
|
+
if (button === 'dial') {
|
|
60
|
+
const adjustedTime = direction === 'right' ? time : 256 - time;
|
|
61
|
+
const dialType = 'rotate';
|
|
62
|
+
const speed = adjustedTime <= 25 ? 'step' : adjustedTime <= 75 ? 'slow' : 'fast';
|
|
63
|
+
payload.action = `${button}_${dialType}_${direction}_${speed}`;
|
|
64
|
+
|
|
65
|
+
// simulated brightness
|
|
66
|
+
if (options.simulated_brightness) {
|
|
67
|
+
const opts = options.simulated_brightness;
|
|
68
|
+
const deltaOpts = typeof opts === 'object' && opts.hasOwnProperty('delta') ? opts.delta : 35;
|
|
69
|
+
const delta = direction === 'right' ? deltaOpts : deltaOpts * -1;
|
|
70
|
+
const brightness = globalStore.getValue(msg.endpoint, 'brightness', 255) + delta;
|
|
71
|
+
payload.brightness = utils.numberWithinRange(brightness, 0, 255);
|
|
72
|
+
globalStore.putValue(msg.endpoint, 'brightness', payload.brightness);
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
payload.action = `${button}_${type}`;
|
|
76
|
+
// duration
|
|
77
|
+
if (type === 'press') globalStore.putValue(msg.endpoint, 'press_start', Date.now());
|
|
78
|
+
else if (type === 'hold' || type === 'hold_release') {
|
|
79
|
+
payload.action_duration = (Date.now() - globalStore.getValue(msg.endpoint, 'press_start')) / 1000;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return payload;
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
43
87
|
module.exports = [
|
|
44
88
|
{
|
|
45
89
|
zigbeeModel: ['929003055801'],
|
|
@@ -2313,6 +2357,29 @@ module.exports = [
|
|
|
2313
2357
|
description: 'Hue White and Color Ambiance A19 1100 lumen',
|
|
2314
2358
|
extend: hueExtend.light_onoff_brightness_colortemp_color({colorTempRange: [153, 500]}),
|
|
2315
2359
|
},
|
|
2360
|
+
{
|
|
2361
|
+
zigbeeModel: ['RDM002'],
|
|
2362
|
+
model: '8719514440937/8719514440999',
|
|
2363
|
+
vendor: 'Philips',
|
|
2364
|
+
description: 'Hue Tap dial switch',
|
|
2365
|
+
fromZigbee: [fz.ignore_command_step, fzLocal.hue_tap_dial, fz.battery],
|
|
2366
|
+
toZigbee: [],
|
|
2367
|
+
exposes: [e.battery(), e.action(['button_1_press', 'button_1_press_release', 'button_1_hold', 'button_1_hold_release',
|
|
2368
|
+
'button_2_press', 'button_2_press_release', 'button_2_hold', 'button_2_hold_release',
|
|
2369
|
+
'button_3_press', 'button_3_press_release', 'button_3_hold', 'button_3_hold_release',
|
|
2370
|
+
'button_4_press', 'button_4_press_release', 'button_4_hold', 'button_4_hold_release',
|
|
2371
|
+
'dial_rotate_left_step', 'dial_rotate_left_slow', 'dial_rotate_left_fast',
|
|
2372
|
+
'dial_rotate_right_step', 'dial_rotate_right_slow', 'dial_rotate_right_fast']),
|
|
2373
|
+
],
|
|
2374
|
+
configure: async (device, coordinatorEndpoint, logger) => {
|
|
2375
|
+
const endpoint = device.getEndpoint(1);
|
|
2376
|
+
await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff', 'genLevelCtrl', 'manuSpecificPhilips', 'genPowerCfg']);
|
|
2377
|
+
const options = {manufacturerCode: 0x100B, disableDefaultResponse: true};
|
|
2378
|
+
await endpoint.write('genBasic', {0x0031: {value: 0x000B, type: 0x19}}, options);
|
|
2379
|
+
await reporting.batteryPercentageRemaining(endpoint);
|
|
2380
|
+
},
|
|
2381
|
+
ota: ota.zigbeeOTA,
|
|
2382
|
+
},
|
|
2316
2383
|
{
|
|
2317
2384
|
fingerprint: [{modelID: 'GreenPower_2', ieeeAddr: /^0x00000000004.....$/}],
|
|
2318
2385
|
model: '8718696743133',
|
package/devices/robb.js
CHANGED
|
@@ -248,4 +248,30 @@ module.exports = [
|
|
|
248
248
|
'recall_*', 'on', 'off', 'toggle', 'brightness_stop', 'brightness_move_up', 'brightness_move_down',
|
|
249
249
|
'color_loop_set', 'enhanced_move_to_hue_and_saturation', 'hue_stop'])],
|
|
250
250
|
},
|
|
251
|
+
{
|
|
252
|
+
zigbeeModel: ['ROB_200-026-0'],
|
|
253
|
+
model: 'ROB_200-026-0',
|
|
254
|
+
vendor: 'ROBB',
|
|
255
|
+
description: '2-gang in-wall switch',
|
|
256
|
+
fromZigbee: [fz.on_off, fz.electrical_measurement, fz.metering, fz.power_on_behavior],
|
|
257
|
+
toZigbee: [tz.on_off, tz.power_on_behavior, tz.electrical_measurement_power],
|
|
258
|
+
exposes: [e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'), e.energy()],
|
|
259
|
+
endpoint: (device) => {
|
|
260
|
+
return {'l1': 1, 'l2': 2};
|
|
261
|
+
},
|
|
262
|
+
meta: {multiEndpoint: true},
|
|
263
|
+
configure: async (device, coordinatorEndpoint, logger) => {
|
|
264
|
+
const endpoint1 = device.getEndpoint(1);
|
|
265
|
+
const endpoint2 = device.getEndpoint(2);
|
|
266
|
+
await reporting.bind(endpoint1, coordinatorEndpoint, ['genOnOff']);
|
|
267
|
+
await reporting.bind(endpoint2, coordinatorEndpoint, ['genOnOff']);
|
|
268
|
+
await reporting.onOff(endpoint1);
|
|
269
|
+
await reporting.onOff(endpoint2);
|
|
270
|
+
await endpoint1.read('haElectricalMeasurement', ['acPowerMultiplier', 'acPowerDivisor']);
|
|
271
|
+
await reporting.bind(endpoint1, coordinatorEndpoint, ['haElectricalMeasurement', 'seMetering']);
|
|
272
|
+
await reporting.activePower(endpoint1);
|
|
273
|
+
await reporting.readMeteringMultiplierDivisor(endpoint1);
|
|
274
|
+
await reporting.currentSummDelivered(endpoint1, {min: 60, change: 1});
|
|
275
|
+
},
|
|
276
|
+
},
|
|
251
277
|
];
|
package/devices/sengled.js
CHANGED
|
@@ -3,18 +3,27 @@ const fz = {...require('../converters/fromZigbee'), legacy: require('../lib/lega
|
|
|
3
3
|
const tz = require('../converters/toZigbee');
|
|
4
4
|
const ota = require('../lib/ota');
|
|
5
5
|
const reporting = require('../lib/reporting');
|
|
6
|
-
const extend = require('../lib/extend');
|
|
7
6
|
const e = exposes.presets;
|
|
8
7
|
|
|
8
|
+
// Make sure extend.ldight_* is not used (sengledExtend should be used instead)
|
|
9
|
+
const extendDontUse = require('../lib/extend');
|
|
10
|
+
const extend = {switch: extendDontUse.switch};
|
|
11
|
+
const sengledExtend = {
|
|
12
|
+
light_onoff_brightness: (options={}) => extendDontUse.light_onoff_brightness({disableEffect: true, ...options}),
|
|
13
|
+
light_onoff_brightness_colortemp: (options={}) => extendDontUse.light_onoff_brightness_colortemp({disableEffect: true, ...options}),
|
|
14
|
+
light_onoff_brightness_colortemp_color: (options={}) =>
|
|
15
|
+
extendDontUse.light_onoff_brightness_colortemp_color({disableEffect: true, ...options}),
|
|
16
|
+
};
|
|
17
|
+
|
|
9
18
|
module.exports = [
|
|
10
19
|
{
|
|
11
20
|
zigbeeModel: ['E13-N11'],
|
|
12
21
|
model: 'E13-N11',
|
|
13
22
|
vendor: 'Sengled',
|
|
14
23
|
description: 'Flood light with motion sensor light outdoor',
|
|
15
|
-
fromZigbee:
|
|
16
|
-
toZigbee:
|
|
17
|
-
exposes:
|
|
24
|
+
fromZigbee: sengledExtend.light_onoff_brightness().fromZigbee.concat([fz.ias_occupancy_alarm_1]),
|
|
25
|
+
toZigbee: sengledExtend.light_onoff_brightness().toZigbee,
|
|
26
|
+
exposes: sengledExtend.light_onoff_brightness().exposes.concat([e.occupancy()]),
|
|
18
27
|
ota: ota.zigbeeOTA,
|
|
19
28
|
},
|
|
20
29
|
{
|
|
@@ -22,7 +31,7 @@ module.exports = [
|
|
|
22
31
|
model: 'E21-N13A',
|
|
23
32
|
vendor: 'Sengled',
|
|
24
33
|
description: 'Smart LED (A19)',
|
|
25
|
-
extend:
|
|
34
|
+
extend: sengledExtend.light_onoff_brightness(),
|
|
26
35
|
ota: ota.zigbeeOTA,
|
|
27
36
|
},
|
|
28
37
|
{
|
|
@@ -30,12 +39,12 @@ module.exports = [
|
|
|
30
39
|
model: 'E21-N1EA',
|
|
31
40
|
vendor: 'Sengled',
|
|
32
41
|
description: 'Smart LED multicolor A19 bulb',
|
|
33
|
-
fromZigbee:
|
|
34
|
-
toZigbee:
|
|
35
|
-
exposes:
|
|
42
|
+
fromZigbee: sengledExtend.light_onoff_brightness_colortemp_color({colorTempRange: [154, 500]}).fromZigbee.concat([fz.metering]),
|
|
43
|
+
toZigbee: sengledExtend.light_onoff_brightness_colortemp_color({colorTempRange: [154, 500]}).toZigbee,
|
|
44
|
+
exposes: sengledExtend.light_onoff_brightness_colortemp_color({colorTempRange: [154, 500]}).exposes.concat([e.power(), e.energy()]),
|
|
36
45
|
ota: ota.zigbeeOTA,
|
|
37
46
|
configure: async (device, coordinatorEndpoint, logger) => {
|
|
38
|
-
await
|
|
47
|
+
await sengledExtend.light_onoff_brightness_colortemp_color({colorTempRange: [154, 500]})
|
|
39
48
|
.configure(device, coordinatorEndpoint, logger);
|
|
40
49
|
device.powerSource = 'Mains (single phase)';
|
|
41
50
|
device.save();
|
|
@@ -52,7 +61,7 @@ module.exports = [
|
|
|
52
61
|
model: 'E12-N1E',
|
|
53
62
|
vendor: 'Sengled',
|
|
54
63
|
description: 'Smart LED multicolor (BR30)',
|
|
55
|
-
extend:
|
|
64
|
+
extend: sengledExtend.light_onoff_brightness_colortemp_color(),
|
|
56
65
|
ota: ota.zigbeeOTA,
|
|
57
66
|
},
|
|
58
67
|
{
|
|
@@ -60,7 +69,7 @@ module.exports = [
|
|
|
60
69
|
model: 'E1G-G8E',
|
|
61
70
|
vendor: 'Sengled',
|
|
62
71
|
description: 'Multicolor light strip (2M)',
|
|
63
|
-
extend:
|
|
72
|
+
extend: sengledExtend.light_onoff_brightness_colortemp_color(),
|
|
64
73
|
ota: ota.zigbeeOTA,
|
|
65
74
|
},
|
|
66
75
|
{
|
|
@@ -68,7 +77,7 @@ module.exports = [
|
|
|
68
77
|
model: 'E11-U21U31',
|
|
69
78
|
vendor: 'Sengled',
|
|
70
79
|
description: 'Element touch (A19)',
|
|
71
|
-
extend:
|
|
80
|
+
extend: sengledExtend.light_onoff_brightness(),
|
|
72
81
|
ota: ota.zigbeeOTA,
|
|
73
82
|
},
|
|
74
83
|
{
|
|
@@ -76,11 +85,11 @@ module.exports = [
|
|
|
76
85
|
model: 'E11-G13',
|
|
77
86
|
vendor: 'Sengled',
|
|
78
87
|
description: 'Element classic (A19)',
|
|
79
|
-
fromZigbee:
|
|
80
|
-
toZigbee:
|
|
88
|
+
fromZigbee: sengledExtend.light_onoff_brightness().fromZigbee.concat([fz.metering]),
|
|
89
|
+
toZigbee: sengledExtend.light_onoff_brightness().toZigbee,
|
|
81
90
|
ota: ota.zigbeeOTA,
|
|
82
91
|
configure: async (device, coordinatorEndpoint, logger) => {
|
|
83
|
-
await
|
|
92
|
+
await sengledExtend.light_onoff_brightness().configure(device, coordinatorEndpoint, logger);
|
|
84
93
|
device.powerSource = 'Mains (single phase)';
|
|
85
94
|
device.save();
|
|
86
95
|
|
|
@@ -90,14 +99,14 @@ module.exports = [
|
|
|
90
99
|
await reporting.currentSummDelivered(endpoint);
|
|
91
100
|
await reporting.instantaneousDemand(endpoint);
|
|
92
101
|
},
|
|
93
|
-
exposes:
|
|
102
|
+
exposes: sengledExtend.light_onoff_brightness().exposes.concat([e.power(), e.energy()]),
|
|
94
103
|
},
|
|
95
104
|
{
|
|
96
105
|
zigbeeModel: ['E11-G23', 'E11-G33'],
|
|
97
106
|
model: 'E11-G23/E11-G33',
|
|
98
107
|
vendor: 'Sengled',
|
|
99
108
|
description: 'Element classic (A60)',
|
|
100
|
-
extend:
|
|
109
|
+
extend: sengledExtend.light_onoff_brightness(),
|
|
101
110
|
ota: ota.zigbeeOTA,
|
|
102
111
|
},
|
|
103
112
|
{
|
|
@@ -105,7 +114,7 @@ module.exports = [
|
|
|
105
114
|
model: 'E11-N13/E11-N13A/E11-N14/E11-N14A',
|
|
106
115
|
vendor: 'Sengled',
|
|
107
116
|
description: 'Element extra bright (A19)',
|
|
108
|
-
extend:
|
|
117
|
+
extend: sengledExtend.light_onoff_brightness(),
|
|
109
118
|
ota: ota.zigbeeOTA,
|
|
110
119
|
},
|
|
111
120
|
{
|
|
@@ -113,7 +122,7 @@ module.exports = [
|
|
|
113
122
|
model: 'Z01-CIA19NAE26',
|
|
114
123
|
vendor: 'Sengled',
|
|
115
124
|
description: 'Element touch (A19)',
|
|
116
|
-
extend:
|
|
125
|
+
extend: sengledExtend.light_onoff_brightness(),
|
|
117
126
|
ota: ota.zigbeeOTA,
|
|
118
127
|
},
|
|
119
128
|
{
|
|
@@ -121,7 +130,7 @@ module.exports = [
|
|
|
121
130
|
model: 'Z01-A19NAE26',
|
|
122
131
|
vendor: 'Sengled',
|
|
123
132
|
description: 'Element plus (A19)',
|
|
124
|
-
extend:
|
|
133
|
+
extend: sengledExtend.light_onoff_brightness_colortemp(),
|
|
125
134
|
ota: ota.zigbeeOTA,
|
|
126
135
|
},
|
|
127
136
|
{
|
|
@@ -129,7 +138,7 @@ module.exports = [
|
|
|
129
138
|
model: 'Z01-A60EAE27',
|
|
130
139
|
vendor: 'Sengled',
|
|
131
140
|
description: 'Element Plus (A60)',
|
|
132
|
-
extend:
|
|
141
|
+
extend: sengledExtend.light_onoff_brightness_colortemp(),
|
|
133
142
|
ota: ota.zigbeeOTA,
|
|
134
143
|
},
|
|
135
144
|
{
|
|
@@ -137,11 +146,11 @@ module.exports = [
|
|
|
137
146
|
model: 'E11-N1EA',
|
|
138
147
|
vendor: 'Sengled',
|
|
139
148
|
description: 'Element plus color (A19)',
|
|
140
|
-
fromZigbee:
|
|
141
|
-
toZigbee:
|
|
149
|
+
fromZigbee: sengledExtend.light_onoff_brightness().fromZigbee.concat([fz.metering]),
|
|
150
|
+
toZigbee: sengledExtend.light_onoff_brightness().toZigbee,
|
|
142
151
|
ota: ota.zigbeeOTA,
|
|
143
152
|
configure: async (device, coordinatorEndpoint, logger) => {
|
|
144
|
-
await
|
|
153
|
+
await sengledExtend.light_onoff_brightness().configure(device, coordinatorEndpoint, logger);
|
|
145
154
|
device.powerSource = 'Mains (single phase)';
|
|
146
155
|
device.save();
|
|
147
156
|
|
|
@@ -151,14 +160,14 @@ module.exports = [
|
|
|
151
160
|
await reporting.currentSummDelivered(endpoint);
|
|
152
161
|
await reporting.instantaneousDemand(endpoint);
|
|
153
162
|
},
|
|
154
|
-
exposes:
|
|
163
|
+
exposes: sengledExtend.light_onoff_brightness().exposes.concat([e.power(), e.energy()]),
|
|
155
164
|
},
|
|
156
165
|
{
|
|
157
166
|
zigbeeModel: ['E11-U2E'],
|
|
158
167
|
model: 'E11-U2E',
|
|
159
168
|
vendor: 'Sengled',
|
|
160
169
|
description: 'Element color plus E27',
|
|
161
|
-
extend:
|
|
170
|
+
extend: sengledExtend.light_onoff_brightness_colortemp_color(),
|
|
162
171
|
ota: ota.zigbeeOTA,
|
|
163
172
|
},
|
|
164
173
|
{
|
|
@@ -166,7 +175,7 @@ module.exports = [
|
|
|
166
175
|
model: 'E11-U3E',
|
|
167
176
|
vendor: 'Sengled',
|
|
168
177
|
description: 'Element color plus B22',
|
|
169
|
-
extend:
|
|
178
|
+
extend: sengledExtend.light_onoff_brightness_colortemp_color(),
|
|
170
179
|
ota: ota.zigbeeOTA,
|
|
171
180
|
},
|
|
172
181
|
{
|
|
@@ -174,12 +183,12 @@ module.exports = [
|
|
|
174
183
|
model: 'E1F-N5E',
|
|
175
184
|
vendor: 'Sengled',
|
|
176
185
|
description: 'Element color plus E12',
|
|
177
|
-
fromZigbee:
|
|
178
|
-
toZigbee:
|
|
179
|
-
exposes:
|
|
186
|
+
fromZigbee: sengledExtend.light_onoff_brightness_colortemp_color({colorTempRange: [154, 500]}).fromZigbee.concat([fz.metering]),
|
|
187
|
+
toZigbee: sengledExtend.light_onoff_brightness_colortemp_color({colorTempRange: [154, 500]}).toZigbee,
|
|
188
|
+
exposes: sengledExtend.light_onoff_brightness_colortemp_color({colorTempRange: [154, 500]}).exposes.concat([e.power(), e.energy()]),
|
|
180
189
|
ota: ota.zigbeeOTA,
|
|
181
190
|
configure: async (device, coordinatorEndpoint, logger) => {
|
|
182
|
-
await
|
|
191
|
+
await sengledExtend.light_onoff_brightness_colortemp_color({colorTempRange: [154, 500]})
|
|
183
192
|
.configure(device, coordinatorEndpoint, logger);
|
|
184
193
|
device.powerSource = 'Mains (single phase)';
|
|
185
194
|
device.save();
|
|
@@ -196,7 +205,7 @@ module.exports = [
|
|
|
196
205
|
model: 'E12-N14',
|
|
197
206
|
vendor: 'Sengled',
|
|
198
207
|
description: 'Element Classic (BR30)',
|
|
199
|
-
extend:
|
|
208
|
+
extend: sengledExtend.light_onoff_brightness(),
|
|
200
209
|
ota: ota.zigbeeOTA,
|
|
201
210
|
},
|
|
202
211
|
{
|
|
@@ -204,7 +213,7 @@ module.exports = [
|
|
|
204
213
|
model: 'E1ACA4ABE38A',
|
|
205
214
|
vendor: 'Sengled',
|
|
206
215
|
description: 'Element downlight smart LED bulb',
|
|
207
|
-
extend:
|
|
216
|
+
extend: sengledExtend.light_onoff_brightness(),
|
|
208
217
|
ota: ota.zigbeeOTA,
|
|
209
218
|
},
|
|
210
219
|
{
|
|
@@ -261,7 +270,7 @@ module.exports = [
|
|
|
261
270
|
model: 'E11-N1G',
|
|
262
271
|
vendor: 'Sengled',
|
|
263
272
|
description: 'Vintage LED edison bulb (ST19)',
|
|
264
|
-
extend:
|
|
273
|
+
extend: sengledExtend.light_onoff_brightness(),
|
|
265
274
|
ota: ota.zigbeeOTA,
|
|
266
275
|
},
|
|
267
276
|
{
|
|
@@ -269,7 +278,7 @@ module.exports = [
|
|
|
269
278
|
model: 'E1F-N9G',
|
|
270
279
|
vendor: 'Sengled',
|
|
271
280
|
description: 'Smart LED filament candle (E12)',
|
|
272
|
-
extend:
|
|
281
|
+
extend: sengledExtend.light_onoff_brightness(),
|
|
273
282
|
ota: ota.zigbeeOTA,
|
|
274
283
|
},
|
|
275
284
|
];
|
package/devices/tuya.js
CHANGED
|
@@ -13,6 +13,7 @@ const zosung = require('../lib/zosung');
|
|
|
13
13
|
const fzZosung = zosung.fzZosung;
|
|
14
14
|
const tzZosung = zosung.tzZosung;
|
|
15
15
|
const ez = zosung.presetsZosung;
|
|
16
|
+
const globalStore = require('../lib/store');
|
|
16
17
|
|
|
17
18
|
const TS011Fplugs = ['_TZ3000_5f43h46b', '_TZ3000_cphmq0q7', '_TZ3000_dpo1ysak', '_TZ3000_ew3ldmgx', '_TZ3000_gjnozsaz',
|
|
18
19
|
'_TZ3000_jvzvulen', '_TZ3000_mraovvmm', '_TZ3000_nfnmi125', '_TZ3000_ps3dmato', '_TZ3000_w0qqde0g', '_TZ3000_u5u4cakc',
|
|
@@ -128,6 +129,131 @@ const tzLocal = {
|
|
|
128
129
|
}
|
|
129
130
|
},
|
|
130
131
|
},
|
|
132
|
+
x5h_thermostat: {
|
|
133
|
+
key: ['system_mode', 'current_heating_setpoint', 'sensor', 'brightness_state', 'sound', 'frost_protection', 'week', 'factory_reset',
|
|
134
|
+
'local_temperature_calibration', 'heating_temp_limit', 'deadzone_temperature', 'upper_temp', 'preset', 'child_lock',
|
|
135
|
+
'schedule'],
|
|
136
|
+
convertSet: async (entity, key, value, meta) => {
|
|
137
|
+
switch (key) {
|
|
138
|
+
case 'system_mode':
|
|
139
|
+
await tuya.sendDataPointBool(entity, tuya.dataPoints.x5hState, value === 'heat');
|
|
140
|
+
break;
|
|
141
|
+
case 'preset': {
|
|
142
|
+
value = value.toLowerCase();
|
|
143
|
+
const lookup = {manual: 0, program: 1};
|
|
144
|
+
utils.validateValue(value, Object.keys(lookup));
|
|
145
|
+
value = lookup[value];
|
|
146
|
+
await tuya.sendDataPointEnum(entity, tuya.dataPoints.x5hMode, value);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case 'upper_temp':
|
|
150
|
+
if (value >= 35 && value <= 95) {
|
|
151
|
+
await tuya.sendDataPointValue(entity, tuya.dataPoints.x5hSetTempCeiling, value);
|
|
152
|
+
const setpoint = globalStore.getValue(entity, 'currentHeatingSetpoint', 20);
|
|
153
|
+
const setpointRaw = Math.round(setpoint * 10);
|
|
154
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
155
|
+
await tuya.sendDataPointValue(entity, tuya.dataPoints.x5hSetTemp, setpointRaw);
|
|
156
|
+
} else {
|
|
157
|
+
throw new Error('Supported values are in range [35, 95]');
|
|
158
|
+
}
|
|
159
|
+
break;
|
|
160
|
+
case 'deadzone_temperature':
|
|
161
|
+
if (value >= 0.5 && value <= 9.5) {
|
|
162
|
+
value = Math.round(value * 10);
|
|
163
|
+
await tuya.sendDataPointValue(entity, tuya.dataPoints.x5hTempDiff, value);
|
|
164
|
+
} else {
|
|
165
|
+
throw new Error('Supported values are in range [0.5, 9.5]');
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
case 'heating_temp_limit':
|
|
169
|
+
if (value >= 5 && value <= 60) {
|
|
170
|
+
await tuya.sendDataPointValue(entity, tuya.dataPoints.x5hProtectionTempLimit, value);
|
|
171
|
+
} else {
|
|
172
|
+
throw new Error('Supported values are in range [5, 60]');
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
case 'local_temperature_calibration':
|
|
176
|
+
if (value >= -9.9 && value <= 9.9) {
|
|
177
|
+
value = Math.round(value * 10);
|
|
178
|
+
|
|
179
|
+
if (value < 0) {
|
|
180
|
+
value = 0xFFFFFFFF + value + 1;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
await tuya.sendDataPointValue(entity, tuya.dataPoints.x5hTempCorrection, value);
|
|
184
|
+
} else {
|
|
185
|
+
throw new Error('Supported values are in range [-9.9, 9.9]');
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
case 'factory_reset':
|
|
189
|
+
await tuya.sendDataPointBool(entity, tuya.dataPoints.x5hFactoryReset, value === 'ON');
|
|
190
|
+
break;
|
|
191
|
+
case 'week':
|
|
192
|
+
await tuya.sendDataPointEnum(entity, tuya.dataPoints.x5hWorkingDaySetting,
|
|
193
|
+
utils.getKey(tuya.thermostatWeekFormat, value, value, Number));
|
|
194
|
+
break;
|
|
195
|
+
case 'frost_protection':
|
|
196
|
+
await tuya.sendDataPointBool(entity, tuya.dataPoints.x5hFrostProtection, value === 'ON');
|
|
197
|
+
break;
|
|
198
|
+
case 'sound':
|
|
199
|
+
await tuya.sendDataPointBool(entity, tuya.dataPoints.x5hSound, value === 'ON');
|
|
200
|
+
break;
|
|
201
|
+
case 'brightness_state': {
|
|
202
|
+
value = value.toLowerCase();
|
|
203
|
+
const lookup = {off: 0, low: 1, medium: 2, high: 3};
|
|
204
|
+
utils.validateValue(value, Object.keys(lookup));
|
|
205
|
+
value = lookup[value];
|
|
206
|
+
await tuya.sendDataPointEnum(entity, tuya.dataPoints.x5hBackplaneBrightness, value);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case 'sensor': {
|
|
210
|
+
value = value.toLowerCase();
|
|
211
|
+
const lookup = {'internal': 0, 'external': 1, 'both': 2};
|
|
212
|
+
utils.validateValue(value, Object.keys(lookup));
|
|
213
|
+
value = lookup[value];
|
|
214
|
+
await tuya.sendDataPointEnum(entity, tuya.dataPoints.x5hSensorSelection, value);
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case 'current_heating_setpoint':
|
|
218
|
+
if (value >= 5 && value <= 60) {
|
|
219
|
+
value = Math.round(value * 10);
|
|
220
|
+
await tuya.sendDataPointValue(entity, tuya.dataPoints.x5hSetTemp, value);
|
|
221
|
+
} else {
|
|
222
|
+
throw new Error(`Unsupported value: ${value}`);
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
case 'child_lock':
|
|
226
|
+
await tuya.sendDataPointBool(entity, tuya.dataPoints.x5hChildLock, value === 'LOCK');
|
|
227
|
+
break;
|
|
228
|
+
case 'schedule': {
|
|
229
|
+
const periods = value.split(' ');
|
|
230
|
+
const periodsNumber = 8;
|
|
231
|
+
const payload = [];
|
|
232
|
+
|
|
233
|
+
for (let i = 0; i < periodsNumber; i++) {
|
|
234
|
+
const timeTemp = periods[i].split('/');
|
|
235
|
+
const hm = timeTemp[0].split(':', 2);
|
|
236
|
+
const h = parseInt(hm[0]);
|
|
237
|
+
const m = parseInt(hm[1]);
|
|
238
|
+
const temp = parseFloat(timeTemp[1]);
|
|
239
|
+
|
|
240
|
+
if (h < 0 || h >= 24 || m < 0 || m >= 60 || temp < 5 || temp > 60) {
|
|
241
|
+
throw new Error('Invalid hour, minute or temperature of: ' + periods[i]);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const tempHexArray = tuya.convertDecimalValueTo2ByteHexArray(Math.round(temp * 10));
|
|
245
|
+
// 1 byte for hour, 1 byte for minutes, 2 bytes for temperature
|
|
246
|
+
payload.push(h, m, ...tempHexArray);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
await tuya.sendDataPointRaw(entity, tuya.dataPoints.x5hWeeklyProcedure, payload);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
default:
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
},
|
|
131
257
|
temperature_unit: {
|
|
132
258
|
key: ['temperature_unit'],
|
|
133
259
|
convertSet: async (entity, key, value, meta) => {
|
|
@@ -275,6 +401,114 @@ const fzLocal = {
|
|
|
275
401
|
return result;
|
|
276
402
|
},
|
|
277
403
|
},
|
|
404
|
+
x5h_thermostat: {
|
|
405
|
+
cluster: 'manuSpecificTuya',
|
|
406
|
+
type: ['commandDataResponse', 'commandDataReport'],
|
|
407
|
+
convert: (model, msg, publish, options, meta) => {
|
|
408
|
+
const dpValue = tuya.firstDpValue(msg, meta, 'x5h_thermostat');
|
|
409
|
+
const dp = dpValue.dp;
|
|
410
|
+
const value = tuya.getDataValue(dpValue);
|
|
411
|
+
|
|
412
|
+
switch (dp) {
|
|
413
|
+
case tuya.dataPoints.x5hState: {
|
|
414
|
+
return {system_mode: value ? 'heat' : 'off'};
|
|
415
|
+
}
|
|
416
|
+
case tuya.dataPoints.x5hWorkingStatus: {
|
|
417
|
+
return {running_state: value ? 'heat' : 'idle'};
|
|
418
|
+
}
|
|
419
|
+
case tuya.dataPoints.x5hSound: {
|
|
420
|
+
return {sound: value ? 'ON' : 'OFF'};
|
|
421
|
+
}
|
|
422
|
+
case tuya.dataPoints.x5hFrostProtection: {
|
|
423
|
+
return {frost_protection: value ? 'ON' : 'OFF'};
|
|
424
|
+
}
|
|
425
|
+
case tuya.dataPoints.x5hWorkingDaySetting: {
|
|
426
|
+
return {week: tuya.thermostatWeekFormat[value]};
|
|
427
|
+
}
|
|
428
|
+
case tuya.dataPoints.x5hFactoryReset: {
|
|
429
|
+
if (value) {
|
|
430
|
+
clearTimeout(globalStore.getValue(msg.endpoint, 'factoryResetTimer'));
|
|
431
|
+
const timer = setTimeout(() => publish({factory_reset: 'OFF'}), 60 * 1000);
|
|
432
|
+
globalStore.putValue(msg.endpoint, 'factoryResetTimer', timer);
|
|
433
|
+
meta.logger.info('The thermostat is resetting now. It will be available in 1 minute.');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return {factory_reset: value ? 'ON' : 'OFF'};
|
|
437
|
+
}
|
|
438
|
+
case tuya.dataPoints.x5hTempDiff: {
|
|
439
|
+
return {deadzone_temperature: parseFloat((value / 10).toFixed(1))};
|
|
440
|
+
}
|
|
441
|
+
case tuya.dataPoints.x5hProtectionTempLimit: {
|
|
442
|
+
return {heating_temp_limit: value};
|
|
443
|
+
}
|
|
444
|
+
case tuya.dataPoints.x5hBackplaneBrightness: {
|
|
445
|
+
const lookup = {0: 'off', 1: 'low', 2: 'medium', 3: 'high'};
|
|
446
|
+
|
|
447
|
+
if (value >= 0 && value <= 3) {
|
|
448
|
+
globalStore.putValue(msg.endpoint, 'brightnessState', value);
|
|
449
|
+
return {brightness_state: lookup[value]};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Sometimes, for example on thermostat restart, it sends message like:
|
|
453
|
+
// {"dpValues":[{"data":{"data":[90],"type":"Buffer"},"datatype":4,"dp":104}
|
|
454
|
+
// It doesn't represent any brightness value and brightness remains the previous value
|
|
455
|
+
const lastValue = globalStore.getValue(msg.endpoint, 'brightnessState') || 1;
|
|
456
|
+
return {brightness_state: lookup[lastValue]};
|
|
457
|
+
}
|
|
458
|
+
case tuya.dataPoints.x5hWeeklyProcedure: {
|
|
459
|
+
const periods = [];
|
|
460
|
+
const periodSize = 4;
|
|
461
|
+
const periodsNumber = 8;
|
|
462
|
+
|
|
463
|
+
for (let i = 0; i < periodsNumber; i++) {
|
|
464
|
+
const hours = value[i * periodSize];
|
|
465
|
+
const minutes = value[i * periodSize + 1];
|
|
466
|
+
const tempHexArray = [value[i * periodSize + 2], value[i * periodSize + 3]];
|
|
467
|
+
const tempRaw = Buffer.from(tempHexArray).readUIntBE(0, tempHexArray.length);
|
|
468
|
+
const strHours = hours.toString().padStart(2, '0');
|
|
469
|
+
const strMinutes = minutes.toString().padStart(2, '0');
|
|
470
|
+
const temp = parseFloat((tempRaw / 10).toFixed(1));
|
|
471
|
+
periods.push(`${strHours}:${strMinutes}/${temp}`);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const schedule = periods.join(' ');
|
|
475
|
+
return {schedule};
|
|
476
|
+
}
|
|
477
|
+
case tuya.dataPoints.x5hChildLock: {
|
|
478
|
+
return {child_lock: value ? 'LOCK' : 'UNLOCK'};
|
|
479
|
+
}
|
|
480
|
+
case tuya.dataPoints.x5hSetTemp: {
|
|
481
|
+
const setpoint = parseFloat((value / 10).toFixed(1));
|
|
482
|
+
globalStore.putValue(msg.endpoint, 'currentHeatingSetpoint', setpoint);
|
|
483
|
+
return {current_heating_setpoint: setpoint};
|
|
484
|
+
}
|
|
485
|
+
case tuya.dataPoints.x5hSetTempCeiling: {
|
|
486
|
+
return {upper_temp: value};
|
|
487
|
+
}
|
|
488
|
+
case tuya.dataPoints.x5hCurrentTemp: {
|
|
489
|
+
const temperature = value & (1 << 15) ? value - (1 << 16) + 1 : value;
|
|
490
|
+
return {local_temperature: parseFloat((temperature / 10).toFixed(1))};
|
|
491
|
+
}
|
|
492
|
+
case tuya.dataPoints.x5hTempCorrection: {
|
|
493
|
+
return {local_temperature_calibration: parseFloat((value / 10).toFixed(1))};
|
|
494
|
+
}
|
|
495
|
+
case tuya.dataPoints.x5hMode: {
|
|
496
|
+
const lookup = {0: 'manual', 1: 'program'};
|
|
497
|
+
return {preset: lookup[value]};
|
|
498
|
+
}
|
|
499
|
+
case tuya.dataPoints.x5hSensorSelection: {
|
|
500
|
+
const lookup = {0: 'internal', 1: 'external', 2: 'both'};
|
|
501
|
+
return {sensor: lookup[value]};
|
|
502
|
+
}
|
|
503
|
+
case tuya.dataPoints.x5hOutputReverse: {
|
|
504
|
+
return {output_reverse: value};
|
|
505
|
+
}
|
|
506
|
+
default: {
|
|
507
|
+
meta.logger.warn(`fromZigbee:x5h_thermostat: Unrecognized DP #${dp} with data ${JSON.stringify(dpValue)}`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
},
|
|
278
512
|
humidity10: {
|
|
279
513
|
cluster: 'msRelativeHumidity',
|
|
280
514
|
type: ['attributeReport', 'readResponse'],
|
|
@@ -1968,13 +2202,20 @@ module.exports = [
|
|
|
1968
2202
|
vendor: 'TuYa',
|
|
1969
2203
|
description: 'Smart light switch - 3 gang without neutral wire',
|
|
1970
2204
|
extend: extend.switch(),
|
|
1971
|
-
|
|
2205
|
+
toZigbee: extend.switch().toZigbee.concat([tz.moes_power_on_behavior, tz.tuya_backlight_mode]),
|
|
2206
|
+
fromZigbee: extend.switch().fromZigbee.concat([fz.moes_power_on_behavior, fz.tuya_backlight_mode]),
|
|
2207
|
+
exposes: [e.switch().withEndpoint('left'), e.switch().withEndpoint('center'), e.switch().withEndpoint('right'),
|
|
2208
|
+
exposes.enum('power_on_behavior', ea.ALL, Object.values(tuya.moesSwitch.powerOnBehavior)),
|
|
2209
|
+
exposes.enum('backlight_mode', ea.ALL, ['LOW', 'MEDIUM', 'HIGH'])
|
|
2210
|
+
.withDescription('Indicator light status: LOW: Off | MEDIUM: On| HIGH: Inverted')],
|
|
1972
2211
|
endpoint: (device) => {
|
|
1973
2212
|
return {'left': 1, 'center': 2, 'right': 3};
|
|
1974
2213
|
},
|
|
1975
2214
|
whiteLabel: [{vendor: 'TUYATEC', model: 'GDKES-03TZXD'}],
|
|
1976
2215
|
meta: {multiEndpoint: true},
|
|
1977
2216
|
configure: async (device, coordinatorEndpoint, logger) => {
|
|
2217
|
+
await device.getEndpoint(1).read('genBasic',
|
|
2218
|
+
['manufacturerName', 'zclVersion', 'appVersion', 'modelId', 'powerSource', 0xfffe]);
|
|
1978
2219
|
try {
|
|
1979
2220
|
for (const ID of [1, 2, 3]) {
|
|
1980
2221
|
const endpoint = device.getEndpoint(ID);
|
|
@@ -2215,6 +2456,39 @@ module.exports = [
|
|
|
2215
2456
|
.withLocalTemperature(ea.STATE)
|
|
2216
2457
|
.withSystemMode(['off', 'auto', 'heat'], ea.STATE_SET).withRunningState(['idle', 'heat'], ea.STATE)],
|
|
2217
2458
|
},
|
|
2459
|
+
{
|
|
2460
|
+
fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_2ekuz3dz'}],
|
|
2461
|
+
model: 'X5H-GB-B',
|
|
2462
|
+
vendor: 'TuYa',
|
|
2463
|
+
description: 'Wall-mount thermostat',
|
|
2464
|
+
fromZigbee: [fz.ignore_basic_report, fzLocal.x5h_thermostat],
|
|
2465
|
+
toZigbee: [tzLocal.x5h_thermostat],
|
|
2466
|
+
whiteLabel: [{vendor: 'Beok', model: 'TGR85-ZB'}],
|
|
2467
|
+
exposes: [
|
|
2468
|
+
exposes.climate().withSetpoint('current_heating_setpoint', 5, 60, 0.5, ea.STATE_SET)
|
|
2469
|
+
.withLocalTemperature(ea.STATE).withLocalTemperatureCalibration(-9.9, 9.9, 0.1, ea.STATE_SET)
|
|
2470
|
+
.withSystemMode(['off', 'heat'], ea.STATE_SET).withRunningState(['idle', 'heat'], ea.STATE)
|
|
2471
|
+
.withPreset(['manual', 'program']).withSensor(['internal', 'external', 'both'], ea.STATE_SET),
|
|
2472
|
+
exposes.text('schedule', ea.STATE_SET), e.child_lock(), e.week(),
|
|
2473
|
+
exposes.enum('brightness_state', ea.STATE_SET, ['off', 'low', 'medium', 'high'])
|
|
2474
|
+
.withDescription('Screen brightness'),
|
|
2475
|
+
exposes.binary('sound', ea.STATE_SET, 'ON', 'OFF')
|
|
2476
|
+
.withDescription('Switches beep sound when interacting with thermostat'),
|
|
2477
|
+
exposes.binary('frost_protection', ea.STATE_SET, 'ON', 'OFF')
|
|
2478
|
+
.withDescription('Antifreeze function'),
|
|
2479
|
+
exposes.binary('factory_reset', ea.STATE_SET, 'ON', 'OFF')
|
|
2480
|
+
.withDescription('Resets all settings to default. Doesn\'t unpair device.'),
|
|
2481
|
+
exposes.numeric('heating_temp_limit', ea.STATE_SET).withUnit('°C').withValueMax(60)
|
|
2482
|
+
.withValueMin(5).withValueStep(1).withPreset('default', 35, 'Default value')
|
|
2483
|
+
.withDescription('Heating temperature limit'),
|
|
2484
|
+
exposes.numeric('deadzone_temperature', ea.STATE_SET).withUnit('°C').withValueMax(9.5)
|
|
2485
|
+
.withValueMin(0.5).withValueStep(0.5).withPreset('default', 1, 'Default value')
|
|
2486
|
+
.withDescription('The delta between local_temperature and current_heating_setpoint to trigger Heat'),
|
|
2487
|
+
exposes.numeric('upper_temp', ea.STATE_SET).withUnit('°C').withValueMax(95)
|
|
2488
|
+
.withValueMin(35).withValueStep(1).withPreset('default', 60, 'Default value'),
|
|
2489
|
+
],
|
|
2490
|
+
onEvent: tuya.onEventSetTime,
|
|
2491
|
+
},
|
|
2218
2492
|
{
|
|
2219
2493
|
fingerprint: [{modelID: 'TS0222', manufacturerName: '_TYZB01_4mdqxxnn'},
|
|
2220
2494
|
{modelID: 'TS0222', manufacturerName: '_TYZB01_m6ec2pgj'}],
|
package/devices/ubisys.js
CHANGED
|
@@ -4,6 +4,7 @@ const tz = require('../converters/toZigbee');
|
|
|
4
4
|
const ota = require('../lib/ota');
|
|
5
5
|
const utils = require('../lib/utils');
|
|
6
6
|
const reporting = require('../lib/reporting');
|
|
7
|
+
const constants = require('../lib/constants');
|
|
7
8
|
const herdsman = require('zigbee-herdsman');
|
|
8
9
|
const e = exposes.presets;
|
|
9
10
|
const ea = exposes.access;
|
|
@@ -90,6 +91,15 @@ const ubisys = {
|
|
|
90
91
|
return {configure_device_setup: result};
|
|
91
92
|
},
|
|
92
93
|
},
|
|
94
|
+
thermostat_vacation_mode: {
|
|
95
|
+
cluster: 'hvacThermostat',
|
|
96
|
+
type: ['attributeReport', 'readResponse'],
|
|
97
|
+
convert: (model, msg, publish, options, meta) => {
|
|
98
|
+
if (msg.data.hasOwnProperty('ocupancy')) {
|
|
99
|
+
return {vacation_mode: msg.data.ocupancy === 0};
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
},
|
|
93
103
|
},
|
|
94
104
|
tz: {
|
|
95
105
|
configure_j1: {
|
|
@@ -503,6 +513,12 @@ const ubisys = {
|
|
|
503
513
|
manufacturerOptions.ubisysNull);
|
|
504
514
|
},
|
|
505
515
|
},
|
|
516
|
+
thermostat_vacation_mode: {
|
|
517
|
+
key: ['vacation_mode'],
|
|
518
|
+
convertGet: async (entity, key, meta) => {
|
|
519
|
+
await entity.read('hvacThermostat', ['ocupancy']);
|
|
520
|
+
},
|
|
521
|
+
},
|
|
506
522
|
},
|
|
507
523
|
};
|
|
508
524
|
|
|
@@ -771,4 +787,65 @@ module.exports = [
|
|
|
771
787
|
},
|
|
772
788
|
ota: ota.ubisys,
|
|
773
789
|
},
|
|
790
|
+
{
|
|
791
|
+
zigbeeModel: ['H1'],
|
|
792
|
+
model: 'H1',
|
|
793
|
+
vendor: 'Ubisys',
|
|
794
|
+
description: 'Heating regulator',
|
|
795
|
+
fromZigbee: [fz.battery, fz.thermostat, fz.thermostat_weekly_schedule, ubisys.fz.thermostat_vacation_mode],
|
|
796
|
+
toZigbee: [
|
|
797
|
+
tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint,
|
|
798
|
+
tz.thermostat_local_temperature, tz.thermostat_system_mode,
|
|
799
|
+
tz.thermostat_weekly_schedule, tz.thermostat_clear_weekly_schedule,
|
|
800
|
+
tz.thermostat_running_mode, ubisys.tz.thermostat_vacation_mode,
|
|
801
|
+
tz.thermostat_pi_heating_demand,
|
|
802
|
+
],
|
|
803
|
+
exposes: [
|
|
804
|
+
e.battery(),
|
|
805
|
+
exposes.climate()
|
|
806
|
+
.withSystemMode(['off', 'heat'], ea.ALL)
|
|
807
|
+
.withRunningMode(['off', 'heat'])
|
|
808
|
+
.withSetpoint('occupied_heating_setpoint', 7, 30, 0.5)
|
|
809
|
+
.withLocalTemperature()
|
|
810
|
+
.withPiHeatingDemand(ea.STATE_GET),
|
|
811
|
+
exposes.binary('vacation_mode', ea.STATE_GET, true, false)
|
|
812
|
+
.withDescription('When Vacation Mode is active the schedule is disabled and unoccupied_heating_setpoint is used.'),
|
|
813
|
+
],
|
|
814
|
+
configure: async (device, coordinatorEndpoint, logger) => {
|
|
815
|
+
const endpoint = device.getEndpoint(1);
|
|
816
|
+
const binds = ['genBasic', 'genPowerCfg', 'genTime', 'hvacThermostat'];
|
|
817
|
+
await reporting.bind(endpoint, coordinatorEndpoint, binds);
|
|
818
|
+
|
|
819
|
+
// reporting
|
|
820
|
+
// NOTE: temperature is 0.5 deg steps
|
|
821
|
+
// NOTE: unoccupied_heating_setpoint cannot be set via the device itself
|
|
822
|
+
// so we do not need to setup reporting for this, as reporting slots
|
|
823
|
+
// seem to be limited.
|
|
824
|
+
await reporting.thermostatSystemMode(endpoint);
|
|
825
|
+
await reporting.thermostatRunningMode(endpoint);
|
|
826
|
+
await reporting.thermostatTemperature(endpoint,
|
|
827
|
+
{min: 0, max: constants.repInterval.HOUR, change: 50});
|
|
828
|
+
await reporting.thermostatOccupiedHeatingSetpoint(endpoint,
|
|
829
|
+
{min: 0, max: constants.repInterval.HOUR, change: 50});
|
|
830
|
+
await reporting.thermostatPIHeatingDemand(endpoint);
|
|
831
|
+
await reporting.thermostatOcupancy(endpoint);
|
|
832
|
+
await reporting.batteryPercentageRemaining(endpoint,
|
|
833
|
+
{min: constants.repInterval.HOUR, max: 43200, change: 1});
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
// read attributes
|
|
837
|
+
// NOTE: configuring reporting on hvacThermostat seems to trigger an imediat
|
|
838
|
+
// report, so the values are available after configure has run.
|
|
839
|
+
// this does not seem to be the case for genPowerCfg, so we read
|
|
840
|
+
// the battery percentage
|
|
841
|
+
await endpoint.read('genPowerCfg', ['batteryPercentageRemaining']);
|
|
842
|
+
|
|
843
|
+
// write attributes
|
|
844
|
+
// NOTE: device checks in every 1h once the device has entered deepsleep
|
|
845
|
+
// this might be a bit long if you want to set the temperature remotely
|
|
846
|
+
// update this to every 15 minutes. (value is in 1/4th of a second)
|
|
847
|
+
await endpoint.write('genPollCtrl', {'checkinInterval': (4 * 60 * 15)});
|
|
848
|
+
},
|
|
849
|
+
ota: ota.ubisys,
|
|
850
|
+
},
|
|
774
851
|
];
|
package/lib/exposes.js
CHANGED
|
@@ -407,6 +407,14 @@ class Climate extends Base {
|
|
|
407
407
|
return this;
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
+
withRunningMode(modes, access=a.STATE_GET) {
|
|
411
|
+
assert(!this.endpoint, 'Cannot add feature after adding endpoint');
|
|
412
|
+
const allowed = ['off', 'cool', 'heat'];
|
|
413
|
+
modes.forEach((m) => assert(allowed.includes(m)));
|
|
414
|
+
this.features.push(new Enum('running_mode', access, modes).withDescription('The current running mode'));
|
|
415
|
+
return this;
|
|
416
|
+
}
|
|
417
|
+
|
|
410
418
|
withFanMode(modes, access=a.ALL) {
|
|
411
419
|
assert(!this.endpoint, 'Cannot add feature after adding endpoint');
|
|
412
420
|
const allowed = ['off', 'low', 'medium', 'high', 'on', 'auto', 'smart'];
|
package/lib/reporting.js
CHANGED
|
@@ -170,6 +170,14 @@ module.exports = {
|
|
|
170
170
|
const p = payload('runningState', 0, repInterval.HOUR, 0, overrides);
|
|
171
171
|
await endpoint.configureReporting('hvacThermostat', p);
|
|
172
172
|
},
|
|
173
|
+
thermostatRunningMode: async (endpoint, overrides) => {
|
|
174
|
+
const p = payload('runningMode', 0, repInterval.HOUR, 0, overrides);
|
|
175
|
+
await endpoint.configureReporting('hvacThermostat', p);
|
|
176
|
+
},
|
|
177
|
+
thermostatOcupancy: async (endpoint, overrides) => {
|
|
178
|
+
const p = payload('ocupancy', 0, repInterval.HOUR, 0, overrides);
|
|
179
|
+
await endpoint.configureReporting('hvacThermostat', p);
|
|
180
|
+
},
|
|
173
181
|
thermostatTemperatureSetpointHold: async (endpoint, overrides) => {
|
|
174
182
|
const p = payload('tempSetpointHold', 0, repInterval.HOUR, 0, overrides);
|
|
175
183
|
await endpoint.configureReporting('hvacThermostat', p);
|
package/lib/tuya.js
CHANGED
|
@@ -669,6 +669,26 @@ const dataPoints = {
|
|
|
669
669
|
// Moes switch with optional neutral
|
|
670
670
|
moesSwitchPowerOnBehavior: 14,
|
|
671
671
|
moesSwitchIndicateLight: 15,
|
|
672
|
+
// X5H thermostat
|
|
673
|
+
x5hState: 1,
|
|
674
|
+
x5hMode: 2,
|
|
675
|
+
x5hWorkingStatus: 3,
|
|
676
|
+
x5hSound: 7,
|
|
677
|
+
x5hFrostProtection: 10,
|
|
678
|
+
x5hSetTemp: 16,
|
|
679
|
+
x5hSetTempCeiling: 19,
|
|
680
|
+
x5hCurrentTemp: 24,
|
|
681
|
+
x5hTempCorrection: 27,
|
|
682
|
+
x5hWeeklyProcedure: 30,
|
|
683
|
+
x5hWorkingDaySetting: 31,
|
|
684
|
+
x5hFactoryReset: 39,
|
|
685
|
+
x5hChildLock: 40,
|
|
686
|
+
x5hSensorSelection: 43,
|
|
687
|
+
x5hFaultAlarm: 45,
|
|
688
|
+
x5hTempDiff: 101,
|
|
689
|
+
x5hProtectionTempLimit: 102,
|
|
690
|
+
x5hOutputReverse: 103,
|
|
691
|
+
x5hBackplaneBrightness: 104,
|
|
672
692
|
// Connecte thermostat
|
|
673
693
|
connecteState: 1,
|
|
674
694
|
connecteMode: 2,
|