homebridge-tuya-plus 3.14.0-dev.18 → 3.14.0-dev.19
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/config.schema.json +1 -1
- package/index.js +2 -1
- package/lib/SimpleDimmerAccessory.js +9 -5
- package/lib/SimpleGarageDoorAccessory.js +9 -9
- package/lib/ValveAccessory.js +7 -7
- package/lib/WledDimmerAccessory.js +43 -80
- package/package.json +1 -1
- package/test/getCategory.test.js +1 -0
- package/wiki/Supported-Device-Types.md +8 -6
package/config.schema.json
CHANGED
|
@@ -318,7 +318,7 @@
|
|
|
318
318
|
"title": "Sync brightness to WLED (IP[:port])",
|
|
319
319
|
"placeholder": "192.168.1.100",
|
|
320
320
|
"condition": {
|
|
321
|
-
"functionBody": "return model.devices && model.devices[arrayIndices] && ['WledDimmer'
|
|
321
|
+
"functionBody": "return model.devices && model.devices[arrayIndices] && ['WledDimmer'].includes(model.devices[arrayIndices].type);"
|
|
322
322
|
}
|
|
323
323
|
},
|
|
324
324
|
"dpColorTemperature": {
|
package/index.js
CHANGED
|
@@ -18,6 +18,7 @@ const ConvectorAccessory = require('./lib/ConvectorAccessory');
|
|
|
18
18
|
const GarageDoorAccessory = require('./lib/GarageDoorAccessory');
|
|
19
19
|
const SimpleGarageDoorAccessory = require('./lib/SimpleGarageDoorAccessory');
|
|
20
20
|
const WledDimmerAccessory = require('./lib/WledDimmerAccessory');
|
|
21
|
+
const SimpleDimmerAccessory = require('./lib/SimpleDimmerAccessory');
|
|
21
22
|
const SimpleDimmer2Accessory = require('./lib/SimpleDimmer2Accessory');
|
|
22
23
|
const SimpleBlindsAccessory = require('./lib/SimpleBlindsAccessory');
|
|
23
24
|
const SimpleHeaterAccessory = require('./lib/SimpleHeaterAccessory');
|
|
@@ -60,7 +61,7 @@ const CLASS_DEF = {
|
|
|
60
61
|
convector: ConvectorAccessory,
|
|
61
62
|
garagedoor: GarageDoorAccessory,
|
|
62
63
|
simplegaragedoor: SimpleGarageDoorAccessory,
|
|
63
|
-
simpledimmer:
|
|
64
|
+
simpledimmer: SimpleDimmerAccessory,
|
|
64
65
|
wleddimmer: WledDimmerAccessory,
|
|
65
66
|
simpledimmer2: SimpleDimmer2Accessory,
|
|
66
67
|
simpleblinds: SimpleBlindsAccessory,
|
|
@@ -25,20 +25,24 @@ class SimpleDimmerAccessory extends BaseAccessory {
|
|
|
25
25
|
this.dpPower = this._getCustomDP(this.device.context.dpPower) || '1';
|
|
26
26
|
this.dpBrightness = this._getCustomDP(this.device.context.dpBrightness) || this._getCustomDP(this.device.context.dp) || '2';
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
this._characteristicOn = service.getCharacteristic(Characteristic.On)
|
|
29
29
|
.updateValue(dps[this.dpPower])
|
|
30
30
|
.onGet(() => this.getStateAsync(this.dpPower))
|
|
31
31
|
.onSet(value => this.setStateAsync(this.dpPower, value));
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
this._characteristicBrightness = service.getCharacteristic(Characteristic.Brightness)
|
|
34
34
|
.updateValue(this.convertBrightnessFromTuyaToHomeKit(dps[this.dpBrightness]))
|
|
35
35
|
.onGet(() => this.getBrightness())
|
|
36
36
|
.onSet(value => this.setBrightness(value));
|
|
37
37
|
|
|
38
|
+
this._registerChangeListener();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
_registerChangeListener() {
|
|
38
42
|
this.device.on('change', changes => {
|
|
39
|
-
if (changes.hasOwnProperty(this.dpPower) &&
|
|
40
|
-
if (changes.hasOwnProperty(this.dpBrightness) && this.convertBrightnessFromHomeKitToTuya(
|
|
41
|
-
|
|
43
|
+
if (changes.hasOwnProperty(this.dpPower) && this._characteristicOn.value !== changes[this.dpPower]) this._characteristicOn.updateValue(changes[this.dpPower]);
|
|
44
|
+
if (changes.hasOwnProperty(this.dpBrightness) && this.convertBrightnessFromHomeKitToTuya(this._characteristicBrightness.value) !== changes[this.dpBrightness])
|
|
45
|
+
this._characteristicBrightness.updateValue(this.convertBrightnessFromTuyaToHomeKit(changes[this.dpBrightness]));
|
|
42
46
|
});
|
|
43
47
|
}
|
|
44
48
|
|
|
@@ -197,7 +197,7 @@ class SimpleGarageDoorAccessory extends BaseAccessory {
|
|
|
197
197
|
if (this.pendingCloseTimer) return;
|
|
198
198
|
const isOpen = this._mapDpState(changes[this.dpState]);
|
|
199
199
|
if (isOpen === null) {
|
|
200
|
-
this.log.
|
|
200
|
+
this.log.debug(`[SimpleGarageDoor] ${this.device.context.name}: ignoring unknown state DP value ${JSON.stringify(changes[this.dpState])}`);
|
|
201
201
|
return;
|
|
202
202
|
}
|
|
203
203
|
this._applyReportedState(isOpen);
|
|
@@ -266,7 +266,7 @@ class SimpleGarageDoorAccessory extends BaseAccessory {
|
|
|
266
266
|
// own, even mid-close. Abandon any pending stop-before-close.
|
|
267
267
|
_sendOpen() {
|
|
268
268
|
this._cancelPendingClose();
|
|
269
|
-
this.log.
|
|
269
|
+
this.log.debug(`[SimpleGarageDoor] ${this.device.context.name}: open (dp${this.dpOpen})`);
|
|
270
270
|
this.setMultiStateLegacyInBackground({[this.dpOpen]: true});
|
|
271
271
|
}
|
|
272
272
|
|
|
@@ -278,19 +278,19 @@ class SimpleGarageDoorAccessory extends BaseAccessory {
|
|
|
278
278
|
if (this.pendingCloseTimer) {
|
|
279
279
|
// A stop-before-close is already running — don't restart it (and
|
|
280
280
|
// push the close out) on a duplicate or retransmitted request.
|
|
281
|
-
this.log.
|
|
281
|
+
this.log.debug(`[SimpleGarageDoor] ${name}: close ignored — a stop-before-close is already running`);
|
|
282
282
|
return;
|
|
283
283
|
}
|
|
284
284
|
if (this._isStopped()) {
|
|
285
|
-
this.log.
|
|
285
|
+
this.log.debug(`[SimpleGarageDoor] ${name}: close (dp${this.dpClose}) — gate already stopped`);
|
|
286
286
|
this.setMultiStateLegacyInBackground({[this.dpClose]: true});
|
|
287
287
|
return;
|
|
288
288
|
}
|
|
289
|
-
this.log.
|
|
289
|
+
this.log.debug(`[SimpleGarageDoor] ${name}: stop-before-close — stop (dp${this.dpStop}) now, close (dp${this.dpClose}) in ${this.stopBeforeCloseMs}ms`);
|
|
290
290
|
this.setMultiStateLegacyInBackground({[this.dpStop]: true});
|
|
291
291
|
this.pendingCloseTimer = setTimeout(() => {
|
|
292
292
|
this.pendingCloseTimer = null;
|
|
293
|
-
this.log.
|
|
293
|
+
this.log.debug(`[SimpleGarageDoor] ${name}: stop-before-close — firing close (dp${this.dpClose})`);
|
|
294
294
|
this.setMultiStateLegacyInBackground({[this.dpClose]: true});
|
|
295
295
|
}, this.stopBeforeCloseMs);
|
|
296
296
|
}
|
|
@@ -323,15 +323,15 @@ class SimpleGarageDoorAccessory extends BaseAccessory {
|
|
|
323
323
|
// Re-entrant press while a partial is already armed (e.g. a
|
|
324
324
|
// HomeKit/iOS WRITE retransmit) — ignore so the stop isn't pushed
|
|
325
325
|
// out, which would let the gate run further than intended.
|
|
326
|
-
this.log.
|
|
326
|
+
this.log.debug(`[SimpleGarageDoor] ${name}: partial press ignored — a partial open is already running`);
|
|
327
327
|
return;
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
-
this.log.
|
|
330
|
+
this.log.debug(`[SimpleGarageDoor] ${name}: partial open — opening, will stop in ${this.partialOpenMs}ms`);
|
|
331
331
|
this._applyTarget(Characteristic.TargetDoorState.OPEN);
|
|
332
332
|
this.partialStopTimer = setTimeout(() => {
|
|
333
333
|
this.partialStopTimer = null;
|
|
334
|
-
this.log.
|
|
334
|
+
this.log.debug(`[SimpleGarageDoor] ${name}: partial open — firing stop (dp${this.dpStop})`);
|
|
335
335
|
this.setMultiStateLegacyInBackground({[this.dpStop]: true});
|
|
336
336
|
}, this.partialOpenMs);
|
|
337
337
|
}
|
package/lib/ValveAccessory.js
CHANGED
|
@@ -55,7 +55,7 @@ class ValveAccessory extends BaseAccessory {
|
|
|
55
55
|
service.getCharacteristic(Characteristic.SetDuration)
|
|
56
56
|
.onGet(() => { if (!this.device.connected) throw this._commError(); return this.setDuration; })
|
|
57
57
|
.on('change', (data) => {
|
|
58
|
-
this.log.
|
|
58
|
+
this.log.debug('Water Valve Time Duration Set to: ' + data.newValue / 60 + ' Minutes');
|
|
59
59
|
this.setDuration = data.newValue;
|
|
60
60
|
|
|
61
61
|
if (service.getCharacteristic(Characteristic.InUse).value) {
|
|
@@ -65,7 +65,7 @@ class ValveAccessory extends BaseAccessory {
|
|
|
65
65
|
|
|
66
66
|
clearTimeout(this.timer);
|
|
67
67
|
this.timer = setTimeout(() => {
|
|
68
|
-
this.log.
|
|
68
|
+
this.log.debug('Water Valve Timer Expired. Shutting OFF Valve');
|
|
69
69
|
this.setStateInBackground(this.dpPower, 0);
|
|
70
70
|
service.getCharacteristic(Characteristic.Active).updateValue(0);
|
|
71
71
|
service.getCharacteristic(Characteristic.InUse).updateValue(0);
|
|
@@ -89,16 +89,16 @@ class ValveAccessory extends BaseAccessory {
|
|
|
89
89
|
service.getCharacteristic(Characteristic.RemainingDuration).updateValue(0);
|
|
90
90
|
service.getCharacteristic(Characteristic.Active).updateValue(0);
|
|
91
91
|
clearTimeout(this.timer);
|
|
92
|
-
this.log.
|
|
92
|
+
this.log.debug('Water Valve is OFF!');
|
|
93
93
|
break;
|
|
94
94
|
case 1:
|
|
95
95
|
this.lastActivationTime = (new Date()).getTime();
|
|
96
96
|
service.getCharacteristic(Characteristic.RemainingDuration).updateValue(this.setDuration);
|
|
97
97
|
service.getCharacteristic(Characteristic.Active).updateValue(1);
|
|
98
|
-
this.log.
|
|
98
|
+
this.log.debug('Water Valve Turning ON with Timer Set to: ' + this.setDuration / 60 + ' Minutes');
|
|
99
99
|
clearTimeout(this.timer);
|
|
100
100
|
this.timer = setTimeout(() => {
|
|
101
|
-
this.log.
|
|
101
|
+
this.log.debug('Water Valve Timer Expired. Shutting OFF Valve');
|
|
102
102
|
this.setStateInBackground(this.dpPower, 0);
|
|
103
103
|
service.getCharacteristic(Characteristic.Active).updateValue(0);
|
|
104
104
|
service.getCharacteristic(Characteristic.InUse).updateValue(0);
|
|
@@ -113,10 +113,10 @@ class ValveAccessory extends BaseAccessory {
|
|
|
113
113
|
service.getCharacteristic(Characteristic.RemainingDuration).updateValue(this.setDuration);
|
|
114
114
|
service.getCharacteristic(Characteristic.Active).updateValue(1);
|
|
115
115
|
service.getCharacteristic(Characteristic.InUse).updateValue(1);
|
|
116
|
-
this.log.
|
|
116
|
+
this.log.debug('Water Valve is ON After Restart. Setting Timer to: ' + this.setDuration / 60 + ' Minutes');
|
|
117
117
|
clearTimeout(this.timer);
|
|
118
118
|
this.timer = setTimeout(() => {
|
|
119
|
-
this.log.
|
|
119
|
+
this.log.debug('Water Valve Timer Expired. Shutting OFF Valve');
|
|
120
120
|
this.setStateInBackground(this.dpPower, 0);
|
|
121
121
|
service.getCharacteristic(Characteristic.Active).updateValue(0);
|
|
122
122
|
this.lastActivationTime = null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const SimpleDimmerAccessory = require('./SimpleDimmerAccessory');
|
|
2
2
|
const http = require('http');
|
|
3
3
|
const maxWledBrightness = 255;
|
|
4
4
|
|
|
@@ -6,30 +6,14 @@ const maxWledBrightness = 255;
|
|
|
6
6
|
const wledDebounceMs = 50;
|
|
7
7
|
const wledWarmupMs = 8000;
|
|
8
8
|
|
|
9
|
-
class WledDimmerAccessory extends
|
|
10
|
-
static getCategory(Categories) {
|
|
11
|
-
return Categories.LIGHTBULB;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
constructor(...props) {
|
|
15
|
-
super(...props);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
_registerPlatformAccessory() {
|
|
19
|
-
const {Service} = this.hap;
|
|
20
|
-
|
|
21
|
-
this.accessory.addService(Service.Lightbulb, this.device.context.name);
|
|
22
|
-
|
|
23
|
-
super._registerPlatformAccessory();
|
|
24
|
-
}
|
|
25
|
-
|
|
9
|
+
class WledDimmerAccessory extends SimpleDimmerAccessory {
|
|
26
10
|
_registerCharacteristics(dps) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
11
|
+
// The Lightbulb service, On/Brightness characteristics, default DPs and
|
|
12
|
+
// the device 'change' listener are all set up by SimpleDimmerAccessory.
|
|
13
|
+
// WLED only layers optional brightness sync and preset-effect switches
|
|
14
|
+
// on top, so without syncBrightnessToWled configured this behaves
|
|
15
|
+
// exactly like a plain SimpleDimmer.
|
|
16
|
+
super._registerCharacteristics(dps);
|
|
33
17
|
|
|
34
18
|
// State for debounced / delayed WLED updates
|
|
35
19
|
this._wledPendingBri = null;
|
|
@@ -47,32 +31,20 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
47
31
|
null;
|
|
48
32
|
this.syncBrightnessToWled = (syncCfg && ('' + syncCfg).trim()) || null;
|
|
49
33
|
|
|
50
|
-
this.log.
|
|
34
|
+
this.log.debug(
|
|
51
35
|
'[WLED Sync] %s: syncBrightnessToWled=%s',
|
|
52
36
|
this.device.context.name,
|
|
53
37
|
this.syncBrightnessToWled || 'disabled'
|
|
54
38
|
);
|
|
55
39
|
|
|
56
|
-
const characteristicOn = service.getCharacteristic(Characteristic.On)
|
|
57
|
-
.updateValue(dps[this.dpPower])
|
|
58
|
-
.onGet(() => this.getStateAsync(this.dpPower))
|
|
59
|
-
.onSet(value => this.setStateAsync(this.dpPower, value));
|
|
60
|
-
|
|
61
|
-
const characteristicBrightness = service.getCharacteristic(Characteristic.Brightness);
|
|
62
|
-
// Keep a reference so we can update brightness from background WLED calls
|
|
63
|
-
this._characteristicBrightness = characteristicBrightness;
|
|
64
|
-
|
|
65
40
|
if (this.syncBrightnessToWled) {
|
|
66
41
|
// Start with 100% locally while we fetch the actual WLED brightness
|
|
67
|
-
|
|
68
|
-
.updateValue(100)
|
|
69
|
-
.onGet(() => this.getBrightness())
|
|
70
|
-
.onSet(value => this.setBrightness(value));
|
|
42
|
+
this._characteristicBrightness.updateValue(100);
|
|
71
43
|
|
|
72
44
|
// Force Tuya dimmer brightness to 100% on startup
|
|
73
45
|
const maxTuyaBrightness = this.convertBrightnessFromHomeKitToTuya(100);
|
|
74
46
|
if (dps[this.dpBrightness] !== maxTuyaBrightness) {
|
|
75
|
-
this.log.
|
|
47
|
+
this.log.debug(
|
|
76
48
|
'[WLED Sync] %s: setting Tuya brightness DP%s to max (%s)',
|
|
77
49
|
this.device.context.name,
|
|
78
50
|
this.dpBrightness,
|
|
@@ -80,19 +52,6 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
80
52
|
);
|
|
81
53
|
this.setState(this.dpBrightness, maxTuyaBrightness, () => {});
|
|
82
54
|
}
|
|
83
|
-
} else {
|
|
84
|
-
const initial = this.convertBrightnessFromTuyaToHomeKit(dps[this.dpBrightness]);
|
|
85
|
-
this.log.debug(
|
|
86
|
-
'%s: initial Tuya brightness DP%s=%s -> HomeKit %s%%',
|
|
87
|
-
this.device.context.name,
|
|
88
|
-
this.dpBrightness,
|
|
89
|
-
dps[this.dpBrightness],
|
|
90
|
-
initial
|
|
91
|
-
);
|
|
92
|
-
characteristicBrightness
|
|
93
|
-
.updateValue(initial)
|
|
94
|
-
.onGet(() => this.getBrightness())
|
|
95
|
-
.onSet(value => this.setBrightness(value));
|
|
96
55
|
}
|
|
97
56
|
|
|
98
57
|
// Optional: create one HomeKit Switch per configured WLED preset effect.
|
|
@@ -148,7 +107,7 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
148
107
|
return callback();
|
|
149
108
|
}
|
|
150
109
|
|
|
151
|
-
this.log.
|
|
110
|
+
this.log.debug(
|
|
152
111
|
'[WLED Sync] %s: setting preset effect "%s" (id=%s, index=%s)',
|
|
153
112
|
this.device.context.name,
|
|
154
113
|
effectName,
|
|
@@ -212,15 +171,17 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
212
171
|
.filter(service => service.UUID === HapService.Switch.UUID && service.subtype && service.subtype.startsWith('wledEffect '))
|
|
213
172
|
.forEach(service => {
|
|
214
173
|
if (!validEffectServices.includes(service)) {
|
|
215
|
-
this.log.
|
|
174
|
+
this.log.debug('Removing', service.displayName);
|
|
216
175
|
this.accessory.removeService(service);
|
|
217
176
|
}
|
|
218
177
|
});
|
|
219
178
|
}
|
|
179
|
+
}
|
|
220
180
|
|
|
181
|
+
_registerChangeListener() {
|
|
221
182
|
this.device.on('change', changes => {
|
|
222
183
|
// Log full DPS changes so we can see exactly what Tuya reported
|
|
223
|
-
this.log.
|
|
184
|
+
this.log.debug(
|
|
224
185
|
'%s DPS changes: %s %s was:',
|
|
225
186
|
this.device.context.name,
|
|
226
187
|
JSON.stringify(changes),
|
|
@@ -229,10 +190,10 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
229
190
|
);
|
|
230
191
|
|
|
231
192
|
if (changes.hasOwnProperty(this.dpPower)) {
|
|
232
|
-
const oldPower = !!
|
|
193
|
+
const oldPower = !!this._characteristicOn.value;
|
|
233
194
|
const newPower = !!changes[this.dpPower];
|
|
234
195
|
|
|
235
|
-
this.log.
|
|
196
|
+
this.log.debug(
|
|
236
197
|
'%s power change detected on DP%s: %s -> %s',
|
|
237
198
|
this.device.context.name,
|
|
238
199
|
this.dpPower,
|
|
@@ -241,13 +202,13 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
241
202
|
);
|
|
242
203
|
|
|
243
204
|
if (oldPower !== newPower) {
|
|
244
|
-
|
|
205
|
+
this._characteristicOn.updateValue(newPower);
|
|
245
206
|
}
|
|
246
207
|
|
|
247
208
|
// Track warmup window for WLED after power ON
|
|
248
209
|
if (this.syncBrightnessToWled && newPower) {
|
|
249
210
|
this._wledReadyAt = Date.now() + wledWarmupMs;
|
|
250
|
-
this.log.
|
|
211
|
+
this.log.debug(
|
|
251
212
|
'[WLED Sync] %s: power ON -> delaying WLED API calls for %sms',
|
|
252
213
|
this.device.context.name,
|
|
253
214
|
wledWarmupMs
|
|
@@ -271,7 +232,7 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
271
232
|
|
|
272
233
|
// Ignore the "forced back to 100%" update to avoid feedback loops
|
|
273
234
|
if (tuyaValue === maxTuyaBrightness) {
|
|
274
|
-
this.log.
|
|
235
|
+
this.log.debug(
|
|
275
236
|
'[WLED Sync] %s: Tuya DP%s reported max brightness (%s), ignoring',
|
|
276
237
|
this.device.context.name,
|
|
277
238
|
this.dpBrightness,
|
|
@@ -283,7 +244,7 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
283
244
|
const percent = this.convertBrightnessFromTuyaToHomeKit(tuyaValue);
|
|
284
245
|
const bri = Math.max(0, Math.min(maxWledBrightness, Math.round((percent / 100) * maxWledBrightness)));
|
|
285
246
|
|
|
286
|
-
this.log.
|
|
247
|
+
this.log.debug(
|
|
287
248
|
'[WLED Sync] %s: Tuya DP%s changed to %s -> %s%% -> WLED bri=%s (debounced)',
|
|
288
249
|
this.device.context.name,
|
|
289
250
|
this.dpBrightness,
|
|
@@ -304,14 +265,14 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
304
265
|
}
|
|
305
266
|
|
|
306
267
|
// Reflect that brightness back into HomeKit
|
|
307
|
-
|
|
268
|
+
this._characteristicBrightness.updateValue(percent);
|
|
308
269
|
|
|
309
270
|
// After a short delay, force Tuya brightness back to 100% so it stops dimming the strip
|
|
310
271
|
if (this._wledForceMaxTimeout) {
|
|
311
272
|
clearTimeout(this._wledForceMaxTimeout);
|
|
312
273
|
}
|
|
313
274
|
this._wledForceMaxTimeout = setTimeout(() => {
|
|
314
|
-
this.log.
|
|
275
|
+
this.log.debug(
|
|
315
276
|
'[WLED Sync] %s: forcing Tuya DP%s back to max (%s) after Tuya app change',
|
|
316
277
|
this.device.context.name,
|
|
317
278
|
this.dpBrightness,
|
|
@@ -320,8 +281,8 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
320
281
|
this.setState(this.dpBrightness, maxTuyaBrightness, () => {});
|
|
321
282
|
}, 5000);
|
|
322
283
|
});
|
|
323
|
-
} else if (!this.syncBrightnessToWled && changes.hasOwnProperty(this.dpBrightness) && this.convertBrightnessFromHomeKitToTuya(
|
|
324
|
-
|
|
284
|
+
} else if (!this.syncBrightnessToWled && changes.hasOwnProperty(this.dpBrightness) && this.convertBrightnessFromHomeKitToTuya(this._characteristicBrightness.value) !== changes[this.dpBrightness]) {
|
|
285
|
+
this._characteristicBrightness.updateValue(this.convertBrightnessFromTuyaToHomeKit(changes[this.dpBrightness]));
|
|
325
286
|
}
|
|
326
287
|
});
|
|
327
288
|
}
|
|
@@ -331,7 +292,7 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
331
292
|
if (this.syncBrightnessToWled) {
|
|
332
293
|
// If we already have a cached WLED brightness, return it immediately.
|
|
333
294
|
if (this._lastWledPercent != null) {
|
|
334
|
-
this.log.
|
|
295
|
+
this.log.debug(
|
|
335
296
|
'[WLED Sync] %s: getBrightness() -> using cached %s%%',
|
|
336
297
|
this.device.context.name,
|
|
337
298
|
this._lastWledPercent
|
|
@@ -340,13 +301,14 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
340
301
|
}
|
|
341
302
|
return 50;
|
|
342
303
|
} else {
|
|
343
|
-
|
|
304
|
+
// No WLED sync: behave as a plain SimpleDimmer.
|
|
305
|
+
return super.getBrightness();
|
|
344
306
|
}
|
|
345
307
|
}
|
|
346
308
|
|
|
347
309
|
setBrightness(value) {
|
|
348
310
|
if (this.syncBrightnessToWled) {
|
|
349
|
-
this.log.
|
|
311
|
+
this.log.debug(
|
|
350
312
|
'[WLED Sync] %s: setBrightness(%s%%)',
|
|
351
313
|
this.device.context.name,
|
|
352
314
|
value
|
|
@@ -358,7 +320,7 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
358
320
|
// Ensure Tuya stays at 100% so it doesn't interfere with WLED.
|
|
359
321
|
const maxTuyaBrightness = this.convertBrightnessFromHomeKitToTuya(100);
|
|
360
322
|
if (this.device.state[this.dpBrightness] !== maxTuyaBrightness) {
|
|
361
|
-
this.log.
|
|
323
|
+
this.log.debug(
|
|
362
324
|
'[WLED Sync] %s: ensuring Tuya DP%s=%s (max)',
|
|
363
325
|
this.device.context.name,
|
|
364
326
|
this.dpBrightness,
|
|
@@ -370,7 +332,7 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
370
332
|
|
|
371
333
|
// Compute desired WLED brightness once.
|
|
372
334
|
const bri = Math.max(0, Math.min(maxWledBrightness, Math.round((value / 100) * maxWledBrightness)));
|
|
373
|
-
this.log.
|
|
335
|
+
this.log.debug(
|
|
374
336
|
'[WLED Sync] %s: mapped HomeKit %s%% -> WLED bri=%s (debounced background)',
|
|
375
337
|
this.device.context.name,
|
|
376
338
|
value,
|
|
@@ -383,7 +345,8 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
383
345
|
// Return to HomeKit immediately; don't wait for network I/O.
|
|
384
346
|
return null;
|
|
385
347
|
} else {
|
|
386
|
-
|
|
348
|
+
// No WLED sync: behave as a plain SimpleDimmer.
|
|
349
|
+
return super.setBrightness(value);
|
|
387
350
|
}
|
|
388
351
|
}
|
|
389
352
|
|
|
@@ -392,7 +355,7 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
392
355
|
const parts = String(this.syncBrightnessToWled).split(':');
|
|
393
356
|
const host = parts[0];
|
|
394
357
|
const port = parts[1] ? parseInt(parts[1], 10) || 80 : 80;
|
|
395
|
-
this.log.
|
|
358
|
+
this.log.debug(
|
|
396
359
|
'[WLED Sync] %s: target host=%s port=%s',
|
|
397
360
|
this.device.context.name,
|
|
398
361
|
host,
|
|
@@ -426,7 +389,7 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
426
389
|
timeout: 1000
|
|
427
390
|
};
|
|
428
391
|
|
|
429
|
-
this.log.
|
|
392
|
+
this.log.debug(
|
|
430
393
|
'[WLED Sync] %s: GET http://%s:%s/json/state',
|
|
431
394
|
this.device.context.name,
|
|
432
395
|
target.host,
|
|
@@ -449,7 +412,7 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
449
412
|
);
|
|
450
413
|
return done(true);
|
|
451
414
|
}
|
|
452
|
-
this.log.
|
|
415
|
+
this.log.debug(
|
|
453
416
|
'[WLED Sync] %s: /json/state -> bri=%s',
|
|
454
417
|
this.device.context.name,
|
|
455
418
|
bri
|
|
@@ -508,7 +471,7 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
508
471
|
timeout: 1000
|
|
509
472
|
};
|
|
510
473
|
|
|
511
|
-
this.log.
|
|
474
|
+
this.log.debug(
|
|
512
475
|
'[WLED Sync] %s: POST http://%s:%s/json/state body=%s',
|
|
513
476
|
this.device.context.name,
|
|
514
477
|
target.host,
|
|
@@ -520,7 +483,7 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
520
483
|
// Consume response and ignore body
|
|
521
484
|
res.on('data', () => {});
|
|
522
485
|
res.on('end', () => {
|
|
523
|
-
this.log.
|
|
486
|
+
this.log.debug(
|
|
524
487
|
'[WLED Sync] %s: WLED brightness set, HTTP %s',
|
|
525
488
|
this.device.context.name,
|
|
526
489
|
res.statusCode
|
|
@@ -583,7 +546,7 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
583
546
|
timeout: 1000
|
|
584
547
|
};
|
|
585
548
|
|
|
586
|
-
this.log.
|
|
549
|
+
this.log.debug(
|
|
587
550
|
'[WLED Sync] %s: POST http://%s:%s/json/state body=%s (preset effect)',
|
|
588
551
|
this.device.context.name,
|
|
589
552
|
target.host,
|
|
@@ -595,7 +558,7 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
595
558
|
// Consume response and ignore body
|
|
596
559
|
res.on('data', () => {});
|
|
597
560
|
res.on('end', () => {
|
|
598
|
-
this.log.
|
|
561
|
+
this.log.debug(
|
|
599
562
|
'[WLED Sync] %s: WLED effect set to %s, HTTP %s',
|
|
600
563
|
this.device.context.name,
|
|
601
564
|
effectId,
|
|
@@ -659,7 +622,7 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
659
622
|
const warmupDelay = this._wledReadyAt && now < this._wledReadyAt ? (this._wledReadyAt - now) : 0;
|
|
660
623
|
const delay = warmupDelay + wledDebounceMs;
|
|
661
624
|
|
|
662
|
-
this.log.
|
|
625
|
+
this.log.debug(
|
|
663
626
|
'[WLED Sync] %s: scheduling WLED bri=%s in %sms (%s)',
|
|
664
627
|
this.device.context.name,
|
|
665
628
|
brightness,
|
|
@@ -672,7 +635,7 @@ class WledDimmerAccessory extends BaseAccessory {
|
|
|
672
635
|
|
|
673
636
|
// If the light is now off, skip the call.
|
|
674
637
|
if (!this.device.state[this.dpPower]) {
|
|
675
|
-
this.log.
|
|
638
|
+
this.log.debug(
|
|
676
639
|
'[WLED Sync] %s: skipping scheduled WLED bri=%s because power is OFF',
|
|
677
640
|
this.device.context.name,
|
|
678
641
|
this._wledPendingBri
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-tuya-plus",
|
|
3
|
-
"version": "3.14.0-dev.
|
|
3
|
+
"version": "3.14.0-dev.19",
|
|
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": {
|
package/test/getCategory.test.js
CHANGED
|
@@ -23,6 +23,7 @@ const CLASS_DEF = {
|
|
|
23
23
|
garagedoor: require('../lib/GarageDoorAccessory'),
|
|
24
24
|
simplegaragedoor: require('../lib/SimpleGarageDoorAccessory'),
|
|
25
25
|
simpledimmer: require('../lib/SimpleDimmerAccessory'),
|
|
26
|
+
wleddimmer: require('../lib/WledDimmerAccessory'),
|
|
26
27
|
simpledimmer2: require('../lib/SimpleDimmer2Accessory'),
|
|
27
28
|
simpleblinds: require('../lib/SimpleBlindsAccessory'),
|
|
28
29
|
simpleheater: require('../lib/SimpleHeaterAccessory'),
|
|
@@ -15,7 +15,8 @@ If you are looking for verified configurations for your specific device, please
|
|
|
15
15
|
|Barely Smart Power Strip|`Outlet`|Smart power strips that don't allow individual control of the outlets|
|
|
16
16
|
|Air Conditioner|`AirConditioner`<sup>[6](#air-conditioners)</sup>|Cooling and heating devices <small>([instructions](#air-conditioners))</small>|
|
|
17
17
|
|Heat Convector|`Convector`<sup>[7](#heat-convectors)</sup>|Heating panels <small>([instructions](#heat-convectors))</small>|
|
|
18
|
-
|
|
|
18
|
+
|Simple Dimmer|`SimpleDimmer`<sup>[8](#simple-dimmers)</sup>|Dimmer switches with power control <small>([instructions](#simple-dimmers))</small>|
|
|
19
|
+
|WLED Dimmer|`WledDimmer`<sup>[8](#simple-dimmers)</sup>|Dimmer switches with power control, plus optional WLED brightness sync and preset-effect switches <small>([instructions](#simple-dimmers))</small>|
|
|
19
20
|
|Simple Heater|`SimpleHeater`<sup>[9](#simple-heaters)</sup>|Heating solutions with only temperature control <small>([instructions](#simple-heaters))</small>|
|
|
20
21
|
|Garage Door|`GarageDoor`<sup>[10](#garage-doors)</sup>|Smart garage doors or garage door openers <small>([instructions](#garage-doors))</small>|
|
|
21
22
|
|Simple Garage Door|`SimpleGarageDoor`<sup>[10](#simple-garage-doors)</sup>|Sliding gate openers and garage door controllers with open/stop/close action DPs and a simple three-value status DP <small>([instructions](#simple-garage-doors))</small>|
|
|
@@ -345,14 +346,15 @@ If your signature doesn't have a variation of _low_ or _high_, `SimpleHeater` wo
|
|
|
345
346
|
```
|
|
346
347
|
|
|
347
348
|
### Simple Dimmers / WLED Dimmers
|
|
348
|
-
These are switches that allow turning on and off, and dimming.
|
|
349
|
+
These are switches that allow turning on and off, and dimming. Two distinct types are available:
|
|
349
350
|
|
|
350
|
-
|
|
351
|
+
- `SimpleDimmer` — a plain dimmer with power and brightness control.
|
|
352
|
+
- `WledDimmer` — a dimmer that can additionally drive a [WLED](https://kno.wled.ge/) controller (e.g. a Tuya-based relay/dimmer feeding power to a WLED strip). With none of the WLED options below configured, it behaves exactly like a `SimpleDimmer`.
|
|
351
353
|
|
|
352
|
-
|
|
353
|
-
- `presetEffects`: array of effect configs to expose as switches in HomeKit (each turns on a WLED fx preset, optionally with staticColor).
|
|
354
|
+
The following options apply to `WledDimmer` only (they are ignored by `SimpleDimmer`):
|
|
354
355
|
|
|
355
|
-
|
|
356
|
+
- `syncBrightnessToWled`: set to the WLED device IP (e.g. "192.168.1.50" or "192.168.1.50:80") to sync HomeKit brightness changes directly to WLED over HTTP, keeping the Tuya dimmer at 100%.
|
|
357
|
+
- `presetEffects`: array of effect configs to expose as switches in HomeKit (each turns on a WLED fx preset, optionally with staticColor).
|
|
356
358
|
|
|
357
359
|
```json5
|
|
358
360
|
{
|