zigbee-herdsman-converters 22.0.1 → 22.1.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 +23 -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/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 +917 -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 +8 -7
- 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 +0 -36
- 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,694 @@ 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
|
+
},
|
|
696
|
+
},
|
|
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
|
+
},
|
|
400
718
|
},
|
|
401
|
-
|
|
402
|
-
|
|
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);
|
|
403
727
|
},
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const endpoint = device.getEndpoint(1);
|
|
409
|
-
await endpoint.bind(cluster, coordinatorEndpoint);
|
|
410
|
-
await endpoint.read(cluster, [attribute], { manufacturerCode });
|
|
411
|
-
},
|
|
412
|
-
];
|
|
413
|
-
return {
|
|
414
|
-
exposes,
|
|
415
|
-
configure,
|
|
416
|
-
fromZigbee,
|
|
417
|
-
toZigbee,
|
|
418
|
-
isModernExtend: true,
|
|
419
|
-
};
|
|
420
|
-
}
|
|
728
|
+
];
|
|
729
|
+
return { fromZigbee, toZigbee, configure, isModernExtend: true };
|
|
730
|
+
},
|
|
731
|
+
};
|
|
421
732
|
const fzLocal = {
|
|
422
733
|
sunricher_SRZGP2801K45C: {
|
|
423
734
|
cluster: 'greenPower',
|
|
@@ -457,7 +768,261 @@ async function syncTime(endpoint) {
|
|
|
457
768
|
/* Do nothing*/
|
|
458
769
|
}
|
|
459
770
|
}
|
|
771
|
+
async function syncTimeWithTimeZone(endpoint) {
|
|
772
|
+
try {
|
|
773
|
+
const time = Math.round((new Date().getTime() - constants.OneJanuary2000) / 1000);
|
|
774
|
+
const timeZone = new Date().getTimezoneOffset() * -1 * 60;
|
|
775
|
+
await endpoint.write('genTime', { time, timeZone });
|
|
776
|
+
}
|
|
777
|
+
catch {
|
|
778
|
+
logger_1.logger.error('Failed to sync time with time zone', NS);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
460
781
|
exports.definitions = [
|
|
782
|
+
{
|
|
783
|
+
zigbeeModel: ['ZG9340'],
|
|
784
|
+
model: 'SR-ZG9093TRV',
|
|
785
|
+
vendor: 'Sunricher',
|
|
786
|
+
description: 'Zigbee thermostatic radiator valve',
|
|
787
|
+
extend: [
|
|
788
|
+
m.deviceAddCustomCluster('hvacThermostat', {
|
|
789
|
+
ID: zigbee_herdsman_1.Zcl.Clusters.hvacThermostat.ID,
|
|
790
|
+
attributes: {
|
|
791
|
+
screenTimeout: {
|
|
792
|
+
ID: 0x100d,
|
|
793
|
+
type: zigbee_herdsman_1.Zcl.DataType.UINT8,
|
|
794
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
795
|
+
},
|
|
796
|
+
antiFreezingTemp: {
|
|
797
|
+
ID: 0x1005,
|
|
798
|
+
type: zigbee_herdsman_1.Zcl.DataType.UINT8,
|
|
799
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
800
|
+
},
|
|
801
|
+
temperatureDisplayMode: {
|
|
802
|
+
ID: 0x1008,
|
|
803
|
+
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
|
|
804
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
805
|
+
},
|
|
806
|
+
windowOpenCheck: {
|
|
807
|
+
ID: 0x1009,
|
|
808
|
+
type: zigbee_herdsman_1.Zcl.DataType.UINT8,
|
|
809
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
810
|
+
},
|
|
811
|
+
hysteresis: {
|
|
812
|
+
ID: 0x100a,
|
|
813
|
+
type: zigbee_herdsman_1.Zcl.DataType.UINT8,
|
|
814
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
815
|
+
},
|
|
816
|
+
windowOpenFlag: {
|
|
817
|
+
ID: 0x100b,
|
|
818
|
+
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
|
|
819
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
820
|
+
},
|
|
821
|
+
forcedHeatingTime: {
|
|
822
|
+
ID: 0x100e,
|
|
823
|
+
type: zigbee_herdsman_1.Zcl.DataType.UINT8,
|
|
824
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
825
|
+
},
|
|
826
|
+
errorCode: {
|
|
827
|
+
ID: 0x2003,
|
|
828
|
+
type: zigbee_herdsman_1.Zcl.DataType.BITMAP8,
|
|
829
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
830
|
+
},
|
|
831
|
+
awayOrBoostMode: {
|
|
832
|
+
ID: 0x2002,
|
|
833
|
+
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
|
|
834
|
+
manufacturerCode: sunricherManufacturerCode,
|
|
835
|
+
},
|
|
836
|
+
},
|
|
837
|
+
commands: {},
|
|
838
|
+
commandsResponse: {},
|
|
839
|
+
}),
|
|
840
|
+
sunricherExtend.thermostatPreset(),
|
|
841
|
+
sunricherExtend.thermostatCurrentHeatingSetpoint(),
|
|
842
|
+
m.battery(),
|
|
843
|
+
m.identify(),
|
|
844
|
+
m.numeric({
|
|
845
|
+
name: 'screen_timeout',
|
|
846
|
+
cluster: 'hvacThermostat',
|
|
847
|
+
attribute: 'screenTimeout',
|
|
848
|
+
valueMin: 10,
|
|
849
|
+
valueMax: 30,
|
|
850
|
+
unit: 's',
|
|
851
|
+
description: 'Screen Timeout for Inactivity (excluding gateway config). Range: 10-30s, Default: 10s',
|
|
852
|
+
access: 'ALL',
|
|
853
|
+
entityCategory: 'config',
|
|
854
|
+
}),
|
|
855
|
+
m.numeric({
|
|
856
|
+
name: 'anti_freezing_temp',
|
|
857
|
+
cluster: 'hvacThermostat',
|
|
858
|
+
attribute: 'antiFreezingTemp',
|
|
859
|
+
valueMin: 0,
|
|
860
|
+
valueMax: 10,
|
|
861
|
+
unit: '°C',
|
|
862
|
+
description: 'Anti Freezing(Low Temp) Mode Configuration. 0: disabled, 5~10: temperature (5°C by default)',
|
|
863
|
+
access: 'ALL',
|
|
864
|
+
entityCategory: 'config',
|
|
865
|
+
}),
|
|
866
|
+
m.enumLookup({
|
|
867
|
+
name: 'temperature_display_mode',
|
|
868
|
+
cluster: 'hvacThermostat',
|
|
869
|
+
attribute: 'temperatureDisplayMode',
|
|
870
|
+
lookup: {
|
|
871
|
+
set_temp: 1,
|
|
872
|
+
room_temp: 2,
|
|
873
|
+
},
|
|
874
|
+
description: 'Temperature Display Mode. 1: displays set temp, 2: displays room temp (default)',
|
|
875
|
+
access: 'ALL',
|
|
876
|
+
}),
|
|
877
|
+
m.numeric({
|
|
878
|
+
name: 'window_open_check',
|
|
879
|
+
cluster: 'hvacThermostat',
|
|
880
|
+
attribute: 'windowOpenCheck',
|
|
881
|
+
valueMin: 0,
|
|
882
|
+
valueMax: 10,
|
|
883
|
+
unit: '°C',
|
|
884
|
+
description: 'The temperature threshold for Window Open Detect, value range 0~10, unit is 1°C, 0 means disabled, default value is 5',
|
|
885
|
+
access: 'ALL',
|
|
886
|
+
entityCategory: 'config',
|
|
887
|
+
}),
|
|
888
|
+
m.numeric({
|
|
889
|
+
name: 'hysteresis',
|
|
890
|
+
cluster: 'hvacThermostat',
|
|
891
|
+
attribute: 'hysteresis',
|
|
892
|
+
valueMin: 5,
|
|
893
|
+
valueMax: 20,
|
|
894
|
+
valueStep: 0.1,
|
|
895
|
+
unit: '°C',
|
|
896
|
+
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.',
|
|
897
|
+
access: 'ALL',
|
|
898
|
+
entityCategory: 'config',
|
|
899
|
+
}),
|
|
900
|
+
m.binary({
|
|
901
|
+
name: 'window_open_flag',
|
|
902
|
+
cluster: 'hvacThermostat',
|
|
903
|
+
attribute: 'windowOpenFlag',
|
|
904
|
+
description: 'Window open flag',
|
|
905
|
+
valueOn: ['opened', 1],
|
|
906
|
+
valueOff: ['not_opened', 0],
|
|
907
|
+
access: 'STATE_GET',
|
|
908
|
+
}),
|
|
909
|
+
m.numeric({
|
|
910
|
+
name: 'forced_heating_time',
|
|
911
|
+
cluster: 'hvacThermostat',
|
|
912
|
+
attribute: 'forcedHeatingTime',
|
|
913
|
+
valueMin: 10,
|
|
914
|
+
valueMax: 90,
|
|
915
|
+
unit: '10s',
|
|
916
|
+
description: 'Forced heating time, range 10~90, unit is 10s, default value is 30(300s)',
|
|
917
|
+
access: 'ALL',
|
|
918
|
+
entityCategory: 'config',
|
|
919
|
+
}),
|
|
920
|
+
m.enumLookup({
|
|
921
|
+
name: 'error_code',
|
|
922
|
+
cluster: 'hvacThermostat',
|
|
923
|
+
attribute: 'errorCode',
|
|
924
|
+
lookup: {
|
|
925
|
+
no_error: 0,
|
|
926
|
+
motor_error: 4,
|
|
927
|
+
motor_timeout: 5,
|
|
928
|
+
},
|
|
929
|
+
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',
|
|
930
|
+
access: 'STATE_GET',
|
|
931
|
+
}),
|
|
932
|
+
m.enumLookup({
|
|
933
|
+
name: 'temperature_display_unit',
|
|
934
|
+
cluster: 'hvacUserInterfaceCfg',
|
|
935
|
+
attribute: { ID: 0x0000, type: 0x30 },
|
|
936
|
+
lookup: {
|
|
937
|
+
celsius: 0x00,
|
|
938
|
+
fahrenheit: 0x01,
|
|
939
|
+
},
|
|
940
|
+
description: 'The temperature unit shown on the display',
|
|
941
|
+
access: 'ALL',
|
|
942
|
+
entityCategory: 'config',
|
|
943
|
+
}),
|
|
944
|
+
sunricherExtend.thermostatWeeklySchedule(),
|
|
945
|
+
sunricherExtend.thermostatChildLock(),
|
|
946
|
+
],
|
|
947
|
+
fromZigbee: [fromZigbee_1.default.thermostat],
|
|
948
|
+
toZigbee: [
|
|
949
|
+
toZigbee_1.default.thermostat_local_temperature,
|
|
950
|
+
toZigbee_1.default.thermostat_local_temperature_calibration,
|
|
951
|
+
toZigbee_1.default.thermostat_running_state,
|
|
952
|
+
toZigbee_1.default.thermostat_temperature_display_mode,
|
|
953
|
+
],
|
|
954
|
+
exposes: [
|
|
955
|
+
e
|
|
956
|
+
.climate()
|
|
957
|
+
.withLocalTemperature(ea.STATE_GET)
|
|
958
|
+
.withSetpoint('current_heating_setpoint', 5, 35, 0.1, ea.ALL)
|
|
959
|
+
.withLocalTemperatureCalibration(-30, 30, 0.5, ea.ALL)
|
|
960
|
+
.withPreset(['off', 'auto', 'away', 'sleep', 'manual', 'forced'], 'Preset of the thermostat. Manual: comfort temp (20°C), Auto: schedule temp (see schedule), ' +
|
|
961
|
+
'Away: eco temp (6°C), Sleep: night temp (17°C), Forced: temporary heating with configurable duration (default 300s)')
|
|
962
|
+
.withSystemMode(['off', 'auto', 'heat', 'sleep'], ea.ALL)
|
|
963
|
+
.withRunningState(['idle', 'heat']),
|
|
964
|
+
],
|
|
965
|
+
configure: async (device, coordinatorEndpoint) => {
|
|
966
|
+
const endpoint = device.getEndpoint(1);
|
|
967
|
+
const bindClusters = ['genBasic', 'genPowerCfg', 'hvacThermostat', 'hvacUserInterfaceCfg', 'genTime'];
|
|
968
|
+
const maxRetries = 3;
|
|
969
|
+
let retryCount = 0;
|
|
970
|
+
let bindSuccess = false;
|
|
971
|
+
while (retryCount < maxRetries) {
|
|
972
|
+
try {
|
|
973
|
+
if (!bindSuccess) {
|
|
974
|
+
await reporting.bind(endpoint, coordinatorEndpoint, bindClusters);
|
|
975
|
+
bindSuccess = true;
|
|
976
|
+
}
|
|
977
|
+
const configPromises = [
|
|
978
|
+
reporting.thermostatTemperature(endpoint),
|
|
979
|
+
reporting.thermostatOccupiedHeatingSetpoint(endpoint),
|
|
980
|
+
reporting.thermostatUnoccupiedHeatingSetpoint(endpoint),
|
|
981
|
+
reporting.thermostatRunningState(endpoint),
|
|
982
|
+
reporting.batteryPercentageRemaining(endpoint),
|
|
983
|
+
endpoint.configureReporting('hvacUserInterfaceCfg', (0, reporting_1.payload)('tempDisplayMode', 10, constants_1.repInterval.MINUTE, null)),
|
|
984
|
+
];
|
|
985
|
+
await Promise.all(configPromises);
|
|
986
|
+
const customAttributes = [
|
|
987
|
+
'screenTimeout',
|
|
988
|
+
'antiFreezingTemp',
|
|
989
|
+
'temperatureDisplayMode',
|
|
990
|
+
'windowOpenCheck',
|
|
991
|
+
'hysteresis',
|
|
992
|
+
'windowOpenFlag',
|
|
993
|
+
'forcedHeatingTime',
|
|
994
|
+
];
|
|
995
|
+
await Promise.all(customAttributes.map((attr) => endpoint.configureReporting('hvacThermostat', (0, reporting_1.payload)(attr, 10, constants_1.repInterval.MINUTE, null))));
|
|
996
|
+
const readPromises = [
|
|
997
|
+
endpoint.read('hvacUserInterfaceCfg', ['tempDisplayMode']),
|
|
998
|
+
endpoint.read('hvacThermostat', ['localTemp', 'runningState']),
|
|
999
|
+
endpoint.read('hvacThermostat', [
|
|
1000
|
+
'screenTimeout',
|
|
1001
|
+
'antiFreezingTemp',
|
|
1002
|
+
'temperatureDisplayMode',
|
|
1003
|
+
'windowOpenCheck',
|
|
1004
|
+
'hysteresis',
|
|
1005
|
+
'windowOpenFlag',
|
|
1006
|
+
'forcedHeatingTime',
|
|
1007
|
+
'errorCode',
|
|
1008
|
+
]),
|
|
1009
|
+
];
|
|
1010
|
+
await Promise.all(readPromises);
|
|
1011
|
+
await syncTimeWithTimeZone(endpoint);
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1014
|
+
catch (e) {
|
|
1015
|
+
retryCount++;
|
|
1016
|
+
logger_1.logger.warning(`Configure attempt ${retryCount} failed: ${e}`, NS);
|
|
1017
|
+
if (retryCount === maxRetries) {
|
|
1018
|
+
logger_1.logger.error(`Failed to configure device after ${maxRetries} attempts`, NS);
|
|
1019
|
+
throw e;
|
|
1020
|
+
}
|
|
1021
|
+
await new Promise((resolve) => setTimeout(resolve, 2000 * retryCount));
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
},
|
|
1025
|
+
},
|
|
461
1026
|
{
|
|
462
1027
|
zigbeeModel: ['HK-SENSOR-SMO'],
|
|
463
1028
|
model: 'SR-ZG9070A-SS',
|
|
@@ -485,7 +1050,7 @@ exports.definitions = [
|
|
|
485
1050
|
commands: {},
|
|
486
1051
|
commandsResponse: {},
|
|
487
1052
|
}),
|
|
488
|
-
|
|
1053
|
+
sunricherExtend.indicatorLight(),
|
|
489
1054
|
m.numeric({
|
|
490
1055
|
name: 'detection_area',
|
|
491
1056
|
cluster: 'sunricherSensor',
|
|
@@ -554,7 +1119,7 @@ exports.definitions = [
|
|
|
554
1119
|
model: 'SR-ZG2835RAC-UK',
|
|
555
1120
|
vendor: 'Sunricher',
|
|
556
1121
|
description: 'Push compatible zigBee knob smart dimmer',
|
|
557
|
-
extend: [m.light(), m.electricityMeter(),
|
|
1122
|
+
extend: [m.light(), m.electricityMeter(), sunricherExtend.externalSwitchType()],
|
|
558
1123
|
},
|
|
559
1124
|
{
|
|
560
1125
|
zigbeeModel: ['ZG2837RAC-K4'],
|
|
@@ -630,7 +1195,7 @@ exports.definitions = [
|
|
|
630
1195
|
model: 'SR-ZG9002K16-Pro',
|
|
631
1196
|
vendor: 'Sunricher',
|
|
632
1197
|
description: 'Zigbee smart wall panel remote',
|
|
633
|
-
extend: [m.battery(),
|
|
1198
|
+
extend: [m.battery(), sunricherExtend.SRZG9002K16Pro()],
|
|
634
1199
|
},
|
|
635
1200
|
{
|
|
636
1201
|
zigbeeModel: ['ZG9030A-MW'],
|
|
@@ -843,14 +1408,14 @@ exports.definitions = [
|
|
|
843
1408
|
model: 'SR-ZG2836D5-Pro',
|
|
844
1409
|
vendor: 'Sunricher',
|
|
845
1410
|
description: 'Zigbee smart remote',
|
|
846
|
-
extend: [m.battery(),
|
|
1411
|
+
extend: [m.battery(), sunricherExtend.SRZG2836D5Pro()],
|
|
847
1412
|
},
|
|
848
1413
|
{
|
|
849
1414
|
zigbeeModel: ['HK-ZRC-K12&RS-E'],
|
|
850
1415
|
model: 'SR-ZG9002KR12-Pro',
|
|
851
1416
|
vendor: 'Sunricher',
|
|
852
1417
|
description: 'Zigbee smart wall panel remote',
|
|
853
|
-
extend: [m.battery(),
|
|
1418
|
+
extend: [m.battery(), sunricherExtend.SRZG9002KR12Pro()],
|
|
854
1419
|
},
|
|
855
1420
|
{
|
|
856
1421
|
zigbeeModel: ['ZV9380A', 'ZG9380A'],
|
|
@@ -864,14 +1429,14 @@ exports.definitions = [
|
|
|
864
1429
|
model: 'SR-ZG2835PAC-AU',
|
|
865
1430
|
vendor: 'Sunricher',
|
|
866
1431
|
description: 'Zigbee push button smart dimmer',
|
|
867
|
-
extend: [m.light({ configureReporting: true }),
|
|
1432
|
+
extend: [m.light({ configureReporting: true }), sunricherExtend.externalSwitchType(), m.electricityMeter()],
|
|
868
1433
|
},
|
|
869
1434
|
{
|
|
870
1435
|
zigbeeModel: ['HK-SL-DIM-CLN'],
|
|
871
1436
|
model: 'SR-ZG9101SAC-HP-CLN',
|
|
872
1437
|
vendor: 'Sunricher',
|
|
873
1438
|
description: 'Zigbee micro smart dimmer',
|
|
874
|
-
extend: [m.light({ configureReporting: true }),
|
|
1439
|
+
extend: [m.light({ configureReporting: true }), sunricherExtend.externalSwitchType(), sunricherExtend.minimumPWM()],
|
|
875
1440
|
},
|
|
876
1441
|
{
|
|
877
1442
|
zigbeeModel: ['HK-SENSOR-CT-MINI'],
|
|
@@ -1134,7 +1699,7 @@ exports.definitions = [
|
|
|
1134
1699
|
model: 'ZG9101SAC-HP-Switch',
|
|
1135
1700
|
vendor: 'Sunricher',
|
|
1136
1701
|
description: 'Zigbee AC in wall switch',
|
|
1137
|
-
extend: [m.onOff({ powerOnBehavior: false }),
|
|
1702
|
+
extend: [m.onOff({ powerOnBehavior: false }), sunricherExtend.externalSwitchType()],
|
|
1138
1703
|
},
|
|
1139
1704
|
{
|
|
1140
1705
|
zigbeeModel: ['Micro Smart Dimmer', 'SM311', 'HK-SL-RDIM-A', 'HK-SL-DIM-EU-A'],
|
|
@@ -1168,7 +1733,7 @@ exports.definitions = [
|
|
|
1168
1733
|
model: 'SR-ZG9040A/ZG9041A-D',
|
|
1169
1734
|
vendor: 'Sunricher',
|
|
1170
1735
|
description: 'Zigbee micro smart dimmer',
|
|
1171
|
-
extend: [m.light({ configureReporting: true }), m.electricityMeter(),
|
|
1736
|
+
extend: [m.light({ configureReporting: true }), m.electricityMeter(), sunricherExtend.externalSwitchType(), sunricherExtend.minimumPWM()],
|
|
1172
1737
|
},
|
|
1173
1738
|
{
|
|
1174
1739
|
zigbeeModel: ['HK-ZD-DIM-A'],
|