zigbee-herdsman-converters 22.0.1 → 22.2.0
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/CHANGELOG.md +39 -0
- package/converters/fromZigbee.d.ts +7 -0
- package/converters/fromZigbee.d.ts.map +1 -1
- package/converters/fromZigbee.js +10 -1
- package/converters/fromZigbee.js.map +1 -1
- package/converters/toZigbee.d.ts +9 -0
- package/converters/toZigbee.d.ts.map +1 -1
- package/converters/toZigbee.js +11 -1
- package/converters/toZigbee.js.map +1 -1
- package/devices/custom_devices_diy.d.ts.map +1 -1
- package/devices/custom_devices_diy.js +9 -0
- package/devices/custom_devices_diy.js.map +1 -1
- package/devices/ewelink.d.ts.map +1 -1
- package/devices/ewelink.js +14 -1
- package/devices/ewelink.js.map +1 -1
- package/devices/hampton_bay.js +1 -1
- package/devices/hampton_bay.js.map +1 -1
- package/devices/inovelli.d.ts.map +1 -1
- package/devices/inovelli.js +9 -4
- package/devices/inovelli.js.map +1 -1
- package/devices/lellki.js +1 -1
- package/devices/lellki.js.map +1 -1
- package/devices/mercator.js +1 -1
- package/devices/mercator.js.map +1 -1
- package/devices/multiterm.js +1 -1
- package/devices/multiterm.js.map +1 -1
- package/devices/orvibo.js +1 -1
- package/devices/orvibo.js.map +1 -1
- package/devices/owon.js +2 -2
- package/devices/owon.js.map +1 -1
- package/devices/philips.d.ts.map +1 -1
- package/devices/philips.js +7 -0
- package/devices/philips.js.map +1 -1
- package/devices/schneider_electric.js +1 -1
- package/devices/schneider_electric.js.map +1 -1
- package/devices/somfy.d.ts.map +1 -1
- package/devices/somfy.js +7 -0
- package/devices/somfy.js.map +1 -1
- package/devices/sonoff.d.ts.map +1 -1
- package/devices/sonoff.js +30 -4
- package/devices/sonoff.js.map +1 -1
- package/devices/sunricher.d.ts.map +1 -1
- package/devices/sunricher.js +986 -352
- package/devices/sunricher.js.map +1 -1
- package/devices/third_reality.d.ts.map +1 -1
- package/devices/third_reality.js +14 -9
- package/devices/third_reality.js.map +1 -1
- package/devices/tuya.d.ts.map +1 -1
- package/devices/tuya.js +165 -8
- package/devices/tuya.js.map +1 -1
- package/devices/ubisys.d.ts.map +1 -1
- package/devices/ubisys.js +1 -0
- package/devices/ubisys.js.map +1 -1
- package/lib/color.d.ts +1 -2
- package/lib/color.d.ts.map +1 -1
- package/lib/color.js +2 -6
- package/lib/color.js.map +1 -1
- package/lib/configureKey.js +0 -1
- package/lib/configureKey.js.map +1 -1
- package/lib/constants.js +0 -33
- package/lib/constants.js.map +1 -1
- package/lib/exposes.d.ts +14 -0
- package/lib/exposes.d.ts.map +1 -1
- package/lib/exposes.js +38 -13
- package/lib/exposes.js.map +1 -1
- package/lib/ikea.js +1 -1
- package/lib/ikea.js.map +1 -1
- package/lib/kelvinToXy.js +0 -1
- package/lib/kelvinToXy.js.map +1 -1
- package/lib/legacy.d.ts +2 -76
- package/lib/legacy.d.ts.map +1 -1
- package/lib/legacy.js +1 -64
- package/lib/legacy.js.map +1 -1
- package/lib/light.d.ts +2 -0
- package/lib/light.d.ts.map +1 -1
- package/lib/light.js +2 -6
- package/lib/light.js.map +1 -1
- package/lib/lumi.d.ts.map +1 -1
- package/lib/lumi.js +2 -17
- package/lib/lumi.js.map +1 -1
- package/lib/philips.d.ts +166 -1
- package/lib/philips.d.ts.map +1 -1
- package/lib/philips.js +12 -15
- package/lib/philips.js.map +1 -1
- package/lib/reporting.js +0 -51
- package/lib/reporting.js.map +1 -1
- package/lib/store.js +0 -5
- package/lib/store.js.map +1 -1
- package/lib/tuya.js +0 -19
- package/lib/tuya.js.map +1 -1
- package/lib/utils.js +1 -37
- package/lib/utils.js.map +1 -1
- package/lib/zosung.js +0 -3
- package/lib/zosung.js.map +1 -1
- package/models-index.json +1 -1
- package/package.json +1 -1
package/devices/sunricher.js
CHANGED
|
@@ -41,383 +41,737 @@ const zigbee_herdsman_1 = require("zigbee-herdsman");
|
|
|
41
41
|
const fromZigbee_1 = __importDefault(require("../converters/fromZigbee"));
|
|
42
42
|
const toZigbee_1 = __importDefault(require("../converters/toZigbee"));
|
|
43
43
|
const constants = __importStar(require("../lib/constants"));
|
|
44
|
+
const constants_1 = require("../lib/constants");
|
|
44
45
|
const exposes = __importStar(require("../lib/exposes"));
|
|
45
46
|
const logger_1 = require("../lib/logger");
|
|
46
47
|
const m = __importStar(require("../lib/modernExtend"));
|
|
47
48
|
const reporting = __importStar(require("../lib/reporting"));
|
|
49
|
+
const reporting_1 = require("../lib/reporting");
|
|
48
50
|
const globalStore = __importStar(require("../lib/store"));
|
|
49
51
|
const sunricher = __importStar(require("../lib/sunricher"));
|
|
50
52
|
const utils = __importStar(require("../lib/utils"));
|
|
53
|
+
const utils_1 = require("../lib/utils");
|
|
51
54
|
const NS = 'zhc:sunricher';
|
|
52
55
|
const e = exposes.presets;
|
|
53
56
|
const ea = exposes.access;
|
|
54
57
|
const sunricherManufacturerCode = 0x1224;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
58
|
+
const sunricherExtend = {
|
|
59
|
+
externalSwitchType: () => {
|
|
60
|
+
const attribute = 0x8803;
|
|
61
|
+
const data_type = 0x20;
|
|
62
|
+
const value_map = {
|
|
63
|
+
0: 'push_button',
|
|
64
|
+
1: 'normal_on_off',
|
|
65
|
+
2: 'three_way',
|
|
66
|
+
};
|
|
67
|
+
const value_lookup = {
|
|
68
|
+
push_button: 0,
|
|
69
|
+
normal_on_off: 1,
|
|
70
|
+
three_way: 2,
|
|
71
|
+
};
|
|
72
|
+
const fromZigbee = [
|
|
73
|
+
{
|
|
74
|
+
cluster: 'genBasic',
|
|
75
|
+
type: ['attributeReport', 'readResponse'],
|
|
76
|
+
convert: (model, msg, publish, options, meta) => {
|
|
77
|
+
if (Object.prototype.hasOwnProperty.call(msg.data, attribute)) {
|
|
78
|
+
const value = msg.data[attribute];
|
|
79
|
+
return {
|
|
80
|
+
external_switch_type: value_map[value] || 'unknown',
|
|
81
|
+
external_switch_type_numeric: value,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
const toZigbee = [
|
|
89
|
+
{
|
|
90
|
+
key: ['external_switch_type'],
|
|
91
|
+
convertSet: async (entity, key, value, meta) => {
|
|
92
|
+
const numericValue = value_lookup[value] ?? parseInt(value, 10);
|
|
93
|
+
await entity.write('genBasic', { [attribute]: { value: numericValue, type: data_type } }, { manufacturerCode: sunricherManufacturerCode });
|
|
94
|
+
return { state: { external_switch_type: value } };
|
|
95
|
+
},
|
|
96
|
+
convertGet: async (entity, key, meta) => {
|
|
97
|
+
await entity.read('genBasic', [attribute], { manufacturerCode: sunricherManufacturerCode });
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
const exposes = [
|
|
102
|
+
e.enum('external_switch_type', ea.ALL, ['push_button', 'normal_on_off', 'three_way']).withLabel('External switch type'),
|
|
103
|
+
];
|
|
104
|
+
const configure = [
|
|
105
|
+
async (device, coordinatorEndpoint, definition) => {
|
|
106
|
+
const endpoint = device.getEndpoint(1);
|
|
107
|
+
try {
|
|
108
|
+
await endpoint.read('genBasic', [attribute], { manufacturerCode: sunricherManufacturerCode });
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.warn(`Failed to read external switch type attribute: ${error}`);
|
|
79
112
|
}
|
|
80
|
-
return undefined;
|
|
81
113
|
},
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
114
|
+
];
|
|
115
|
+
return {
|
|
116
|
+
fromZigbee,
|
|
117
|
+
toZigbee,
|
|
118
|
+
exposes,
|
|
119
|
+
configure,
|
|
120
|
+
isModernExtend: true,
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
minimumPWM: () => {
|
|
124
|
+
const attribute = 0x7809;
|
|
125
|
+
const data_type = 0x20;
|
|
126
|
+
const fromZigbee = [
|
|
127
|
+
{
|
|
128
|
+
cluster: 'genBasic',
|
|
129
|
+
type: ['attributeReport', 'readResponse'],
|
|
130
|
+
convert: (model, msg, publish, options, meta) => {
|
|
131
|
+
if (Object.prototype.hasOwnProperty.call(msg.data, attribute)) {
|
|
132
|
+
console.log(`from `, msg.data[attribute]);
|
|
133
|
+
const value = Math.round(msg.data[attribute] / 5.1);
|
|
134
|
+
return {
|
|
135
|
+
minimum_pwm: value,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
},
|
|
91
140
|
},
|
|
92
|
-
|
|
93
|
-
|
|
141
|
+
];
|
|
142
|
+
const toZigbee = [
|
|
143
|
+
{
|
|
144
|
+
key: ['minimum_pwm'],
|
|
145
|
+
convertSet: async (entity, key, value, meta) => {
|
|
146
|
+
console.log(`to `, value);
|
|
147
|
+
const numValue = typeof value === 'string' ? parseInt(value) : value;
|
|
148
|
+
const zgValue = Math.round(numValue * 5.1);
|
|
149
|
+
await entity.write('genBasic', { [attribute]: { value: zgValue, type: data_type } }, { manufacturerCode: sunricherManufacturerCode });
|
|
150
|
+
return { state: { minimum_pwm: numValue } };
|
|
151
|
+
},
|
|
152
|
+
convertGet: async (entity, key, meta) => {
|
|
153
|
+
await entity.read('genBasic', [attribute], { manufacturerCode: sunricherManufacturerCode });
|
|
154
|
+
},
|
|
94
155
|
},
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
exposes,
|
|
115
|
-
configure,
|
|
116
|
-
isModernExtend: true,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
function sunricherMinimumPWM() {
|
|
120
|
-
const attribute = 0x7809;
|
|
121
|
-
const data_type = 0x20;
|
|
122
|
-
const fromZigbee = [
|
|
123
|
-
{
|
|
124
|
-
cluster: 'genBasic',
|
|
125
|
-
type: ['attributeReport', 'readResponse'],
|
|
126
|
-
convert: (model, msg, publish, options, meta) => {
|
|
127
|
-
if (Object.prototype.hasOwnProperty.call(msg.data, attribute)) {
|
|
128
|
-
console.log(`from `, msg.data[attribute]);
|
|
129
|
-
const value = Math.round(msg.data[attribute] / 5.1);
|
|
130
|
-
return {
|
|
131
|
-
minimum_pwm: value,
|
|
132
|
-
};
|
|
156
|
+
];
|
|
157
|
+
const exposes = [
|
|
158
|
+
e
|
|
159
|
+
.numeric('minimum_pwm', ea.ALL)
|
|
160
|
+
.withLabel('Minimum PWM')
|
|
161
|
+
.withDescription('Power off the device and wait for 3 seconds before reconnecting to apply the settings.')
|
|
162
|
+
.withValueMin(0)
|
|
163
|
+
.withValueMax(50)
|
|
164
|
+
.withUnit('%')
|
|
165
|
+
.withValueStep(1),
|
|
166
|
+
];
|
|
167
|
+
const configure = [
|
|
168
|
+
async (device, coordinatorEndpoint, definition) => {
|
|
169
|
+
const endpoint = device.getEndpoint(1);
|
|
170
|
+
try {
|
|
171
|
+
await endpoint.read('genBasic', [attribute], { manufacturerCode: sunricherManufacturerCode });
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
console.warn(`Failed to read external switch type attribute: ${error}`);
|
|
133
175
|
}
|
|
134
|
-
return undefined;
|
|
135
176
|
},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
177
|
+
];
|
|
178
|
+
return {
|
|
179
|
+
fromZigbee,
|
|
180
|
+
toZigbee,
|
|
181
|
+
exposes,
|
|
182
|
+
configure,
|
|
183
|
+
isModernExtend: true,
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
SRZG9002KR12Pro: () => {
|
|
187
|
+
const cluster = 0xff03;
|
|
188
|
+
const fromZigbee = [
|
|
189
|
+
{
|
|
190
|
+
cluster: 0xff03,
|
|
191
|
+
type: ['raw'],
|
|
192
|
+
convert: (model, msg, publish, options, meta) => {
|
|
193
|
+
const bytes = [...msg.data];
|
|
194
|
+
const messageType = bytes[3];
|
|
195
|
+
let action = 'unknown';
|
|
196
|
+
if (messageType === 0x01) {
|
|
197
|
+
const pressTypeMask = bytes[6];
|
|
198
|
+
const pressTypeLookup = {
|
|
199
|
+
0x01: 'short_press',
|
|
200
|
+
0x02: 'double_press',
|
|
201
|
+
0x03: 'hold',
|
|
202
|
+
0x04: 'hold_released',
|
|
203
|
+
};
|
|
204
|
+
action = pressTypeLookup[pressTypeMask] || 'unknown';
|
|
205
|
+
const buttonMask = (bytes[4] << 8) | bytes[5];
|
|
206
|
+
const specialButtonMap = {
|
|
207
|
+
9: 'knob',
|
|
208
|
+
11: 'k9',
|
|
209
|
+
12: 'k10',
|
|
210
|
+
15: 'k11',
|
|
211
|
+
16: 'k12',
|
|
212
|
+
};
|
|
213
|
+
const actionButtons = [];
|
|
214
|
+
for (let i = 0; i < 16; i++) {
|
|
215
|
+
if ((buttonMask >> i) & 1) {
|
|
216
|
+
const button = i + 1;
|
|
217
|
+
actionButtons.push(specialButtonMap[button] ?? `k${button}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return { action, action_buttons: actionButtons };
|
|
221
|
+
}
|
|
222
|
+
else if (messageType === 0x03) {
|
|
223
|
+
const directionMask = bytes[4];
|
|
224
|
+
const actionSpeed = bytes[6];
|
|
225
|
+
const directionMap = {
|
|
226
|
+
0x01: 'clockwise',
|
|
227
|
+
0x02: 'anti_clockwise',
|
|
228
|
+
};
|
|
229
|
+
const direction = directionMap[directionMask] || 'unknown';
|
|
230
|
+
action = `${direction}_rotation`;
|
|
231
|
+
return { action, action_speed: actionSpeed };
|
|
232
|
+
}
|
|
233
|
+
return { action };
|
|
234
|
+
},
|
|
147
235
|
},
|
|
148
|
-
|
|
149
|
-
|
|
236
|
+
];
|
|
237
|
+
const exposes = [
|
|
238
|
+
e.action(['short_press', 'double_press', 'hold', 'hold_released', 'clockwise_rotation', 'anti_clockwise_rotation']),
|
|
239
|
+
];
|
|
240
|
+
const configure = [
|
|
241
|
+
async (device, coordinatorEndpoint, definition) => {
|
|
242
|
+
const endpoint = device.getEndpoint(1);
|
|
243
|
+
await endpoint.bind(cluster, coordinatorEndpoint);
|
|
150
244
|
},
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (messageType === 0x01) {
|
|
193
|
-
const pressTypeMask = bytes[6];
|
|
194
|
-
const pressTypeLookup = {
|
|
195
|
-
0x01: 'short_press',
|
|
196
|
-
0x02: 'double_press',
|
|
197
|
-
0x03: 'hold',
|
|
198
|
-
0x04: 'hold_released',
|
|
199
|
-
};
|
|
200
|
-
action = pressTypeLookup[pressTypeMask] || 'unknown';
|
|
201
|
-
const buttonMask = (bytes[4] << 8) | bytes[5];
|
|
202
|
-
const specialButtonMap = {
|
|
203
|
-
9: 'knob',
|
|
204
|
-
11: 'k9',
|
|
205
|
-
12: 'k10',
|
|
206
|
-
15: 'k11',
|
|
207
|
-
16: 'k12',
|
|
208
|
-
};
|
|
209
|
-
const actionButtons = [];
|
|
210
|
-
for (let i = 0; i < 16; i++) {
|
|
211
|
-
if ((buttonMask >> i) & 1) {
|
|
212
|
-
const button = i + 1;
|
|
213
|
-
actionButtons.push(specialButtonMap[button] ?? `k${button}`);
|
|
245
|
+
];
|
|
246
|
+
return {
|
|
247
|
+
fromZigbee,
|
|
248
|
+
exposes,
|
|
249
|
+
configure,
|
|
250
|
+
isModernExtend: true,
|
|
251
|
+
};
|
|
252
|
+
},
|
|
253
|
+
SRZG2836D5Pro: () => {
|
|
254
|
+
const cluster = 0xff03;
|
|
255
|
+
const fromZigbee = [
|
|
256
|
+
{
|
|
257
|
+
cluster: 0xff03,
|
|
258
|
+
type: ['raw'],
|
|
259
|
+
convert: (model, msg, publish, options, meta) => {
|
|
260
|
+
const bytes = [...msg.data];
|
|
261
|
+
const messageType = bytes[3];
|
|
262
|
+
let action = 'unknown';
|
|
263
|
+
if (messageType === 0x01) {
|
|
264
|
+
const pressTypeMask = bytes[6];
|
|
265
|
+
const pressTypeLookup = {
|
|
266
|
+
0x01: 'short_press',
|
|
267
|
+
0x02: 'double_press',
|
|
268
|
+
0x03: 'hold',
|
|
269
|
+
0x04: 'hold_released',
|
|
270
|
+
};
|
|
271
|
+
action = pressTypeLookup[pressTypeMask] || 'unknown';
|
|
272
|
+
const buttonMask = bytes[5];
|
|
273
|
+
const specialButtonLookup = {
|
|
274
|
+
0x01: 'top_left',
|
|
275
|
+
0x02: 'top_right',
|
|
276
|
+
0x03: 'bottom_left',
|
|
277
|
+
0x04: 'bottom_right',
|
|
278
|
+
0x05: 'center',
|
|
279
|
+
};
|
|
280
|
+
const actionButtons = [];
|
|
281
|
+
for (let i = 0; i < 5; i++) {
|
|
282
|
+
if ((buttonMask >> i) & 1) {
|
|
283
|
+
const button = i + 1;
|
|
284
|
+
actionButtons.push(specialButtonLookup[button] || `unknown_${button}`);
|
|
285
|
+
}
|
|
214
286
|
}
|
|
287
|
+
return { action, action_buttons: actionButtons };
|
|
215
288
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
289
|
+
else if (messageType === 0x03) {
|
|
290
|
+
const directionMask = bytes[4];
|
|
291
|
+
const actionSpeed = bytes[6];
|
|
292
|
+
const isStop = bytes[5] === 0x02;
|
|
293
|
+
const directionMap = {
|
|
294
|
+
0x01: 'clockwise',
|
|
295
|
+
0x02: 'anti_clockwise',
|
|
296
|
+
};
|
|
297
|
+
const direction = isStop ? 'stop' : directionMap[directionMask] || 'unknown';
|
|
298
|
+
action = `${direction}_rotation`;
|
|
299
|
+
return { action, action_speed: actionSpeed };
|
|
300
|
+
}
|
|
301
|
+
return { action };
|
|
302
|
+
},
|
|
230
303
|
},
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
304
|
+
];
|
|
305
|
+
const exposes = [
|
|
306
|
+
e.action(['short_press', 'double_press', 'hold', 'hold_released', 'clockwise_rotation', 'anti_clockwise_rotation', 'stop_rotation']),
|
|
307
|
+
];
|
|
308
|
+
const configure = [
|
|
309
|
+
async (device, coordinatorEndpoint, definition) => {
|
|
310
|
+
const endpoint = device.getEndpoint(1);
|
|
311
|
+
await endpoint.bind(cluster, coordinatorEndpoint);
|
|
312
|
+
},
|
|
313
|
+
];
|
|
314
|
+
return {
|
|
315
|
+
fromZigbee,
|
|
316
|
+
exposes,
|
|
317
|
+
configure,
|
|
318
|
+
isModernExtend: true,
|
|
319
|
+
};
|
|
320
|
+
},
|
|
321
|
+
SRZG9002K16Pro: () => {
|
|
322
|
+
const cluster = 0xff03;
|
|
323
|
+
const fromZigbee = [
|
|
324
|
+
{
|
|
325
|
+
cluster,
|
|
326
|
+
type: ['raw'],
|
|
327
|
+
convert: (model, msg, publish, options, meta) => {
|
|
328
|
+
const bytes = [...msg.data];
|
|
329
|
+
const messageType = bytes[3];
|
|
330
|
+
let action = 'unknown';
|
|
331
|
+
if (messageType === 0x01) {
|
|
332
|
+
const pressTypeMask = bytes[6];
|
|
333
|
+
const pressTypeLookup = {
|
|
334
|
+
0x01: 'short_press',
|
|
335
|
+
0x02: 'double_press',
|
|
336
|
+
0x03: 'hold',
|
|
337
|
+
0x04: 'hold_released',
|
|
338
|
+
};
|
|
339
|
+
action = pressTypeLookup[pressTypeMask] || 'unknown';
|
|
340
|
+
const buttonMask = (bytes[4] << 8) | bytes[5];
|
|
341
|
+
const getButtonNumber = (input) => {
|
|
342
|
+
const row = Math.floor((input - 1) / 4);
|
|
343
|
+
const col = (input - 1) % 4;
|
|
344
|
+
return col * 4 + row + 1;
|
|
345
|
+
};
|
|
346
|
+
const actionButtons = [];
|
|
347
|
+
for (let i = 0; i < 16; i++) {
|
|
348
|
+
if ((buttonMask >> i) & 1) {
|
|
349
|
+
const button = i + 1;
|
|
350
|
+
actionButtons.push(`k${getButtonNumber(button)}`);
|
|
351
|
+
}
|
|
279
352
|
}
|
|
353
|
+
return { action, action_buttons: actionButtons };
|
|
280
354
|
}
|
|
281
|
-
return { action
|
|
282
|
-
}
|
|
283
|
-
else if (messageType === 0x03) {
|
|
284
|
-
const directionMask = bytes[4];
|
|
285
|
-
const actionSpeed = bytes[6];
|
|
286
|
-
const isStop = bytes[5] === 0x02;
|
|
287
|
-
const directionMap = {
|
|
288
|
-
0x01: 'clockwise',
|
|
289
|
-
0x02: 'anti_clockwise',
|
|
290
|
-
};
|
|
291
|
-
const direction = isStop ? 'stop' : directionMap[directionMask] || 'unknown';
|
|
292
|
-
action = `${direction}_rotation`;
|
|
293
|
-
return { action, action_speed: actionSpeed };
|
|
294
|
-
}
|
|
295
|
-
return { action };
|
|
355
|
+
return { action };
|
|
356
|
+
},
|
|
296
357
|
},
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
358
|
+
];
|
|
359
|
+
const exposes = [e.action(['short_press', 'double_press', 'hold', 'hold_released'])];
|
|
360
|
+
const configure = [
|
|
361
|
+
async (device, coordinatorEndpoint, definition) => {
|
|
362
|
+
const endpoint = device.getEndpoint(1);
|
|
363
|
+
await endpoint.bind(cluster, coordinatorEndpoint);
|
|
364
|
+
},
|
|
365
|
+
];
|
|
366
|
+
return {
|
|
367
|
+
fromZigbee,
|
|
368
|
+
exposes,
|
|
369
|
+
configure,
|
|
370
|
+
isModernExtend: true,
|
|
371
|
+
};
|
|
372
|
+
},
|
|
373
|
+
indicatorLight() {
|
|
374
|
+
const cluster = 0xfc8b;
|
|
375
|
+
const attribute = 0xf001;
|
|
376
|
+
const data_type = 0x20;
|
|
377
|
+
const manufacturerCode = 0x120b;
|
|
378
|
+
const exposes = [
|
|
379
|
+
e.enum('indicator_light', ea.ALL, ['on', 'off']).withDescription('Enable/disable the LED indicator').withCategory('config'),
|
|
380
|
+
];
|
|
381
|
+
const fromZigbee = [
|
|
382
|
+
{
|
|
383
|
+
cluster,
|
|
384
|
+
type: ['attributeReport', 'readResponse'],
|
|
385
|
+
convert: (model, msg, publish, options, meta) => {
|
|
386
|
+
if (!Object.prototype.hasOwnProperty.call(msg.data, attribute))
|
|
387
|
+
return;
|
|
388
|
+
const indicatorLight = msg.data[attribute];
|
|
389
|
+
const firstBit = indicatorLight & 0x01;
|
|
390
|
+
return { indicator_light: firstBit === 1 ? 'on' : 'off' };
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
];
|
|
394
|
+
const toZigbee = [
|
|
395
|
+
{
|
|
396
|
+
key: ['indicator_light'],
|
|
397
|
+
convertSet: async (entity, key, value, meta) => {
|
|
398
|
+
const attributeRead = await entity.read(cluster, [attribute]);
|
|
399
|
+
if (attributeRead === undefined)
|
|
400
|
+
return;
|
|
401
|
+
// @ts-expect-error ignore
|
|
402
|
+
const currentValue = attributeRead[attribute];
|
|
403
|
+
const newValue = value === 'on' ? currentValue | 0x01 : currentValue & ~0x01;
|
|
404
|
+
await entity.write(cluster, { [attribute]: { value: newValue, type: data_type } }, { manufacturerCode });
|
|
405
|
+
return { state: { indicator_light: value } };
|
|
406
|
+
},
|
|
407
|
+
convertGet: async (entity, key, meta) => {
|
|
408
|
+
await entity.read(cluster, [attribute], { manufacturerCode });
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
];
|
|
412
|
+
const configure = [
|
|
413
|
+
async (device, coordinatorEndpoint, definition) => {
|
|
414
|
+
const endpoint = device.getEndpoint(1);
|
|
415
|
+
await endpoint.bind(cluster, coordinatorEndpoint);
|
|
416
|
+
await endpoint.read(cluster, [attribute], { manufacturerCode });
|
|
417
|
+
},
|
|
418
|
+
];
|
|
419
|
+
return {
|
|
420
|
+
exposes,
|
|
421
|
+
configure,
|
|
422
|
+
fromZigbee,
|
|
423
|
+
toZigbee,
|
|
424
|
+
isModernExtend: true,
|
|
425
|
+
};
|
|
426
|
+
},
|
|
427
|
+
thermostatWeeklySchedule: () => {
|
|
428
|
+
const exposes = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'].map((day) => e
|
|
429
|
+
.text(`schedule_${day}`, ea.ALL)
|
|
430
|
+
.withDescription(`Schedule for ${day.charAt(0).toUpperCase() + day.slice(1)}, example: "06:00/21.0 12:00/21.0 18:00/21.0 22:00/16.0"`)
|
|
431
|
+
.withCategory('config'));
|
|
432
|
+
const fromZigbee = [
|
|
433
|
+
{
|
|
434
|
+
cluster: 'hvacThermostat',
|
|
435
|
+
type: ['commandGetWeeklyScheduleRsp'],
|
|
436
|
+
convert: (model, msg, publish, options, meta) => {
|
|
437
|
+
const day = Object.entries(constants.thermostatDayOfWeek).find((d) => msg.data.dayofweek & (1 << +d[0]))[1];
|
|
438
|
+
const transitions = msg.data.transitions
|
|
439
|
+
.map((t) => {
|
|
440
|
+
const hours = Math.floor(t.transitionTime / 60);
|
|
441
|
+
const minutes = t.transitionTime % 60;
|
|
442
|
+
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}/${t.heatSetpoint / 100}`;
|
|
443
|
+
})
|
|
444
|
+
.sort()
|
|
445
|
+
.join(' ');
|
|
446
|
+
return {
|
|
447
|
+
...meta.state.weekly_schedule,
|
|
448
|
+
[`schedule_${day}`]: transitions,
|
|
332
449
|
};
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
];
|
|
453
|
+
const toZigbee = [
|
|
454
|
+
{
|
|
455
|
+
key: [
|
|
456
|
+
'schedule_sunday',
|
|
457
|
+
'schedule_monday',
|
|
458
|
+
'schedule_tuesday',
|
|
459
|
+
'schedule_wednesday',
|
|
460
|
+
'schedule_thursday',
|
|
461
|
+
'schedule_friday',
|
|
462
|
+
'schedule_saturday',
|
|
463
|
+
],
|
|
464
|
+
convertSet: async (entity, key, value, meta) => {
|
|
465
|
+
const transitionRegex = /^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9])\/(\d+(\.\d+)?)$/;
|
|
466
|
+
const dayOfWeekName = key.replace('schedule_', '');
|
|
467
|
+
utils.assertString(value, dayOfWeekName);
|
|
468
|
+
const dayKey = utils.getKey(constants.thermostatDayOfWeek, dayOfWeekName.toLowerCase(), null);
|
|
469
|
+
if (!dayKey)
|
|
470
|
+
throw new Error(`Invalid schedule: invalid day name, found: ${dayOfWeekName}`);
|
|
471
|
+
const transitions = value.split(' ').sort();
|
|
472
|
+
if (transitions.length !== 4) {
|
|
473
|
+
throw new Error('Invalid schedule: days must have exactly 4 transitions');
|
|
474
|
+
}
|
|
475
|
+
const payload = {
|
|
476
|
+
dayofweek: 1 << Number(dayKey),
|
|
477
|
+
numoftrans: transitions.length,
|
|
478
|
+
mode: 1 << 0,
|
|
479
|
+
transitions: transitions.map((transition) => {
|
|
480
|
+
const matches = transition.match(transitionRegex);
|
|
481
|
+
if (!matches) {
|
|
482
|
+
throw new Error(`Invalid schedule: transitions must be in format HH:mm/temperature (e.g. 12:00/15.5), found: ${transition}`);
|
|
483
|
+
}
|
|
484
|
+
const [, hours, minutes, temp] = matches;
|
|
485
|
+
const temperature = parseFloat(temp);
|
|
486
|
+
if (temperature < 4 || temperature > 35) {
|
|
487
|
+
throw new Error(`Invalid schedule: temperature value must be between 4-35 (inclusive), found: ${temperature}`);
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
transitionTime: parseInt(hours) * 60 + parseInt(minutes),
|
|
491
|
+
heatSetpoint: Math.round(temperature * 100),
|
|
492
|
+
};
|
|
493
|
+
}),
|
|
339
494
|
};
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
495
|
+
await entity.command('hvacThermostat', 'setWeeklySchedule', payload, utils.getOptions(meta.mapped, entity));
|
|
496
|
+
},
|
|
497
|
+
convertGet: async (entity, key, meta) => {
|
|
498
|
+
const dayOfWeekName = key.replace('schedule_', '');
|
|
499
|
+
const dayKey = utils.getKey(constants.thermostatDayOfWeek, dayOfWeekName.toLowerCase(), null);
|
|
500
|
+
await entity.command('hvacThermostat', 'getWeeklySchedule', {
|
|
501
|
+
daystoreturn: dayKey !== null ? 1 << Number(dayKey) : 0xff,
|
|
502
|
+
modetoreturn: 1,
|
|
503
|
+
}, utils.getOptions(meta.mapped, entity));
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
];
|
|
507
|
+
const configure = [
|
|
508
|
+
async (device, coordinatorEndpoint, definition) => {
|
|
509
|
+
const endpoint = device.getEndpoint(1);
|
|
510
|
+
await endpoint.command('hvacThermostat', 'getWeeklySchedule', {
|
|
511
|
+
daystoreturn: 0xff,
|
|
512
|
+
modetoreturn: 1,
|
|
513
|
+
});
|
|
514
|
+
},
|
|
515
|
+
];
|
|
516
|
+
return { exposes, fromZigbee, toZigbee, configure, isModernExtend: true };
|
|
517
|
+
},
|
|
518
|
+
thermostatChildLock: () => {
|
|
519
|
+
const exposes = [e.binary('child_lock', ea.ALL, 'LOCK', 'UNLOCK').withDescription('Enables/disables physical input on the device')];
|
|
520
|
+
const fromZigbee = [
|
|
521
|
+
{
|
|
522
|
+
cluster: 'hvacUserInterfaceCfg',
|
|
523
|
+
type: ['attributeReport', 'readResponse'],
|
|
524
|
+
convert: (model, msg, publish, options, meta) => {
|
|
525
|
+
if (Object.hasOwn(msg.data, 'keypadLockout')) {
|
|
526
|
+
return { child_lock: msg.data.keypadLockout === 0 ? 'UNLOCK' : 'LOCK' };
|
|
527
|
+
}
|
|
528
|
+
return {};
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
];
|
|
532
|
+
const toZigbee = [
|
|
533
|
+
{
|
|
534
|
+
key: ['child_lock'],
|
|
535
|
+
convertSet: async (entity, key, value, meta) => {
|
|
536
|
+
const keypadLockout = Number(value === 'LOCK');
|
|
537
|
+
await entity.write('hvacUserInterfaceCfg', { keypadLockout });
|
|
538
|
+
return { state: { child_lock: value } };
|
|
539
|
+
},
|
|
540
|
+
convertGet: async (entity, key, meta) => {
|
|
541
|
+
await entity.read('hvacUserInterfaceCfg', ['keypadLockout']);
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
];
|
|
545
|
+
const configure = [
|
|
546
|
+
async (device, coordinatorEndpoint, definition) => {
|
|
547
|
+
const endpoint = device.getEndpoint(1);
|
|
548
|
+
await reporting.bind(endpoint, coordinatorEndpoint, ['hvacUserInterfaceCfg']);
|
|
549
|
+
await endpoint.read('hvacUserInterfaceCfg', ['keypadLockout']);
|
|
550
|
+
await reporting.thermostatKeypadLockMode(endpoint);
|
|
551
|
+
},
|
|
552
|
+
];
|
|
553
|
+
return { exposes, fromZigbee, toZigbee, configure, isModernExtend: true };
|
|
554
|
+
},
|
|
555
|
+
thermostatPreset: () => {
|
|
556
|
+
const systemModeLookup = {
|
|
557
|
+
0: 'off',
|
|
558
|
+
1: 'auto',
|
|
559
|
+
3: 'cool',
|
|
560
|
+
4: 'manual',
|
|
561
|
+
5: 'emergency_heating',
|
|
562
|
+
6: 'precooling',
|
|
563
|
+
7: 'fan_only',
|
|
564
|
+
8: 'dry',
|
|
565
|
+
9: 'sleep',
|
|
566
|
+
};
|
|
567
|
+
const awayOrBoostModeLookup = { 0: 'normal', 1: 'away', 2: 'forced' };
|
|
568
|
+
const fromZigbee = [
|
|
569
|
+
{
|
|
570
|
+
cluster: 'hvacThermostat',
|
|
571
|
+
type: ['attributeReport', 'readResponse'],
|
|
572
|
+
convert: (model, msg, publish, options, meta) => {
|
|
573
|
+
if (!Object.hasOwn(msg.data, 'systemMode') && !Object.hasOwn(msg.data, 'awayOrBoostMode'))
|
|
574
|
+
return;
|
|
575
|
+
const systemMode = msg.data.systemMode ?? globalStore.getValue(msg.device, 'systemMode');
|
|
576
|
+
const awayOrBoostMode = msg.data.awayOrBoostMode ?? globalStore.getValue(msg.device, 'awayOrBoostMode');
|
|
577
|
+
globalStore.putValue(msg.device, 'systemMode', systemMode);
|
|
578
|
+
globalStore.putValue(msg.device, 'awayOrBoostMode', awayOrBoostMode);
|
|
579
|
+
const result = {};
|
|
580
|
+
if (awayOrBoostMode !== undefined && awayOrBoostMode !== 0) {
|
|
581
|
+
result.preset = utils.getFromLookup(awayOrBoostMode, awayOrBoostModeLookup);
|
|
582
|
+
result.away_or_boost_mode = utils.getFromLookup(awayOrBoostMode, awayOrBoostModeLookup);
|
|
583
|
+
if (systemMode !== undefined) {
|
|
584
|
+
result.system_mode = constants.thermostatSystemModes[systemMode];
|
|
345
585
|
}
|
|
346
586
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
587
|
+
else if (systemMode !== undefined) {
|
|
588
|
+
result.preset = utils.getFromLookup(systemMode, systemModeLookup);
|
|
589
|
+
result.system_mode = constants.thermostatSystemModes[systemMode];
|
|
590
|
+
if (awayOrBoostMode !== undefined) {
|
|
591
|
+
result.away_or_boost_mode = utils.getFromLookup(awayOrBoostMode, awayOrBoostModeLookup);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return result;
|
|
595
|
+
},
|
|
350
596
|
},
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
if (!Object.prototype.hasOwnProperty.call(msg.data, attribute))
|
|
381
|
-
return;
|
|
382
|
-
const indicatorLight = msg.data[attribute];
|
|
383
|
-
const firstBit = indicatorLight & 0x01;
|
|
384
|
-
return { indicator_light: firstBit === 1 ? 'on' : 'off' };
|
|
597
|
+
];
|
|
598
|
+
const toZigbee = [
|
|
599
|
+
{
|
|
600
|
+
key: ['preset'],
|
|
601
|
+
convertSet: async (entity, key, value, meta) => {
|
|
602
|
+
if (value === 'away' || value === 'forced') {
|
|
603
|
+
const awayOrBoostMode = value === 'away' ? 1 : 2;
|
|
604
|
+
globalStore.putValue(entity, 'awayOrBoostMode', awayOrBoostMode);
|
|
605
|
+
if (value === 'away') {
|
|
606
|
+
await entity.read('hvacThermostat', ['unoccupiedHeatingSetpoint']);
|
|
607
|
+
}
|
|
608
|
+
await entity.write('hvacThermostat', { awayOrBoostMode });
|
|
609
|
+
return { state: { preset: value, away_or_boost_mode: value } };
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
globalStore.putValue(entity, 'awayOrBoostMode', 0);
|
|
613
|
+
const systemMode = utils.getKey(systemModeLookup, value, undefined, Number);
|
|
614
|
+
await entity.write('hvacThermostat', { systemMode });
|
|
615
|
+
if (typeof systemMode === 'number') {
|
|
616
|
+
return {
|
|
617
|
+
state: {
|
|
618
|
+
// @ts-expect-error ignore
|
|
619
|
+
preset: systemModeLookup[systemMode],
|
|
620
|
+
system_mode: constants.thermostatSystemModes[systemMode],
|
|
621
|
+
},
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
},
|
|
385
626
|
},
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
return
|
|
627
|
+
{
|
|
628
|
+
key: ['system_mode'],
|
|
629
|
+
convertSet: async (entity, key, value, meta) => {
|
|
630
|
+
const systemMode = utils.getKey(constants.thermostatSystemModes, value, undefined, Number);
|
|
631
|
+
if (systemMode === undefined || typeof systemMode !== 'number') {
|
|
632
|
+
throw new Error(`Invalid system mode: ${value}`);
|
|
633
|
+
}
|
|
634
|
+
await entity.write('hvacThermostat', { systemMode });
|
|
635
|
+
return {
|
|
636
|
+
state: {
|
|
637
|
+
// @ts-expect-error ignore
|
|
638
|
+
preset: systemModeLookup[systemMode],
|
|
639
|
+
system_mode: constants.thermostatSystemModes[systemMode],
|
|
640
|
+
},
|
|
641
|
+
};
|
|
642
|
+
},
|
|
643
|
+
convertGet: async (entity, key, meta) => {
|
|
644
|
+
await entity.read('hvacThermostat', ['systemMode']);
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
];
|
|
648
|
+
const configure = [
|
|
649
|
+
async (device, coordinatorEndpoint, definition) => {
|
|
650
|
+
const endpoint = device.getEndpoint(1);
|
|
651
|
+
await endpoint.read('hvacThermostat', ['systemMode']);
|
|
652
|
+
await endpoint.read('hvacThermostat', ['awayOrBoostMode']);
|
|
653
|
+
await reporting.bind(endpoint, coordinatorEndpoint, ['hvacThermostat']);
|
|
654
|
+
await reporting.thermostatSystemMode(endpoint);
|
|
655
|
+
await endpoint.configureReporting('hvacThermostat', (0, reporting_1.payload)('awayOrBoostMode', 10, constants_1.repInterval.HOUR, null));
|
|
656
|
+
},
|
|
657
|
+
];
|
|
658
|
+
return { fromZigbee, toZigbee, configure, isModernExtend: true };
|
|
659
|
+
},
|
|
660
|
+
thermostatCurrentHeatingSetpoint: () => {
|
|
661
|
+
const getAwayOrBoostMode = async (entity) => {
|
|
662
|
+
let result = globalStore.getValue(entity, 'awayOrBoostMode');
|
|
663
|
+
if (result === undefined) {
|
|
664
|
+
const attributeRead = await entity.read('hvacThermostat', ['awayOrBoostMode']);
|
|
395
665
|
// @ts-expect-error ignore
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
666
|
+
result = attributeRead['awayOrBoostMode'];
|
|
667
|
+
globalStore.putValue(entity, 'awayOrBoostMode', result);
|
|
668
|
+
}
|
|
669
|
+
return result;
|
|
670
|
+
};
|
|
671
|
+
const fromZigbee = [
|
|
672
|
+
{
|
|
673
|
+
cluster: 'hvacThermostat',
|
|
674
|
+
type: ['attributeReport', 'readResponse'],
|
|
675
|
+
convert: async (model, msg, publish, options, meta) => {
|
|
676
|
+
const hasHeatingSetpoints = Object.hasOwn(msg.data, 'occupiedHeatingSetpoint') || Object.hasOwn(msg.data, 'unoccupiedHeatingSetpoint');
|
|
677
|
+
if (!hasHeatingSetpoints)
|
|
678
|
+
return;
|
|
679
|
+
const processSetpoint = (value) => {
|
|
680
|
+
if (value === undefined)
|
|
681
|
+
return undefined;
|
|
682
|
+
return (0, utils_1.precisionRound)(value, 2) / 100;
|
|
683
|
+
};
|
|
684
|
+
const occupiedSetpoint = processSetpoint(msg.data.occupiedHeatingSetpoint);
|
|
685
|
+
const unoccupiedSetpoint = processSetpoint(msg.data.unoccupiedHeatingSetpoint);
|
|
686
|
+
const awayOrBoostMode = msg.data.awayOrBoostMode ?? (await getAwayOrBoostMode(msg.device.getEndpoint(1)));
|
|
687
|
+
const result = {};
|
|
688
|
+
if (awayOrBoostMode === 1 && unoccupiedSetpoint !== undefined) {
|
|
689
|
+
result.current_heating_setpoint = unoccupiedSetpoint;
|
|
690
|
+
}
|
|
691
|
+
else if (occupiedSetpoint !== undefined) {
|
|
692
|
+
result.current_heating_setpoint = occupiedSetpoint;
|
|
693
|
+
}
|
|
694
|
+
return result;
|
|
695
|
+
},
|
|
400
696
|
},
|
|
401
|
-
|
|
402
|
-
|
|
697
|
+
];
|
|
698
|
+
const toZigbee = [
|
|
699
|
+
{
|
|
700
|
+
key: ['current_heating_setpoint'],
|
|
701
|
+
convertSet: async (entity, key, value, meta) => {
|
|
702
|
+
utils.assertNumber(value, key);
|
|
703
|
+
const awayOrBoostMode = await getAwayOrBoostMode(entity);
|
|
704
|
+
let convertedValue;
|
|
705
|
+
if (meta.options.thermostat_unit === 'fahrenheit') {
|
|
706
|
+
convertedValue = Math.round(utils.normalizeCelsiusVersionOfFahrenheit(value) * 100);
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
convertedValue = Number((Math.round(Number((value * 2).toFixed(1))) / 2).toFixed(1)) * 100;
|
|
710
|
+
}
|
|
711
|
+
const attribute = awayOrBoostMode === 1 ? 'unoccupiedHeatingSetpoint' : 'occupiedHeatingSetpoint';
|
|
712
|
+
await entity.write('hvacThermostat', { [attribute]: convertedValue });
|
|
713
|
+
return { state: { current_heating_setpoint: value } };
|
|
714
|
+
},
|
|
715
|
+
convertGet: async (entity, key, meta) => {
|
|
716
|
+
await entity.read('hvacThermostat', ['occupiedHeatingSetpoint', 'unoccupiedHeatingSetpoint']);
|
|
717
|
+
},
|
|
403
718
|
},
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
719
|
+
];
|
|
720
|
+
const configure = [
|
|
721
|
+
async (device, coordinatorEndpoint, definition) => {
|
|
722
|
+
const endpoint = device.getEndpoint(1);
|
|
723
|
+
await endpoint.read('hvacThermostat', ['occupiedHeatingSetpoint', 'unoccupiedHeatingSetpoint']);
|
|
724
|
+
await reporting.bind(endpoint, coordinatorEndpoint, ['hvacThermostat']);
|
|
725
|
+
await reporting.thermostatOccupiedHeatingSetpoint(endpoint);
|
|
726
|
+
await reporting.thermostatUnoccupiedHeatingSetpoint(endpoint);
|
|
727
|
+
},
|
|
728
|
+
];
|
|
729
|
+
return { fromZigbee, toZigbee, configure, isModernExtend: true };
|
|
730
|
+
},
|
|
731
|
+
SRZG2856Pro: () => {
|
|
732
|
+
const fromZigbee = [
|
|
733
|
+
{
|
|
734
|
+
cluster: 'sunricherRemote',
|
|
735
|
+
type: ['commandPress'],
|
|
736
|
+
convert: (model, msg, publish, options, meta) => {
|
|
737
|
+
let action = 'unknown';
|
|
738
|
+
if (msg.data.messageType === 0x01) {
|
|
739
|
+
const pressTypeLookup = {
|
|
740
|
+
0x01: 'short_press',
|
|
741
|
+
0x02: 'double_press',
|
|
742
|
+
0x03: 'hold',
|
|
743
|
+
0x04: 'hold_released',
|
|
744
|
+
};
|
|
745
|
+
action = pressTypeLookup[msg.data.pressType] || 'unknown';
|
|
746
|
+
const buttonMask = (msg.data.button2 << 8) | msg.data.button1;
|
|
747
|
+
const actionButtons = [];
|
|
748
|
+
for (let i = 0; i < 16; i++) {
|
|
749
|
+
if ((buttonMask >> i) & 1) {
|
|
750
|
+
const button = i + 1;
|
|
751
|
+
actionButtons.push(`k${button}`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return { action, action_buttons: actionButtons };
|
|
755
|
+
}
|
|
756
|
+
return { action };
|
|
757
|
+
},
|
|
758
|
+
},
|
|
759
|
+
];
|
|
760
|
+
const exposes = [e.action(['short_press', 'double_press', 'hold', 'hold_released'])];
|
|
761
|
+
const configure = [
|
|
762
|
+
async (device, coordinatorEndpoint, definition) => {
|
|
763
|
+
const endpoint = device.getEndpoint(1);
|
|
764
|
+
await endpoint.bind('sunricherRemote', coordinatorEndpoint);
|
|
765
|
+
},
|
|
766
|
+
];
|
|
767
|
+
return {
|
|
768
|
+
fromZigbee,
|
|
769
|
+
exposes,
|
|
770
|
+
configure,
|
|
771
|
+
isModernExtend: true,
|
|
772
|
+
};
|
|
773
|
+
},
|
|
774
|
+
};
|
|
421
775
|
const fzLocal = {
|
|
422
776
|
sunricher_SRZGP2801K45C: {
|
|
423
777
|
cluster: 'greenPower',
|
|
@@ -457,7 +811,287 @@ async function syncTime(endpoint) {
|
|
|
457
811
|
/* Do nothing*/
|
|
458
812
|
}
|
|
459
813
|
}
|
|
814
|
+
async function syncTimeWithTimeZone(endpoint) {
|
|
815
|
+
try {
|
|
816
|
+
const time = Math.round((new Date().getTime() - constants.OneJanuary2000) / 1000);
|
|
817
|
+
const timeZone = new Date().getTimezoneOffset() * -1 * 60;
|
|
818
|
+
await endpoint.write('genTime', { time, timeZone });
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
logger_1.logger.error('Failed to sync time with time zone', NS);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
460
824
|
exports.definitions = [
|
|
825
|
+
{
|
|
826
|
+
zigbeeModel: ['HK-ZRC-K10N-E'],
|
|
827
|
+
model: 'SR-ZG2856-Pro',
|
|
828
|
+
vendor: 'Sunricher',
|
|
829
|
+
description: 'Zigbee smart remote',
|
|
830
|
+
extend: [
|
|
831
|
+
m.battery(),
|
|
832
|
+
m.deviceAddCustomCluster('sunricherRemote', {
|
|
833
|
+
ID: 0xff03,
|
|
834
|
+
attributes: {},
|
|
835
|
+
commands: {
|
|
836
|
+
press: {
|
|
837
|
+
ID: 0x01,
|
|
838
|
+
parameters: [
|
|
839
|
+
{ name: 'messageType', type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
|
|
840
|
+
{ name: 'button2', type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
|
|
841
|
+
{ name: 'button1', type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
|
|
842
|
+
{ name: 'pressType', type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
|
|
843
|
+
],
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
commandsResponse: {},
|
|
847
|
+
}),
|
|
848
|
+
sunricherExtend.SRZG2856Pro(),
|
|
849
|
+
],
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
zigbeeModel: ['ZG9340'],
|
|
853
|
+
model: 'SR-ZG9093TRV',
|
|
854
|
+
vendor: 'Sunricher',
|
|
855
|
+
description: 'Zigbee thermostatic radiator valve',
|
|
856
|
+
extend: [
|
|
857
|
+
m.deviceAddCustomCluster('hvacThermostat', {
|
|
858
|
+
ID: zigbee_herdsman_1.Zcl.Clusters.hvacThermostat.ID,
|
|
859
|
+
attributes: {
|
|
860
|
+
screenTimeout: {
|
|
861
|
+
ID: 0x100d,
|
|
862
|
+
type: zigbee_herdsman_1.Zcl.DataType.UINT8,
|
|
863
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
864
|
+
},
|
|
865
|
+
antiFreezingTemp: {
|
|
866
|
+
ID: 0x1005,
|
|
867
|
+
type: zigbee_herdsman_1.Zcl.DataType.UINT8,
|
|
868
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
869
|
+
},
|
|
870
|
+
temperatureDisplayMode: {
|
|
871
|
+
ID: 0x1008,
|
|
872
|
+
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
|
|
873
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
874
|
+
},
|
|
875
|
+
windowOpenCheck: {
|
|
876
|
+
ID: 0x1009,
|
|
877
|
+
type: zigbee_herdsman_1.Zcl.DataType.UINT8,
|
|
878
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
879
|
+
},
|
|
880
|
+
hysteresis: {
|
|
881
|
+
ID: 0x100a,
|
|
882
|
+
type: zigbee_herdsman_1.Zcl.DataType.UINT8,
|
|
883
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
884
|
+
},
|
|
885
|
+
windowOpenFlag: {
|
|
886
|
+
ID: 0x100b,
|
|
887
|
+
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
|
|
888
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
889
|
+
},
|
|
890
|
+
forcedHeatingTime: {
|
|
891
|
+
ID: 0x100e,
|
|
892
|
+
type: zigbee_herdsman_1.Zcl.DataType.UINT8,
|
|
893
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
894
|
+
},
|
|
895
|
+
errorCode: {
|
|
896
|
+
ID: 0x2003,
|
|
897
|
+
type: zigbee_herdsman_1.Zcl.DataType.BITMAP8,
|
|
898
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
899
|
+
},
|
|
900
|
+
awayOrBoostMode: {
|
|
901
|
+
ID: 0x2002,
|
|
902
|
+
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
|
|
903
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
904
|
+
},
|
|
905
|
+
},
|
|
906
|
+
commands: {},
|
|
907
|
+
commandsResponse: {},
|
|
908
|
+
}),
|
|
909
|
+
sunricherExtend.thermostatPreset(),
|
|
910
|
+
sunricherExtend.thermostatCurrentHeatingSetpoint(),
|
|
911
|
+
m.battery(),
|
|
912
|
+
m.identify(),
|
|
913
|
+
m.numeric({
|
|
914
|
+
name: 'screen_timeout',
|
|
915
|
+
cluster: 'hvacThermostat',
|
|
916
|
+
attribute: 'screenTimeout',
|
|
917
|
+
valueMin: 10,
|
|
918
|
+
valueMax: 30,
|
|
919
|
+
unit: 's',
|
|
920
|
+
description: 'Screen Timeout for Inactivity (excluding gateway config). Range: 10-30s, Default: 10s',
|
|
921
|
+
access: 'ALL',
|
|
922
|
+
entityCategory: 'config',
|
|
923
|
+
}),
|
|
924
|
+
m.numeric({
|
|
925
|
+
name: 'anti_freezing_temp',
|
|
926
|
+
cluster: 'hvacThermostat',
|
|
927
|
+
attribute: 'antiFreezingTemp',
|
|
928
|
+
valueMin: 0,
|
|
929
|
+
valueMax: 10,
|
|
930
|
+
unit: '°C',
|
|
931
|
+
description: 'Anti Freezing(Low Temp) Mode Configuration. 0: disabled, 5~10: temperature (5°C by default)',
|
|
932
|
+
access: 'ALL',
|
|
933
|
+
entityCategory: 'config',
|
|
934
|
+
}),
|
|
935
|
+
m.enumLookup({
|
|
936
|
+
name: 'temperature_display_mode',
|
|
937
|
+
cluster: 'hvacThermostat',
|
|
938
|
+
attribute: 'temperatureDisplayMode',
|
|
939
|
+
lookup: {
|
|
940
|
+
set_temp: 1,
|
|
941
|
+
room_temp: 2,
|
|
942
|
+
},
|
|
943
|
+
description: 'Temperature Display Mode. 1: displays set temp, 2: displays room temp (default)',
|
|
944
|
+
access: 'ALL',
|
|
945
|
+
}),
|
|
946
|
+
m.numeric({
|
|
947
|
+
name: 'window_open_check',
|
|
948
|
+
cluster: 'hvacThermostat',
|
|
949
|
+
attribute: 'windowOpenCheck',
|
|
950
|
+
valueMin: 0,
|
|
951
|
+
valueMax: 10,
|
|
952
|
+
unit: '°C',
|
|
953
|
+
description: 'The temperature threshold for Window Open Detect, value range 0~10, unit is 1°C, 0 means disabled, default value is 5',
|
|
954
|
+
access: 'ALL',
|
|
955
|
+
entityCategory: 'config',
|
|
956
|
+
}),
|
|
957
|
+
m.numeric({
|
|
958
|
+
name: 'hysteresis',
|
|
959
|
+
cluster: 'hvacThermostat',
|
|
960
|
+
attribute: 'hysteresis',
|
|
961
|
+
valueMin: 5,
|
|
962
|
+
valueMax: 20,
|
|
963
|
+
valueStep: 0.1,
|
|
964
|
+
unit: '°C',
|
|
965
|
+
description: 'Control hysteresis setting, range is 5-20, unit is 0.1°C, default value is 10. Because the sensor accuracy is 0.5°C, it is recommended not to set this value below 1°C to avoid affecting the battery life.',
|
|
966
|
+
access: 'ALL',
|
|
967
|
+
entityCategory: 'config',
|
|
968
|
+
}),
|
|
969
|
+
m.binary({
|
|
970
|
+
name: 'window_open_flag',
|
|
971
|
+
cluster: 'hvacThermostat',
|
|
972
|
+
attribute: 'windowOpenFlag',
|
|
973
|
+
description: 'Window open flag',
|
|
974
|
+
valueOn: ['opened', 1],
|
|
975
|
+
valueOff: ['not_opened', 0],
|
|
976
|
+
access: 'STATE_GET',
|
|
977
|
+
}),
|
|
978
|
+
m.numeric({
|
|
979
|
+
name: 'forced_heating_time',
|
|
980
|
+
cluster: 'hvacThermostat',
|
|
981
|
+
attribute: 'forcedHeatingTime',
|
|
982
|
+
valueMin: 10,
|
|
983
|
+
valueMax: 90,
|
|
984
|
+
unit: '10s',
|
|
985
|
+
description: 'Forced heating time, range 10~90, unit is 10s, default value is 30(300s)',
|
|
986
|
+
access: 'ALL',
|
|
987
|
+
entityCategory: 'config',
|
|
988
|
+
}),
|
|
989
|
+
m.enumLookup({
|
|
990
|
+
name: 'error_code',
|
|
991
|
+
cluster: 'hvacThermostat',
|
|
992
|
+
attribute: 'errorCode',
|
|
993
|
+
lookup: {
|
|
994
|
+
no_error: 0,
|
|
995
|
+
motor_error: 4,
|
|
996
|
+
motor_timeout: 5,
|
|
997
|
+
},
|
|
998
|
+
description: 'Error code: 0=No hardware error, 4=Motor error (detected not running), 5=The motor runs exceeding the self-check time without finding the boundary',
|
|
999
|
+
access: 'STATE_GET',
|
|
1000
|
+
}),
|
|
1001
|
+
m.enumLookup({
|
|
1002
|
+
name: 'temperature_display_unit',
|
|
1003
|
+
cluster: 'hvacUserInterfaceCfg',
|
|
1004
|
+
attribute: { ID: 0x0000, type: 0x30 },
|
|
1005
|
+
lookup: {
|
|
1006
|
+
celsius: 0x00,
|
|
1007
|
+
fahrenheit: 0x01,
|
|
1008
|
+
},
|
|
1009
|
+
description: 'The temperature unit shown on the display',
|
|
1010
|
+
access: 'ALL',
|
|
1011
|
+
entityCategory: 'config',
|
|
1012
|
+
}),
|
|
1013
|
+
sunricherExtend.thermostatWeeklySchedule(),
|
|
1014
|
+
sunricherExtend.thermostatChildLock(),
|
|
1015
|
+
],
|
|
1016
|
+
fromZigbee: [fromZigbee_1.default.thermostat],
|
|
1017
|
+
toZigbee: [
|
|
1018
|
+
toZigbee_1.default.thermostat_local_temperature,
|
|
1019
|
+
toZigbee_1.default.thermostat_local_temperature_calibration,
|
|
1020
|
+
toZigbee_1.default.thermostat_running_state,
|
|
1021
|
+
toZigbee_1.default.thermostat_temperature_display_mode,
|
|
1022
|
+
],
|
|
1023
|
+
exposes: [
|
|
1024
|
+
e
|
|
1025
|
+
.climate()
|
|
1026
|
+
.withLocalTemperature(ea.STATE_GET)
|
|
1027
|
+
.withSetpoint('current_heating_setpoint', 5, 35, 0.1, ea.ALL)
|
|
1028
|
+
.withLocalTemperatureCalibration(-30, 30, 0.5, ea.ALL)
|
|
1029
|
+
.withPreset(['off', 'auto', 'away', 'sleep', 'manual', 'forced'], 'Preset of the thermostat. Manual: comfort temp (20°C), Auto: schedule temp (see schedule), ' +
|
|
1030
|
+
'Away: eco temp (6°C), Sleep: night temp (17°C), Forced: temporary heating with configurable duration (default 300s)')
|
|
1031
|
+
.withSystemMode(['off', 'auto', 'heat', 'sleep'], ea.ALL)
|
|
1032
|
+
.withRunningState(['idle', 'heat']),
|
|
1033
|
+
],
|
|
1034
|
+
configure: async (device, coordinatorEndpoint) => {
|
|
1035
|
+
const endpoint = device.getEndpoint(1);
|
|
1036
|
+
const bindClusters = ['genBasic', 'genPowerCfg', 'hvacThermostat', 'hvacUserInterfaceCfg', 'genTime'];
|
|
1037
|
+
const maxRetries = 3;
|
|
1038
|
+
let retryCount = 0;
|
|
1039
|
+
let bindSuccess = false;
|
|
1040
|
+
while (retryCount < maxRetries) {
|
|
1041
|
+
try {
|
|
1042
|
+
if (!bindSuccess) {
|
|
1043
|
+
await reporting.bind(endpoint, coordinatorEndpoint, bindClusters);
|
|
1044
|
+
bindSuccess = true;
|
|
1045
|
+
}
|
|
1046
|
+
const configPromises = [
|
|
1047
|
+
reporting.thermostatTemperature(endpoint),
|
|
1048
|
+
reporting.thermostatOccupiedHeatingSetpoint(endpoint),
|
|
1049
|
+
reporting.thermostatUnoccupiedHeatingSetpoint(endpoint),
|
|
1050
|
+
reporting.thermostatRunningState(endpoint),
|
|
1051
|
+
reporting.batteryPercentageRemaining(endpoint),
|
|
1052
|
+
endpoint.configureReporting('hvacUserInterfaceCfg', (0, reporting_1.payload)('tempDisplayMode', 10, constants_1.repInterval.MINUTE, null)),
|
|
1053
|
+
];
|
|
1054
|
+
await Promise.all(configPromises);
|
|
1055
|
+
const customAttributes = [
|
|
1056
|
+
'screenTimeout',
|
|
1057
|
+
'antiFreezingTemp',
|
|
1058
|
+
'temperatureDisplayMode',
|
|
1059
|
+
'windowOpenCheck',
|
|
1060
|
+
'hysteresis',
|
|
1061
|
+
'windowOpenFlag',
|
|
1062
|
+
'forcedHeatingTime',
|
|
1063
|
+
];
|
|
1064
|
+
await Promise.all(customAttributes.map((attr) => endpoint.configureReporting('hvacThermostat', (0, reporting_1.payload)(attr, 10, constants_1.repInterval.MINUTE, null))));
|
|
1065
|
+
const readPromises = [
|
|
1066
|
+
endpoint.read('hvacUserInterfaceCfg', ['tempDisplayMode']),
|
|
1067
|
+
endpoint.read('hvacThermostat', ['localTemp', 'runningState']),
|
|
1068
|
+
endpoint.read('hvacThermostat', [
|
|
1069
|
+
'screenTimeout',
|
|
1070
|
+
'antiFreezingTemp',
|
|
1071
|
+
'temperatureDisplayMode',
|
|
1072
|
+
'windowOpenCheck',
|
|
1073
|
+
'hysteresis',
|
|
1074
|
+
'windowOpenFlag',
|
|
1075
|
+
'forcedHeatingTime',
|
|
1076
|
+
'errorCode',
|
|
1077
|
+
]),
|
|
1078
|
+
];
|
|
1079
|
+
await Promise.all(readPromises);
|
|
1080
|
+
await syncTimeWithTimeZone(endpoint);
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
catch (e) {
|
|
1084
|
+
retryCount++;
|
|
1085
|
+
logger_1.logger.warning(`Configure attempt ${retryCount} failed: ${e}`, NS);
|
|
1086
|
+
if (retryCount === maxRetries) {
|
|
1087
|
+
logger_1.logger.error(`Failed to configure device after ${maxRetries} attempts`, NS);
|
|
1088
|
+
throw e;
|
|
1089
|
+
}
|
|
1090
|
+
await new Promise((resolve) => setTimeout(resolve, 2000 * retryCount));
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
},
|
|
1094
|
+
},
|
|
461
1095
|
{
|
|
462
1096
|
zigbeeModel: ['HK-SENSOR-SMO'],
|
|
463
1097
|
model: 'SR-ZG9070A-SS',
|
|
@@ -485,7 +1119,7 @@ exports.definitions = [
|
|
|
485
1119
|
commands: {},
|
|
486
1120
|
commandsResponse: {},
|
|
487
1121
|
}),
|
|
488
|
-
|
|
1122
|
+
sunricherExtend.indicatorLight(),
|
|
489
1123
|
m.numeric({
|
|
490
1124
|
name: 'detection_area',
|
|
491
1125
|
cluster: 'sunricherSensor',
|
|
@@ -554,7 +1188,7 @@ exports.definitions = [
|
|
|
554
1188
|
model: 'SR-ZG2835RAC-UK',
|
|
555
1189
|
vendor: 'Sunricher',
|
|
556
1190
|
description: 'Push compatible zigBee knob smart dimmer',
|
|
557
|
-
extend: [m.light(), m.electricityMeter(),
|
|
1191
|
+
extend: [m.light(), m.electricityMeter(), sunricherExtend.externalSwitchType()],
|
|
558
1192
|
},
|
|
559
1193
|
{
|
|
560
1194
|
zigbeeModel: ['ZG2837RAC-K4'],
|
|
@@ -630,7 +1264,7 @@ exports.definitions = [
|
|
|
630
1264
|
model: 'SR-ZG9002K16-Pro',
|
|
631
1265
|
vendor: 'Sunricher',
|
|
632
1266
|
description: 'Zigbee smart wall panel remote',
|
|
633
|
-
extend: [m.battery(),
|
|
1267
|
+
extend: [m.battery(), sunricherExtend.SRZG9002K16Pro()],
|
|
634
1268
|
},
|
|
635
1269
|
{
|
|
636
1270
|
zigbeeModel: ['ZG9030A-MW'],
|
|
@@ -843,14 +1477,14 @@ exports.definitions = [
|
|
|
843
1477
|
model: 'SR-ZG2836D5-Pro',
|
|
844
1478
|
vendor: 'Sunricher',
|
|
845
1479
|
description: 'Zigbee smart remote',
|
|
846
|
-
extend: [m.battery(),
|
|
1480
|
+
extend: [m.battery(), sunricherExtend.SRZG2836D5Pro()],
|
|
847
1481
|
},
|
|
848
1482
|
{
|
|
849
1483
|
zigbeeModel: ['HK-ZRC-K12&RS-E'],
|
|
850
1484
|
model: 'SR-ZG9002KR12-Pro',
|
|
851
1485
|
vendor: 'Sunricher',
|
|
852
1486
|
description: 'Zigbee smart wall panel remote',
|
|
853
|
-
extend: [m.battery(),
|
|
1487
|
+
extend: [m.battery(), sunricherExtend.SRZG9002KR12Pro()],
|
|
854
1488
|
},
|
|
855
1489
|
{
|
|
856
1490
|
zigbeeModel: ['ZV9380A', 'ZG9380A'],
|
|
@@ -864,14 +1498,14 @@ exports.definitions = [
|
|
|
864
1498
|
model: 'SR-ZG2835PAC-AU',
|
|
865
1499
|
vendor: 'Sunricher',
|
|
866
1500
|
description: 'Zigbee push button smart dimmer',
|
|
867
|
-
extend: [m.light({ configureReporting: true }),
|
|
1501
|
+
extend: [m.light({ configureReporting: true }), sunricherExtend.externalSwitchType(), m.electricityMeter()],
|
|
868
1502
|
},
|
|
869
1503
|
{
|
|
870
1504
|
zigbeeModel: ['HK-SL-DIM-CLN'],
|
|
871
1505
|
model: 'SR-ZG9101SAC-HP-CLN',
|
|
872
1506
|
vendor: 'Sunricher',
|
|
873
1507
|
description: 'Zigbee micro smart dimmer',
|
|
874
|
-
extend: [m.light({ configureReporting: true }),
|
|
1508
|
+
extend: [m.light({ configureReporting: true }), sunricherExtend.externalSwitchType(), sunricherExtend.minimumPWM()],
|
|
875
1509
|
},
|
|
876
1510
|
{
|
|
877
1511
|
zigbeeModel: ['HK-SENSOR-CT-MINI'],
|
|
@@ -1134,7 +1768,7 @@ exports.definitions = [
|
|
|
1134
1768
|
model: 'ZG9101SAC-HP-Switch',
|
|
1135
1769
|
vendor: 'Sunricher',
|
|
1136
1770
|
description: 'Zigbee AC in wall switch',
|
|
1137
|
-
extend: [m.onOff({ powerOnBehavior: false }),
|
|
1771
|
+
extend: [m.onOff({ powerOnBehavior: false }), sunricherExtend.externalSwitchType()],
|
|
1138
1772
|
},
|
|
1139
1773
|
{
|
|
1140
1774
|
zigbeeModel: ['Micro Smart Dimmer', 'SM311', 'HK-SL-RDIM-A', 'HK-SL-DIM-EU-A'],
|
|
@@ -1168,7 +1802,7 @@ exports.definitions = [
|
|
|
1168
1802
|
model: 'SR-ZG9040A/ZG9041A-D',
|
|
1169
1803
|
vendor: 'Sunricher',
|
|
1170
1804
|
description: 'Zigbee micro smart dimmer',
|
|
1171
|
-
extend: [m.light({ configureReporting: true }), m.electricityMeter(),
|
|
1805
|
+
extend: [m.light({ configureReporting: true }), m.electricityMeter(), sunricherExtend.externalSwitchType(), sunricherExtend.minimumPWM()],
|
|
1172
1806
|
},
|
|
1173
1807
|
{
|
|
1174
1808
|
zigbeeModel: ['HK-ZD-DIM-A'],
|