zigbee-herdsman-converters 14.0.444 → 14.0.445
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 +34 -38
- package/converters/toZigbee.js +26 -0
- package/devices/livolo.js +22 -5
- package/devices/woox.js +2 -2
- package/devices/xiaomi.js +1 -1
- package/lib/ota/zigbeeOTA.js +4 -2
- package/lib/utils.js +1 -2
- package/lib/xiaomi.js +13 -3
- package/npm-shrinkwrap.json +1 -1
- package/package.json +1 -1
package/converters/fromZigbee.js
CHANGED
|
@@ -428,7 +428,7 @@ const converters = {
|
|
|
428
428
|
convert: (model, msg, publish, options, meta) => {
|
|
429
429
|
if (msg.data.hasOwnProperty('occupancy')) {
|
|
430
430
|
const payload = {occupancy: (msg.data.occupancy % 2) > 0};
|
|
431
|
-
utils.noOccupancySince(msg.endpoint,
|
|
431
|
+
utils.noOccupancySince(msg.endpoint, options, publish, payload.occupancy ? 'stop' : 'start');
|
|
432
432
|
return payload;
|
|
433
433
|
}
|
|
434
434
|
},
|
|
@@ -464,7 +464,7 @@ const converters = {
|
|
|
464
464
|
}
|
|
465
465
|
|
|
466
466
|
const payload = {occupancy: true};
|
|
467
|
-
utils.noOccupancySince(msg.endpoint,
|
|
467
|
+
utils.noOccupancySince(msg.endpoint, options, publish, 'start');
|
|
468
468
|
return payload;
|
|
469
469
|
},
|
|
470
470
|
},
|
|
@@ -2790,6 +2790,33 @@ const converters = {
|
|
|
2790
2790
|
}
|
|
2791
2791
|
},
|
|
2792
2792
|
},
|
|
2793
|
+
livolo_new_switch_state_4gang: {
|
|
2794
|
+
cluster: 'genPowerCfg',
|
|
2795
|
+
type: ['raw'],
|
|
2796
|
+
convert: (model, msg, publish, options, meta) => {
|
|
2797
|
+
const stateHeader = Buffer.from([122, 209]);
|
|
2798
|
+
if (msg.data.indexOf(stateHeader) === 0) {
|
|
2799
|
+
if (msg.data[10] === 7) {
|
|
2800
|
+
const status = msg.data[14];
|
|
2801
|
+
return {
|
|
2802
|
+
state_left: status & 1 ? 'ON' : 'OFF',
|
|
2803
|
+
state_right: status & 2 ? 'ON' : 'OFF',
|
|
2804
|
+
state_bottom_left: status & 4 ? 'ON' : 'OFF',
|
|
2805
|
+
state_bottom_right: status & 8 ? 'ON' : 'OFF',
|
|
2806
|
+
};
|
|
2807
|
+
}
|
|
2808
|
+
if (msg.data[10] === 13) {
|
|
2809
|
+
const status = msg.data[13];
|
|
2810
|
+
return {
|
|
2811
|
+
state_left: status & 1 ? 'ON' : 'OFF',
|
|
2812
|
+
state_right: status & 2 ? 'ON' : 'OFF',
|
|
2813
|
+
state_bottom_left: status & 4 ? 'ON' : 'OFF',
|
|
2814
|
+
state_bottom_right: status & 8 ? 'ON' : 'OFF',
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
},
|
|
2819
|
+
},
|
|
2793
2820
|
livolo_curtain_switch_state: {
|
|
2794
2821
|
cluster: 'genPowerCfg',
|
|
2795
2822
|
type: ['raw'],
|
|
@@ -2949,7 +2976,8 @@ const converters = {
|
|
|
2949
2976
|
meta.device.modelID = 'TI0001-socket';
|
|
2950
2977
|
meta.device.save();
|
|
2951
2978
|
}
|
|
2952
|
-
|
|
2979
|
+
// No need to detect this switches, will be done by universal procedure
|
|
2980
|
+
/* if (msg.data.includes(Buffer.from([19, 1, 0]), 13)) {
|
|
2953
2981
|
// new switch, hack
|
|
2954
2982
|
meta.device.modelID = 'TI0001-switch';
|
|
2955
2983
|
meta.device.save();
|
|
@@ -2958,7 +2986,7 @@ const converters = {
|
|
|
2958
2986
|
// new switch, hack
|
|
2959
2987
|
meta.device.modelID = 'TI0001-switch-2gang';
|
|
2960
2988
|
meta.device.save();
|
|
2961
|
-
}
|
|
2989
|
+
}*/
|
|
2962
2990
|
if (msg.data.includes(Buffer.from([19, 5, 0]), 13)) {
|
|
2963
2991
|
if (meta.logger) meta.logger.debug('Detected Livolo Curtain Switch');
|
|
2964
2992
|
// curtain switch, hack
|
|
@@ -5231,40 +5259,8 @@ const converters = {
|
|
|
5231
5259
|
type: ['attributeReport', 'readResponse'],
|
|
5232
5260
|
options: [exposes.options.precision('temperature'), exposes.options.calibration('temperature')],
|
|
5233
5261
|
convert: (model, msg, publish, options, meta) => {
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
const payload = {};
|
|
5237
|
-
|
|
5238
|
-
if (data.hasOwnProperty('100')) {
|
|
5239
|
-
if (['QBKG03LM', 'QBKG12LM', 'LLKZMK11LM'].includes(model.model)) {
|
|
5240
|
-
const mapping = model.model === 'LLKZMK11LM' ? ['l1', 'l2'] : ['left', 'right'];
|
|
5241
|
-
payload[`state_${mapping[0]}`] = data['100'] === 1 ? 'ON' : 'OFF';
|
|
5242
|
-
payload[`state_${mapping[1]}`] = data['101'] === 1 ? 'ON' : 'OFF';
|
|
5243
|
-
} else {
|
|
5244
|
-
payload.state = data['100'] === 1 ? 'ON' : 'OFF';
|
|
5245
|
-
}
|
|
5246
|
-
}
|
|
5247
|
-
|
|
5248
|
-
if (data.hasOwnProperty('152')) {
|
|
5249
|
-
payload.power = precisionRound(data['152'], 2);
|
|
5250
|
-
}
|
|
5251
|
-
|
|
5252
|
-
if (data.hasOwnProperty('149')) {
|
|
5253
|
-
// Consumption is deprecated
|
|
5254
|
-
payload.consumption = precisionRound(data['149'], 2);
|
|
5255
|
-
payload.energy = precisionRound(data['149'], 2);
|
|
5256
|
-
}
|
|
5257
|
-
|
|
5258
|
-
if (data.hasOwnProperty('3')) {
|
|
5259
|
-
payload.temperature = calibrateAndPrecisionRoundOptions(data['3'], options, 'temperature');
|
|
5260
|
-
}
|
|
5261
|
-
|
|
5262
|
-
if (data.hasOwnProperty('150')) {
|
|
5263
|
-
payload.voltage = precisionRound(data['150'] * 0.1, 1);
|
|
5264
|
-
}
|
|
5265
|
-
|
|
5266
|
-
return payload;
|
|
5267
|
-
}
|
|
5262
|
+
const payload = xiaomi.numericAttributes2Payload(msg, meta, model, options, msg.data);
|
|
5263
|
+
return payload;
|
|
5268
5264
|
},
|
|
5269
5265
|
},
|
|
5270
5266
|
xiaomi_basic_raw: {
|
package/converters/toZigbee.js
CHANGED
|
@@ -1599,13 +1599,23 @@ const converters = {
|
|
|
1599
1599
|
}
|
|
1600
1600
|
|
|
1601
1601
|
const state = value.toLowerCase();
|
|
1602
|
+
let oldstate = 1;
|
|
1603
|
+
if (state === 'on') {
|
|
1604
|
+
oldstate = 108;
|
|
1605
|
+
}
|
|
1606
|
+
let channel = 1.0;
|
|
1602
1607
|
const postfix = meta.endpoint_name || 'left';
|
|
1603
1608
|
await entity.command('genOnOff', 'toggle', {}, {transactionSequenceNumber: 0});
|
|
1604
1609
|
const payloadOn = {0x0001: {value: Buffer.from([1, 0, 0, 0, 0, 0, 0, 0]), type: 1}};
|
|
1605
1610
|
const payloadOff = {0x0001: {value: Buffer.from([0, 0, 0, 0, 0, 0, 0, 0]), type: 1}};
|
|
1606
1611
|
const payloadOnRight = {0x0001: {value: Buffer.from([2, 0, 0, 0, 0, 0, 0, 0]), type: 2}};
|
|
1607
1612
|
const payloadOffRight = {0x0001: {value: Buffer.from([0, 0, 0, 0, 0, 0, 0, 0]), type: 2}};
|
|
1613
|
+
const payloadOnBottomLeft = {0x0001: {value: Buffer.from([4, 0, 0, 0, 0, 0, 0, 0]), type: 4}};
|
|
1614
|
+
const payloadOffBottomLeft = {0x0001: {value: Buffer.from([0, 0, 0, 0, 0, 0, 0, 0]), type: 4}};
|
|
1615
|
+
const payloadOnBottomRight = {0x0001: {value: Buffer.from([8, 0, 0, 0, 0, 0, 0, 0]), type: 136}};
|
|
1616
|
+
const payloadOffBottomRight = {0x0001: {value: Buffer.from([0, 0, 0, 0, 0, 0, 0, 0]), type: 136}};
|
|
1608
1617
|
if (postfix === 'left') {
|
|
1618
|
+
await entity.command('genLevelCtrl', 'moveToLevelWithOnOff', {level: oldstate, transtime: channel});
|
|
1609
1619
|
await entity.write('genPowerCfg', (state === 'on') ? payloadOn : payloadOff,
|
|
1610
1620
|
{
|
|
1611
1621
|
manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true,
|
|
@@ -1613,12 +1623,28 @@ const converters = {
|
|
|
1613
1623
|
});
|
|
1614
1624
|
return {state: {state_left: value.toUpperCase()}, readAfterWriteTime: 250};
|
|
1615
1625
|
} else if (postfix === 'right') {
|
|
1626
|
+
channel = 2.0;
|
|
1627
|
+
await entity.command('genLevelCtrl', 'moveToLevelWithOnOff', {level: oldstate, transtime: channel});
|
|
1616
1628
|
await entity.write('genPowerCfg', (state === 'on') ? payloadOnRight : payloadOffRight,
|
|
1617
1629
|
{
|
|
1618
1630
|
manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true,
|
|
1619
1631
|
reservedBits: 3, direction: 1, transactionSequenceNumber: 0xe9,
|
|
1620
1632
|
});
|
|
1621
1633
|
return {state: {state_right: value.toUpperCase()}, readAfterWriteTime: 250};
|
|
1634
|
+
} else if (postfix === 'bottom_right') {
|
|
1635
|
+
await entity.write('genPowerCfg', (state === 'on') ? payloadOnBottomRight : payloadOffBottomRight,
|
|
1636
|
+
{
|
|
1637
|
+
manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true,
|
|
1638
|
+
reservedBits: 3, direction: 1, transactionSequenceNumber: 0xe9,
|
|
1639
|
+
});
|
|
1640
|
+
return {state: {state_bottom_right: value.toUpperCase()}, readAfterWriteTime: 250};
|
|
1641
|
+
} else if (postfix === 'bottom_left') {
|
|
1642
|
+
await entity.write('genPowerCfg', (state === 'on') ? payloadOnBottomLeft : payloadOffBottomLeft,
|
|
1643
|
+
{
|
|
1644
|
+
manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true,
|
|
1645
|
+
reservedBits: 3, direction: 1, transactionSequenceNumber: 0xe9,
|
|
1646
|
+
});
|
|
1647
|
+
return {state: {state_bottom_left: value.toUpperCase()}, readAfterWriteTime: 250};
|
|
1622
1648
|
}
|
|
1623
1649
|
return {state: {state: value.toUpperCase()}, readAfterWriteTime: 250};
|
|
1624
1650
|
},
|
package/devices/livolo.js
CHANGED
|
@@ -20,13 +20,18 @@ module.exports = [
|
|
|
20
20
|
{
|
|
21
21
|
zigbeeModel: ['TI0001 '],
|
|
22
22
|
model: 'TI0001',
|
|
23
|
-
description: 'Zigbee switch (1
|
|
23
|
+
description: 'Zigbee switch (1, 2, 3, 4 gang)',
|
|
24
24
|
vendor: 'Livolo',
|
|
25
|
-
exposes: [
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
exposes: [
|
|
26
|
+
e.switch().withEndpoint('left'),
|
|
27
|
+
e.switch().withEndpoint('right'),
|
|
28
|
+
e.switch().withEndpoint('bottom_left'),
|
|
29
|
+
e.switch().withEndpoint('bottom_right'),
|
|
30
|
+
],
|
|
31
|
+
fromZigbee: [fz.livolo_switch_state, fz.livolo_switch_state_raw, fz.livolo_new_switch_state_4gang],
|
|
32
|
+
toZigbee: [tz.livolo_socket_switch_on_off],
|
|
28
33
|
endpoint: (device) => {
|
|
29
|
-
return {'left': 6, 'right': 6};
|
|
34
|
+
return {'left': 6, 'right': 6, 'bottom_left': 6, 'bottom_right': 6};
|
|
30
35
|
},
|
|
31
36
|
configure: poll,
|
|
32
37
|
onEvent: async (type, data, device) => {
|
|
@@ -41,6 +46,18 @@ module.exports = [
|
|
|
41
46
|
globalStore.putValue(device, 'interval', interval);
|
|
42
47
|
}
|
|
43
48
|
}
|
|
49
|
+
if (data.cluster === 'genPowerCfg' && data.type === 'raw') {
|
|
50
|
+
const dp = data.data[10];
|
|
51
|
+
if (data.data[0] === 0x7a && data.data[1] === 0xd1) {
|
|
52
|
+
const endpoint = device.getEndpoint(6);
|
|
53
|
+
if (dp === 0x01) {
|
|
54
|
+
const options = {manufacturerCode: 0x1ad2, disableDefaultResponse: true, disableResponse: true,
|
|
55
|
+
reservedBits: 3, direction: 1, writeUndiv: true};
|
|
56
|
+
const payload = {0x2002: {value: [0, 0, 0, 0, 0, 0, 0], type: 0x0e}};
|
|
57
|
+
await endpoint.readResponse('genPowerCfg', 0xe9, payload, options);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
44
61
|
},
|
|
45
62
|
},
|
|
46
63
|
{
|
package/devices/woox.js
CHANGED
|
@@ -13,10 +13,10 @@ module.exports = [
|
|
|
13
13
|
model: 'R7060',
|
|
14
14
|
vendor: 'Woox',
|
|
15
15
|
description: 'Smart garden irrigation control',
|
|
16
|
-
fromZigbee: [fz.on_off, fz.ignore_tuya_set_time, fz.ignore_basic_report, fz.woox_R7060],
|
|
16
|
+
fromZigbee: [fz.on_off, fz.ignore_tuya_set_time, fz.ignore_basic_report, fz.woox_R7060, fz.battery],
|
|
17
17
|
toZigbee: [tz.on_off],
|
|
18
18
|
onEvent: tuya.onEventSetTime,
|
|
19
|
-
exposes: [e.switch()],
|
|
19
|
+
exposes: [e.switch(), e.battery(), e.battery_voltage()],
|
|
20
20
|
meta: {disableDefaultResponse: true},
|
|
21
21
|
},
|
|
22
22
|
{
|
package/devices/xiaomi.js
CHANGED
|
@@ -1298,7 +1298,7 @@ module.exports = [
|
|
|
1298
1298
|
endpoint: (device) => {
|
|
1299
1299
|
return {'l1': 1, 'l2': 2};
|
|
1300
1300
|
},
|
|
1301
|
-
exposes: [e.power().withAccess(ea.STATE_GET), e.energy(), e.temperature(), e.voltage().withAccess(ea.STATE),
|
|
1301
|
+
exposes: [e.power().withAccess(ea.STATE_GET), e.energy(), e.temperature(), e.voltage().withAccess(ea.STATE), e.current(),
|
|
1302
1302
|
e.switch().withEndpoint('l1'), e.switch().withEndpoint('l2'),
|
|
1303
1303
|
exposes.binary('interlock', ea.STATE_SET, true, false)
|
|
1304
1304
|
.withDescription('Enabling prevents both relais being on at the same time')],
|
package/lib/ota/zigbeeOTA.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const url = 'https://raw.githubusercontent.com/Koenkk/zigbee-OTA/master/index.json';
|
|
2
|
-
const assert = require('assert');
|
|
3
2
|
const common = require('./common');
|
|
4
3
|
const axios = common.getAxios();
|
|
5
4
|
const fs = require('fs');
|
|
@@ -110,7 +109,10 @@ async function getImageMeta(current, logger, device) {
|
|
|
110
109
|
(!i.minFileVersion || current.fileVersion >= i.minFileVersion) && (!i.maxFileVersion || current.fileVersion <= i.maxFileVersion) &&
|
|
111
110
|
(!i.modelId || i.modelId === modelId) && (!i.manufacturerName || i.manufacturerName.includes(manufacturerName)));
|
|
112
111
|
|
|
113
|
-
|
|
112
|
+
if (!image) {
|
|
113
|
+
throw new Error(`No image available for imageType '${imageType}'`);
|
|
114
|
+
}
|
|
115
|
+
|
|
114
116
|
return {
|
|
115
117
|
fileVersion: image.fileVersion,
|
|
116
118
|
fileSize: image.fileSize,
|
package/lib/utils.js
CHANGED
|
@@ -397,13 +397,12 @@ function extendDevice(deviceConfigs, modelName, adjustments) {
|
|
|
397
397
|
return {...baseDevice, ...adjustments};
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
function noOccupancySince(endpoint,
|
|
400
|
+
function noOccupancySince(endpoint, options, publish, action) {
|
|
401
401
|
if (options && options.no_occupancy_since) {
|
|
402
402
|
if (action == 'start') {
|
|
403
403
|
globalStore.getValue(endpoint, 'no_occupancy_since_timers', []).forEach((t) => clearTimeout(t));
|
|
404
404
|
globalStore.putValue(endpoint, 'no_occupancy_since_timers', []);
|
|
405
405
|
|
|
406
|
-
newPayload.no_occupancy_since = 0;
|
|
407
406
|
options.no_occupancy_since.forEach((since) => {
|
|
408
407
|
const timer = setTimeout(() => {
|
|
409
408
|
publish({no_occupancy_since: since});
|
package/lib/xiaomi.js
CHANGED
|
@@ -168,7 +168,7 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
|
|
|
168
168
|
payload.switch_type = {1: 'toggle', 2: 'momentary'}[value];
|
|
169
169
|
break;
|
|
170
170
|
case '100':
|
|
171
|
-
if (['QBKG20LM', 'QBKG31LM', 'QBKG39LM', 'QBKG41LM', 'QBCZ15LM', 'LLKZMK11LM'].includes(model.model)) {
|
|
171
|
+
if (['QBKG20LM', 'QBKG31LM', 'QBKG39LM', 'QBKG41LM', 'QBCZ15LM', 'LLKZMK11LM', 'QBKG12LM', 'QBKG03LM'].includes(model.model)) {
|
|
172
172
|
let mapping;
|
|
173
173
|
switch (model.model) {
|
|
174
174
|
case 'QBCZ15LM':
|
|
@@ -191,7 +191,7 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
|
|
|
191
191
|
}
|
|
192
192
|
break;
|
|
193
193
|
case '101':
|
|
194
|
-
if (['QBKG20LM', 'QBKG31LM', 'QBKG39LM', 'QBKG41LM', 'QBCZ15LM', 'QBKG25LM', 'QBKG34LM', 'LLKZMK11LM']
|
|
194
|
+
if (['QBKG20LM', 'QBKG31LM', 'QBKG39LM', 'QBKG41LM', 'QBCZ15LM', 'QBKG25LM', 'QBKG34LM', 'LLKZMK11LM', 'QBKG12LM', 'QBKG03LM']
|
|
195
195
|
.includes(model.model)) {
|
|
196
196
|
let mapping;
|
|
197
197
|
switch (model.model) {
|
|
@@ -244,7 +244,11 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
|
|
|
244
244
|
payload.voltage = precisionRound(value * 0.1, 1); // 0x96
|
|
245
245
|
break;
|
|
246
246
|
case '151':
|
|
247
|
-
|
|
247
|
+
if (['LLKZMK11LM'].includes(model.model)) {
|
|
248
|
+
payload.current = precisionRound(value, 4);
|
|
249
|
+
} else {
|
|
250
|
+
payload.current = precisionRound(value * 0.001, 4);
|
|
251
|
+
}
|
|
248
252
|
break;
|
|
249
253
|
case '152':
|
|
250
254
|
payload.power = precisionRound(value, 2); // 0x98
|
|
@@ -355,6 +359,12 @@ const numericAttributes2Payload = (msg, meta, model, options, dataObject) => {
|
|
|
355
359
|
case '1289':
|
|
356
360
|
payload.dimmer_mode = {3: 'rgbw', 1: 'dual_ct'}[value];
|
|
357
361
|
break;
|
|
362
|
+
case '65281':
|
|
363
|
+
{
|
|
364
|
+
const payload65281 = numericAttributes2Payload(msg, meta, model, options, value);
|
|
365
|
+
payload = {...payload, ...payload65281};
|
|
366
|
+
}
|
|
367
|
+
break;
|
|
358
368
|
case 'mode':
|
|
359
369
|
payload.operation_mode = ['command', 'event'][value];
|
|
360
370
|
break;
|
package/npm-shrinkwrap.json
CHANGED