@homebridge-plugins/homebridge-ewelink 12.14.1 → 12.15.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/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/config.schema.json +8 -0
- package/eslint.config.js +0 -1
- package/lib/device/simulation/garage-one.js +42 -14
- package/lib/platform.js +1 -0
- package/lib/utils/constants.js +8 -2
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@homebridge-plugins/homebridge-ewelink` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## v12.15.0 (2026-07-12)
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- feat: add a per-device `invertGarageState` option to swap the reported open/closed state on garage doors without a contact sensor (#787)
|
|
10
|
+
- feat: add support for UIID 20006 dimmable LED controller (#780), the SONOFF AirGuard TH sensor (7038) (#767) and the SONOFF MINI DUO 2-gang switch (7040) (#768)
|
|
11
|
+
- chore: update `actions/checkout` workflow action to v7
|
|
12
|
+
- chore: remove stray `exit 1` from the deprecation workflow script
|
|
13
|
+
- chore: dependency updates
|
|
14
|
+
- docs: note homebridge v2 support in the prerequisites
|
|
15
|
+
|
|
5
16
|
## v12.14.1 (2026-06-03)
|
|
6
17
|
|
|
7
18
|
### Changed
|
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ Homebridge plugin to integrate eWeLink devices into HomeKit
|
|
|
30
30
|
|
|
31
31
|
- To use this plugin, you will need to already have:
|
|
32
32
|
- [Node](https://nodejs.org): latest version of `v22` or `v24` - any other major version is not supported.
|
|
33
|
-
- [Homebridge](https://homebridge.io): `v1.6` - refer to link for more information and installation instructions.
|
|
33
|
+
- [Homebridge](https://homebridge.io): `v1.6` or above - refer to link for more information and installation instructions.
|
|
34
34
|
|
|
35
35
|
### Setup
|
|
36
36
|
|
package/config.schema.json
CHANGED
|
@@ -423,6 +423,14 @@
|
|
|
423
423
|
"functionBody": "return (model.singleDevices && model.singleDevices[arrayIndices] && ['garage'].includes(model.singleDevices[arrayIndices].showAs) && !model.singleDevices[arrayIndices].ignoreDevice);"
|
|
424
424
|
}
|
|
425
425
|
},
|
|
426
|
+
"invertGarageState": {
|
|
427
|
+
"type": "boolean",
|
|
428
|
+
"title": "Invert Garage State",
|
|
429
|
+
"description": "Swap the reported open and closed states for this garage door. Useful if HomeKit shows the door as open when it is physically closed (and vice versa). This applies to garage doors without a defined contact sensor.",
|
|
430
|
+
"condition": {
|
|
431
|
+
"functionBody": "return (model.singleDevices && model.singleDevices[arrayIndices] && ['garage'].includes(model.singleDevices[arrayIndices].showAs) && !model.singleDevices[arrayIndices].sensorId && !model.singleDevices[arrayIndices].ignoreDevice);"
|
|
432
|
+
}
|
|
433
|
+
},
|
|
426
434
|
"sensorId": {
|
|
427
435
|
"type": "string",
|
|
428
436
|
"title": "Sensor",
|
package/eslint.config.js
CHANGED
|
@@ -35,7 +35,6 @@ export default antfu(
|
|
|
35
35
|
'style/brace-style': ['error', '1tbs', { allowSingleLine: true }],
|
|
36
36
|
'style/quote-props': ['error', 'consistent-as-needed'],
|
|
37
37
|
'test/no-only-tests': 'error',
|
|
38
|
-
'unicorn/no-useless-spread': 'error',
|
|
39
38
|
'unused-imports/no-unused-vars': ['error', { caughtErrors: 'none' }],
|
|
40
39
|
},
|
|
41
40
|
typescript: false,
|
|
@@ -25,6 +25,11 @@ export default class {
|
|
|
25
25
|
this.operationTimeUp = deviceConf.operationTime || platformConsts.defaultValues.operationTime
|
|
26
26
|
this.operationTimeDown = deviceConf.operationTimeDown || this.operationTimeUp
|
|
27
27
|
|
|
28
|
+
// Some users' garage door reports the opposite state to reality (e.g. a
|
|
29
|
+
// MINI-D wired to the NO contact). This option swaps the reported open and
|
|
30
|
+
// closed states for this device.
|
|
31
|
+
this.invertGarage = deviceConf.invertGarageState === true
|
|
32
|
+
|
|
28
33
|
// Set the correct logging variables for this accessory
|
|
29
34
|
switch (deviceConf.overrideLogging) {
|
|
30
35
|
case 'standard':
|
|
@@ -64,8 +69,8 @@ export default class {
|
|
|
64
69
|
this.service = this.accessory.getService(this.hapServ.GarageDoorOpener)
|
|
65
70
|
if (!this.service) {
|
|
66
71
|
this.service = this.accessory.addService(this.hapServ.GarageDoorOpener)
|
|
67
|
-
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, 1)
|
|
68
|
-
this.service.updateCharacteristic(this.hapChar.TargetDoorState, 1)
|
|
72
|
+
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.invPos(1))
|
|
73
|
+
this.service.updateCharacteristic(this.hapChar.TargetDoorState, this.invPos(1))
|
|
69
74
|
this.service.updateCharacteristic(this.hapChar.ObstructionDetected, false)
|
|
70
75
|
this.service.addCharacteristic(this.eveChar.LastActivation)
|
|
71
76
|
this.service.addCharacteristic(this.eveChar.ResetTotal)
|
|
@@ -134,8 +139,9 @@ export default class {
|
|
|
134
139
|
|
|
135
140
|
// Add the set handler to the target position characteristic
|
|
136
141
|
this.service.getCharacteristic(this.hapChar.TargetDoorState).onSet((value) => {
|
|
137
|
-
// We don't use await as we want the callback to be run straight away
|
|
138
|
-
|
|
142
|
+
// We don't use await as we want the callback to be run straight away.
|
|
143
|
+
// Map the HomeKit target to the internal logical position first.
|
|
144
|
+
this.internalUpdate(this.invPos(value))
|
|
139
145
|
})
|
|
140
146
|
|
|
141
147
|
// Add the get handlers only if the user hasn't disabled the disableNoResponse setting
|
|
@@ -184,27 +190,49 @@ export default class {
|
|
|
184
190
|
this.log('[%s] %s %s.', this.name, this.lang.devInitOpts, opts)
|
|
185
191
|
}
|
|
186
192
|
|
|
193
|
+
// Swap Open<->Closed and Opening<->Closing when this device is inverted.
|
|
194
|
+
// This is an involution, so the same function maps a HomeKit-facing value to
|
|
195
|
+
// the internal logical value and back. A no-op when inversion is disabled, or
|
|
196
|
+
// when a contact sensor is defined (the sensor already reports the real state).
|
|
197
|
+
invPos(pos) {
|
|
198
|
+
if (!this.invertGarage || this.definedSensor) {
|
|
199
|
+
return pos
|
|
200
|
+
}
|
|
201
|
+
switch (pos) {
|
|
202
|
+
case 0:
|
|
203
|
+
return 1
|
|
204
|
+
case 1:
|
|
205
|
+
return 0
|
|
206
|
+
case 2:
|
|
207
|
+
return 3
|
|
208
|
+
case 3:
|
|
209
|
+
return 2
|
|
210
|
+
default:
|
|
211
|
+
return pos
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
187
215
|
async internalUpdate(value) {
|
|
188
216
|
try {
|
|
189
217
|
const newPos = value
|
|
190
218
|
const params = {}
|
|
191
219
|
let delay = 0
|
|
192
|
-
const prevState = this.service.getCharacteristic(this.hapChar.CurrentDoorState).value
|
|
220
|
+
const prevState = this.invPos(this.service.getCharacteristic(this.hapChar.CurrentDoorState).value)
|
|
193
221
|
if (newPos === prevState % 2) {
|
|
194
222
|
return
|
|
195
223
|
}
|
|
196
224
|
this.inUse = true
|
|
197
225
|
this.cacheState = value
|
|
198
226
|
if (this.setup === 'switchSingle' && [2, 3].includes(prevState)) {
|
|
199
|
-
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, ((prevState * 2) % 3) + 2)
|
|
227
|
+
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.invPos(((prevState * 2) % 3) + 2))
|
|
200
228
|
delay = 1500
|
|
201
229
|
}
|
|
202
230
|
if (this.cacheState !== newPos) {
|
|
203
231
|
return
|
|
204
232
|
}
|
|
205
233
|
await sleep(delay)
|
|
206
|
-
this.service.updateCharacteristic(this.hapChar.TargetDoorState, newPos)
|
|
207
|
-
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, newPos + 2)
|
|
234
|
+
this.service.updateCharacteristic(this.hapChar.TargetDoorState, this.invPos(newPos))
|
|
235
|
+
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.invPos(newPos + 2))
|
|
208
236
|
// SCM (single-channel multi-format) devices use the switches[] array
|
|
209
237
|
// but only expose outlet 0. True dual-channel garage controllers
|
|
210
238
|
// (e.g. DUALR3) use separate outlets for open/close relay action.
|
|
@@ -254,7 +282,7 @@ export default class {
|
|
|
254
282
|
const operationTime = newPos === 0 ? this.operationTimeUp : this.operationTimeDown
|
|
255
283
|
await sleep(Math.max((operationTime - 20) * 100, 0))
|
|
256
284
|
if (!this.definedSensor) {
|
|
257
|
-
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, newPos)
|
|
285
|
+
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.invPos(newPos))
|
|
258
286
|
if (newPos === 1) {
|
|
259
287
|
this.accessory.eveService.addEntry({ status: 1 })
|
|
260
288
|
}
|
|
@@ -263,7 +291,7 @@ export default class {
|
|
|
263
291
|
'[%s] %s [%s].',
|
|
264
292
|
this.name,
|
|
265
293
|
this.lang.curState,
|
|
266
|
-
newPos === 0 ? this.lang.doorOpen : this.lang.doorClosed,
|
|
294
|
+
this.invPos(newPos) === 0 ? this.lang.doorOpen : this.lang.doorClosed,
|
|
267
295
|
)
|
|
268
296
|
}
|
|
269
297
|
}
|
|
@@ -344,7 +372,7 @@ export default class {
|
|
|
344
372
|
}
|
|
345
373
|
|
|
346
374
|
if (!this.inUse && !this.definedSensor) {
|
|
347
|
-
const prevState = this.service.getCharacteristic(this.hapChar.CurrentDoorState).value
|
|
375
|
+
const prevState = this.invPos(this.service.getCharacteristic(this.hapChar.CurrentDoorState).value)
|
|
348
376
|
const newPos = [0, 2].includes(prevState) ? 3 : 2
|
|
349
377
|
if (this.setup === 'switchMulti' && params.switches?.length >= 2) {
|
|
350
378
|
if (
|
|
@@ -361,8 +389,8 @@ export default class {
|
|
|
361
389
|
return
|
|
362
390
|
}
|
|
363
391
|
this.inUse = true
|
|
364
|
-
this.service.updateCharacteristic(this.hapChar.TargetDoorState, newPos - 2)
|
|
365
|
-
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, newPos)
|
|
392
|
+
this.service.updateCharacteristic(this.hapChar.TargetDoorState, this.invPos(newPos - 2))
|
|
393
|
+
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.invPos(newPos))
|
|
366
394
|
await sleep(2000)
|
|
367
395
|
this.inUse = false
|
|
368
396
|
if (newPos === 2) {
|
|
@@ -379,7 +407,7 @@ export default class {
|
|
|
379
407
|
}
|
|
380
408
|
const operationTime = newPos === 2 ? this.operationTimeUp : this.operationTimeDown
|
|
381
409
|
await sleep(Math.max((operationTime - 20) * 100, 0))
|
|
382
|
-
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, newPos - 2)
|
|
410
|
+
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.invPos(newPos - 2))
|
|
383
411
|
if (newPos === 3) {
|
|
384
412
|
this.accessory.eveService.addEntry({ status: 1 })
|
|
385
413
|
}
|
package/lib/platform.js
CHANGED
package/lib/utils/constants.js
CHANGED
|
@@ -95,6 +95,7 @@ export default {
|
|
|
95
95
|
'hideSensor',
|
|
96
96
|
'obstructId',
|
|
97
97
|
'sensorType',
|
|
98
|
+
'invertGarageState',
|
|
98
99
|
'ipAddress',
|
|
99
100
|
],
|
|
100
101
|
multiDevices: [
|
|
@@ -114,6 +115,7 @@ export default {
|
|
|
114
115
|
'sensorId',
|
|
115
116
|
'hideSensor',
|
|
116
117
|
'obstructId',
|
|
118
|
+
'invertGarageState',
|
|
117
119
|
'ipAddress',
|
|
118
120
|
],
|
|
119
121
|
lightDevices: [
|
|
@@ -324,12 +326,13 @@ export default {
|
|
|
324
326
|
4256,
|
|
325
327
|
7029,
|
|
326
328
|
7030,
|
|
329
|
+
7040,
|
|
327
330
|
20004,
|
|
328
331
|
],
|
|
329
332
|
switchMultiPower: [126, 165, 262],
|
|
330
333
|
switchMate: [177],
|
|
331
334
|
switchMan: [174],
|
|
332
|
-
lightDimmer: [36, 44, 57, 277],
|
|
335
|
+
lightDimmer: [36, 44, 57, 277, 20006],
|
|
333
336
|
lightRGB: [22],
|
|
334
337
|
lightCCT: [52, 103],
|
|
335
338
|
lightRGBCCT: [33, 59, 104, 135, 136, 137, 173],
|
|
@@ -353,7 +356,7 @@ export default {
|
|
|
353
356
|
zbLightCCT: [1258],
|
|
354
357
|
zbLightRGBCCT: [3258, 7009],
|
|
355
358
|
zbMotor: [1514, 7006, 7034],
|
|
356
|
-
zbSensorAmbient: [1770, 1771, 7014, 7033],
|
|
359
|
+
zbSensorAmbient: [1770, 1771, 7014, 7033, 7038],
|
|
357
360
|
zbSensorMotion: [2026, 7002],
|
|
358
361
|
zbSensorOccupancy: [7016],
|
|
359
362
|
zbSensorContact: [3026, 7003],
|
|
@@ -575,8 +578,11 @@ export default {
|
|
|
575
578
|
7030: 3, // ZBM5-3C-80/86 Zigbee 3-gang switch
|
|
576
579
|
7032: 1, // Zigbee Power Monitoring Switch
|
|
577
580
|
7033: 1, // SNZB-02WD Zigbee Temperature and Humidity Display Sensor
|
|
581
|
+
7038: 1, // SONOFF AirGuard TH Zigbee temperature and humidity sensor (#767)
|
|
582
|
+
7040: 2, // SONOFF MINI DUO 2-gang Zigbee switch (#768)
|
|
578
583
|
20001: 1, // iHost sub-device single switch
|
|
579
584
|
20004: 4, // iHost sub-device 4-channel switch
|
|
585
|
+
20006: 1, // single-colour dimmable LED controller (#780)
|
|
580
586
|
},
|
|
581
587
|
|
|
582
588
|
cannotSupportDevices: {
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"displayName": "Homebridge eWeLink",
|
|
4
4
|
"alias": "eWeLink",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"version": "12.
|
|
6
|
+
"version": "12.15.0",
|
|
7
7
|
"description": "Homebridge plugin to integrate eWeLink devices into HomeKit.",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "bwp91",
|
|
@@ -61,14 +61,14 @@
|
|
|
61
61
|
"lint:fix": "npm run lint -- --fix"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@homebridge/plugin-ui-utils": "^2.2.
|
|
65
|
-
"axios": "^1.
|
|
64
|
+
"@homebridge/plugin-ui-utils": "^2.2.5",
|
|
65
|
+
"axios": "^1.18.1",
|
|
66
66
|
"node-persist": "^4.0.4",
|
|
67
|
-
"p-queue": "^9.3.
|
|
67
|
+
"p-queue": "^9.3.1",
|
|
68
68
|
"websocket-as-promised": "^3.0.1",
|
|
69
69
|
"ws": "^8.21.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@antfu/eslint-config": "^9.
|
|
72
|
+
"@antfu/eslint-config": "^9.1.0"
|
|
73
73
|
}
|
|
74
74
|
}
|