homebridge-tuya-plus 3.11.0 → 3.11.1-beta.2
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
CHANGED
|
@@ -518,6 +518,14 @@
|
|
|
518
518
|
"functionBody": "return model.devices && model.devices[arrayIndices] && ['SimpleGarageDoor'].includes(model.devices[arrayIndices].type);"
|
|
519
519
|
}
|
|
520
520
|
},
|
|
521
|
+
"commandDelay": {
|
|
522
|
+
"type": "integer",
|
|
523
|
+
"placeholder": "2000",
|
|
524
|
+
"description": "Milliseconds to wait between the stop command and the open/close command. Increase if your controller drops the direction command after a stop.",
|
|
525
|
+
"condition": {
|
|
526
|
+
"functionBody": "return model.devices && model.devices[arrayIndices] && ['SimpleGarageDoor'].includes(model.devices[arrayIndices].type);"
|
|
527
|
+
}
|
|
528
|
+
},
|
|
521
529
|
"flipState": {
|
|
522
530
|
"type": "boolean",
|
|
523
531
|
"condition": {
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
const BaseAccessory = require('./BaseAccessory');
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// Delay between the stop command and the open/close command. Some controllers
|
|
4
|
+
// drop the direction command when it arrives while they are still processing
|
|
5
|
+
// the stop, so we wait this long before sending the direction.
|
|
6
|
+
const DEFAULT_COMMAND_DELAY_MS = 2000;
|
|
7
|
+
|
|
8
|
+
// Delay between the direction command and flipping CurrentDoorState. The
|
|
9
|
+
// device has no position feedback so this is purely cosmetic — it keeps
|
|
10
|
+
// HomeKit's "Opening..."/"Closing..." caption visible for at least this long.
|
|
6
11
|
const CURRENT_STATE_DELAY_MS = 1000;
|
|
7
12
|
|
|
13
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
14
|
+
|
|
8
15
|
class SimpleGarageDoorAccessory extends BaseAccessory {
|
|
9
16
|
static getCategory(Categories) {
|
|
10
17
|
return Categories.GARAGE_DOOR_OPENER;
|
|
@@ -31,6 +38,11 @@ class SimpleGarageDoorAccessory extends BaseAccessory {
|
|
|
31
38
|
this.dpStop = this._getCustomDP(this.device.context.dpStop) || '2';
|
|
32
39
|
this.dpClose = this._getCustomDP(this.device.context.dpClose) || '3';
|
|
33
40
|
|
|
41
|
+
const configuredDelay = parseInt(this.device.context.commandDelay, 10);
|
|
42
|
+
this.commandDelayMs = Number.isFinite(configuredDelay) && configuredDelay >= 0
|
|
43
|
+
? configuredDelay
|
|
44
|
+
: DEFAULT_COMMAND_DELAY_MS;
|
|
45
|
+
|
|
34
46
|
// The device only exposes momentary action DPs, so the target state is
|
|
35
47
|
// tracked locally and persisted via the homebridge accessory context.
|
|
36
48
|
if (this.accessory.context.cachedTargetDoorState !== Characteristic.TargetDoorState.OPEN &&
|
|
@@ -42,6 +54,10 @@ class SimpleGarageDoorAccessory extends BaseAccessory {
|
|
|
42
54
|
? Characteristic.CurrentDoorState.OPEN
|
|
43
55
|
: Characteristic.CurrentDoorState.CLOSED;
|
|
44
56
|
|
|
57
|
+
// Each setTargetDoorState invocation bumps this token; an older
|
|
58
|
+
// in-flight chain bails out at its next await when the token changes.
|
|
59
|
+
this.opToken = 0;
|
|
60
|
+
|
|
45
61
|
this.characteristicTargetDoorState = service.getCharacteristic(Characteristic.TargetDoorState)
|
|
46
62
|
.updateValue(initialTarget)
|
|
47
63
|
.onGet(() => this.accessory.context.cachedTargetDoorState)
|
|
@@ -56,28 +72,43 @@ class SimpleGarageDoorAccessory extends BaseAccessory {
|
|
|
56
72
|
.onGet(() => false);
|
|
57
73
|
}
|
|
58
74
|
|
|
59
|
-
|
|
75
|
+
// Wraps the synchronous Tuya write so the caller can await it. The Tuya
|
|
76
|
+
// transport is fire-and-forget at the JS level — the data has been handed
|
|
77
|
+
// to the kernel by the time setMultiStateLegacyAsync returns — but using
|
|
78
|
+
// an awaited call keeps the command sequence in one readable async chain.
|
|
79
|
+
async _sendDps(dps) {
|
|
80
|
+
this.setMultiStateLegacyAsync(dps);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async setTargetDoorState(value) {
|
|
60
84
|
const {Characteristic} = this.hap;
|
|
61
85
|
|
|
62
86
|
this.accessory.context.cachedTargetDoorState = value;
|
|
63
87
|
|
|
88
|
+
const opToken = ++this.opToken;
|
|
89
|
+
|
|
64
90
|
// Send stop first so reversing direction mid-motion works; if the gate
|
|
65
91
|
// is already idle, the stop is a no-op on the device side.
|
|
66
|
-
this.
|
|
92
|
+
await this._sendDps({[this.dpStop]: true});
|
|
93
|
+
if (opToken !== this.opToken) return;
|
|
94
|
+
|
|
95
|
+
await sleep(this.commandDelayMs);
|
|
96
|
+
if (opToken !== this.opToken) return;
|
|
97
|
+
|
|
67
98
|
if (value === Characteristic.TargetDoorState.OPEN) {
|
|
68
|
-
this.
|
|
99
|
+
await this._sendDps({[this.dpOpen]: true});
|
|
69
100
|
} else {
|
|
70
|
-
this.
|
|
101
|
+
await this._sendDps({[this.dpClose]: true});
|
|
71
102
|
}
|
|
103
|
+
if (opToken !== this.opToken) return;
|
|
104
|
+
|
|
105
|
+
await sleep(CURRENT_STATE_DELAY_MS);
|
|
106
|
+
if (opToken !== this.opToken) return;
|
|
72
107
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
? Characteristic.CurrentDoorState.OPEN
|
|
78
|
-
: Characteristic.CurrentDoorState.CLOSED;
|
|
79
|
-
this.characteristicCurrentDoorState.updateValue(this.currentDoorState);
|
|
80
|
-
}, CURRENT_STATE_DELAY_MS);
|
|
108
|
+
this.currentDoorState = value === Characteristic.TargetDoorState.OPEN
|
|
109
|
+
? Characteristic.CurrentDoorState.OPEN
|
|
110
|
+
: Characteristic.CurrentDoorState.CLOSED;
|
|
111
|
+
this.characteristicCurrentDoorState.updateValue(this.currentDoorState);
|
|
81
112
|
}
|
|
82
113
|
}
|
|
83
114
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-tuya-plus",
|
|
3
|
-
"version": "3.11.
|
|
3
|
+
"version": "3.11.1-beta.2",
|
|
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": {
|
|
@@ -5,6 +5,10 @@ const { HAP, makeInstance } = require('./support/mocks');
|
|
|
5
5
|
|
|
6
6
|
const { CurrentDoorState: CDS, TargetDoorState: TDS } = HAP.Characteristic;
|
|
7
7
|
|
|
8
|
+
const DEFAULT_COMMAND_DELAY_MS = 500;
|
|
9
|
+
const CURRENT_STATE_DELAY_MS = 1000;
|
|
10
|
+
const TOTAL_DELAY_MS = DEFAULT_COMMAND_DELAY_MS + CURRENT_STATE_DELAY_MS;
|
|
11
|
+
|
|
8
12
|
function makeSimpleGarage(contextOverrides = {}) {
|
|
9
13
|
const { instance, device, accessory, platform } = makeInstance(
|
|
10
14
|
SimpleGarageDoorAccessory,
|
|
@@ -16,6 +20,8 @@ function makeSimpleGarage(contextOverrides = {}) {
|
|
|
16
20
|
instance.dpOpen = '1';
|
|
17
21
|
instance.dpStop = '2';
|
|
18
22
|
instance.dpClose = '3';
|
|
23
|
+
instance.commandDelayMs = DEFAULT_COMMAND_DELAY_MS;
|
|
24
|
+
instance.opToken = 0;
|
|
19
25
|
instance.currentDoorState = CDS.OPEN;
|
|
20
26
|
instance.characteristicCurrentDoorState = {
|
|
21
27
|
value: CDS.OPEN,
|
|
@@ -29,35 +35,108 @@ function makeSimpleGarage(contextOverrides = {}) {
|
|
|
29
35
|
// setTargetDoorState — device commands
|
|
30
36
|
// ---------------------------------------------------------------------------
|
|
31
37
|
describe('SimpleGarageDoorAccessory.setTargetDoorState — device commands', () => {
|
|
32
|
-
|
|
38
|
+
beforeEach(() => jest.useFakeTimers());
|
|
39
|
+
afterEach(() => jest.useRealTimers());
|
|
40
|
+
|
|
41
|
+
test('OPEN sends stop=true, then open=true after commandDelay', async () => {
|
|
33
42
|
const { instance, device } = makeSimpleGarage();
|
|
34
|
-
instance.setTargetDoorState(TDS.OPEN);
|
|
43
|
+
const op = instance.setTargetDoorState(TDS.OPEN);
|
|
44
|
+
|
|
45
|
+
await jest.advanceTimersByTimeAsync(0);
|
|
46
|
+
expect(device.update).toHaveBeenCalledTimes(1);
|
|
35
47
|
expect(device.update).toHaveBeenNthCalledWith(1, { '2': true });
|
|
48
|
+
|
|
49
|
+
await jest.advanceTimersByTimeAsync(DEFAULT_COMMAND_DELAY_MS - 1);
|
|
50
|
+
expect(device.update).toHaveBeenCalledTimes(1);
|
|
51
|
+
|
|
52
|
+
await jest.advanceTimersByTimeAsync(1);
|
|
53
|
+
expect(device.update).toHaveBeenCalledTimes(2);
|
|
36
54
|
expect(device.update).toHaveBeenNthCalledWith(2, { '1': true });
|
|
55
|
+
|
|
56
|
+
await jest.advanceTimersByTimeAsync(CURRENT_STATE_DELAY_MS);
|
|
57
|
+
await op;
|
|
37
58
|
});
|
|
38
59
|
|
|
39
|
-
test('CLOSED sends stop=true then close=true', () => {
|
|
60
|
+
test('CLOSED sends stop=true, then close=true after commandDelay', async () => {
|
|
40
61
|
const { instance, device } = makeSimpleGarage();
|
|
41
|
-
instance.setTargetDoorState(TDS.CLOSED);
|
|
62
|
+
const op = instance.setTargetDoorState(TDS.CLOSED);
|
|
63
|
+
|
|
64
|
+
await jest.advanceTimersByTimeAsync(0);
|
|
42
65
|
expect(device.update).toHaveBeenNthCalledWith(1, { '2': true });
|
|
66
|
+
|
|
67
|
+
await jest.advanceTimersByTimeAsync(DEFAULT_COMMAND_DELAY_MS);
|
|
43
68
|
expect(device.update).toHaveBeenNthCalledWith(2, { '3': true });
|
|
44
|
-
});
|
|
45
69
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
device.connected = false;
|
|
49
|
-
instance.setTargetDoorState(TDS.OPEN);
|
|
50
|
-
expect(device.update).not.toHaveBeenCalled();
|
|
70
|
+
await jest.advanceTimersByTimeAsync(CURRENT_STATE_DELAY_MS);
|
|
71
|
+
await op;
|
|
51
72
|
});
|
|
52
73
|
|
|
53
|
-
test('Custom DPs are respected', () => {
|
|
74
|
+
test('Custom DPs are respected', async () => {
|
|
54
75
|
const { instance, device } = makeSimpleGarage();
|
|
55
76
|
instance.dpOpen = '101';
|
|
56
77
|
instance.dpStop = '102';
|
|
57
78
|
instance.dpClose = '103';
|
|
58
|
-
instance.setTargetDoorState(TDS.OPEN);
|
|
79
|
+
const op = instance.setTargetDoorState(TDS.OPEN);
|
|
80
|
+
|
|
81
|
+
await jest.advanceTimersByTimeAsync(0);
|
|
59
82
|
expect(device.update).toHaveBeenNthCalledWith(1, { '102': true });
|
|
83
|
+
|
|
84
|
+
await jest.advanceTimersByTimeAsync(DEFAULT_COMMAND_DELAY_MS);
|
|
60
85
|
expect(device.update).toHaveBeenNthCalledWith(2, { '101': true });
|
|
86
|
+
|
|
87
|
+
await jest.advanceTimersByTimeAsync(CURRENT_STATE_DELAY_MS);
|
|
88
|
+
await op;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('Skips writes when the device is disconnected', async () => {
|
|
92
|
+
const { instance, device } = makeSimpleGarage();
|
|
93
|
+
device.connected = false;
|
|
94
|
+
const op = instance.setTargetDoorState(TDS.OPEN);
|
|
95
|
+
await jest.advanceTimersByTimeAsync(TOTAL_DELAY_MS);
|
|
96
|
+
await op;
|
|
97
|
+
expect(device.update).not.toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('Configured commandDelay is used between stop and direction', async () => {
|
|
101
|
+
const { instance, device } = makeSimpleGarage();
|
|
102
|
+
instance.commandDelayMs = 5000;
|
|
103
|
+
const op = instance.setTargetDoorState(TDS.OPEN);
|
|
104
|
+
|
|
105
|
+
await jest.advanceTimersByTimeAsync(0);
|
|
106
|
+
expect(device.update).toHaveBeenCalledTimes(1);
|
|
107
|
+
|
|
108
|
+
await jest.advanceTimersByTimeAsync(4999);
|
|
109
|
+
expect(device.update).toHaveBeenCalledTimes(1);
|
|
110
|
+
|
|
111
|
+
await jest.advanceTimersByTimeAsync(1);
|
|
112
|
+
expect(device.update).toHaveBeenCalledTimes(2);
|
|
113
|
+
expect(device.update).toHaveBeenNthCalledWith(2, { '1': true });
|
|
114
|
+
|
|
115
|
+
await jest.advanceTimersByTimeAsync(CURRENT_STATE_DELAY_MS);
|
|
116
|
+
await op;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('Reversing during the stop->direction window cancels the pending direction command', async () => {
|
|
120
|
+
const { instance, device } = makeSimpleGarage();
|
|
121
|
+
const op1 = instance.setTargetDoorState(TDS.OPEN);
|
|
122
|
+
await jest.advanceTimersByTimeAsync(0);
|
|
123
|
+
expect(device.update).toHaveBeenNthCalledWith(1, { '2': true });
|
|
124
|
+
|
|
125
|
+
await jest.advanceTimersByTimeAsync(500);
|
|
126
|
+
const op2 = instance.setTargetDoorState(TDS.CLOSED);
|
|
127
|
+
await jest.advanceTimersByTimeAsync(0);
|
|
128
|
+
|
|
129
|
+
// A second stop is sent, but the originally-pending open must not fire.
|
|
130
|
+
expect(device.update).toHaveBeenCalledTimes(2);
|
|
131
|
+
expect(device.update).toHaveBeenNthCalledWith(2, { '2': true });
|
|
132
|
+
|
|
133
|
+
await jest.advanceTimersByTimeAsync(DEFAULT_COMMAND_DELAY_MS);
|
|
134
|
+
expect(device.update).toHaveBeenCalledTimes(3);
|
|
135
|
+
expect(device.update).toHaveBeenNthCalledWith(3, { '3': true });
|
|
136
|
+
|
|
137
|
+
await jest.advanceTimersByTimeAsync(CURRENT_STATE_DELAY_MS);
|
|
138
|
+
await op1;
|
|
139
|
+
await op2;
|
|
61
140
|
});
|
|
62
141
|
});
|
|
63
142
|
|
|
@@ -68,48 +147,51 @@ describe('SimpleGarageDoorAccessory.setTargetDoorState — CurrentDoorState tran
|
|
|
68
147
|
beforeEach(() => jest.useFakeTimers());
|
|
69
148
|
afterEach(() => jest.useRealTimers());
|
|
70
149
|
|
|
71
|
-
test('CurrentDoorState
|
|
150
|
+
test('CurrentDoorState updates to OPEN after commandDelay + currentStateDelay', async () => {
|
|
72
151
|
const { instance } = makeSimpleGarage();
|
|
73
152
|
instance.currentDoorState = CDS.CLOSED;
|
|
74
153
|
instance.characteristicCurrentDoorState.value = CDS.CLOSED;
|
|
75
154
|
|
|
76
|
-
instance.setTargetDoorState(TDS.OPEN);
|
|
155
|
+
const op = instance.setTargetDoorState(TDS.OPEN);
|
|
156
|
+
await jest.advanceTimersByTimeAsync(0);
|
|
77
157
|
expect(instance.characteristicCurrentDoorState.value).toBe(CDS.CLOSED);
|
|
78
158
|
|
|
79
|
-
jest.
|
|
159
|
+
await jest.advanceTimersByTimeAsync(TOTAL_DELAY_MS - 1);
|
|
80
160
|
expect(instance.characteristicCurrentDoorState.value).toBe(CDS.CLOSED);
|
|
81
161
|
|
|
82
|
-
jest.
|
|
162
|
+
await jest.advanceTimersByTimeAsync(1);
|
|
163
|
+
await op;
|
|
83
164
|
expect(instance.currentDoorState).toBe(CDS.OPEN);
|
|
84
165
|
expect(instance.characteristicCurrentDoorState.value).toBe(CDS.OPEN);
|
|
85
166
|
});
|
|
86
167
|
|
|
87
|
-
test('CurrentDoorState
|
|
168
|
+
test('CurrentDoorState updates to CLOSED after commandDelay + currentStateDelay', async () => {
|
|
88
169
|
const { instance } = makeSimpleGarage();
|
|
89
170
|
instance.currentDoorState = CDS.OPEN;
|
|
90
171
|
instance.characteristicCurrentDoorState.value = CDS.OPEN;
|
|
91
172
|
|
|
92
|
-
instance.setTargetDoorState(TDS.CLOSED);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
jest.advanceTimersByTime(1000);
|
|
173
|
+
const op = instance.setTargetDoorState(TDS.CLOSED);
|
|
174
|
+
await jest.advanceTimersByTimeAsync(TOTAL_DELAY_MS);
|
|
175
|
+
await op;
|
|
96
176
|
expect(instance.currentDoorState).toBe(CDS.CLOSED);
|
|
97
177
|
expect(instance.characteristicCurrentDoorState.value).toBe(CDS.CLOSED);
|
|
98
178
|
});
|
|
99
179
|
|
|
100
|
-
test('Reversing direction
|
|
180
|
+
test('Reversing direction mid-transition cancels the stale CurrentDoorState update', async () => {
|
|
101
181
|
const { instance } = makeSimpleGarage();
|
|
102
182
|
instance.currentDoorState = CDS.OPEN;
|
|
103
183
|
instance.characteristicCurrentDoorState.value = CDS.OPEN;
|
|
104
184
|
|
|
105
|
-
instance.setTargetDoorState(TDS.CLOSED);
|
|
106
|
-
jest.
|
|
107
|
-
instance.setTargetDoorState(TDS.OPEN);
|
|
185
|
+
const op1 = instance.setTargetDoorState(TDS.CLOSED);
|
|
186
|
+
await jest.advanceTimersByTimeAsync(DEFAULT_COMMAND_DELAY_MS + 500);
|
|
187
|
+
const op2 = instance.setTargetDoorState(TDS.OPEN);
|
|
108
188
|
|
|
109
|
-
jest.
|
|
189
|
+
await jest.advanceTimersByTimeAsync(500);
|
|
110
190
|
expect(instance.characteristicCurrentDoorState.value).toBe(CDS.OPEN);
|
|
111
191
|
|
|
112
|
-
jest.
|
|
192
|
+
await jest.advanceTimersByTimeAsync(TOTAL_DELAY_MS);
|
|
193
|
+
await op1;
|
|
194
|
+
await op2;
|
|
113
195
|
expect(instance.currentDoorState).toBe(CDS.OPEN);
|
|
114
196
|
expect(instance.characteristicCurrentDoorState.value).toBe(CDS.OPEN);
|
|
115
197
|
});
|
|
@@ -119,11 +201,18 @@ describe('SimpleGarageDoorAccessory.setTargetDoorState — CurrentDoorState tran
|
|
|
119
201
|
// Persistence
|
|
120
202
|
// ---------------------------------------------------------------------------
|
|
121
203
|
describe('SimpleGarageDoorAccessory persistence', () => {
|
|
122
|
-
|
|
204
|
+
beforeEach(() => jest.useFakeTimers());
|
|
205
|
+
afterEach(() => jest.useRealTimers());
|
|
206
|
+
|
|
207
|
+
test('Stores the latest target on the accessory context', async () => {
|
|
123
208
|
const { instance, accessory } = makeSimpleGarage();
|
|
124
|
-
instance.setTargetDoorState(TDS.CLOSED);
|
|
209
|
+
const op1 = instance.setTargetDoorState(TDS.CLOSED);
|
|
125
210
|
expect(accessory.context.cachedTargetDoorState).toBe(TDS.CLOSED);
|
|
126
|
-
instance.setTargetDoorState(TDS.OPEN);
|
|
211
|
+
const op2 = instance.setTargetDoorState(TDS.OPEN);
|
|
127
212
|
expect(accessory.context.cachedTargetDoorState).toBe(TDS.OPEN);
|
|
213
|
+
|
|
214
|
+
await jest.advanceTimersByTimeAsync(TOTAL_DELAY_MS);
|
|
215
|
+
await op1;
|
|
216
|
+
await op2;
|
|
128
217
|
});
|
|
129
218
|
});
|
|
@@ -18,6 +18,7 @@ If you are looking for verified configurations for your specific device, please
|
|
|
18
18
|
|Simple Dimmer|`SimpleDimmer`<sup>[8](#simple-dimmers)</sup>|Dimmer switches with power control <small>([instructions](#simple-dimmers))</small>|
|
|
19
19
|
|Simple Heater|`SimpleHeater`<sup>[9](#simple-heaters)</sup>|Heating solutions with only temperature control <small>([instructions](#simple-heaters))</small>|
|
|
20
20
|
|Garage Door|`GarageDoor`<sup>[10](#garage-doors)</sup>|Smart garage doors or garage door openers <small>([instructions](#garage-doors))</small>|
|
|
21
|
+
|Simple Garage Door|`SimpleGarageDoor`<sup>[10](#simple-garage-doors)</sup>|Garage doors and sliding gate openers that expose only three momentary action DPs: open, stop, close <small>([instructions](#simple-garage-doors))</small>|
|
|
21
22
|
|Simple Blinds|`SimpleBlinds`<sup>[11](#simple-blinds)</sup>|Smart blinds and smart switches that control blinds <small>([instructions](#simple-blinds))</small>|
|
|
22
23
|
|Simple Blinds2|`SimpleBlinds2`<sup>[11](#simple-blinds)</sup>|Smart blinds and smart switches that control blinds(Use if simple Blinds (1) doesn't work for you. <small>([instructions](#simple-blinds))</small>|
|
|
23
24
|
|Vertical Blinds with Tilt|`VerticalBlindsWithTilt`<sup>[11](#vertical-blinds-with-tilt)</sup>|Smart vertical blinds with open/close and panel rotation <small>([instructions](#vertical-blinds-with-tilt))</small>|
|
|
@@ -402,6 +403,38 @@ While still in early testing, you can use this to open and close the garage door
|
|
|
402
403
|
}
|
|
403
404
|
```
|
|
404
405
|
|
|
406
|
+
### Simple Garage Doors
|
|
407
|
+
For very basic garage door openers and sliding gate controllers that expose only three momentary action DPs — one to open, one to stop, one to close — with no position or status feedback. The plugin tracks the target state locally and persists it across restarts, so HomeKit always reflects whatever was last requested. Triggering a change sends the stop command first (so reversing direction mid-motion works; it is a no-op when the gate is idle), waits `commandDelay` milliseconds, and then sends the open or close command. There is no obstruction detection.
|
|
408
|
+
|
|
409
|
+
If your controller stops the gate but does not start moving in the requested direction afterwards, increase `commandDelay` — some devices drop the direction command if it arrives too soon after the stop.
|
|
410
|
+
|
|
411
|
+
```json5
|
|
412
|
+
{
|
|
413
|
+
"name": "My Sliding Gate",
|
|
414
|
+
"type": "SimpleGarageDoor",
|
|
415
|
+
"manufacturer": "Generic",
|
|
416
|
+
"model": "Generic Sliding Gate Controller",
|
|
417
|
+
"id": "032000123456789abcde",
|
|
418
|
+
"key": "0123456789abcdef",
|
|
419
|
+
|
|
420
|
+
/* Additional parameters to override defaults only if needed */
|
|
421
|
+
|
|
422
|
+
/* Override the default datapoint identifier for the open action */
|
|
423
|
+
"dpOpen": 1,
|
|
424
|
+
|
|
425
|
+
/* Override the default datapoint identifier for the stop action */
|
|
426
|
+
"dpStop": 2,
|
|
427
|
+
|
|
428
|
+
/* Override the default datapoint identifier for the close action */
|
|
429
|
+
"dpClose": 3,
|
|
430
|
+
|
|
431
|
+
/* Milliseconds to wait between the stop and the open/close command.
|
|
432
|
+
Default is 2000. Raise it if your controller ignores the direction
|
|
433
|
+
command when it arrives too quickly after the stop. */
|
|
434
|
+
"commandDelay": 2000
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
405
438
|
### Simple Blinds
|
|
406
439
|
Normally the blinds don't report their position. This plugin attempts to time the movements to guesstimate the positions. You can adjust a few parameters to make it really close for you.
|
|
407
440
|
|