homebridge-tuya-plus 3.7.0 → 3.8.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/lib/BaseAccessory.js
CHANGED
|
@@ -82,13 +82,19 @@ class BaseAccessory {
|
|
|
82
82
|
setMultiStateLegacy(dps, callback) {
|
|
83
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
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)
|
|
85
|
+
if (!this.device.connected) {
|
|
86
|
+
this.log.debug(`${this.device.context.name}: skipping write, device not connected`);
|
|
87
|
+
return callback && callback();
|
|
88
|
+
}
|
|
86
89
|
const ret = this.device.update(dps);
|
|
87
90
|
callback && callback(!ret);
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
setMultiState(dps, callback) {
|
|
91
|
-
if (!this.device.connected)
|
|
94
|
+
if (!this.device.connected) {
|
|
95
|
+
this.log.debug(`${this.device.context.name}: skipping write, device not connected`);
|
|
96
|
+
return callback && callback();
|
|
97
|
+
}
|
|
92
98
|
for (const dp in dps) {
|
|
93
99
|
if (dps.hasOwnProperty(dp) && dps[dp] !== this.device.state[dp]){
|
|
94
100
|
this.__ret = this.device.update({[dp.toString()] : dps[dp]});
|
|
@@ -121,7 +127,10 @@ class BaseAccessory {
|
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
setMultiStateAsync(dps) {
|
|
124
|
-
if (!this.device.connected)
|
|
130
|
+
if (!this.device.connected) {
|
|
131
|
+
this.log.debug(`${this.device.context.name}: skipping write, device not connected`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
125
134
|
for (const dp in dps) {
|
|
126
135
|
if (dps.hasOwnProperty(dp) && dps[dp] !== this.device.state[dp]) {
|
|
127
136
|
this.device.update({[dp.toString()]: dps[dp]});
|
|
@@ -130,7 +139,10 @@ class BaseAccessory {
|
|
|
130
139
|
}
|
|
131
140
|
|
|
132
141
|
setMultiStateLegacyAsync(dps) {
|
|
133
|
-
if (!this.device.connected)
|
|
142
|
+
if (!this.device.connected) {
|
|
143
|
+
this.log.debug(`${this.device.context.name}: skipping write, device not connected`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
134
146
|
this.device.update(dps);
|
|
135
147
|
}
|
|
136
148
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
const BaseAccessory = require('./BaseAccessory');
|
|
2
2
|
|
|
3
|
+
const DEFAULT_MIN_WHITE_COLOR = 140;
|
|
4
|
+
const DEFAULT_MAX_WHITE_COLOR = 400;
|
|
5
|
+
|
|
3
6
|
class RGBTWLightAccessory extends BaseAccessory {
|
|
4
7
|
static getCategory(Categories) {
|
|
5
8
|
return Categories.LIGHTBULB;
|
|
@@ -28,6 +31,9 @@ class RGBTWLightAccessory extends BaseAccessory {
|
|
|
28
31
|
this.dpColorTemperature = this._getCustomDP(this.device.context.dpColorTemperature) || '4';
|
|
29
32
|
this.dpColor = this._getCustomDP(this.device.context.dpColor) || '5';
|
|
30
33
|
|
|
34
|
+
this.minWhiteColor = this.device.context.minWhiteColor ?? DEFAULT_MIN_WHITE_COLOR;
|
|
35
|
+
this.maxWhiteColor = this.device.context.maxWhiteColor ?? DEFAULT_MAX_WHITE_COLOR;
|
|
36
|
+
|
|
31
37
|
this._detectColorFunction(dps[this.dpColor]);
|
|
32
38
|
|
|
33
39
|
this.cmdWhite = 'white';
|
|
@@ -57,10 +63,10 @@ class RGBTWLightAccessory extends BaseAccessory {
|
|
|
57
63
|
|
|
58
64
|
const characteristicColorTemperature = service.getCharacteristic(Characteristic.ColorTemperature)
|
|
59
65
|
.setProps({
|
|
60
|
-
minValue: this.
|
|
61
|
-
maxValue: this.
|
|
66
|
+
minValue: this.minWhiteColor,
|
|
67
|
+
maxValue: this.maxWhiteColor
|
|
62
68
|
})
|
|
63
|
-
.updateValue(dps[this.dpMode] === this.cmdWhite ? this.convertColorTemperatureFromTuyaToHomeKit(dps[this.dpColorTemperature]) : this.
|
|
69
|
+
.updateValue(dps[this.dpMode] === this.cmdWhite ? this.convertColorTemperatureFromTuyaToHomeKit(dps[this.dpColorTemperature]) : this.minWhiteColor)
|
|
64
70
|
.onGet(() => this.getColorTemperature())
|
|
65
71
|
.onSet(value => this.setColorTemperature(value));
|
|
66
72
|
|
|
@@ -119,9 +125,9 @@ class RGBTWLightAccessory extends BaseAccessory {
|
|
|
119
125
|
if (oldColor.b !== newColor.b) characteristicBrightness.updateValue(newColor.b);
|
|
120
126
|
if (oldColor.h !== newColor.h) characteristicHue.updateValue(newColor.h);
|
|
121
127
|
if (oldColor.s !== newColor.s) characteristicSaturation.updateValue(newColor.s);
|
|
122
|
-
if (characteristicColorTemperature.value !== this.
|
|
128
|
+
if (characteristicColorTemperature.value !== this.minWhiteColor) characteristicColorTemperature.updateValue(this.minWhiteColor);
|
|
123
129
|
} else if (changes[this.dpMode]) {
|
|
124
|
-
if (characteristicColorTemperature.value !== this.
|
|
130
|
+
if (characteristicColorTemperature.value !== this.minWhiteColor) characteristicColorTemperature.updateValue(this.minWhiteColor);
|
|
125
131
|
}
|
|
126
132
|
}
|
|
127
133
|
});
|
|
@@ -139,7 +145,7 @@ class RGBTWLightAccessory extends BaseAccessory {
|
|
|
139
145
|
|
|
140
146
|
getColorTemperature() {
|
|
141
147
|
this.log.debug(`getColorTemperature`);
|
|
142
|
-
if (this.device.state[this.dpMode] !== this.cmdWhite) return this.
|
|
148
|
+
if (this.device.state[this.dpMode] !== this.cmdWhite) return this.minWhiteColor;
|
|
143
149
|
return this.convertColorTemperatureFromTuyaToHomeKit(this.device.state[this.dpColorTemperature]);
|
|
144
150
|
}
|
|
145
151
|
|
|
@@ -204,7 +210,7 @@ class RGBTWLightAccessory extends BaseAccessory {
|
|
|
204
210
|
if (!(this.device.state[this.dpMode] === this.cmdWhite && isSham)) {
|
|
205
211
|
this.setMultiStateAsync({[this.dpMode]: this.cmdColor, [this.dpColor]: newValue});
|
|
206
212
|
}
|
|
207
|
-
this.characteristicColorTemperature.updateValue(this.
|
|
213
|
+
this.characteristicColorTemperature.updateValue(this.minWhiteColor);
|
|
208
214
|
|
|
209
215
|
resolvers.forEach(r => r());
|
|
210
216
|
}, 500);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-tuya-plus",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "A community-maintained Homebridge plugin for controlling Tuya devices locally over LAN. Includes new features, fixes, and updated device support.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -178,10 +178,11 @@ describe('setStateAsync', () => {
|
|
|
178
178
|
expect(device.update).not.toHaveBeenCalled();
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
-
test('
|
|
181
|
+
test('skips silently when device is not connected (issue #34)', () => {
|
|
182
182
|
const { instance, device } = make({ '1': false });
|
|
183
183
|
device.connected = false;
|
|
184
|
-
expect(() => instance.setStateAsync('1', true)).toThrow(
|
|
184
|
+
expect(() => instance.setStateAsync('1', true)).not.toThrow();
|
|
185
|
+
expect(device.update).not.toHaveBeenCalled();
|
|
185
186
|
});
|
|
186
187
|
});
|
|
187
188
|
|
|
@@ -199,6 +200,13 @@ describe('setMultiStateAsync', () => {
|
|
|
199
200
|
instance.setMultiStateAsync({ '1': true, '2': 50 });
|
|
200
201
|
expect(device.update).not.toHaveBeenCalled();
|
|
201
202
|
});
|
|
203
|
+
|
|
204
|
+
test('skips silently when device is not connected (issue #34)', () => {
|
|
205
|
+
const { instance, device } = make({ '1': false });
|
|
206
|
+
device.connected = false;
|
|
207
|
+
expect(() => instance.setMultiStateAsync({ '1': true, '2': 50 })).not.toThrow();
|
|
208
|
+
expect(device.update).not.toHaveBeenCalled();
|
|
209
|
+
});
|
|
202
210
|
});
|
|
203
211
|
|
|
204
212
|
describe('setMultiStateLegacyAsync', () => {
|
|
@@ -208,6 +216,13 @@ describe('setMultiStateLegacyAsync', () => {
|
|
|
208
216
|
expect(device.update).toHaveBeenCalledTimes(1);
|
|
209
217
|
expect(device.update).toHaveBeenCalledWith({ '1': true, '3': '2' });
|
|
210
218
|
});
|
|
219
|
+
|
|
220
|
+
test('skips silently when device is not connected (issue #34)', () => {
|
|
221
|
+
const { instance, device } = make();
|
|
222
|
+
device.connected = false;
|
|
223
|
+
expect(() => instance.setMultiStateLegacyAsync({ '1': true })).not.toThrow();
|
|
224
|
+
expect(device.update).not.toHaveBeenCalled();
|
|
225
|
+
});
|
|
211
226
|
});
|
|
212
227
|
|
|
213
228
|
describe('getDividedStateAsync', () => {
|
|
@@ -222,6 +237,34 @@ describe('getDividedStateAsync', () => {
|
|
|
222
237
|
});
|
|
223
238
|
});
|
|
224
239
|
|
|
240
|
+
describe('setMultiState (legacy callback)', () => {
|
|
241
|
+
test('skips silently and invokes callback without error when not connected (issue #34)', () => {
|
|
242
|
+
const { instance, device } = make({ '1': false });
|
|
243
|
+
device.connected = false;
|
|
244
|
+
const cb = jest.fn();
|
|
245
|
+
instance.setMultiState({ '1': true }, cb);
|
|
246
|
+
expect(device.update).not.toHaveBeenCalled();
|
|
247
|
+
expect(cb).toHaveBeenCalledWith();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('tolerates a missing callback when not connected', () => {
|
|
251
|
+
const { instance, device } = make({ '1': false });
|
|
252
|
+
device.connected = false;
|
|
253
|
+
expect(() => instance.setMultiState({ '1': true })).not.toThrow();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('setMultiStateLegacy (legacy callback)', () => {
|
|
258
|
+
test('skips silently and invokes callback without error when not connected (issue #34)', () => {
|
|
259
|
+
const { instance, device } = make();
|
|
260
|
+
device.connected = false;
|
|
261
|
+
const cb = jest.fn();
|
|
262
|
+
instance.setMultiStateLegacy({ '1': true }, cb);
|
|
263
|
+
expect(device.update).not.toHaveBeenCalled();
|
|
264
|
+
expect(cb).toHaveBeenCalledWith();
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
225
268
|
// ---------------------------------------------------------------------------
|
|
226
269
|
// Utility helpers
|
|
227
270
|
// ---------------------------------------------------------------------------
|
|
@@ -5,7 +5,7 @@ const { makeInstance, makeMockCharacteristic } = require('./support/mocks');
|
|
|
5
5
|
|
|
6
6
|
function makeLight(state = {}, context = {}) {
|
|
7
7
|
const result = makeInstance(RGBTWLightAccessory, state, { colorFunction: 'HEXHSB', ...context });
|
|
8
|
-
const { instance } = result;
|
|
8
|
+
const { instance, device } = result;
|
|
9
9
|
|
|
10
10
|
// Set up the state that _registerCharacteristics would normally establish
|
|
11
11
|
instance.dpPower = '1';
|
|
@@ -16,6 +16,8 @@ function makeLight(state = {}, context = {}) {
|
|
|
16
16
|
instance.cmdWhite = 'white';
|
|
17
17
|
instance.cmdColor = 'colour';
|
|
18
18
|
instance.colorFunction = 'HEXHSB';
|
|
19
|
+
instance.minWhiteColor = device.context.minWhiteColor ?? 140;
|
|
20
|
+
instance.maxWhiteColor = device.context.maxWhiteColor ?? 400;
|
|
19
21
|
|
|
20
22
|
// Provide the cross-characteristic references _setHueSaturation writes to
|
|
21
23
|
instance.characteristicColorTemperature = makeMockCharacteristic(0);
|
|
@@ -64,8 +66,7 @@ describe('RGBTWLightAccessory.setBrightness', () => {
|
|
|
64
66
|
// ---------------------------------------------------------------------------
|
|
65
67
|
describe('RGBTWLightAccessory.getColorTemperature', () => {
|
|
66
68
|
test('returns minWhiteColor when in color mode', () => {
|
|
67
|
-
const { instance
|
|
68
|
-
device.context.minWhiteColor = 140;
|
|
69
|
+
const { instance } = makeLight({ '2': 'colour' }, { minWhiteColor: 140 });
|
|
69
70
|
expect(instance.getColorTemperature()).toBe(140);
|
|
70
71
|
});
|
|
71
72
|
|
|
@@ -74,6 +75,35 @@ describe('RGBTWLightAccessory.getColorTemperature', () => {
|
|
|
74
75
|
// convertColorTemperatureFromTuyaToHomeKit(255) = 140
|
|
75
76
|
expect(instance.getColorTemperature()).toBe(140);
|
|
76
77
|
});
|
|
78
|
+
|
|
79
|
+
test('returns a finite default when minWhiteColor is not configured (issue #34)', () => {
|
|
80
|
+
const { instance } = makeLight({ '2': 'colour' });
|
|
81
|
+
const result = instance.getColorTemperature();
|
|
82
|
+
expect(typeof result).toBe('number');
|
|
83
|
+
expect(Number.isFinite(result)).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// setColorTemperature
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
describe('RGBTWLightAccessory.setColorTemperature', () => {
|
|
91
|
+
test('does not throw when device is not connected (issue #34)', () => {
|
|
92
|
+
const { instance, device } = makeLight({ '2': 'white', '4': 255 });
|
|
93
|
+
instance.characteristicHue = makeMockCharacteristic(0);
|
|
94
|
+
instance.characteristicSaturation = makeMockCharacteristic(0);
|
|
95
|
+
device.connected = false;
|
|
96
|
+
expect(() => instance.setColorTemperature(200)).not.toThrow();
|
|
97
|
+
expect(device.update).not.toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('writes color temperature when device is connected', () => {
|
|
101
|
+
const { instance, device } = makeLight({ '2': 'white', '4': 255 });
|
|
102
|
+
instance.characteristicHue = makeMockCharacteristic(0);
|
|
103
|
+
instance.characteristicSaturation = makeMockCharacteristic(0);
|
|
104
|
+
instance.setColorTemperature(200);
|
|
105
|
+
expect(device.update).toHaveBeenCalled();
|
|
106
|
+
});
|
|
77
107
|
});
|
|
78
108
|
|
|
79
109
|
// ---------------------------------------------------------------------------
|
|
@@ -133,11 +163,21 @@ describe('RGBTWLightAccessory._setHueSaturation', () => {
|
|
|
133
163
|
});
|
|
134
164
|
|
|
135
165
|
test('updates characteristicColorTemperature to minWhiteColor after firing', async () => {
|
|
136
|
-
const { instance } = makeLight({ '2': 'colour', '5': '00000000b46464' });
|
|
137
|
-
instance.device.context.minWhiteColor = 140;
|
|
166
|
+
const { instance } = makeLight({ '2': 'colour', '5': '00000000b46464' }, { minWhiteColor: 140 });
|
|
138
167
|
const p = instance.setHue(180);
|
|
139
168
|
jest.advanceTimersByTime(500);
|
|
140
169
|
await p;
|
|
141
170
|
expect(instance.characteristicColorTemperature.updateValue).toHaveBeenCalledWith(140);
|
|
142
171
|
});
|
|
172
|
+
|
|
173
|
+
test('updates characteristicColorTemperature to a finite default when minWhiteColor unset (issue #34)', async () => {
|
|
174
|
+
const { instance } = makeLight({ '2': 'colour', '5': '00000000b46464' });
|
|
175
|
+
const p = instance.setHue(180);
|
|
176
|
+
jest.advanceTimersByTime(500);
|
|
177
|
+
await p;
|
|
178
|
+
const calls = instance.characteristicColorTemperature.updateValue.mock.calls;
|
|
179
|
+
const lastValue = calls[calls.length - 1][0];
|
|
180
|
+
expect(typeof lastValue).toBe('number');
|
|
181
|
+
expect(Number.isFinite(lastValue)).toBe(true);
|
|
182
|
+
});
|
|
143
183
|
});
|