@homebridge-plugins/homebridge-ewelink 12.18.2-beta.2 → 12.18.2-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
@@ -17,6 +17,13 @@ All notable changes to `@homebridge-plugins/homebridge-ewelink` will be document
17
17
  - fix: make the per-device logging override settable, and drop the debug option that never was
18
18
  - fix: move the two-door garage one door at a time, so the second is not cut short
19
19
  - fix: stop the lan discovery printing straight to the console, whatever the log level
20
+ - fix: trigger rf sensor channels one at a time, so the reset delay is actually waited on
21
+ - fix: reject an operation or sensor time so large it would make the reset timer fire immediately
22
+ - fix(zigbee): stop forcing debug logs on zigbee motors, which ignored the logging setting
23
+ - test: record what every device handler builds, so a refactor can be shown to change nothing
24
+ - fix: honour the per-device logging setting on groups and the zigbee water valve
25
+ - fix(zigbee): read the snzb-02ld temperature as degrees, so 18.4c stops showing as 0.18c
26
+ - test: check the device handlers against payloads real devices sent
20
27
 
21
28
  ## v12.18.1 (2026-08-02)
22
29
 
@@ -421,6 +421,7 @@
421
421
  "description": "Garage doors: the time to open/close the device. Locks: the time to show as unlocked. This setting is in deciseconds - count the time in seconds and multiply by 10, for example 75 for 7.5 seconds. Must be 20 or more.",
422
422
  "placeholder": 100,
423
423
  "minimum": 20,
424
+ "maximum": 2073600,
424
425
  "condition": {
425
426
  "functionBody": "return (model.singleDevices && model.singleDevices[arrayIndices] && ((model.singleDevices[arrayIndices].deviceModel === 'gddc5' && model.singleDevices[arrayIndices].showAsEachen !== 'lock') || ['garage', 'lock', 'gate'].includes(model.singleDevices[arrayIndices].showAs)) && !model.singleDevices[arrayIndices].ignoreDevice);"
426
427
  }
@@ -431,6 +432,7 @@
431
432
  "description": "If your garage door has a different operation time for up and down, use this setting to specify the operation time DOWN (in deciseconds) and the other setting to specify the operation time UP. Otherwise leave blank. Must be 20 or more.",
432
433
  "placeholder": 100,
433
434
  "minimum": 20,
435
+ "maximum": 2073600,
434
436
  "condition": {
435
437
  "functionBody": "return (model.singleDevices && model.singleDevices[arrayIndices] && ['garage'].includes(model.singleDevices[arrayIndices].showAs) && !model.singleDevices[arrayIndices].ignoreDevice);"
436
438
  }
@@ -760,6 +762,7 @@
760
762
  "description": "Blinds/garage doors: the time to open/close the device. Locks: the time to show as unlocked. This setting is in deciseconds - count the time in seconds and multiply by 10, for example 75 for 7.5 seconds. Must be 20 or more.",
761
763
  "placeholder": 100,
762
764
  "minimum": 20,
765
+ "maximum": 2073600,
763
766
  "condition": {
764
767
  "functionBody": "return (model.multiDevices && model.multiDevices[arrayIndices] && ['garage', 'garage_two', 'garage_four', 'blind', 'door', 'window', 'lock', 'gate'].includes(model.multiDevices[arrayIndices].showAs) && !model.multiDevices[arrayIndices].ignoreDevice);"
765
768
  }
@@ -770,6 +773,7 @@
770
773
  "description": "If your garage door or blind has a different operation time for up and down, use this setting to specify the operation time DOWN (in deciseconds) and the other setting to specify the operation time UP. Otherwise leave blank. Must be 20 or more.",
771
774
  "placeholder": 100,
772
775
  "minimum": 20,
