homebridge-tuya-plus 3.14.0-dev.51 → 3.14.0-dev.52

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.
@@ -271,10 +271,6 @@ class SimpleGarageDoorAccessory extends BaseAccessory {
271
271
  // silently dropping the command (the write helpers would otherwise just
272
272
  // log and skip, leaving HomeKit to believe the open/close succeeded).
273
273
  if (!this.device.connected) throw this._commError();
274
- // A direct open/close (the GarageDoorOpener target, a Force switch, or
275
- // the partial switch tapped OFF) is manual control: cancel any pending
276
- // partial-open auto-stop so it can't halt this movement part-way.
277
- this._cancelPartialStop();
278
274
 
279
275
  // The opener is level-triggered: act only when the requested target
280
276
  // differs from the one we've already committed to. HomeKit re-sends the
@@ -291,6 +287,17 @@ class SimpleGarageDoorAccessory extends BaseAccessory {
291
287
  this.log.debug(`[SimpleGarageDoor] ${this.device.context.name}: target unchanged — ignoring repeat request`);
292
288
  return;
293
289
  }
290
+
291
+ // A direct open/close (the GarageDoorOpener target, a Force switch, or
292
+ // the partial switch tapped OFF) is manual control: cancel any pending
293
+ // partial-open auto-stop so it can't halt this movement part-way. This
294
+ // must run only for a genuine target change, AFTER the repeat check
295
+ // above: a partial open drives the committed target to OPEN itself, so
296
+ // HomeKit's repeat of that OPEN re-enters here, and cancelling on that
297
+ // swallowed repeat would tear down the auto-stop the partial just armed —
298
+ // letting the gate run all the way open instead of parking part-way.
299
+ this._cancelPartialStop();
300
+
294
301
  this._applyTarget(value);
295
302
  }
296
303
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-tuya-plus",
3
- "version": "3.14.0-dev.51",
3
+ "version": "3.14.0-dev.52",
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": {
@@ -602,6 +602,53 @@ describe('SimpleGarageDoorAccessory._handlePartialOpen', () => {
602
602
  expect(device.update).toHaveBeenCalledTimes(3);
603
603
  });
604
604
 
605
+ test('A repeat of the partial open\'s own OPEN target does not cancel the pending auto-stop', () => {
606
+ // The partial open drives TargetDoorState to OPEN itself, then waits for
607
+ // the gate to report it's moving before arming the auto-stop. HomeKit
608
+ // re-sends that same OPEN in the meantime (a second controller echoing
609
+ // it, or its own retry). That swallowed repeat must NOT tear down the
610
+ // pending partial, or the gate runs all the way open instead of parking
611
+ // part-way — the reported bug.
612
+ const { instance, device } = makeSimpleGarage();
613
+ instance.partialOpenMs = 2000;
614
+ device.state['105'] = STATE_CLOSING_OR_CLOSED; // starts closed → stop waits for movement
615
+
616
+ instance._handlePartialOpen();
617
+ expect(device.update).toHaveBeenCalledTimes(1);
618
+ expect(device.update).toHaveBeenNthCalledWith(1, { '101': true }); // open
619
+ expect(instance.partialPending).toBe(true);
620
+
621
+ // HomeKit's repeat of the same OPEN lands before the gate reports moving.
622
+ instance.setTargetDoorState(TDS.OPEN);
623
+ expect(instance.partialPending).toBe(true); // still pending — not cancelled
624
+ expect(device.update).toHaveBeenCalledTimes(1); // and no second open
625
+
626
+ // The gate now reports it's moving: the countdown arms and the stop fires.
627
+ emitState(device, STATE_OPENING_OR_OPEN);
628
+ expect(instance.partialStopTimer).not.toBeNull();
629
+ jest.advanceTimersByTime(2000);
630
+ expect(device.update).toHaveBeenNthCalledWith(2, { '103': true }); // partial stop
631
+ });
632
+
633
+ test('A repeat of OPEN after the auto-stop is armed leaves the timer intact', () => {
634
+ const { instance, device } = makeSimpleGarage();
635
+ instance.partialOpenMs = 2000;
636
+ device.state['105'] = STATE_OPENING_OR_OPEN; // arms immediately
637
+
638
+ instance._handlePartialOpen();
639
+ expect(instance.partialStopTimer).not.toBeNull();
640
+
641
+ // A repeat OPEN partway through must not cancel or push out the timer.
642
+ jest.advanceTimersByTime(500);
643
+ instance.setTargetDoorState(TDS.OPEN);
644
+ expect(instance.partialStopTimer).not.toBeNull();
645
+ expect(device.update).toHaveBeenCalledTimes(1); // no second open
646
+
647
+ // The original 2000ms deadline still fires the stop.
648
+ jest.advanceTimersByTime(1500);
649
+ expect(device.update).toHaveBeenNthCalledWith(2, { '103': true });
650
+ });
651
+
605
652
  test('Does nothing when partialOpenMs is not configured', () => {
606
653
  const { instance, device } = makeSimpleGarage();
607
654
  instance.partialOpenMs = 0;