@homebridge-plugins/homebridge-ewelink 12.18.3-beta.0 → 12.18.4-beta.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 +9 -1
- package/lib/device/zigbee/index.js +2 -0
- package/lib/device/zigbee/sensor-occupancy.js +22 -9
- package/lib/device/zigbee/siren.js +132 -0
- package/lib/homebridge-ui/server.js +1 -0
- package/lib/platform.js +10 -0
- package/lib/utils/constants.js +4 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@homebridge-plugins/homebridge-ewelink` will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## v12.18.
|
|
5
|
+
## v12.18.4 (Pending Release)
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- feat(zigbee): add the snzb-03pr2 presence sensor, with occupancy and light level
|
|
10
|
+
- feat(zigbee): add the snzb-09p indoor siren, as a switch that sounds the alarm
|
|
11
|
+
- chore(deps): dependency updates
|
|
12
|
+
|
|
13
|
+
## v12.18.3 (2026-08-11)
|
|
6
14
|
|
|
7
15
|
### Changed
|
|
8
16
|
|
|
@@ -8,6 +8,7 @@ import deviceSensorLeak from './sensor-leak.js'
|
|
|
8
8
|
import deviceSensorMotion from './sensor-motion.js'
|
|
9
9
|
import deviceSensorOccupancy from './sensor-occupancy.js'
|
|
10
10
|
import deviceSensorSmoke from './sensor-smoke.js'
|
|
11
|
+
import deviceSiren from './siren.js'
|
|
11
12
|
import deviceSwitchStateless from './switch-stateless.js'
|
|
12
13
|
import deviceThermostat from './thermostat.js'
|
|
13
14
|
import deviceZigbeeWaterValve from './zigbee-water-valve.js'
|
|
@@ -23,6 +24,7 @@ export default {
|
|
|
23
24
|
deviceSensorMotion,
|
|
24
25
|
deviceSensorOccupancy,
|
|
25
26
|
deviceSensorSmoke,
|
|
27
|
+
deviceSiren,
|
|
26
28
|
deviceSwitchStateless,
|
|
27
29
|
deviceThermostat,
|
|
28
30
|
deviceZigbeeWaterValve,
|
|
@@ -53,8 +53,9 @@ export default class {
|
|
|
53
53
|
this.battService = this.accessory.getService(this.hapServ.Battery)
|
|
54
54
|
|| this.accessory.addService(this.hapServ.Battery)
|
|
55
55
|
|
|
56
|
-
// SNZB-06P
|
|
57
|
-
|
|
56
|
+
// SNZB-06P (uiid 7016) also reports a binary bright/dark light level, and
|
|
57
|
+
// SNZB-03PR2 (uiid 7055) a numeric illumination reading
|
|
58
|
+
if ([7016, 7055].includes(accessory.context.eweUIID)) {
|
|
58
59
|
this.lightService = this.accessory.getService(this.hapServ.LightSensor)
|
|
59
60
|
|| this.accessory.addService(this.hapServ.LightSensor)
|
|
60
61
|
} else if (this.accessory.getService(this.hapServ.LightSensor)) {
|
|
@@ -90,6 +91,18 @@ export default class {
|
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
}
|
|
94
|
+
if (this.lightService && hasProperty(params, 'illumination')) {
|
|
95
|
+
// Some models report a numeric light reading instead of a binary one.
|
|
96
|
+
// HomeKit accepts 0.0001 to 100000 lux, so keep it inside that
|
|
97
|
+
const newLux = Math.max(Math.min(Number(params.illumination), 100000), 0.0001)
|
|
98
|
+
if (Number.isFinite(newLux) && newLux !== this.cacheLux) {
|
|
99
|
+
this.cacheLux = newLux
|
|
100
|
+
this.lightService.updateCharacteristic(this.hapChar.CurrentAmbientLightLevel, this.cacheLux)
|
|
101
|
+
if (params.updateSource && this.enableLogging) {
|
|
102
|
+
this.log('[%s] %s [%s].', this.name, this.lang.curLightLevel, this.cacheLux)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
93
106
|
if (hasProperty(params, 'battery') && params.battery !== this.cacheBatt) {
|
|
94
107
|
this.cacheBatt = params.battery
|
|
95
108
|
this.cacheBattScaled = Math.max(Math.min(this.cacheBatt, 100), 0)
|
|
@@ -102,17 +115,17 @@ export default class {
|
|
|
102
115
|
this.log('[%s] %s [%s%].', this.name, this.lang.curBatt, this.cacheBattScaled)
|
|
103
116
|
}
|
|
104
117
|
}
|
|
105
|
-
if (
|
|
106
|
-
hasProperty(params, 'human')
|
|
107
|
-
&& hasProperty(params, 'trigTime')
|
|
108
|
-
&& params.human !== this.cacheState
|
|
109
|
-
) {
|
|
118
|
+
if (hasProperty(params, 'human') && params.human !== this.cacheState) {
|
|
110
119
|
this.cacheState = params.human
|
|
111
|
-
|
|
120
|
+
// A report that carries a timestamp can be checked for staleness; the
|
|
121
|
+
// models that report live state without one are taken at face value
|
|
122
|
+
const isFresh = hasProperty(params, 'trigTime')
|
|
123
|
+
? (Date.now() - params.trigTime) / 1000 < this.sensorTimeDifference
|
|
124
|
+
: true
|
|
112
125
|
const occupancyDetected = !!(
|
|
113
126
|
params.updateSource
|
|
114
127
|
&& params.human === 1
|
|
115
|
-
&&
|
|
128
|
+
&& isFresh
|
|
116
129
|
)
|
|
117
130
|
this.service.updateCharacteristic(this.hapChar.OccupancyDetected, occupancyDetected)
|
|
118
131
|
if (occupancyDetected) {
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import platformConsts from '../../utils/constants.js'
|
|
2
|
+
import { hasProperty } from '../../utils/functions.js'
|
|
3
|
+
|
|
4
|
+
// How long the HomeKit tile stays on after a trigger. The device runs its
|
|
5
|
+
// siren for whatever duration is configured in the eWeLink app and turns
|
|
6
|
+
// itself off without reporting that it has, so the tile resets itself rather
|
|
7
|
+
// than waiting for a state that never arrives.
|
|
8
|
+
const TILE_RESET_MS = 3000
|
|
9
|
+
|
|
10
|
+
export default class {
|
|
11
|
+
constructor(platform, accessory) {
|
|
12
|
+
// Set up variables from the platform
|
|
13
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
14
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
15
|
+
this.hapServ = platform.api.hap.Service
|
|
16
|
+
this.lang = platform.lang
|
|
17
|
+
this.log = platform.log
|
|
18
|
+
this.platform = platform
|
|
19
|
+
|
|
20
|
+
// Set up variables from the accessory
|
|
21
|
+
this.name = accessory.displayName
|
|
22
|
+
this.accessory = accessory
|
|
23
|
+
|
|
24
|
+
// Set up custom variables for this device type
|
|
25
|
+
const deviceConf = platform.deviceConf[accessory.context.eweDeviceId] || {}
|
|
26
|
+
this.lowBattThreshold = deviceConf.lowBattThreshold
|
|
27
|
+
? Math.min(deviceConf.lowBattThreshold, 100)
|
|
28
|
+
: platformConsts.defaultValues.lowBattThreshold
|
|
29
|
+
|
|
30
|
+
// Set the correct logging variables for this accessory
|
|
31
|
+
switch (deviceConf.overrideLogging) {
|
|
32
|
+
case 'standard':
|
|
33
|
+
this.enableLogging = true
|
|
34
|
+
this.enableDebugLogging = false
|
|
35
|
+
break
|
|
36
|
+
case 'debug':
|
|
37
|
+
this.enableLogging = true
|
|
38
|
+
this.enableDebugLogging = true
|
|
39
|
+
break
|
|
40
|
+
case 'disable':
|
|
41
|
+
this.enableLogging = false
|
|
42
|
+
this.enableDebugLogging = false
|
|
43
|
+
break
|
|
44
|
+
default:
|
|
45
|
+
this.enableLogging = !platform.config.disableDeviceLogging
|
|
46
|
+
this.enableDebugLogging = false
|
|
47
|
+
break
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Add the switch service if it doesn't already exist
|
|
51
|
+
this.service = this.accessory.getService(this.hapServ.Switch)
|
|
52
|
+
|| this.accessory.addService(this.hapServ.Switch)
|
|
53
|
+
this.service.updateCharacteristic(this.hapChar.On, false)
|
|
54
|
+
|
|
55
|
+
// Add the battery service if it doesn't already exist
|
|
56
|
+
this.battService = this.accessory.getService(this.hapServ.Battery)
|
|
57
|
+
|| this.accessory.addService(this.hapServ.Battery)
|
|
58
|
+
|
|
59
|
+
// Set up the handler for the switch
|
|
60
|
+
this.service.getCharacteristic(this.hapChar.On).onSet(async (value) => {
|
|
61
|
+
try {
|
|
62
|
+
if (!value) {
|
|
63
|
+
// The device stops on its own after its configured duration and
|
|
64
|
+
// offers no early-stop command, so an off is only the tile resetting
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// The trigger rides on the device's own alarm settings, so send the
|
|
69
|
+
// last reported settings back with the trigger flag added - a bare
|
|
70
|
+
// flag could otherwise reset the volume and duration to defaults
|
|
71
|
+
const setting = { ...(this.accessory.context.cacheAlarmSetting || {}), test: true }
|
|
72
|
+
await this.platform.sendDeviceUpdate(this.accessory, { alarmSetting: setting })
|
|
73
|
+
|
|
74
|
+
if (this.enableLogging) {
|
|
75
|
+
this.log('[%s] %s [on].', this.name, this.lang.curState)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Reset the tile shortly after - see TILE_RESET_MS
|
|
79
|
+
clearTimeout(this.resetTimer)
|
|
80
|
+
this.resetTimer = setTimeout(() => {
|
|
81
|
+
this.service.updateCharacteristic(this.hapChar.On, false)
|
|
82
|
+
}, TILE_RESET_MS)
|
|
83
|
+
} catch (err) {
|
|
84
|
+
this.platform.deviceUpdateError(this.accessory, err, false)
|
|
85
|
+
setTimeout(() => {
|
|
86
|
+
this.service.updateCharacteristic(this.hapChar.On, false)
|
|
87
|
+
}, 2000)
|
|
88
|
+
throw new this.hapErr(-70402)
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// Output the customised options to the log
|
|
93
|
+
const normalLogging = this.enableLogging ? 'standard' : 'disable'
|
|
94
|
+
const opts = JSON.stringify({
|
|
95
|
+
logging: this.enableDebugLogging ? 'debug' : normalLogging,
|
|
96
|
+
lowBattThreshold: this.lowBattThreshold,
|
|
97
|
+
})
|
|
98
|
+
this.log('[%s] %s %s.', this.name, this.lang.devInitOpts, opts)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async externalUpdate(params) {
|
|
102
|
+
try {
|
|
103
|
+
// Keep the device's own alarm settings so a trigger can send them back
|
|
104
|
+
if (hasProperty(params, 'alarmSetting') && typeof params.alarmSetting === 'object') {
|
|
105
|
+
this.accessory.context.cacheAlarmSetting = { ...params.alarmSetting }
|
|
106
|
+
delete this.accessory.context.cacheAlarmSetting.test
|
|
107
|
+
}
|
|
108
|
+
if (hasProperty(params, 'battery') && params.battery !== this.cacheBatt) {
|
|
109
|
+
this.cacheBatt = params.battery
|
|
110
|
+
this.cacheBattScaled = Math.max(Math.min(this.cacheBatt, 100), 0)
|
|
111
|
+
this.battService.updateCharacteristic(this.hapChar.BatteryLevel, this.cacheBattScaled)
|
|
112
|
+
this.battService.updateCharacteristic(
|
|
113
|
+
this.hapChar.StatusLowBattery,
|
|
114
|
+
this.cacheBattScaled < this.lowBattThreshold ? 1 : 0,
|
|
115
|
+
)
|
|
116
|
+
if (params.updateSource && this.enableLogging) {
|
|
117
|
+
this.log('[%s] %s [%s%].', this.name, this.lang.curBatt, this.cacheBattScaled)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch (err) {
|
|
121
|
+
this.platform.deviceUpdateError(this.accessory, err, false)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
destroy() {
|
|
126
|
+
clearTimeout(this.resetTimer)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
markStatus(isOnline) {
|
|
130
|
+
this.isOnline = isOnline
|
|
131
|
+
}
|
|
132
|
+
}
|
package/lib/platform.js
CHANGED
|
@@ -1729,6 +1729,16 @@ export default class {
|
|
|
1729
1729
|
this.applyAccessoryLogging(accessory)
|
|
1730
1730
|
accessory.control = new deviceTypes.zb.deviceSensorSmoke(this, accessory)
|
|
1731
1731
|
/** */
|
|
1732
|
+
} else if (platformConsts.devices.zbSiren.includes(uiid)) {
|
|
1733
|
+
/**
|
|
1734
|
+
**************
|
|
1735
|
+
ZB SIREN
|
|
1736
|
+
***************
|
|
1737
|
+
*/
|
|
1738
|
+
accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
|
|
1739
|
+
this.applyAccessoryLogging(accessory)
|
|
1740
|
+
accessory.control = new deviceTypes.zb.deviceSiren(this, accessory)
|
|
1741
|
+
/** */
|
|
1732
1742
|
} else if (platformConsts.devices.zbThermostat.includes(uiid)) {
|
|
1733
1743
|
/**
|
|
1734
1744
|
*******************
|
package/lib/utils/constants.js
CHANGED
|
@@ -377,10 +377,11 @@ export default {
|
|
|
377
377
|
zbMotor: [1514, 7006, 7034],
|
|
378
378
|
zbSensorAmbient: [242, 1770, 1771, 7014, 7033, 7038],
|
|
379
379
|
zbSensorMotion: [2026, 7002],
|
|
380
|
-
zbSensorOccupancy: [7016],
|
|
380
|
+
zbSensorOccupancy: [7016, 7055],
|
|
381
381
|
zbSensorContact: [3026, 7003],
|
|
382
382
|
zbSensorWater: [4026, 7019],
|
|
383
383
|
zbSensorSmoke: [5026],
|
|
384
|
+
zbSiren: [7056],
|
|
384
385
|
zbThermostat: [7017],
|
|
385
386
|
zbWaterValve: [7027, 7047],
|
|
386
387
|
group: [5000],
|
|
@@ -604,6 +605,8 @@ export default {
|
|
|
604
605
|
7038: 1, // SONOFF AirGuard TH Zigbee temperature and humidity sensor (#767)
|
|
605
606
|
7040: 2, // SONOFF MINI DUO 2-gang Zigbee switch (#768)
|
|
606
607
|
7047: 1, // SONOFF Zigbee Irrigation Valve (#778)
|
|
608
|
+
7055: 1, // SNZB-03PR2 Zigbee presence sensor with light level
|
|
609
|
+
7056: 1, // SNZB-09P Zigbee indoor siren
|
|
607
610
|
20001: 1, // iHost sub-device single switch
|
|
608
611
|
20004: 4, // iHost sub-device 4-channel switch
|
|
609
612
|
20006: 1, // single-colour dimmable LED controller (#780)
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"displayName": "Homebridge eWeLink",
|
|
4
4
|
"alias": "eWeLink",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"version": "12.18.
|
|
6
|
+
"version": "12.18.4-beta.0",
|
|
7
7
|
"description": "Homebridge plugin to integrate eWeLink devices into HomeKit.",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "bwp91",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"changelog:sync": "node .github/scripts/sync-changelog.mjs"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@homebridge/plugin-ui-utils": "^2.2.
|
|
70
|
-
"axios": "^1.
|
|
69
|
+
"@homebridge/plugin-ui-utils": "^2.2.6",
|
|
70
|
+
"axios": "^1.20.0",
|
|
71
71
|
"node-persist": "^4.0.4",
|
|
72
72
|
"p-queue": "^9.3.3",
|
|
73
73
|
"websocket-as-promised": "^3.0.1",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
77
|
"@antfu/eslint-config": "^9.3.0",
|
|
78
|
-
"@vitest/coverage-v8": "^4.1.
|
|
79
|
-
"vitest": "^4.1.
|
|
78
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
79
|
+
"vitest": "^4.1.11"
|
|
80
80
|
}
|
|
81
81
|
}
|