776
+ "maximum": 2073600,
773
777
  "condition": {
774
778
  "functionBody": "return (model.multiDevices && model.multiDevices[arrayIndices] && ['garage', 'garage_two', 'garage_four', 'blind', 'door', 'window'].includes(model.multiDevices[arrayIndices].showAs) && !model.multiDevices[arrayIndices].ignoreDevice);"
775
779
  }
@@ -1383,6 +1387,7 @@
1383
1387
  "description": "An offset in seconds to ignore notifications if the delay between this Zigbee Motion/Leak activating or Zigbee Button press and the plugin receiving the notification is greater than this number. Must be 5 or more.",
1384
1388
  "placeholder": 60,
1385
1389
  "minimum": 5,
1390
+ "maximum": 2073600,
1386
1391
  "condition": {
1387
1392
  "functionBody": "return (model.sensorDevices && model.sensorDevices[arrayIndices] && ['snzb01', 'snzb03', 'leak'].includes(model.sensorDevices[arrayIndices].deviceModel) && !model.sensorDevices[arrayIndices].ignoreDevice);"
1388
1393
  }
@@ -1522,6 +1527,7 @@
1522
1527
  "description": "The time to open/close the device. This setting is in deciseconds - count the time in seconds and multiply by 10, for example 75 for 7.5 seconds. Must be 20 or more.",
1523
1528
  "placeholder": 100,
1524
1529
  "minimum": 20,
1530
+ "maximum": 2073600,
1525
1531
  "condition": {
1526
1532
  "functionBody": "return (model.bridgeSensors && model.bridgeSensors[arrayIndices] && model.bridgeSensors[arrayIndices].fullDeviceId && model.bridgeSensors[arrayIndices].fullDeviceId.length >= 13 && model.bridgeSensors[arrayIndices].deviceType === 'curtain' && ['blind', 'door', 'window'].includes(model.bridgeSensors[arrayIndices].curtainType));"
1527
1533
  }
@@ -1532,6 +1538,7 @@
1532
1538
  "description": "If your device has a different operation time for up and down, use this setting to specify the operation time DOWN (in deciseconds) and the other setting to specify the operation time UP. Otherwise leave blank. Must be 20 or more.",
1533
1539
  "placeholder": 100,
1534
1540
  "minimum": 20,
1541
+ "maximum": 2073600,
1535
1542
  "condition": {
1536
1543
  "functionBody": "return (model.bridgeSensors && model.bridgeSensors[arrayIndices] && model.bridgeSensors[arrayIndices].fullDeviceId && model.bridgeSensors[arrayIndices].fullDeviceId.length >= 13 && model.bridgeSensors[arrayIndices].deviceType === 'curtain' && ['blind', 'door', 'window'].includes(model.bridgeSensors[arrayIndices].curtainType));"
1537
1544
  }
@@ -1588,6 +1595,7 @@
1588
1595
  "description": "The number of seconds which the sensor tile in the Home app will light up for if this sensor is activated. Must be 1 or more.",
1589
1596
  "placeholder": 60,
1590
1597
  "minimum": 1,
1598
+ "maximum": 2073600,
1591
1599
  "condition": {
1592
1600
  "functionBody": "return (model.bridgeSensors && model.bridgeSensors[arrayIndices] && model.bridgeSensors[arrayIndices].type && model.bridgeSensors[arrayIndices].deviceType === 'sensor' && ['motion', 'smoke', 'water', 'co', 'co2', 'occupancy', 'contact'].includes(model.bridgeSensors[arrayIndices].type));"
1593
1601
  }
@@ -1598,6 +1606,7 @@
1598
1606
  "description": "An offset in seconds to ignore notifications if the delay between this sensor activating and the plugin receiving the notification is greater than this number. Must be 5 or more.",
1599
1607
  "placeholder": 60,
1600
1608
  "minimum": 5,
