homebridge-tuya-plus 3.1.2

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.
Files changed (42) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  2. package/.github/ISSUE_TEMPLATE/new-device.md +24 -0
  3. package/.github/workflows/codeql-analysis.yml +67 -0
  4. package/.github/workflows/lint.yml +23 -0
  5. package/Changelog.md +63 -0
  6. package/LICENSE +21 -0
  7. package/Readme.MD +106 -0
  8. package/assets/Tuya-Plugin-Branding.png +0 -0
  9. package/bin/cli-decode.js +197 -0
  10. package/bin/cli-find.js +207 -0
  11. package/bin/cli.js +13 -0
  12. package/config-example.MD +43 -0
  13. package/config.schema.json +538 -0
  14. package/eslint.config.mjs +15 -0
  15. package/index.js +242 -0
  16. package/lib/AirConditionerAccessory.js +445 -0
  17. package/lib/AirPurifierAccessory.js +532 -0
  18. package/lib/BaseAccessory.js +290 -0
  19. package/lib/ConvectorAccessory.js +313 -0
  20. package/lib/CustomMultiOutletAccessory.js +111 -0
  21. package/lib/DehumidifierAccessory.js +301 -0
  22. package/lib/DoorbellAccessory.js +208 -0
  23. package/lib/EnergyCharacteristics.js +86 -0
  24. package/lib/GarageDoorAccessory.js +307 -0
  25. package/lib/MultiOutletAccessory.js +106 -0
  26. package/lib/OilDiffuserAccessory.js +480 -0
  27. package/lib/OutletAccessory.js +83 -0
  28. package/lib/RGBTWLightAccessory.js +234 -0
  29. package/lib/RGBTWOutletAccessory.js +296 -0
  30. package/lib/SimpleBlindsAccessory.js +299 -0
  31. package/lib/SimpleDimmer2Accessory.js +54 -0
  32. package/lib/SimpleDimmerAccessory.js +54 -0
  33. package/lib/SimpleFanAccessory.js +137 -0
  34. package/lib/SimpleFanLightAccessory.js +201 -0
  35. package/lib/SimpleHeaterAccessory.js +154 -0
  36. package/lib/SimpleLightAccessory.js +39 -0
  37. package/lib/SwitchAccessory.js +106 -0
  38. package/lib/TWLightAccessory.js +91 -0
  39. package/lib/TuyaAccessory.js +746 -0
  40. package/lib/TuyaDiscovery.js +165 -0
  41. package/lib/ValveAccessory.js +150 -0
  42. package/package.json +49 -0
