homebridge-tuya-plus 3.13.0 → 3.13.1-dev.1

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.
@@ -1,8 +1,19 @@
1
1
  const BaseAccessory = require('./BaseAccessory');
2
2
 
3
+ // HomeKit color temperature is expressed in mireds (reciprocal megakelvin):
4
+ // LOW mireds = cool/blue, HIGH mireds = warm/orange.
5
+ // Tuya's standard `temp_value` DP runs the other way: 0 = warmest, scale = coolest.
6
+ // The two scales must therefore be inverted when converting between them.
7
+ // Defaults below describe a typical CCT lamp range of ~2700K..6500K.
8
+ const DEFAULT_MIN_WHITE_COLOR = 154; // ~6500K (coolest, lowest mired)
9
+ const DEFAULT_MAX_WHITE_COLOR = 370; // ~2700K (warmest, highest mired)
10
+ const COLOR_TEMP_TUYA_SCALE = 1000; // Tuya temp_value range: 0 (warm) .. 1000 (cool)
11
+
3
12
  class SimpleFanLightAccessory extends BaseAccessory {
4
13
  static getCategory(Categories) {
5
- return Categories.FANLIGHT;
14
+ // HAP has no combined fan+light category; the fan is the primary
15
+ // service, so categorise as a FAN (matches SimpleFanAccessory).
16
+ return Categories.FAN;
6
17
  }
7
18
 
8
19
  constructor(...props) {
@@ -34,10 +45,24 @@ class SimpleFanLightAccessory extends BaseAccessory {
34
45
  this.useLight = this._coerceBoolean(this.device.context.useLight, true);
35
46
  this.useBrightness = this._coerceBoolean(this.device.context.useBrightness, true);
36
47
  this.useColorTemp = this._coerceBoolean(this.device.context.useColorTemp, true);
48
+
49
+ let minWhiteColor = parseInt(this.device.context.minWhiteColor);
50
+ let maxWhiteColor = parseInt(this.device.context.maxWhiteColor);
51
+ if (isNaN(minWhiteColor)) minWhiteColor = DEFAULT_MIN_WHITE_COLOR;
52
+ if (isNaN(maxWhiteColor)) maxWhiteColor = DEFAULT_MAX_WHITE_COLOR;
53
+ if (maxWhiteColor <= minWhiteColor) {
54
+ this.log.warn(`${this.device.context.name}: maxWhiteColor (${maxWhiteColor}) must be greater than minWhiteColor (${minWhiteColor}); falling back to defaults.`);
55
+ minWhiteColor = DEFAULT_MIN_WHITE_COLOR;
56
+ maxWhiteColor = DEFAULT_MAX_WHITE_COLOR;
57
+ }
58
+ this.minWhiteColor = minWhiteColor;
59
+ this.maxWhiteColor = maxWhiteColor;
60
+
37
61
  this.maxSpeed = parseInt(this.device.context.maxSpeed) || 6;
38
62
  this.fanDefaultSpeed = parseInt(this.device.context.fanDefaultSpeed) || 1;
39
63
  this.fanCurrentSpeed = 0;
40
64
  this.useStrings = this._coerceBoolean(this.device.context.useStrings, true);
65
+ this.singleDpWrites = this._coerceBoolean(this.device.context.singleDpWrites, false);
41
66
 
42
67
  const characteristicFanOn = serviceFan.getCharacteristic(Characteristic.On)
43
68
  .updateValue(this._getFanOn(dps[this.dpFanOn]))
@@ -79,7 +104,7 @@ class SimpleFanLightAccessory extends BaseAccessory {
79
104
 
80
105
  if (this.useColorTemp) {
81
106
  characteristicColorTemp = serviceLightbulb.getCharacteristic(Characteristic.ColorTemperature)
82
- .setProps({ minValue: 140, maxValue: 500 })
107
+ .setProps({ minValue: this.minWhiteColor, maxValue: this.maxWhiteColor })
83
108
  .updateValue(this.convertColorTempFromTuyaToHomeKit(dps[this.dpColorTemp]))
84
109
  .onGet(() => this.getColorTemp())
85
110
  .onSet(value => this.setColorTemp(value));
@@ -126,6 +151,17 @@ class SimpleFanLightAccessory extends BaseAccessory {
126
151
  return !!dp;
127
152
  }
128
153
 
154
+ // Fan on/speed touch two DPs (60 + 62) at once. By default we send them in a
155
+ // single legacy control packet, which is what most fans expect. Some firmwares
156
+ // (e.g. certain `fsd` ceiling fans) silently ignore any LAN packet carrying more
157
+ // than one DP, so when `singleDpWrites` is set we send each DP as its own packet
158
+ // instead — mirroring how Tuya's own cloud issues these commands.
159
+ _writeFanState(payload) {
160
+ return this.singleDpWrites
161
+ ? this.setMultiStateAsync(payload)
162
+ : this.setMultiStateLegacyAsync(payload);
163
+ }
164
+
129
165
  setFanOn(value) {
130
166
  if (value === false) {
131
167
  this.fanCurrentSpeed = 0;
@@ -136,7 +172,7 @@ class SimpleFanLightAccessory extends BaseAccessory {
136
172
  [this.dpFanOn]: true,
137
173
  [this.dpRotationSpeed]: this.useStrings ? target.toString() : target
138
174
  };
139
- return this.setMultiStateLegacyAsync(payload);
175
+ return this._writeFanState(payload);
140
176
  }
141
177
  }
142
178
 
@@ -150,7 +186,7 @@ class SimpleFanLightAccessory extends BaseAccessory {
150
186
  [this.dpFanOn]: false,
151
187
  [this.dpRotationSpeed]: this.useStrings ? this.fanDefaultSpeed.toString() : this.fanDefaultSpeed
152
188
  };
153
- return this.setMultiStateLegacyAsync(payload);
189
+ return this._writeFanState(payload);
154
190
  } else {
155
191
  const tuya = this.convertRotationSpeedFromHomeKitToTuya(value);
156
192
  this.fanCurrentSpeed = tuya;
@@ -158,7 +194,7 @@ class SimpleFanLightAccessory extends BaseAccessory {
158
194
  [this.dpFanOn]: true,
159
195
  [this.dpRotationSpeed]: this.useStrings ? tuya.toString() : tuya
160
196
  };
161
- return this.setMultiStateLegacyAsync(payload);
197
+ return this._writeFanState(payload);
162
198
  }
163
199
  }
164
200
 
@@ -238,19 +274,25 @@ class SimpleFanLightAccessory extends BaseAccessory {
238
274
  }
239
275
 
240
276
  convertColorTempFromTuyaToHomeKit(value) {
277
+ const min = this.minWhiteColor ?? DEFAULT_MIN_WHITE_COLOR;
278
+ const max = this.maxWhiteColor ?? DEFAULT_MAX_WHITE_COLOR;
241
279
  let v = parseInt(value);
242
280
  if (isNaN(v)) v = 0;
243
- v = Math.max(0, Math.min(1000, v));
244
- const hk = Math.round(140 + (v * (500 - 140)) / 1000);
245
- return Math.max(140, Math.min(500, hk));
281
+ v = Math.max(0, Math.min(COLOR_TEMP_TUYA_SCALE, v));
282
+ // Invert: Tuya 0 (warm) -> max mireds (warm); Tuya scale (cool) -> min mireds (cool).
283
+ const hk = Math.round(max - (v * (max - min)) / COLOR_TEMP_TUYA_SCALE);
284
+ return Math.max(min, Math.min(max, hk));
246
285
  }
247
286
 
248
287
  convertColorTempFromHomeKitToTuya(value) {
288
+ const min = this.minWhiteColor ?? DEFAULT_MIN_WHITE_COLOR;
289
+ const max = this.maxWhiteColor ?? DEFAULT_MAX_WHITE_COLOR;
249
290
  let v = parseInt(value);
250
- if (isNaN(v)) v = 140;
251
- v = Math.max(140, Math.min(500, v));
252
- const tuya = Math.round(((v - 140) * 1000) / (500 - 140));
253
- return Math.max(0, Math.min(1000, tuya));
291
+ if (isNaN(v)) v = min;
292
+ v = Math.max(min, Math.min(max, v));
293
+ // Invert: max mireds (warm) -> Tuya 0 (warm); min mireds (cool) -> Tuya scale (cool).
294
+ const tuya = Math.round(((max - v) * COLOR_TEMP_TUYA_SCALE) / (max - min));
295
+ return Math.max(0, Math.min(COLOR_TEMP_TUYA_SCALE, tuya));
254
296
  }
255
297
  }
256
298