@homebridge-plugins/homebridge-ewelink 12.15.3-beta.2 → 12.15.3-beta.3

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 CHANGED
@@ -7,6 +7,18 @@ All notable changes to `@homebridge-plugins/homebridge-ewelink` will be document
7
7
  ### Changed
8
8
 
9
9
  - chore: remove the looking-for-maintainers message
10
+ - feat: add support for the snzb-01m zigbee button (7039)
11
+ - feat: add support for the wireless temperature and humidity sensor (242)
12
+ - feat: add support for the three gear fan (17)
13
+ - feat: expose the light level on the snzb-03p and snzb-06p sensors (7002/7016)
14
+ - feat: add power readings for outlets reporting phase values (226)
15
+ - feat: add power readings for the mini dimmer (277)
16
+ - feat: expose the spm-4relay sub units as multi channel outlets with power readings (130)
17
+ - feat: expose the dust and light levels on the sonoff sc (18)
18
+ - feat: add a child lock control for the trvzb radiator valve (7017)
19
+ - feat: expose the wall button presses on the mini r3/r4 for detached relay setups (138)
20
+ - fix: correct the multi channel format and add power readings for the s60zbtpf zigbee outlet (7032)
21
+ - fix: stop treating the dualr3 lite as a power monitoring device (165)
10
22
  - fix: time out stalled http requests so device fetching cannot hang forever (#788) (@glenmarks-hub)
11
23
  - fix: use the switch_00 param for the swv-zne irrigation valve (7047) (#778)
12
24
  - chore(github): update the setup-node action to v7
@@ -0,0 +1,234 @@
1
+ import { generateRandomString, sleep } from '../utils/functions.js'
2
+
3
+ /*
4
+ UIID 17: the "three gear fan", controlled with named params rather than
5
+ switch channels (matching AlexxIT/SonoffLAN's XFan17):
6
+ - fan: 'on'/'off'
7
+ - speed: 'slow'/'moderate'/'fast'
8
+ - shake: 'on'/'off' (oscillation)
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
+ // Initially set the online flag as true (to be then updated as false if necessary)
25
+ this.isOnline = true
26
+
27
+ // Set up custom variables for this device type
28
+ const deviceConf = platform.deviceConf[accessory.context.eweDeviceId] || {}
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 = platform.config.debug
47
+ break
48
+ }
49
+
50
+ // Conversion objects between eWeLink speed labels and HomeKit values
51
+ this.speed2value = {
52
+ slow: 1,
53
+ moderate: 2,
54
+ fast: 3,
55
+ }
56
+ this.value2speed = {
57
+ 1: 'slow',
58
+ 2: 'moderate',
59
+ 3: 'fast',
60
+ }
61
+
62
+ // Add the fan service if it doesn't already exist
63
+ this.service = this.accessory.getService(this.hapServ.Fan) || this.accessory.addService(this.hapServ.Fan)
64
+
65
+ // Add the set handler to the fan on/off characteristic
66
+ this.service
67
+ .getCharacteristic(this.hapChar.On)
68
+ .onSet(async value => this.internalStateUpdate(value))
69
+
70
+ // Add the set handler to the fan rotation speed characteristic
71
+ this.service
72
+ .getCharacteristic(this.hapChar.RotationSpeed)
73
+ .setProps({
74
+ maxValue: 3,
75
+ minStep: 1,
76
+ minValue: 0,
77
+ unit: 'unitless', // This is actually from HAP for Bluetooth LE Specification, but fits
78
+ })
79
+ .onSet(async value => this.internalSpeedUpdate(value))
80
+
81
+ // Add the set handler to the fan swing mode (oscillation) characteristic
82
+ this.service
83
+ .getCharacteristic(this.hapChar.SwingMode)
84
+ .onSet(async value => this.internalShakeUpdate(value))
85
+
86
+ // Add the get handlers only if the user hasn't disabled the disableNoResponse setting
87
+ if (!platform.config.disableNoResponse) {
88
+ this.service.getCharacteristic(this.hapChar.On).onGet(() => {
89
+ if (!this.isOnline) {
90
+ throw new this.hapErr(-70402)
91
+ }
92
+ return this.service.getCharacteristic(this.hapChar.On).value
93
+ })
94
+ this.service.getCharacteristic(this.hapChar.RotationSpeed).onGet(() => {
95
+ if (!this.isOnline) {
96
+ throw new this.hapErr(-70402)
97
+ }
98
+ return this.service.getCharacteristic(this.hapChar.RotationSpeed).value
99
+ })
100
+ this.service.getCharacteristic(this.hapChar.SwingMode).onGet(() => {
101
+ if (!this.isOnline) {
102
+ throw new this.hapErr(-70402)
103
+ }
104
+ return this.service.getCharacteristic(this.hapChar.SwingMode).value
105
+ })
106
+ }
107
+
108
+ // Output the customised options to the log
109
+ const normalLogging = this.enableLogging ? 'standard' : 'disable'
110
+ const opts = JSON.stringify({
111
+ logging: this.enableDebugLogging ? 'debug' : normalLogging,
112
+ })
113
+ this.log('[%s] %s %s.', this.name, this.lang.devInitOpts, opts)
114
+ }
115
+
116
+ async internalStateUpdate(value) {
117
+ try {
118
+ const newState = value ? 'on' : 'off'
119
+ if (newState === this.cacheState) {
120
+ return
121
+ }
122
+ await this.platform.sendDeviceUpdate(this.accessory, { fan: newState })
123
+ this.cacheState = newState
124
+ if (this.enableLogging) {
125
+ this.log('[%s] %s [%s].', this.name, this.lang.curState, newState)
126
+ }
127
+ } catch (err) {
128
+ this.platform.deviceUpdateError(this.accessory, err, true)
129
+ setTimeout(() => {
130
+ this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
131
+ }, 2000)
132
+ throw new this.hapErr(-70402)
133
+ }
134
+ }
135
+
136
+ async internalSpeedUpdate(value) {
137
+ try {
138
+ // This acts like a debounce function when endlessly sliding the slider
139
+ const updateKey = generateRandomString(5)
140
+ this.updateKey = updateKey
141
+ await sleep(500)
142
+ if (updateKey !== this.updateKey) {
143
+ return
144
+ }
145
+ if (value === 0) {
146
+ // A zero speed value means turning the fan off
147
+ await this.platform.sendDeviceUpdate(this.accessory, { fan: 'off' })
148
+ this.cacheState = 'off'
149
+ if (this.enableLogging) {
150
+ this.log('[%s] %s [off].', this.name, this.lang.curState)
151
+ }
152
+ return
153
+ }
154
+ const newSpeed = this.value2speed[value]
155
+ if (newSpeed === this.cacheSpeed) {
156
+ return
157
+ }
158
+ await this.platform.sendDeviceUpdate(this.accessory, { fan: 'on', speed: newSpeed })
159
+ this.cacheState = 'on'
160
+ this.cacheSpeed = newSpeed
161
+ if (this.enableLogging) {
162
+ this.log('[%s] %s [%s].', this.name, this.lang.curSpeed, newSpeed)
163
+ }
164
+ } catch (err) {
165
+ this.platform.deviceUpdateError(this.accessory, err, true)
166
+ setTimeout(() => {
167
+ this.service.updateCharacteristic(
168
+ this.hapChar.RotationSpeed,
169
+ this.speed2value[this.cacheSpeed] || 0,
170
+ )
171
+ }, 2000)
172
+ throw new this.hapErr(-70402)
173
+ }
174
+ }
175
+
176
+ async internalShakeUpdate(value) {
177
+ try {
178
+ const newShake = value === this.hapChar.SwingMode.SWING_ENABLED ? 'on' : 'off'
179
+ if (newShake === this.cacheShake) {
180
+ return
181
+ }
182
+ await this.platform.sendDeviceUpdate(this.accessory, { shake: newShake })
183
+ this.cacheShake = newShake
184
+ if (this.enableLogging) {
185
+ this.log('[%s] %s [%s].', this.name, this.lang.curOscillation, newShake)
186
+ }
187
+ } catch (err) {
188
+ this.platform.deviceUpdateError(this.accessory, err, true)
189
+ setTimeout(() => {
190
+ this.service.updateCharacteristic(
191
+ this.hapChar.SwingMode,
192
+ this.cacheShake === 'on'
193
+ ? this.hapChar.SwingMode.SWING_ENABLED
194
+ : this.hapChar.SwingMode.SWING_DISABLED,
195
+ )
196
+ }, 2000)
197
+ throw new this.hapErr(-70402)
198
+ }
199
+ }
200
+
201
+ async externalUpdate(params) {
202
+ try {
203
+ if (params.fan && params.fan !== this.cacheState) {
204
+ this.cacheState = params.fan
205
+ this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
206
+ if (params.updateSource && this.enableLogging) {
207
+ this.log('[%s] %s [%s].', this.name, this.lang.curState, this.cacheState)
208
+ }
209
+ }
210
+ if (params.speed && params.speed !== this.cacheSpeed && this.speed2value[params.speed]) {
211
+ this.cacheSpeed = params.speed
212
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.speed2value[this.cacheSpeed])
213
+ if (params.updateSource && this.enableLogging) {
214
+ this.log('[%s] %s [%s].', this.name, this.lang.curSpeed, this.cacheSpeed)
215
+ }
216
+ }
217
+ if (params.shake && params.shake !== this.cacheShake) {
218
+ this.cacheShake = params.shake
219
+ this.service.updateCharacteristic(
220
+ this.hapChar.SwingMode,
221
+ this.cacheShake === 'on'
222
+ ? this.hapChar.SwingMode.SWING_ENABLED
223
+ : this.hapChar.SwingMode.SWING_DISABLED,
224
+ )
225
+ }
226
+ } catch (err) {
227
+ this.platform.deviceUpdateError(this.accessory, err, false)
228
+ }
229
+ }
230
+
231
+ markStatus(isOnline) {
232
+ this.isOnline = isOnline
233
+ }
234
+ }
@@ -1,6 +1,7 @@
1
1
  import deviceAirConditioner from './air-conditioner.js'
2
2
  import deviceCurtain from './curtain.js'
3
3
  import deviceDiffuser from './diffuser.js'
4
+ import deviceFanGear from './fan-gear.js'
4
5
  import deviceFan from './fan.js'
5
6
  import deviceGroup from './group.js'
6
7
  import deviceHumidifier from './humidifier.js'
@@ -36,6 +37,7 @@ export default {
36
37
  deviceCurtain,
37
38
  deviceDiffuser,
38
39
  deviceFan,
40
+ deviceFanGear,
39
41
  deviceGroup,
40
42
  deviceHumidifier,
41
43
  deviceLightCCT,
@@ -4,6 +4,7 @@ import { generateRandomString, hasProperty, sleep } from '../utils/functions.js'
4
4
  export default class {
5
5
  constructor(platform, accessory) {
6
6
  // Set up variables from the platform
7
+ this.eveChar = platform.eveChar
7
8
  this.hapChar = platform.api.hap.Characteristic
8
9
  this.hapErr = platform.api.hap.HapStatusError
9
10
  this.hapServ = platform.api.hap.Service
@@ -54,6 +55,20 @@ export default class {
54
55
  this.service = this.accessory.getService(this.hapServ.Lightbulb)
55
56
  || this.accessory.addService(this.hapServ.Lightbulb)
56
57
 
58
+ // The MINI dimmer also reports power readings
59
+ if (this.accessory.context.eweUIID === 277) {
60
+ this.powerReadings = true
61
+ if (!this.service.testCharacteristic(this.eveChar.CurrentConsumption)) {
62
+ this.service.addCharacteristic(this.eveChar.CurrentConsumption)
63
+ }
64
+ if (!this.service.testCharacteristic(this.eveChar.ElectricCurrent)) {
65
+ this.service.addCharacteristic(this.eveChar.ElectricCurrent)
66
+ }
67
+ if (!this.service.testCharacteristic(this.eveChar.Voltage)) {
68
+ this.service.addCharacteristic(this.eveChar.Voltage)
69
+ }
70
+ }
71
+
57
72
  // Add the set handler to the lightbulb on/off characteristic
58
73
  this.service
59
74
  .getCharacteristic(this.hapChar.On)
@@ -232,6 +247,37 @@ export default class {
232
247
  break
233
248
  default:
234
249
  }
250
+
251
+ if (this.powerReadings) {
252
+ let logger = false
253
+ let power
254
+ let voltage
255
+ let current
256
+ if (hasProperty(params, 'power')) {
257
+ power = Number.parseFloat(params.power) / 100
258
+ this.service.updateCharacteristic(this.eveChar.CurrentConsumption, power)
259
+ logger = true
260
+ }
261
+ if (hasProperty(params, 'voltage')) {
262
+ voltage = Number.parseFloat(params.voltage) / 100
263
+ this.service.updateCharacteristic(this.eveChar.Voltage, voltage)
264
+ logger = true
265
+ }
266
+ if (hasProperty(params, 'current')) {
267
+ current = Number.parseFloat(params.current) / 100
268
+ this.service.updateCharacteristic(this.eveChar.ElectricCurrent, current)
269
+ logger = true
270
+ }
271
+ if (params.updateSource && logger && this.enableLogging) {
272
+ this.log(
273
+ '[%s] %s%s%s.',
274
+ this.name,
275
+ power !== undefined ? `${this.lang.curPower} [${power}W]` : '',
276
+ voltage !== undefined ? ` ${this.lang.curVolt} [${voltage}V]` : '',
277
+ current !== undefined ? ` ${this.lang.curCurr} [${current}A]` : '',
278
+ )
279
+ }
280
+ }
235
281
  } catch (err) {
236
282
  this.platform.deviceUpdateError(this.accessory, err, false)
237
283
  }
@@ -55,6 +55,14 @@ export default class {
55
55
  minStep: 0.1,
56
56
  })
57
57
  this.cacheTemp = this.tempService.getCharacteristic(this.hapChar.CurrentTemperature).value
58
+
59
+ // The Sonoff SC also reports dust, light and noise levels (uiid 18)
60
+ if (this.accessory.context.eweUIID === 18) {
61
+ this.airService = this.accessory.getService(this.hapServ.AirQualitySensor)
62
+ || this.accessory.addService(this.hapServ.AirQualitySensor)
63
+ this.lightService = this.accessory.getService(this.hapServ.LightSensor)
64
+ || this.accessory.addService(this.hapServ.LightSensor)
65
+ }
58
66
  this.updateCache()
59
67
 
60
68
  // Add the humidity sensor service if it doesn't already exist
@@ -101,6 +109,35 @@ export default class {
101
109
 
102
110
  async externalUpdate(params) {
103
111
  try {
112
+ if (this.airService && hasProperty(params, 'dusty')) {
113
+ // The device reports a level from 1 (best) to 10 (worst)
114
+ const rawDust = Number.parseInt(params.dusty, 10)
115
+ if (!Number.isNaN(rawDust)) {
116
+ const newQuality = Math.max(Math.min(Math.ceil(rawDust / 2), 5), 1)
117
+ if (newQuality !== this.cacheDust) {
118
+ this.cacheDust = newQuality
119
+ this.airService.updateCharacteristic(this.hapChar.AirQuality, this.cacheDust)
120
+ if (params.updateSource && this.enableLogging) {
121
+ this.log('[%s] %s [%s].', this.name, this.lang.curAirQuality, rawDust)
122
+ }
123
+ }
124
+ }
125
+ }
126
+ if (this.lightService && hasProperty(params, 'light')) {
127
+ // The device reports a level from 1 (darkest) to 10 (brightest),
128
+ // mapped to a nominal lux value for HomeKit
129
+ const rawLight = Number.parseInt(params.light, 10)
130
+ if (!Number.isNaN(rawLight)) {
131
+ const newLux = Math.max(rawLight * 10, 0.0001)
132
+ if (newLux !== this.cacheLux) {
133
+ this.cacheLux = newLux
134
+ this.lightService.updateCharacteristic(this.hapChar.CurrentAmbientLightLevel, this.cacheLux)
135
+ if (params.updateSource && this.enableLogging) {
136
+ this.log('[%s] %s [%s].', this.name, this.lang.curLightLevel, rawLight)
137
+ }
138
+ }
139
+ }
140
+ }
104
141
  if (hasProperty(params, 'temperature')) {
105
142
  const rawTemp = Number(params.temperature)
106
143
  if (Number.isNaN(rawTemp) || rawTemp < -270 || rawTemp > 270) {
@@ -88,7 +88,9 @@ export default class {
88
88
  case 32:
89
89
  case 182:
90
90
  case 190:
91
+ case 226:
91
92
  case 276:
93
+ case 7032:
92
94
  // Add Eve power characteristics
93
95
  this.powerReadings = true
94
96
  if (!this.service.testCharacteristic(this.hapChar.OutletInUse)) {
@@ -104,7 +106,7 @@ export default class {
104
106
  this.service.addCharacteristic(this.eveChar.Voltage)
105
107
  }
106
108
 
107
- if ([190, 276].includes(this.accessory.context.eweUIID)) {
109
+ if ([190, 276, 7032].includes(this.accessory.context.eweUIID)) {
108
110
  this.divisor = 100
109
111
  }
110
112
  break
@@ -134,6 +136,14 @@ export default class {
134
136
  this.isSCM = true
135
137
  }
136
138
 
139
+ // The MINI R3/R4 family reports wall button presses, useful in detached relay mode
140
+ if (this.accessory.context.eweUIID === 138) {
141
+ this.buttonService = this.accessory.getService(this.hapServ.StatelessProgrammableSwitch)
142
+ || this.accessory.addService(this.hapServ.StatelessProgrammableSwitch)
143
+ } else if (this.accessory.getService(this.hapServ.StatelessProgrammableSwitch)) {
144
+ this.accessory.removeService(this.accessory.getService(this.hapServ.StatelessProgrammableSwitch))
145
+ }
146
+
137
147
  // Add the set handler to the switch on/off characteristic
138
148
  this.service
139
149
  .getCharacteristic(this.hapChar.On)
@@ -225,6 +235,21 @@ export default class {
225
235
 
226
236
  async externalUpdate(params) {
227
237
  try {
238
+ if (
239
+ this.buttonService
240
+ && hasProperty(params, 'key')
241
+ && [0, 1, 2].includes(params.key)
242
+ && params.trigTime
243
+ && params.trigTime !== this.lastTrigTime
244
+ ) {
245
+ this.lastTrigTime = params.trigTime
246
+ this.buttonService.updateCharacteristic(this.hapChar.ProgrammableSwitchEvent, params.key)
247
+ if (params.updateSource && this.enableLogging) {
248
+ const doubleLong = params.key === 1 ? this.lang.buttonDouble : this.lang.buttonLong
249
+ const textLabel = params.key === 0 ? this.lang.buttonSingle : doubleLong
250
+ this.log('[%s] %s [%s].', this.name, this.lang.buttonEvent, textLabel)
251
+ }
252
+ }
228
253
  if (!this.isSCM && hasProperty(params, 'switch')) {
229
254
  // Normalize boolean switch values (UIID 226 uses true/false)
230
255
  const newState = typeof params.switch === 'boolean'
@@ -250,6 +275,17 @@ export default class {
250
275
  if (!this.powerReadings) {
251
276
  return
252
277
  }
278
+
279
+ // Some models (e.g. uiid 226) report their readings under phase names
280
+ if (hasProperty(params, 'phase_0_p')) {
281
+ params.power = params.phase_0_p
282
+ }
283
+ if (hasProperty(params, 'phase_0_v')) {
284
+ params.voltage = params.phase_0_v
285
+ }
286
+ if (hasProperty(params, 'phase_0_c')) {
287
+ params.current = params.phase_0_c
288
+ }
253
289
  let logger = false
254
290
  let power
255
291
  let voltage
@@ -53,6 +53,14 @@ 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-03P models also report a binary bright/dark light level (uiid 7002)
57
+ if (accessory.context.eweUIID === 7002) {
58
+ this.lightService = this.accessory.getService(this.hapServ.LightSensor)
59
+ || this.accessory.addService(this.hapServ.LightSensor)
60
+ } else if (this.accessory.getService(this.hapServ.LightSensor)) {
61
+ this.accessory.removeService(this.accessory.getService(this.hapServ.LightSensor))
62
+ }
63
+
56
64
  // Pass the accessory to Fakegato to set up with Eve
57
65
  this.accessory.eveService = new platform.eveService('motion', this.accessory, {
58
66
  log: () => {},
@@ -70,6 +78,18 @@ export default class {
70
78
 
71
79
  async externalUpdate(params) {
72
80
  try {
81
+ if (this.lightService && hasProperty(params, 'brState')) {
82
+ // The device reports a binary state, so map brighter/darker to a
83
+ // nominal high/low lux value for HomeKit
84
+ const newLux = params.brState === 'brighter' ? 100 : 0.0001
85
+ if (newLux !== this.cacheLux) {
86
+ this.cacheLux = newLux
87
+ this.lightService.updateCharacteristic(this.hapChar.CurrentAmbientLightLevel, this.cacheLux)
88
+ if (params.updateSource && this.enableLogging) {
89
+ this.log('[%s] %s [%s].', this.name, this.lang.curLightLevel, params.brState)
90
+ }
91
+ }
92
+ }
73
93
  if (hasProperty(params, 'battery') && params.battery !== this.cacheBatt) {
74
94
  this.cacheBatt = params.battery
75
95
  this.cacheBattScaled = Math.max(Math.min(this.cacheBatt, 100), 0)
@@ -53,6 +53,14 @@ 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 models also report a binary bright/dark light level (uiid 7016)
57
+ if (accessory.context.eweUIID === 7016) {
58
+ this.lightService = this.accessory.getService(this.hapServ.LightSensor)
59
+ || this.accessory.addService(this.hapServ.LightSensor)
60
+ } else if (this.accessory.getService(this.hapServ.LightSensor)) {
61
+ this.accessory.removeService(this.accessory.getService(this.hapServ.LightSensor))
62
+ }
63
+
56
64
  // Pass the accessory to Fakegato to set up with Eve
57
65
  this.accessory.eveService = new platform.eveService('motion', this.accessory, {
58
66
  log: () => {},
@@ -70,6 +78,18 @@ export default class {
70
78
 
71
79
  async externalUpdate(params) {
72
80
  try {
81
+ if (this.lightService && hasProperty(params, 'brState')) {
82
+ // The device reports a binary state, so map brighter/darker to a
83
+ // nominal high/low lux value for HomeKit
84
+ const newLux = params.brState === 'brighter' ? 100 : 0.0001
85
+ if (newLux !== this.cacheLux) {
86
+ this.cacheLux = newLux
87
+ this.lightService.updateCharacteristic(this.hapChar.CurrentAmbientLightLevel, this.cacheLux)
88
+ if (params.updateSource && this.enableLogging) {
89
+ this.log('[%s] %s [%s].', this.name, this.lang.curLightLevel, params.brState)
90
+ }
91
+ }
92
+ }
73
93
  if (hasProperty(params, 'battery') && params.battery !== this.cacheBatt) {
74
94
  this.cacheBatt = params.battery
75
95
  this.cacheBattScaled = Math.max(Math.min(this.cacheBatt, 100), 0)
@@ -77,6 +77,14 @@ export default class {
77
77
  this.service = this.accessory.getService(this.hapServ.Thermostat)
78
78
  || this.accessory.addService(this.hapServ.Thermostat)
79
79
 
80
+ // The TRVZB supports a child lock, exposed as the lock physical controls characteristic
81
+ if (!this.service.testCharacteristic(this.hapChar.LockPhysicalControls)) {
82
+ this.service.addCharacteristic(this.hapChar.LockPhysicalControls)
83
+ }
84
+ this.service
85
+ .getCharacteristic(this.hapChar.LockPhysicalControls)
86
+ .onSet(async value => this.internalLockUpdate(value))
87
+
80
88
  // Set custom properties of the current temperature characteristic
81
89
  this.service.getCharacteristic(this.hapChar.CurrentTemperature).setProps({
82
90
  minStep: 0.1,
@@ -223,8 +231,45 @@ export default class {
223
231
  }
224
232
  }
225
233
 
234
+ async internalLockUpdate(value) {
235
+ try {
236
+ const newLock = value === this.hapChar.LockPhysicalControls.CONTROL_LOCK_ENABLED
237
+ if (newLock === this.cacheLock) {
238
+ return
239
+ }
240
+ await this.platform.sendDeviceUpdate(this.accessory, { childLock: newLock })
241
+ this.cacheLock = newLock
242
+ if (this.enableLogging) {
243
+ this.log('[%s] %s [%s].', this.name, this.lang.curChildLock, newLock ? 'on' : 'off')
244
+ }
245
+ } catch (err) {
246
+ this.platform.deviceUpdateError(this.accessory, err, true)
247
+ setTimeout(() => {
248
+ this.service.updateCharacteristic(
249
+ this.hapChar.LockPhysicalControls,
250
+ this.cacheLock
251
+ ? this.hapChar.LockPhysicalControls.CONTROL_LOCK_ENABLED
252
+ : this.hapChar.LockPhysicalControls.CONTROL_LOCK_DISABLED,
253
+ )
254
+ }, 2000)
255
+ throw new this.hapErr(-70402)
256
+ }
257
+ }
258
+
226
259
  async externalUpdate(params) {
227
260
  try {
261
+ if (hasProperty(params, 'childLock') && params.childLock !== this.cacheLock) {
262
+ this.cacheLock = params.childLock
263
+ this.service.updateCharacteristic(
264
+ this.hapChar.LockPhysicalControls,
265
+ this.cacheLock
266
+ ? this.hapChar.LockPhysicalControls.CONTROL_LOCK_ENABLED
267
+ : this.hapChar.LockPhysicalControls.CONTROL_LOCK_DISABLED,
268
+ )
269
+ if (params.updateSource && this.enableLogging) {
270
+ this.log('[%s] %s [%s].', this.name, this.lang.curChildLock, this.cacheLock ? 'on' : 'off')
271
+ }
272
+ }
228
273
  if (hasProperty(params, 'workMode')) {
229
274
  let workMode
230
275
  let hkWorkMode
package/lib/platform.js CHANGED
@@ -1107,6 +1107,15 @@ export default class {
1107
1107
  this.applyAccessoryLogging(accessory)
1108
1108
  accessory.control = new deviceTypes.deviceFan(this, accessory)
1109
1109
  /**/
1110
+ } else if (platformConsts.devices.fanGear.includes(uiid)) {
1111
+ /**
1112
+ FANS [GEAR]
1113
+ *
1114
+ */
1115
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1116
+ this.applyAccessoryLogging(accessory)
1117
+ accessory.control = new deviceTypes.deviceFanGear(this, accessory)
1118
+ /**/
1110
1119
  } else if (platformConsts.devices.diffuser.includes(uiid)) {
1111
1120
  /**
1112
1121
  ******
@@ -291,9 +291,9 @@ export default {
291
291
  275,
292
292
  277,
293
293
  ],
294
- switchSingle: [1, 6, 14, 24, 27, 216, 225, 226, 268, 1009, 1256, 7004, 7005, 7010, 7028, 7032, 20001],
294
+ switchSingle: [1, 6, 14, 24, 27, 216, 225, 226, 268, 1009, 1256, 7004, 7005, 7010, 7028, 20001],
295
295
  switchSCM: [77, 78, 81, 107, 112, 138, 160, 191, 209, 264],
296
- switchSCMPower: [182, 190, 276],
296
+ switchSCMPower: [182, 190, 276, 7032],
297
297
  switchSinglePower: [5, 32],
298
298
  switchMulti: [
299
299
  2,
@@ -316,6 +316,7 @@ export default {
316
316
  141,
317
317
  161,
318
318
  162,
319
+ 165,
319
320
  210,
320
321
  211,
321
322
  212,
@@ -329,7 +330,7 @@ export default {
329
330
  7040,
330
331
  20004,
331
332
  ],
332
- switchMultiPower: [126, 165, 262],
333
+ switchMultiPower: [126, 130, 262],
333
334
  switchMate: [177],
334
335
  switchMan: [174],
335
336
  lightDimmer: [36, 44, 57, 277, 20006],
@@ -346,17 +347,18 @@ export default {
346
347
  panel: [133, 195, 228],
347
348
  virtual: [265],
348
349
  fan: [34],
350
+ fanGear: [17],
349
351
  humidifier: [19],
350
352
  diffuser: [25],
351
353
  camera: [87],
352
354
  rfBridge: [28, 98],
353
355
  zbBridge: [66, 128, 168, 243], // 128 not technically Zigbee, but same data structure
354
- zbSwitchStateless: [1000, 7000],
356
+ zbSwitchStateless: [1000, 7000, 7039],
355
357
  zbLightDimmer: [1257],
356
358
  zbLightCCT: [1258],
357
359
  zbLightRGBCCT: [3258, 7009],
358
360
  zbMotor: [1514, 7006, 7034],
359
- zbSensorAmbient: [1770, 1771, 7014, 7033, 7038],
361
+ zbSensorAmbient: [242, 1770, 1771, 7014, 7033, 7038],
360
362
  zbSensorMotion: [2026, 7002],
361
363
  zbSensorOccupancy: [7016],
362
364
  zbSensorContact: [3026, 7003],
@@ -365,7 +367,7 @@ export default {
365
367
  zbThermostat: [7017],
366
368
  zbWaterValve: [7027, 7047],
367
369
  group: [5000],
368
- template: [130], // SPM sub unit
370
+ template: [], // Currently unused
369
371
  cannotSupport: [0, 65, 118, 119, 120, 121, 204, 223, 20000],
370
372
  garageSensors: [102, 154, 3026],
371
373
  skipUpdateRequest: [1000],
@@ -464,6 +466,7 @@ export default {
464
466
  11: 1, // "CURTAIN" (King Q4 Cover)
465
467
  14: 1, // "SWITCH_CHANGE" (Sonoff SV)
466
468
  15: 1, // "THERMOSTAT" (TH10, TH16)
469
+ 17: 1, // Three gear fan
467
470
  18: 1, // "SENSORS_CENTER" (Sonoff SC)
468
471
  19: 1, // "HUMIDIFIER"
469
472
  22: 1, // "RGB_BALL_LIGHT" (B1, B1_R2)
@@ -504,7 +507,8 @@ export default {
504
507
  126: 2, // "DUALR3"
505
508
  127: 1, // "GTTA127"
506
509
  128: 1, // SPM Main Unit (same data structure as zigbee bridge)
507
- 130: 1, // SPM Sub Unit
510
+ 130: 4, // SPM-4Relay sub unit
511
+ 242: 1, // Wireless temperature and humidity sensor
508
512
  133: 1, // "NSPANEL"
509
513
  135: 1, // ""
510
514
  136: 1, // RGB Five-Color Light_Support 2.4G eWeLink-Remote
@@ -561,6 +565,7 @@ export default {
561
565
  5000: 1, // Custom uiid for groups
562
566
  5026: 1, // Zigbee Smoke Sensor
563
567
  7000: 1, // Zigbee Button?
568
+ 7039: 1, // SNZB-01M Zigbee Button
564
569
  7002: 1, // Zigbee Human Body Sensor_Support OTA
565
570
  7003: 1, // Zigbee Door Magnet_Support OTA
566
571
  7004: 1, // Zigbee Single-Channel Switch ­_Support OTA
@@ -605,7 +610,6 @@ export default {
605
610
  12: 0, // "EW-RE"
606
611
  13: 0, // "FIREPLACE"
607
612
  16: 0, // "COLD_WARM_LED"
608
- 17: 0, // "THREE_GEAR_FAN"
609
613
  23: 0, // "NEST_THERMOSTAT"
610
614
  26: 0, // "RuiMiTeWenKongQi"
611
615
  35: 0, // "EZVIZ_CAMERA",
@@ -37,6 +37,11 @@ export default {
37
37
  curPur: 'current purifying',
38
38
  curScene: 'current scene',
39
39
  curSpeed: 'current speed',
40
+ curOscillation: 'current oscillation',
41
+ curAirQuality: 'current air quality level',
42
+ curChildLock: 'current child lock',
43
+ buttonEvent: 'button event',
44
+ curLightLevel: 'current light level',
40
45
  curState: 'current state',
41
46
  curTarg: 'current target',
42
47
  curTemp: 'current temperature',
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "displayName": "Homebridge eWeLink",
4
4
  "alias": "eWeLink",
5
5
  "type": "module",
6
- "version": "12.15.3-beta.2",
6
+ "version": "12.15.3-beta.3",
7
7
  "description": "Homebridge plugin to integrate eWeLink devices into HomeKit.",
8
8
  "author": {
9
9
  "name": "bwp91",