1609
+ "maximum": 2073600,
1601
1610
  "condition": {
1602
1611
  "functionBody": "return (model.bridgeSensors && model.bridgeSensors[arrayIndices] && model.bridgeSensors[arrayIndices].type && model.bridgeSensors[arrayIndices].deviceType === 'sensor' && ['motion', 'smoke', 'water', 'co', 'co2', 'occupancy', 'contact'].includes(model.bridgeSensors[arrayIndices].type));"
1603
1612
  }
@@ -15,9 +15,28 @@ export default class {
15
15
  this.name = accessory.displayName
16
16
  this.accessory = accessory
17
17
 
18
+ // Set up custom variables for this device type
19
+ const deviceConf = platform.deviceConf[accessory.context.eweDeviceId] || {}
20
+
18
21
  // Set the correct logging variables for this accessory
19
- this.enableLogging = !platform.config.disableDeviceLogging
20
- this.enableDebugLogging = false
22
+ switch (deviceConf.overrideLogging) {
23
+ case 'standard':
24
+ this.enableLogging = true
25
+ this.enableDebugLogging = false
26
+ break
27
+ case 'debug':
28
+ this.enableLogging = true
29
+ this.enableDebugLogging = true
30
+ break
31
+ case 'disable':
32
+ this.enableLogging = false
33
+ this.enableDebugLogging = false
34
+ break
35
+ default:
36
+ this.enableLogging = !platform.config.disableDeviceLogging
37
+ this.enableDebugLogging = false
38
+ break
39
+ }
21
40
 
22
41
  if (platformConsts.devices.switchSCM.includes(this.accessory.context.groupUIID)) {
23
42
  this.isSCM = true
@@ -116,202 +116,204 @@ export default class {
116
116
  || params.updateSource === 'LAN'
117
117
  ) {
118
118
  // RF Sensor
119
- Object.keys(params)
120
- .filter(name => rfTrigRegex.test(name))
121
- .forEach(async (chan) => {
122
- try {
123
- this.devicesInHB.forEach((acc) => {
124
- if (
125
- acc.context.eweDeviceId === this.accessory.context.eweDeviceId
126
- && hasProperty(acc.context, 'buttons')
127
- && hasProperty(acc.context.buttons, chan.split('g')[1].toString())
128
- ) {
129
- subAccessory = acc
130
- channel = chan.split('g')[1].toString()
131
- }
132
- })
133
- if (subAccessory) {
134
- // Avoid duplicate triggers from LAN and WS
135
- if (params[chan] === subAccessory.context.cacheLastAct) {
136
- return
119
+ // One channel at a time. This was a `forEach(async ...)`, so every
120
+ // triggered channel ran at once and the promise each returned was thrown
121
+ // away - the `sleep` calls below held nothing back, and a rejection
122
+ // escaped the try in externalUpdate rather than being caught by it.
123
+ for (const chan of Object.keys(params).filter(name => rfTrigRegex.test(name))) {
124
+ try {
125
+ this.devicesInHB.forEach((acc) => {
126
+ if (
127
+ acc.context.eweDeviceId === this.accessory.context.eweDeviceId
128
+ && hasProperty(acc.context, 'buttons')
129
+ && hasProperty(acc.context.buttons, chan.split('g')[1].toString())
130
+ ) {
131
+ subAccessory = acc
132
+ channel = chan.split('g')[1].toString()
133
+ }
134
+ })
135
+ if (subAccessory) {
136
+ // Avoid duplicate triggers from LAN and WS
137
+ if (params[chan] === subAccessory.context.cacheLastAct) {
138
+ continue
139
+ }
140
+ const deviceConf = this.platform.rfSubdevices[subAccessory.context.hbDeviceId] || {}
141
+ let enableLogging
142
+ switch (deviceConf.overrideLogging) {
143
+ case 'standard':
144
+ case 'debug':
145
+ enableLogging = true
146
+ break
147
+ case 'disable':
148
+ enableLogging = false
149
+ break
150
+ default:
151
+ enableLogging = !this.disableDeviceLogging
152
+ break
153
+ }
154
+ switch (subAccessory.context.subType) {
155
+ case 'blind':
156
+ case 'door':
157
+ case 'window':
158
+ // Simulations don't support external updates
159
+ continue
160
+ case 'button':
161
+ case 'curtain': {
162
+ const service = subAccessory.context.buttons[channel]
163
+ subAccessory.getService(service).updateCharacteristic(this.hapChar.On, true)
164
+ this.log(
165
+ '[%s] %s [%s].',
166
+ subAccessory.displayName,
167
+ this.lang.curState,
168
+ this.lang.buttonTrig,
169
+ )
170
+ await sleep(3000)
171
+ subAccessory.getService(service).updateCharacteristic(this.hapChar.On, false)
172
+ continue
137
173
  }
138
- const deviceConf = this.platform.rfSubdevices[subAccessory.context.hbDeviceId] || {}
139
- let enableLogging
140
- switch (deviceConf.overrideLogging) {
141
- case 'standard':
142
- case 'debug':
143
- enableLogging = true
174
+ default:
175
+ break
176
+ }
177
+ const sensorTimeLength = deviceConf.sensorTimeLength || platformConsts.defaultValues.sensorTimeLength
178
+ const sensorTimeDifference = deviceConf.sensorTimeDifference || platformConsts.defaultValues.sensorTimeDifference
179
+ subAccessory.context.cacheLastAct = params[chan]
180
+ const timeOfMotion = new Date(params[chan])
181
+ const diff = (timeNow.getTime() - timeOfMotion.getTime()) / 1000
182
+ let serv
183
+ let char
184
+ if (diff < sensorTimeDifference) {
185
+ switch (deviceConf.type) {
186
+ case 'water':
187
+ serv = this.hapServ.LeakSensor
188
+ char = this.hapChar.LeakDetected
144
189
  break
145
- case 'disable':
146
- enableLogging = false
190
+ case 'fire':
191
+ case 'smoke':
192
+ serv = this.hapServ.SmokeSensor
193
+ char = this.hapChar.SmokeDetected
147
194
  break
148
- default:
149
- enableLogging = !this.disableDeviceLogging
195
+ case 'co':
196
+ serv = this.hapServ.CarbonMonoxideSensor
197
+ char = this.hapChar.CarbonMonoxideDetected
198
+ break
199
+ case 'co2':
200
+ serv = this.hapServ.CarbonDioxideSensor
201
+ char = this.hapChar.CarbonDioxideDetected
202
+ break
203
+ case 'contact': {
204
+ serv = this.hapServ.ContactSensor
205
+ char = this.hapChar.ContactSensorState
206
+ const initialTime = subAccessory.eveService.getInitialTime()
207
+ subAccessory
208
+ .getService(serv)
209
+ .updateCharacteristic(
210
+ this.eveChar.LastActivation,
211
+ Math.round(new Date().valueOf() / 1000) - initialTime,
212
+ )
150
213
  break
151
- }
152
- switch (subAccessory.context.subType) {
153
- case 'blind':
154
- case 'door':
155
- case 'window':
156
- // Simulations don't support external updates
157
- return
158
- case 'button':
159
- case 'curtain': {
160
- const service = subAccessory.context.buttons[channel]
161
- subAccessory.getService(service).updateCharacteristic(this.hapChar.On, true)
162
- this.log(
163
- '[%s] %s [%s].',
164
- subAccessory.displayName,
165
- this.lang.curState,
166
- this.lang.buttonTrig,
167
- )
168
- await sleep(3000)
169
- subAccessory.getService(service).updateCharacteristic(this.hapChar.On, false)
170
- return
171
214
  }
172
- default:
215
+ case 'occupancy':
216
+ serv = this.hapServ.OccupancySensor
217
+ char = this.hapChar.OccupancyDetected
218
+ break
219
+ case 'p_button':
220
+ serv = this.hapServ.StatelessProgrammableSwitch
221
+ char = this.hapChar.ProgrammableSwitchEvent
222
+ break
223
+ case 'doorbell':
224
+ serv = this.hapServ.Doorbell
225
+ char = this.hapChar.ProgrammableSwitchEvent
226
+ break
227
+ default: {
228
+ serv = this.hapServ.MotionSensor
229
+ char = this.hapChar.MotionDetected
230
+ const initialTime = subAccessory.eveService.getInitialTime()
231
+ subAccessory
232
+ .getService(serv)
233
+ .updateCharacteristic(
234
+ this.eveChar.LastActivation,
235
+ Math.round(new Date().valueOf() / 1000) - initialTime,
236
+ )
173
237
  break
174
- }
175
- const sensorTimeLength = deviceConf.sensorTimeLength || platformConsts.defaultValues.sensorTimeLength
176
- const sensorTimeDifference = deviceConf.sensorTimeDifference || platformConsts.defaultValues.sensorTimeDifference
177
- subAccessory.context.cacheLastAct = params[chan]
178
- const timeOfMotion = new Date(params[chan])
179
- const diff = (timeNow.getTime() - timeOfMotion.getTime()) / 1000
180
- let serv
181
- let char
182
- if (diff < sensorTimeDifference) {
183
- switch (deviceConf.type) {
184
- case 'water':
185
- serv = this.hapServ.LeakSensor
186
- char = this.hapChar.LeakDetected
187
- break
188
- case 'fire':
189
- case 'smoke':
190
- serv = this.hapServ.SmokeSensor
191
- char = this.hapChar.SmokeDetected
192
- break
193
- case 'co':
194
- serv = this.hapServ.CarbonMonoxideSensor
195
- char = this.hapChar.CarbonMonoxideDetected
196
- break
197
- case 'co2':
198
- serv = this.hapServ.CarbonDioxideSensor
199
- char = this.hapChar.CarbonDioxideDetected
200
- break
201
- case 'contact': {
202
- serv = this.hapServ.ContactSensor
203
- char = this.hapChar.ContactSensorState
204
- const initialTime = subAccessory.eveService.getInitialTime()
205
- subAccessory
206
- .getService(serv)
207
- .updateCharacteristic(
208
- this.eveChar.LastActivation,
209
- Math.round(new Date().valueOf() / 1000) - initialTime,
210
- )
211
- break
212
- }
213
- case 'occupancy':
214
- serv = this.hapServ.OccupancySensor
215
- char = this.hapChar.OccupancyDetected
216
- break
217
- case 'p_button':
218
- serv = this.hapServ.StatelessProgrammableSwitch
219
- char = this.hapChar.ProgrammableSwitchEvent
220
- break
221
- case 'doorbell':
222
- serv = this.hapServ.Doorbell
223
- char = this.hapChar.ProgrammableSwitchEvent
224
- break
225
- default: {
226
- serv = this.hapServ.MotionSensor
227
- char = this.hapChar.MotionDetected
228
- const initialTime = subAccessory.eveService.getInitialTime()
229
- subAccessory
230
- .getService(serv)
231
- .updateCharacteristic(
232
- this.eveChar.LastActivation,
233
- Math.round(new Date().valueOf() / 1000) - initialTime,
234
- )
235
- break
236
- }
237
238
  }
239
+ }
238
240
 
239
- // Call any webhook defined in the config
240
- if (deviceConf.sensorWebHook) {
241
- try {
242
- await axios.get(deviceConf.sensorWebHook, {
243
- timeout: 5000,
244
- })
245
- if (enableLogging) {
246
- this.log(
247
- '[%s] %s [GET %s].',
248
- subAccessory.displayName,
249
- 'http webhook request successful',
250
- deviceConf.sensorWebHook,
251
- )
252
- }
253
- } catch (e) {
254
- if (enableLogging) {
255
- this.log(
256
- '[%s] %s %s.',
257
- subAccessory.displayName,
258
- 'http webhook request failed as',
259
- e.message,
260
- )
261
- }
241
+ // Call any webhook defined in the config
242
+ if (deviceConf.sensorWebHook) {
243
+ try {
244
+ await axios.get(deviceConf.sensorWebHook, {
245
+ timeout: 5000,
246
+ })
247
+ if (enableLogging) {
248
+ this.log(
249
+ '[%s] %s [GET %s].',
250
+ subAccessory.displayName,
251
+ 'http webhook request successful',
252
+ deviceConf.sensorWebHook,
253
+ )
262
254
  }
263
- }
264
-
265
- // Programmable button and doorbell activate differently
266
- if (char === this.hapChar.ProgrammableSwitchEvent) {
267
- subAccessory.getService(serv).updateCharacteristic(char, 0)
268
- if (params.updateSource && enableLogging) {
255
+ } catch (e) {
256
+ if (enableLogging) {
269
257
  this.log(
270
- '[%s] %s [%s].',
258
+ '[%s] %s %s.',
271
259
  subAccessory.displayName,
272
- this.lang.curState,
273
- this.lang.buttonSingle,
260
+ 'http webhook request failed as',
261
+ e.message,
274
262
  )
275
263
  }
276
- return
277
- }
278
- subAccessory.getService(serv).updateCharacteristic(char, 1)
279
- subAccessory.eveService.addEntry({ status: 1 })
280
- if (params.updateSource && enableLogging) {
281
- this.log(
282
- '[%s] %s [%s].',
283
- subAccessory.displayName,
284
- this.lang.curState,
285
- this.lang.rfTrigYes,
286
- )
287
264
  }
265
+ }
288
266
 
289
- const updateKey = Math.random()
290
- .toString(36)
291
- .substring(2, 10)
292
- subAccessory.context.updateKey = updateKey
293
- await sleep(sensorTimeLength * 1000)
294
- if (updateKey !== subAccessory.context.updateKey) {
295
- return
296
- }
267
+ // Programmable button and doorbell activate differently
268
+ if (char === this.hapChar.ProgrammableSwitchEvent) {
297
269
  subAccessory.getService(serv).updateCharacteristic(char, 0)
298
- subAccessory.eveService.addEntry({ status: 0 })
299
270
  if (params.updateSource && enableLogging) {
300
271
  this.log(
301
272
  '[%s] %s [%s].',
302
273
  subAccessory.displayName,
303
274
  this.lang.curState,
304
- this.lang.rfTrigNo,
275
+ this.lang.buttonSingle,
305
276
  )
306
277
  }
278
+ continue
279
+ }
280
+ subAccessory.getService(serv).updateCharacteristic(char, 1)
281
+ subAccessory.eveService.addEntry({ status: 1 })
282
+ if (params.updateSource && enableLogging) {
283
+ this.log(
284
+ '[%s] %s [%s].',
285
+ subAccessory.displayName,
286
+ this.lang.curState,
287
+ this.lang.rfTrigYes,
288
+ )
289
+ }
290
+
291
+ const updateKey = Math.random()
292
+ .toString(36)
293
+ .substring(2, 10)
294
+ subAccessory.context.updateKey = updateKey
295
+ await sleep(sensorTimeLength * 1000)
296
+ if (updateKey !== subAccessory.context.updateKey) {
297
+ continue
298
+ }
299
+ subAccessory.getService(serv).updateCharacteristic(char, 0)
300
+ subAccessory.eveService.addEntry({ status: 0 })
301
+ if (params.updateSource && enableLogging) {
302
+ this.log(
303
+ '[%s] %s [%s].',
304
+ subAccessory.displayName,
305
+ this.lang.curState,
306
+ this.lang.rfTrigNo,
307
+ )
307
308
  }
308
- } else {
309
- throw new Error(`${this.lang.rfNotFound}[${chan}]`)
310
309
  }
311
- } catch (err) {
312
- this.log.warn('[%s] %s %s.', this.name, this.lang.devNotRf, parseError(err))
310
+ } else {
311
+ throw new Error(`${this.lang.rfNotFound}[${chan}]`)
313
312
  }
314
- })
313
+ } catch (err) {
314
+ this.log.warn('[%s] %s %s.', this.name, this.lang.devNotRf, parseError(err))
315
+ }
316
+ }
315
317
  }
316
318
  } catch (err) {
317
319
  this.platform.deviceUpdateError(this.accessory, err, false)
@@ -42,10 +42,6 @@ export default class {
42
42
  break
43
43
  }
44
44
 
45
- // Temp override for debugging
46
- this.enableLogging = true
47
- this.enableDebugLogging = true
48
-
49
45
  // Add the battery service if it doesn't already exist
50
46
  this.battService = this.accessory.getService(this.hapServ.Battery)
51
47
  || this.accessory.addService(this.hapServ.Battery)
@@ -98,11 +98,19 @@ export default class {
98
98
  }
99
99
  }
100
100
  if (hasProperty(params, 'temperature')) {
101
- const rawTemp = Number.parseInt(params.temperature, 10)
101
+ // Two conventions arrive here. Most of these sensors send hundredths of
102
+ // a degree as a whole number - 2620 for 26.20C. The SNZB-02LD and 02WD
103
+ // send the degrees themselves, with a decimal point ("18.4"), and
104
+ // reading that as hundredths showed 18.4C in HomeKit as 0.18C (#701).
105
+ // The decimal point is the only thing that tells them apart.
106
+ const sendsDegrees = String(params.temperature).includes('.')
107
+ const rawTemp = sendsDegrees
108
+ ? Number.parseFloat(params.temperature)
109
+ : Number.parseInt(params.temperature, 10)
102
110
  if (Number.isNaN(rawTemp) || rawTemp < -27000 || rawTemp > 27000) {
103
111
  return
104
112
  }
105
- let newTemp = rawTemp / 100
113
+ let newTemp = sendsDegrees ? rawTemp : rawTemp / 100
106
114
  if (this.tempOffsetFactor) {
107
115
  newTemp *= this.tempOffset
108
116
  } else {
@@ -121,11 +129,15 @@ export default class {
121
129
  }
122
130
  }
123
131
  if (hasProperty(params, 'humidity')) {
124
- const rawHumi = Number.parseInt(params.humidity, 10)
132
+ // Same two conventions as the temperature above, handled the same way
133
+ const sendsPercent = String(params.humidity).includes('.')
134
+ const rawHumi = sendsPercent
135
+ ? Number.parseFloat(params.humidity)
136
+ : Number.parseInt(params.humidity, 10)
125
137
  if (rawHumi === 0) {
126
138
  return
127
139
  }
128
- let newHumi = rawHumi / 100
140
+ let newHumi = sendsPercent ? rawHumi : rawHumi / 100
129
141
  if (this.humiOffsetFactor) {
130
142
  newHumi *= this.humiOffset
131
143
  } else {
@@ -12,7 +12,27 @@ export default class {
12
12
  this.accessory = accessory
13
13
 
14
14
  // Set up custom variables for this device type
15
- this.enableLogging = !platform.config.disableDeviceLogging
15
+ const deviceConf = platform.deviceConf[accessory.context.eweDeviceId] || {}
16
+
17
+ // Set the correct logging variables for this accessory
18
+ switch (deviceConf.overrideLogging) {
19
+ case 'standard':
20
+ this.enableLogging = true
21
+ this.enableDebugLogging = false
22
+ break
23
+ case 'debug':
24
+ this.enableLogging = true
25
+ this.enableDebugLogging = true
26
+ break
27
+ case 'disable':
28
+ this.enableLogging = false
29
+ this.enableDebugLogging = false
30
+ break
31
+ default:
32
+ this.enableLogging = !platform.config.disableDeviceLogging
33
+ this.enableDebugLogging = false
34
+ break
35
+ }
16
36
 
17
37
  // The newer SWV-ZNE irrigation valve (uiid 7047) reports and accepts its
18
38
  // state under 'switch_00' rather than 'switch' (#778)
package/lib/platform.js CHANGED
@@ -113,6 +113,9 @@ export default class {
113
113
  const logIncrease = (k, min) => {
114
114
  this.log.warn('%s [%s] %s %s.', this.lang.cfgItem, k, this.lang.cfgLow, min)
115
115
  }
116
+ const logDecrease = (k, max) => {
117
+ this.log.warn('%s [%s] %s %s.', this.lang.cfgItem, k, this.lang.cfgHigh, max)
118
+ }
116
119
  const logQuotes = (k) => {
117
120
  this.log.warn('%s [%s] %s.', this.lang.cfgItem, k, this.lang.cfgQts)
118
121
  }
@@ -200,6 +203,9 @@ export default class {
200
203
  } else if (intVal < platformConsts.minValues[k]) {
201
204
  logIncrease(`${key}.${id}.${k}`, platformConsts.minValues[k])
202
205
  this.rfSubdevices[id][k] = platformConsts.minValues[k]
206
+ } else if (platformConsts.maxValues[k] !== undefined && intVal > platformConsts.maxValues[k]) {
207
+ logDecrease(`${key}.${id}.${k}`, platformConsts.maxValues[k])
208
+ this.rfSubdevices[id][k] = platformConsts.maxValues[k]
203
209
  } else {
204
210
  this.rfSubdevices[id][k] = intVal
205
211
  }
@@ -282,6 +288,9 @@ export default class {
282
288
  } else if (intVal < platformConsts.minValues[k]) {
283
289
  logIncrease(`${key}.${id}.${k}`, platformConsts.minValues[k])
284
290
  this.deviceConf[id][k] = platformConsts.minValues[k]
291
+ } else if (platformConsts.maxValues[k] !== undefined && intVal > platformConsts.maxValues[k]) {
292
+ logDecrease(`${key}.${id}.${k}`, platformConsts.maxValues[k])
293
+ this.deviceConf[id][k] = platformConsts.maxValues[k]
285
294
  } else {
286
295
  this.deviceConf[id][k] = intVal
287
296
  }
@@ -61,6 +61,18 @@ export default {
61
61
  targetTempThreshold: 0,
62
62
  },
63
63
 
64
+ // A Node timer holds its delay in a signed 32-bit integer. Over 2147483647 ms
65
+ // it does not throw - it quietly becomes 1 ms, so a device set past that point
66
+ // would run its reset timer immediately and repeatedly. operationTime is
67
+ // multiplied by 100 and sensorTimeLength by 1000, so both are capped well
68
+ // inside the limit.
69
+ maxValues: {
70
+ operationTime: 2073600,
71
+ operationTimeDown: 2073600,
72
+ sensorTimeDifference: 2073600,
73
+ sensorTimeLength: 2073600,
74
+ },
75
+
64
76
  appId: 'Uw83EKZFxdif7XFXEsrpduz5YyjP7nTl',
65
77
 
66
78
  appSecret: 'mXLOjea0woSMvK9gw7Fjsy7YlFO4iSu6',
@@ -14,6 +14,7 @@ export default {
14
14
  cfgIgn: 'is not configured correctly so ignoring',
15
15
  cfgIgnItem: 'has an invalid entry which will be ignored',
16
16
  cfgItem: 'Config entry',
17
+ cfgHigh: 'is set too high so reducing to',
17
18
  cfgLow: 'is set too low so increasing to',
18
19
  cfgRmv: 'is unused and can be removed',
19
20
  cfgQts: 'should not have quotes around its entry',
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.2-beta.2",
6
+ "version": "12.18.2-beta.3",
7
7
  "description": "Homebridge plugin to integrate eWeLink devices into HomeKit.",
8
8
  "author": {
9
9
  "name": "bwp91",