@@ -0,0 +1,290 @@
1
+ class BaseAccessory {
2
+ constructor(...props) {
3
+ let isNew;
4
+ [this.platform, this.accessory, this.device, isNew = true] = [...props];
5
+ ({log: this.log, api: {hap: this.hap}} = this.platform);
6
+
7
+ if (isNew) this._registerPlatformAccessory();
8
+
9
+ this.accessory.on('identify', function(paired, callback) {
10
+ // ToDo: Add identification routine
11
+ this.log("%s - identify", this.device.context.name);
12
+ callback();
13
+ }.bind(this));
14
+
15
+ this.device.once('connect', () => {
16
+ this.log('Connected to', this.device.context.name);
17
+ });
18
+
19
+ this.device.once('change', () => {
20
+ this.log(`Ready to handle ${this.device.context.name} (${this.device.context.type}:${this.device.context.version}) with signature ${JSON.stringify(this.device.state)}`);
21
+
22
+ this._registerCharacteristics(this.device.state);
23
+ });
24
+
25
+ this.device._connect();
26
+ }
27
+
28
+ _registerPlatformAccessory() {
29
+ this.platform.registerPlatformAccessories(this.accessory);
30
+ }
31
+
32
+ _checkServiceName(service, name) {
33
+ const {Characteristic} = this.hap;
34
+
35
+ if (service.displayName !== name) {
36
+ const nameCharacteristic = service.getCharacteristic(Characteristic.Name) || service.addCharacteristic(Characteristic.Name);
37
+ nameCharacteristic.setValue(name);
38
+ service.displayName = name;
39
+ }
40
+ }
41
+
42
+ _removeCharacteristic(service, characteristicType) {
43
+ if (!service || !characteristicType || !characteristicType.UUID) return;
44
+
45
+ service.characteristics.some(characteristic => {
46
+ if (!characteristic || characteristic.UUID !== characteristicType.UUID) return false;
47
+ service.removeCharacteristic(characteristic);
48
+ return true;
49
+ });
50
+ }
51
+
52
+ _getCustomDP(numeral) {
53
+ return (isFinite(numeral) && parseInt(numeral) > 0) ? String(numeral) : false;
54
+ }
55
+
56
+ _coerceBoolean(b, defaultValue) {
57
+ const df = defaultValue || false;
58
+ return typeof b === 'boolean' ? b : (typeof b === 'string' ? b.toLowerCase().trim() === 'true' : (typeof b === 'number' ? b !== 0 : df));
59
+ }
60
+
61
+ getState(dp, callback) {
62
+ if (!this.device.connected) return callback(true);
63
+ const _callback = () => {
64
+ if (Array.isArray(dp)) {
65
+ const ret = {};
66
+ dp.forEach(p => {
67
+ ret[p] = this.device.state[p];
68
+ });
69
+ callback(null, ret);
70
+ } else {
71
+ callback(null, this.device.state[dp]);
72
+ }
73
+ };
74
+
75
+ process.nextTick(_callback);
76
+ }
77
+
78
+ setState(dp, value, callback) {
79
+ this.setMultiState({[dp.toString()]: value}, callback);
80
+ }
81
+
82
+ setMultiStateLegacy(dps, callback) {
83
+ //Adding back the original set multistate command as the multistate command from PR #267 Breaks the Fan Code by splitting the command into two.
84
+ //For devices like the DETA Smart Fan Controller Switch that by default set the speed as 3 the new code in the setMultiState function causes issues.
85
+ if (!this.device.connected) return callback(true);
86
+ const ret = this.device.update(dps);
87
+ callback && callback(!ret);
88
+ }
89
+
90
+ setMultiState(dps, callback) {
91
+ if (!this.device.connected) return callback(true);
92
+ for (const dp in dps) {
93
+ if (dps.hasOwnProperty(dp) && dps[dp] !== this.device.state[dp]){
94
+ this.__ret = this.device.update({[dp.toString()] : dps[dp]});
95
+ }
96
+ }
97
+ callback && callback(!this.__ret);
98
+ }
99
+
100
+ getDividedState(dp, divisor, callback) {
101
+ this.getState(dp, (err, data) => {
102
+ if (err) return callback(err);
103
+ if (!isFinite(data)) return callback(true);
104
+
105
+ callback(null, this._getDividedState(data, divisor));
106
+ });
107
+ }
108
+
109
+ _getDividedState(dp, divisor) {
110
+ return (parseFloat(dp) / divisor) || 0;
111
+ }
112
+
113
+ _detectColorFunction(value) {
114
+ this.colorFunction = this.device.context.colorFunction && {HSB: 'HSB', HEXHSB: 'HEXHSB'}[this.device.context.colorFunction.toUpperCase()];
115
+ if (!this.colorFunction && value) {
116
+ this.colorFunction = {12: 'HSB', 14: 'HEXHSB'}[value.length] || 'Unknown';
117
+ if (this.colorFunction) this.log.info(`Color format for ${this.device.context.name} (${this.device.context.version}) identified as ${this.colorFunction} (length: ${value.length}).`);
118
+ }
119
+ if (!this.colorFunction) {
120
+ this.colorFunction = 'Unknown';
121
+ this.log.info(`Color format for ${this.device.context.name} (${this.device.context.version}) is undetectable.`);
122
+ } else if (this.colorFunction === 'HSB') {
123
+ // If not overridden by config, use the scale of 1000
124
+ if (!this.device.context.scaleBrightness) this.device.context.scaleBrightness = 1000;
125
+ if (!this.device.context.scaleWhiteColor) this.device.context.scaleWhiteColor = 1000;
126
+ }
127
+ }
128
+
129
+ convertBrightnessFromHomeKitToTuya(value) {
130
+ const min = this.device.context.minBrightness || 27;
131
+ const scale = this.device.context.scaleBrightness || 255;
132
+ return Math.round(((scale - min) * value + 100 * min - scale) / 99);
133
+ }
134
+
135
+ convertBrightnessFromTuyaToHomeKit(value) {
136
+ const min = this.device.context.minBrightness || 27;
137
+ const scale = this.device.context.scaleBrightness || 255;
138
+ return Math.round((99 * (value || 0) - 100 * min + scale) / (scale - min));
139
+ }
140
+
141
+ convertRotationSpeedFromHomeKitToTuya(value) {
142
+ const max = this.device.context.maxSpeed || 3;
143
+ const scale = Math.floor(100 / max);
144
+ return Math.round(value / scale)
145
+ }
146
+
147
+ convertRotationSpeedFromTuyaToHomeKit(value) {
148
+ const max = this.device.context.maxSpeed || 3;
149
+ const scale = Math.max(100 / max);
150
+ return Math.round(value * scale)
151
+ }
152
+
153
+ convertColorTemperatureFromHomeKitToTuya(value) {
154
+ const min = this.device.context.minWhiteColor || 140;
155
+ const max = this.device.context.maxWhiteColor || 400;
156
+ const scale = this.device.context.scaleWhiteColor || 255;
157
+ const adjustedValue = (value - 71) * (max - min) / (600 - 71) + 153;
158
+ const convertedValue = Math.round((scale * min / (max - min)) * ((max / adjustedValue) - 1));
159
+ return Math.min(scale, Math.max(0, convertedValue));
160
+ }
161
+
162
+ convertColorTemperatureFromTuyaToHomeKit(value) {
163
+ const min = this.device.context.minWhiteColor || 140;
164
+ const max = this.device.context.maxWhiteColor || 400;
165
+ const scale = this.device.context.scaleWhiteColor || 255;
166
+ const unadjustedValue = max / ((value * (max - min) / (scale * min)) + 1);
167
+ const convertedValue = Math.round((unadjustedValue - 153) * (600 - 71) / (max - min) + 71);
168
+ return Math.min(600, Math.max(71, convertedValue));
169
+ }
170
+
171
+ convertColorFromHomeKitToTuya(value, dpValue) {
172
+ switch (this.device.context.colorFunction) {
173
+ case 'HSB':
174
+ return this.convertColorFromHomeKitToTuya_HSB(value, dpValue);
175
+
176
+ default:
177
+ return this.convertColorFromHomeKitToTuya_HEXHSB(value, dpValue);
178
+ }
179
+ }
180
+ convertColorFromHomeKitToTuya_HEXHSB(value, dpValue) {
181
+ const cached = this.convertColorFromTuya_HEXHSB_ToHomeKit(dpValue || this.device.state[this.dpColor]);
182
+ let {h, s, b} = {...cached, ...value};
183
+ const hsb = h.toString(16).padStart(4, '0') + Math.round(2.55 * s).toString(16).padStart(2, '0') + Math.round(2.55 * b).toString(16).padStart(2, '0');
184
+ h /= 60;
185
+ s /= 100;
186
+ b *= 2.55;
187
+
188
+ const
189
+ i = Math.floor(h),
190
+ f = h - i,
191
+ p = b * (1 - s),
192
+ q = b * (1 - s * f),
193
+ t = b * (1 - s * (1 - f)),
194
+ rgb = (() => {
195
+ switch (i % 6) {
196
+ case 0:
197
+ return [b, t, p];
198
+ case 1:
199
+ return [q, b, p];
200
+ case 2:
201
+ return [p, b, t];
202
+ case 3:
203
+ return [p, q, b];
204
+ case 4:
205
+ return [t, p, b];
206
+ case 5:
207
+ return [b, p, q];
208
+ }
209
+ })().map(c => Math.round(c).toString(16).padStart(2, '0')),
210
+ hex = rgb.join('');
211
+
212
+ return hex + hsb;
213
+ }
214
+
215
+ convertColorFromHomeKitToTuya_HSB(value, dpValue) {
216
+ const cached = this.convertColorFromTuya_HSB_ToHomeKit(dpValue || this.device.state[this.dpColor]);
217
+ let {h, s, b} = {...cached, ...value};
218
+ return h.toString(16).padStart(4, '0') + (10 * s).toString(16).padStart(4, '0') + (10 * b).toString(16).padStart(4, '0');
219
+ }
220
+
221
+ convertColorFromTuyaToHomeKit(value) {
222
+ switch (this.device.context.colorFunction) {
223
+ case 'HSB':
224
+ return this.convertColorFromTuya_HSB_ToHomeKit(value);
225
+
226
+ default:
227
+ return this.convertColorFromTuya_HEXHSB_ToHomeKit(value);
228
+ }
229
+ }
230
+
231
+ convertColorFromTuya_HEXHSB_ToHomeKit(value) {
232
+ const [, h, s, b] = (value || '0000000000ffff').match(/^.{6}([0-9a-f]{4})([0-9a-f]{2})([0-9a-f]{2})$/i) || [0, '0', 'ff', 'ff'];
233
+ return {
234
+ h: parseInt(h, 16),
235
+ s: Math.round(parseInt(s, 16) / 2.55),
236
+ b: Math.round(parseInt(b, 16) / 2.55)
237
+ };
238
+ }
239
+
240
+ convertColorFromTuya_HSB_ToHomeKit(value) {
241
+ const [, h, s, b] = (value || '000003e803e8').match(/^([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})$/i) || [0, '0', '3e8', '3e8'];
242
+ return {
243
+ h: parseInt(h, 16),
244
+ s: Math.round(parseInt(s, 16) / 10),
245
+ b: Math.round(parseInt(b, 16) / 10)
246
+ };
247
+ }
248
+
249
+
250
+ /* Based on works of:
251
+ * Tanner Helland (http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/)
252
+ * Neil Bartlett (http://www.zombieprototypes.com/?p=210)
253
+ */
254
+
255
+ convertHomeKitColorTemperatureToHomeKitColor(value) {
256
+ const dKelvin = 10000 / value;
257
+ const rgb = [
258
+ dKelvin > 66 ? 351.97690566805693 + 0.114206453784165 * (dKelvin - 55) - 40.25366309332127 * Math.log(dKelvin - 55) : 255,
259
+ dKelvin > 66 ? 325.4494125711974 + 0.07943456536662342 * (dKelvin - 50) - 28.0852963507957 * Math.log(dKelvin - 55) : 104.49216199393888 * Math.log(dKelvin - 2) - 0.44596950469579133 * (dKelvin - 2) - 155.25485562709179,
260
+ dKelvin > 66 ? 255 : 115.67994401066147 * Math.log(dKelvin - 10) + 0.8274096064007395 * (dKelvin - 10) - 254.76935184120902
261
+ ].map(v => Math.max(0, Math.min(255, v)) / 255);
262
+ const max = Math.max(...rgb);
263
+ const min = Math.min(...rgb);
264
+ let d = max - min,
265
+ h = 0,
266
+ s = max ? 100 * d / max : 0,
267
+ b = 100 * max;
268
+
269
+ if (d) {
270
+ switch (max) {
271
+ case rgb[0]: h = (rgb[1] - rgb[2]) / d + (rgb[1] < rgb[2] ? 6 : 0); break;
272
+ case rgb[1]: h = (rgb[2] - rgb[0]) / d + 2; break;
273
+ default: h = (rgb[0] - rgb[1]) / d + 4; break;
274
+ }
275
+ h *= 60;
276
+ }
277
+ return {
278
+ h: Math.round(h),
279
+ s: Math.round(s),
280
+ b: Math.round(b)
281
+ };
282
+ }
283
+
284
+ // Checks homebridge version to see if Adaptive Lighting is supported
285
+ adaptiveLightingSupport() {
286
+ return (this.platform.api.versionGreaterOrEqual && this.platform.api.versionGreaterOrEqual('v1.3.0-beta.23'))
287
+ }
288
+ }
289
+
290
+ module.exports = BaseAccessory;
@@ -0,0 +1,313 @@
1
+ const BaseAccessory = require('./BaseAccessory');
2
+
3
+ class ConvectorAccessory extends BaseAccessory {
4
+ static getCategory(Categories) {
5
+ return Categories.AIR_HEATER;
6
+ }
7
+
8
+ constructor(...props) {
9
+ super(...props);
10
+ }
11
+
12
+ _registerPlatformAccessory() {
13
+ const {Service} = this.hap;
14
+
15
+ this.accessory.addService(Service.HeaterCooler, this.device.context.name);
16
+
17
+ super._registerPlatformAccessory();
18
+ }
19
+
20
+ _registerCharacteristics(dps) {
21
+ const {Service, Characteristic} = this.hap;
22
+ const service = this.accessory.getService(Service.HeaterCooler);
23
+ this._checkServiceName(service, this.device.context.name);
24
+
25
+ this.dpActive = this._getCustomDP(this.device.context.dpActive) || '7';
26
+ this.dpDesiredTemperature = this._getCustomDP(this.device.context.dpDesiredTemperature) || '2';
27
+ this.dpCurrentTemperature = this._getCustomDP(this.device.context.dpCurrentTemperature) || '3';
28
+ this.dpRotationSpeed = this._getCustomDP(this.device.context.dpRotationSpeed) || '4';
29
+ this.dpChildLock = this._getCustomDP(this.device.context.dpChildLock) || '6';
30
+ this.dpTemperatureDisplayUnits = this._getCustomDP(this.device.context.dpTemperatureDisplayUnits) || '19';
31
+
32
+ this.cmdLow = 'LOW';
33
+ if (this.device.context.cmdLow) {
34
+ if (/^[a-z0-9]+$/i.test(this.device.context.cmdLow)) this.cmdLow = ('' + this.device.context.cmdLow).trim();
35
+ else throw new Error('The cmdLow doesn\'t appear to be valid: ' + this.device.context.cmdLow);
36
+ }
37
+
38
+ this.cmdHigh = 'HIGH';
39
+ if (this.device.context.cmdHigh) {
40
+ if (/^[a-z0-9]+$/i.test(this.device.context.cmdHigh)) this.cmdHigh = ('' + this.device.context.cmdHigh).trim();
41
+ else throw new Error('The cmdHigh doesn\'t appear to be valid: ' + this.device.context.cmdHigh);
42
+ }
43
+
44
+ this.enableFlipSpeedSlider = !!this.device.context.enableFlipSpeedSlider;
45
+
46
+ const characteristicActive = service.getCharacteristic(Characteristic.Active)
47
+ .updateValue(this._getActive(dps[this.dpActive]))
48
+ .on('get', this.getActive.bind(this))
49
+ .on('set', this.setActive.bind(this));
50
+
51
+ service.getCharacteristic(Characteristic.CurrentHeaterCoolerState)
52
+ .updateValue(this._getCurrentHeaterCoolerState(dps))
53
+ .on('get', this.getCurrentHeaterCoolerState.bind(this));
54
+
55
+ service.getCharacteristic(Characteristic.TargetHeaterCoolerState)
56
+ .setProps({
57
+ minValue: 1,
58
+ maxValue: 1,
59
+ validValues: [Characteristic.TargetHeaterCoolerState.HEAT]
60
+ })
61
+ .updateValue(this._getTargetHeaterCoolerState())
62
+ .on('get', this.getTargetHeaterCoolerState.bind(this))
63
+ .on('set', this.setTargetHeaterCoolerState.bind(this));
64
+
65
+ const characteristicCurrentTemperature = service.getCharacteristic(Characteristic.CurrentTemperature)
66
+ .updateValue(dps[this.dpCurrentTemperature])
67
+ .on('get', this.getState.bind(this, this.dpCurrentTemperature));
68
+
69
+
70
+ const characteristicHeatingThresholdTemperature = service.getCharacteristic(Characteristic.HeatingThresholdTemperature)
71
+ .setProps({
72
+ minValue: this.device.context.minTemperature || 15,
73
+ maxValue: this.device.context.maxTemperature || 35,
74
+ minStep: this.device.context.minTemperatureSteps || 1
75
+ })
76
+ .updateValue(dps[this.dpDesiredTemperature])
77
+ .on('get', this.getState.bind(this, this.dpDesiredTemperature))
78
+ .on('set', this.setTargetThresholdTemperature.bind(this));
79
+
80
+
81
+ let characteristicTemperatureDisplayUnits;
82
+ if (!this.device.context.noTemperatureUnit) {
83
+ characteristicTemperatureDisplayUnits = service.getCharacteristic(Characteristic.TemperatureDisplayUnits)
84
+ .updateValue(this._getTemperatureDisplayUnits(dps[this.dpTemperatureDisplayUnits]))
85
+ .on('get', this.getTemperatureDisplayUnits.bind(this))
86
+ .on('set', this.setTemperatureDisplayUnits.bind(this));
87
+ } else this._removeCharacteristic(service, Characteristic.TemperatureDisplayUnits);
88
+
89
+ let characteristicLockPhysicalControls;
90
+ if (!this.device.context.noChildLock) {
91
+ characteristicLockPhysicalControls = service.getCharacteristic(Characteristic.LockPhysicalControls)
92
+ .updateValue(this._getLockPhysicalControls(dps[this.dpChildLock]))
93
+ .on('get', this.getLockPhysicalControls.bind(this))
94
+ .on('set', this.setLockPhysicalControls.bind(this));
95
+ } else this._removeCharacteristic(service, Characteristic.LockPhysicalControls);
96
+
97
+ const characteristicRotationSpeed = service.getCharacteristic(Characteristic.RotationSpeed)
98
+ .updateValue(this._getRotationSpeed(dps))
99
+ .on('get', this.getRotationSpeed.bind(this))
100
+ .on('set', this.setRotationSpeed.bind(this));
101
+
102
+ this.characteristicActive = characteristicActive;
103
+ this.characteristicHeatingThresholdTemperature = characteristicHeatingThresholdTemperature;
104
+ this.characteristicRotationSpeed = characteristicRotationSpeed;
105
+
106
+ this.device.on('change', (changes, state) => {
107
+ if (changes.hasOwnProperty(this.dpActive)) {
108
+ const newActive = this._getActive(changes[this.dpActive]);
109
+ if (characteristicActive.value !== newActive) {
110
+ characteristicActive.updateValue(newActive);
111
+
112
+ if (!changes.hasOwnProperty(this.dpRotationSpeed)) {
113
+ characteristicRotationSpeed.updateValue(this._getRotationSpeed(state));
114
+ }
115
+ }
116
+ }
117
+
118
+ if (characteristicLockPhysicalControls && changes.hasOwnProperty(this.dpChildLock)) {
119
+ const newLockPhysicalControls = this._getLockPhysicalControls(changes[this.dpChildLock]);
120
+ if (characteristicLockPhysicalControls.value !== newLockPhysicalControls) {
121
+ characteristicLockPhysicalControls.updateValue(newLockPhysicalControls);
122
+ }
123
+ }
124
+
125
+ if (changes.hasOwnProperty(this.dpDesiredTemperature)) {
126
+ if (characteristicHeatingThresholdTemperature.value !== changes[this.dpDesiredTemperature])
127
+ characteristicHeatingThresholdTemperature.updateValue(changes[this.dpDesiredTemperature]);
128
+ }
129
+
130
+ if (changes.hasOwnProperty(this.dpCurrentTemperature) && characteristicCurrentTemperature.value !== changes[this.dpCurrentTemperature]) characteristicCurrentTemperature.updateValue(changes[this.dpCurrentTemperature]);
131
+
132
+ if (characteristicTemperatureDisplayUnits && changes.hasOwnProperty(this.dpTemperatureDisplayUnits)) {
133
+ const newTemperatureDisplayUnits = this._getTemperatureDisplayUnits(changes[this.dpTemperatureDisplayUnits]);
134
+ if (characteristicTemperatureDisplayUnits.value !== newTemperatureDisplayUnits) characteristicTemperatureDisplayUnits.updateValue(newTemperatureDisplayUnits);
135
+ }
136
+
137
+ if (changes.hasOwnProperty(this.dpRotationSpeed)) {
138
+ const newRotationSpeed = this._getRotationSpeed(state);
139
+ if (characteristicRotationSpeed.value !== newRotationSpeed) characteristicRotationSpeed.updateValue(newRotationSpeed);
140
+ }
141
+ });
142
+ }
143
+
144
+ getActive(callback) {
145
+ this.getState(this.dpActive, (err, dp) => {
146
+ if (err) return callback(err);
147
+
148
+ callback(null, this._getActive(dp));
149
+ });
150
+ }
151
+
152
+ _getActive(dp) {
153
+ const {Characteristic} = this.hap;
154
+
155
+ return dp ? Characteristic.Active.ACTIVE : Characteristic.Active.INACTIVE;
156
+ }
157
+
158
+ setActive(value, callback) {
159
+ const {Characteristic} = this.hap;
160
+
161
+ switch (value) {
162
+ case Characteristic.Active.ACTIVE:
163
+ return this.setState(this.dpActive, true, callback);
164
+
165
+ case Characteristic.Active.INACTIVE:
166
+ return this.setState(this.dpActive, false, callback);
167
+ }
168
+
169
+ callback();
170
+ }
171
+
172
+ getLockPhysicalControls(callback) {
173
+ this.getState(this.dpChildLock, (err, dp) => {
174
+ if (err) return callback(err);
175
+
176
+ callback(null, this._getLockPhysicalControls(dp));
177
+ });
178
+ }
179
+
180
+ _getLockPhysicalControls(dp) {
181
+ const {Characteristic} = this.hap;
182
+
183
+ return dp ? Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED : Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED;
184
+ }
185
+
186
+ setLockPhysicalControls(value, callback) {
187
+ const {Characteristic} = this.hap;
188
+
189
+ switch (value) {
190
+ case Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED:
191
+ return this.setState(this.dpChildLock, true, callback);
192
+
193
+ case Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED:
194
+ return this.setState(this.dpChildLock, false, callback);
195
+ }
196
+
197
+ callback();
198
+ }
199
+
200
+ getCurrentHeaterCoolerState(callback) {
201
+ this.getState([this.dpActive], (err, dps) => {
202
+ if (err) return callback(err);
203
+
204
+ callback(null, this._getCurrentHeaterCoolerState(dps));
205
+ });
206
+ }
207
+
208
+ _getCurrentHeaterCoolerState(dps) {
209
+ const {Characteristic} = this.hap;
210
+ return dps[this.dpActive] ? Characteristic.CurrentHeaterCoolerState.HEATING : Characteristic.CurrentHeaterCoolerState.INACTIVE;
211
+ }
212
+
213
+ getTargetHeaterCoolerState(callback) {
214
+ callback(null, this._getTargetHeaterCoolerState());
215
+ }
216
+
217
+ _getTargetHeaterCoolerState() {
218
+ const {Characteristic} = this.hap;
219
+ return Characteristic.TargetHeaterCoolerState.HEAT;
220
+ }
221
+
222
+ setTargetHeaterCoolerState(value, callback) {
223
+ this.setState(this.dpActive, true, callback);
224
+ }
225
+
226
+ setTargetThresholdTemperature(value, callback) {
227
+ this.setState(this.dpDesiredTemperature, value, err => {
228
+ if (err) return callback(err);
229
+
230
+ if (this.characteristicHeatingThresholdTemperature) {
231
+ this.characteristicHeatingThresholdTemperature.updateValue(value);
232
+ }
233
+
234
+ callback();
235
+ });
236
+ }
237
+
238
+ getTemperatureDisplayUnits(callback) {
239
+ this.getState(this.dpTemperatureDisplayUnits, (err, dp) => {
240
+ if (err) return callback(err);
241
+
242
+ callback(null, this._getTemperatureDisplayUnits(dp));
243
+ });
244
+ }
245
+
246
+ _getTemperatureDisplayUnits(dp) {
247
+ const {Characteristic} = this.hap;
248
+
249
+ return dp === 'F' ? Characteristic.TemperatureDisplayUnits.FAHRENHEIT : Characteristic.TemperatureDisplayUnits.CELSIUS;
250
+ }
251
+
252
+ setTemperatureDisplayUnits(value, callback) {
253
+ const {Characteristic} = this.hap;
254
+
255
+ this.setState(this.dpTemperatureDisplayUnits, value === Characteristic.TemperatureDisplayUnits.FAHRENHEIT ? 'F' : 'C', callback);
256
+ }
257
+
258
+ getRotationSpeed(callback) {
259
+ this.getState([this.dpActive, this.dpRotationSpeed], (err, dps) => {
260
+ if (err) return callback(err);
261
+
262
+ callback(null, this._getRotationSpeed(dps));
263
+ });
264
+ }
265
+
266
+ _getRotationSpeed(dps) {
267
+ if (!dps[this.dpActive]) return 0;
268
+
269
+ if (this._hkRotationSpeed) {
270
+ const currntRotationSpeed = this.convertRotationSpeedFromHomeKitToTuya(this._hkRotationSpeed);
271
+
272
+ return currntRotationSpeed === dps[this.dpRotationSpeed] ? this._hkRotationSpeed : this.convertRotationSpeedFromTuyaToHomeKit(dps[this.dpRotationSpeed]);
273
+ }
274
+
275
+ return this._hkRotationSpeed = this.convertRotationSpeedFromTuyaToHomeKit(dps[this.dpRotationSpeed]);
276
+ }
277
+
278
+ setRotationSpeed(value, callback) {
279
+ const {Characteristic} = this.hap;
280
+
281
+ if (value === 0) {
282
+ this.setActive(Characteristic.Active.INACTIVE, callback);
283
+ } else {
284
+ this._hkRotationSpeed = value;
285
+
286
+ const newSpeed = this.convertRotationSpeedFromHomeKitToTuya(value);
287
+ const currentSpeed = this.convertRotationSpeedFromHomeKitToTuya(this.characteristicRotationSpeed.value);
288
+
289
+ if (this.enableFlipSpeedSlider) this._hkRotationSpeed = this.convertRotationSpeedFromTuyaToHomeKit(newSpeed);
290
+
291
+ if (newSpeed !== currentSpeed) {
292
+ this.characteristicRotationSpeed.updateValue(this._hkRotationSpeed);
293
+ this.setMultiState({[this.dpActive]: true, [this.dpRotationSpeed]: newSpeed}, callback);
294
+ } else {
295
+ callback();
296
+ if (this.enableFlipSpeedSlider)
297
+ process.nextTick(() => {
298
+ this.characteristicRotationSpeed.updateValue(this._hkRotationSpeed);
299
+ });
300
+ }
301
+ }
302
+ }
303
+
304
+ convertRotationSpeedFromTuyaToHomeKit(value) {
305
+ return {[this.cmdLow]: 1, [this.cmdHigh]: 100}[value];
306
+ }
307
+
308
+ convertRotationSpeedFromHomeKitToTuya(value) {
309
+ return value < 50 ? this.cmdLow : this.cmdHigh;
310
+ }
311
+ }
312
+
313
+ module.exports = ConvectorAccessory;