homebridge-tuya-plus 3.14.0-dev.47 → 3.14.0-dev.48
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.
|
@@ -120,6 +120,11 @@ class SimpleGarageDoorAccessory extends BaseAccessory {
|
|
|
120
120
|
? Characteristic.TargetDoorState.OPEN
|
|
121
121
|
: Characteristic.TargetDoorState.CLOSED;
|
|
122
122
|
this.accessory.context.cachedTargetDoorState = initialTarget;
|
|
123
|
+
// The level-triggered dispatch baseline (see setTargetDoorState). Held
|
|
124
|
+
// separately from cachedTargetDoorState because that one mirrors every
|
|
125
|
+
// reported value for HomeKit's benefit — including the transient "stopped"
|
|
126
|
+
// our own stop-before-close produces — which must not move this baseline.
|
|
127
|
+
this._committedTarget = initialTarget;
|
|
123
128
|
|
|
124
129
|
this.characteristicTargetDoorState = service.getCharacteristic(Characteristic.TargetDoorState)
|
|
125
130
|
.updateValue(initialTarget)
|
|
@@ -201,11 +206,24 @@ class SimpleGarageDoorAccessory extends BaseAccessory {
|
|
|
201
206
|
// (=OPEN), which would fight the close we're about to send. Ignore
|
|
202
207
|
// status reports until the close has gone out.
|
|
203
208
|
if (this.pendingCloseTimer) return;
|
|
204
|
-
const
|
|
209
|
+
const raw = typeof changes[this.dpState] === 'string' ? parseInt(changes[this.dpState], 10) : changes[this.dpState];
|
|
210
|
+
const isOpen = this._mapDpState(raw);
|
|
205
211
|
if (isOpen === null) {
|
|
206
212
|
this.log.debug(`[SimpleGarageDoor] ${this.device.context.name}: ignoring unknown state DP value ${JSON.stringify(changes[this.dpState])}`);
|
|
207
213
|
return;
|
|
208
214
|
}
|
|
215
|
+
// Keep the level-triggered dispatch baseline honest with the gate's real
|
|
216
|
+
// position, but only from a DEFINITIVE report — 12 (opening/open) or 13
|
|
217
|
+
// (closing/closed). A bare 11 (stopped) is skipped: it is exactly what our
|
|
218
|
+
// own stop-before-close pulse makes the controller report, and over the LAN
|
|
219
|
+
// that report can land just after the stop→close window. Letting it flip
|
|
220
|
+
// the committed target back to OPEN is what let HomeKit's repeat close fire
|
|
221
|
+
// a second stop-before-close into the already-closing gate. A genuine
|
|
222
|
+
// external open always passes through 12 first, so nothing is missed.
|
|
223
|
+
if (raw === STATE_OPENING_OR_OPEN || raw === STATE_CLOSING_OR_CLOSED) {
|
|
224
|
+
const {Characteristic} = this.hap;
|
|
225
|
+
this._committedTarget = isOpen ? Characteristic.TargetDoorState.OPEN : Characteristic.TargetDoorState.CLOSED;
|
|
226
|
+
}
|
|
209
227
|
this._applyReportedState(isOpen);
|
|
210
228
|
}
|
|
211
229
|
|
|
@@ -259,16 +277,17 @@ class SimpleGarageDoorAccessory extends BaseAccessory {
|
|
|
259
277
|
this._cancelPartialStop();
|
|
260
278
|
|
|
261
279
|
// The opener is level-triggered: act only when the requested target
|
|
262
|
-
// differs from the one already committed. HomeKit re-sends the
|
|
263
|
-
// target after a tap — a second controller echoing it, or its own
|
|
264
|
-
// a few seconds later — and acting on that repeat would fire another
|
|
280
|
+
// differs from the one we've already committed to. HomeKit re-sends the
|
|
281
|
+
// same target after a tap — a second controller echoing it, or its own
|
|
282
|
+
// retry a few seconds later — and acting on that repeat would fire another
|
|
265
283
|
// open/close. For a close that means a second stop-before-close into the
|
|
266
|
-
// already-closing gate, halting it mid-travel and restarting it
|
|
267
|
-
// request always flips the target (the Home
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
|
|
284
|
+
// already-closing gate, halting it mid-travel and restarting it (the
|
|
285
|
+
// reported stutter). A genuine request always flips the target (the Home
|
|
286
|
+
// app toggles to the opposite state). The committed target is reconciled
|
|
287
|
+
// with the gate's real position in _onDeviceChange so external operation
|
|
288
|
+
// still works — but only from a definitive report, never from the
|
|
289
|
+
// "stopped" transient our own stop produces, so the repeat stays caught.
|
|
290
|
+
if (value === this._committedTarget) {
|
|
272
291
|
this.log.debug(`[SimpleGarageDoor] ${this.device.context.name}: target unchanged — ignoring repeat request`);
|
|
273
292
|
return;
|
|
274
293
|
}
|
|
@@ -282,6 +301,7 @@ class SimpleGarageDoorAccessory extends BaseAccessory {
|
|
|
282
301
|
_applyTarget(value) {
|
|
283
302
|
const {Characteristic} = this.hap;
|
|
284
303
|
const open = value === Characteristic.TargetDoorState.OPEN;
|
|
304
|
+
this._committedTarget = value;
|
|
285
305
|
this.accessory.context.cachedTargetDoorState = value;
|
|
286
306
|
if (this.characteristicTargetDoorState) this.characteristicTargetDoorState.updateValue(value);
|
|
287
307
|
if (open) this._sendOpen();
|
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.48",
|
|
4
4
|
"description": "A community-maintained Homebridge plugin for controlling Tuya devices in HomeKit. LAN-first, with an optional Tuya Cloud fallback for any device the LAN can't reach.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -68,6 +68,7 @@ function makeSimpleGarage(initialContext = {}) {
|
|
|
68
68
|
updateValue: jest.fn().mockImplementation(function(v) { this.value = v; return this; }),
|
|
69
69
|
};
|
|
70
70
|
accessory.context.cachedTargetDoorState = TDS.CLOSED;
|
|
71
|
+
instance._committedTarget = TDS.CLOSED;
|
|
71
72
|
|
|
72
73
|
// Mirror the persistent change listener registered in production.
|
|
73
74
|
device.on('change', changes => instance._onDeviceChange(changes));
|
|
@@ -233,6 +234,7 @@ describe('SimpleGarageDoorAccessory.setTargetDoorState — close (stop-before-cl
|
|
|
233
234
|
function openGate(instance, device, state = STATE_OPENING_OR_OPEN) {
|
|
234
235
|
if (state !== undefined) device.state['105'] = state;
|
|
235
236
|
instance.accessory.context.cachedTargetDoorState = TDS.OPEN;
|
|
237
|
+
instance._committedTarget = TDS.OPEN;
|
|
236
238
|
}
|
|
237
239
|
|
|
238
240
|
test('CLOSE fires immediately when the gate is already stopped (state 11)', () => {
|
|
@@ -330,6 +332,25 @@ describe('SimpleGarageDoorAccessory.setTargetDoorState — close (stop-before-cl
|
|
|
330
332
|
expect(device.update).toHaveBeenCalledTimes(2); // swallowed — no second stop/close
|
|
331
333
|
});
|
|
332
334
|
|
|
335
|
+
test('A stopped (11) report after the close does not re-enable the repeat (LAN stutter)', () => {
|
|
336
|
+
// Over the LAN the stop pulse makes the controller report 11 (=OPEN) just
|
|
337
|
+
// after the close has gone out — outside the stop-before-close window. That
|
|
338
|
+
// 11 must NOT move the committed target back to OPEN, or HomeKit's repeat
|
|
339
|
+
// close would fire a second stop-before-close into the already-closing gate.
|
|
340
|
+
const { instance, device } = makeSimpleGarage();
|
|
341
|
+
instance.stopBeforeCloseMs = 1500;
|
|
342
|
+
openGate(instance, device, STATE_OPENING_OR_OPEN);
|
|
343
|
+
|
|
344
|
+
instance.setTargetDoorState(TDS.CLOSED);
|
|
345
|
+
jest.advanceTimersByTime(1500);
|
|
346
|
+
expect(device.update).toHaveBeenCalledTimes(2); // stop + close
|
|
347
|
+
|
|
348
|
+
emitState(device, STATE_STOPPED); // the stop's late 11 report
|
|
349
|
+
expect(instance._committedTarget).toBe(TDS.CLOSED); // not bounced to OPEN
|
|
350
|
+
instance.setTargetDoorState(TDS.CLOSED); // HomeKit's repeat
|
|
351
|
+
expect(device.update).toHaveBeenCalledTimes(2); // swallowed
|
|
352
|
+
});
|
|
353
|
+
|
|
333
354
|
test('A close runs again once the gate is reported open (committed target tracks reports)', () => {
|
|
334
355
|
const { instance, device } = makeSimpleGarage();
|
|
335
356
|
instance.stopBeforeCloseMs = 1500;
|
|
@@ -631,7 +652,7 @@ describe('SimpleGarageDoorAccessory — force switches', () => {
|
|
|
631
652
|
test('Force Close fires the close action (immediately when already stopped)', () => {
|
|
632
653
|
const { instance, device, accessory } = makeSimpleGarage();
|
|
633
654
|
device.state['105'] = STATE_STOPPED;
|
|
634
|
-
|
|
655
|
+
instance._committedTarget = TDS.OPEN; // gate open → close is a real transition
|
|
635
656
|
|
|
636
657
|
instance.setTargetDoorState(TDS.CLOSED);
|
|
637
658
|
|