@homebridge-plugins/homebridge-ewelink 12.19.0 → 12.19.1-beta.1

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
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to `@homebridge-plugins/homebridge-ewelink` will be documented in this file.
4
4
 
5
+ ## v12.19.1 (Pending Release)
6
+
7
+ ### Changed
8
+
9
+ - feat(fan): add an option to show ifan speed as a percentage, so siri's levels work (#551)
10
+ - fix(api): send fan speeds on the fan's own scale, so medium no longer turns it off
11
+ - ci: update github actions to their latest versions
12
+
5
13
  ## v12.19.0 (2026-10-18)
6
14
 
7
15
  ### Changed
@@ -1218,6 +1218,14 @@
1218
1218
  "functionBody": "return (model.fanDevices && model.fanDevices[arrayIndices] && ['ifan'].includes(model.fanDevices[arrayIndices].deviceModel) && !model.fanDevices[arrayIndices].ignoreDevice);"
1219
1219
  }
1220
1220
  },
1221
+ "speedAsPercent": {
1222
+ "type": "boolean",
1223
+ "title": "Show Speed As Percentage",
1224
+ "description": "If true, the fan speed shows as a percentage (low 33%, medium 66%, high 99%) instead of levels 1 to 3. Siri's low, medium and high only work with a percentage. Changing this re-adds the fan to HomeKit, so check any scenes or automations that use it.",
1225
+ "condition": {
1226
+ "functionBody": "return (model.fanDevices && model.fanDevices[arrayIndices] && ['ifan'].includes(model.fanDevices[arrayIndices].deviceModel) && !model.fanDevices[arrayIndices].ignoreDevice);"
1227
+ }
1228
+ },
1221
1229
  "ipAddress": {
1222
1230
  "type": "string",
1223
1231
  "title": "IP Address",
@@ -1788,6 +1796,7 @@
1788
1796
  "fanDevices[].overrideLogging",
1789
1797
  "fanDevices[].deviceModel",
1790
1798
  "fanDevices[].hideLight",
1799
+ "fanDevices[].speedAsPercent",
1791
1800
  "fanDevices[].ipAddress"
1792
1801
  ]
1793
1802
  }
@@ -446,21 +446,16 @@ export default class {
446
446
  await accessory.control.internalColourUpdate(newHKStatus)
447
447
  break
448
448
  }
449
- case 'speed':
450
- // The new status for speed must be low, medium or high
451
- switch (newValue) {
452
- case 'low':
453
- newHKStatus = 33
454
- break
455
- case 'medium':
456
- newHKStatus = 66
457
- break
458
- case 'high':
459
- newHKStatus = 99
460
- break
461
- default:
462
- throw new Error('Value must be \'low\', \'medium\' or \'high\' for characteristic:speed')
449
+ case 'speed': {
450
+ // The new status for speed must be low, medium or high. Some fans count
451
+ // their speed in levels 1 to 3 rather than a percentage, and sending
452
+ // those a percentage read as out of range, so medium turned them off
453
+ const levels = { low: 1, medium: 2, high: 3 }
454
+ if (!levels[newValue]) {
455
+ throw new Error('Value must be \'low\', \'medium\' or \'high\' for characteristic:speed')
463
456
  }
457
+ const inLevels = accServ.getCharacteristic(this.hapChar.RotationSpeed).props.maxValue <= 3
458
+ newHKStatus = inLevels ? levels[newValue] : levels[newValue] * 33
464
459
 
465
460
  // Check the accessory has a correct set handler for speed
466
461
  if (!accessory.control.internalSpeedUpdate) {
@@ -470,6 +465,7 @@ export default class {
470
465
  // Call the set handler to send the request to eWeLink
471
466
  await accessory.control.internalSpeedUpdate(newHKStatus)
472
467
  break
468
+ }
473
469
  case 'state':
474
470
  // The new status for state must be on, off or toggle
475
471
  switch (newValue) {
package/lib/device/fan.js CHANGED
@@ -20,6 +20,7 @@ export default class {
20
20
  // Set up custom variables for this device type
21
21
  const deviceConf = platform.deviceConf[accessory.context.eweDeviceId] || {}
22
22
  this.hideLight = deviceConf.hideLight
23
+ this.speedAsPercent = deviceConf.speedAsPercent
23
24
 
24
25
  // Set the correct logging variables for this accessory
25
26
  switch (deviceConf.overrideLogging) {
@@ -41,6 +42,16 @@ export default class {
41
42
  break
42
43
  }
43
44
 
45
+ // Rebuild the fan service if it was cached under the other speed scale, as
46
+ // HomeKit keeps the old props until the service itself is replaced
47
+ const existingService = this.accessory.getService(this.hapServ.Fan)
48
+ if (existingService) {
49
+ const { props } = existingService.getCharacteristic(this.hapChar.RotationSpeed)
50
+ if (props.maxValue !== (this.speedAsPercent ? 100 : 3)) {
51
+ this.accessory.removeService(existingService)
52
+ }
53
+ }
54
+
44
55
  // Add the fan service if it doesn't already exist
45
56
  this.service = this.accessory.getService(this.hapServ.Fan) || this.accessory.addService(this.hapServ.Fan)
46
57
 
@@ -49,15 +60,19 @@ export default class {
49
60
  .getCharacteristic(this.hapChar.On)
50
61
  .onSet(async value => this.internalStateUpdate(value))
51
62
 
52
- // Add the set handler to the fan rotation speed characteristic
63
+ // Add the set handler to the fan rotation speed characteristic. Levels 1-3
64
+ // match the remote, but Siri and a tap on the slider only work with a
65
+ // percentage, so that is offered as an option (#551)
53
66
  this.service
54
67
  .getCharacteristic(this.hapChar.RotationSpeed)
55
- .setProps({
56
- maxValue: 3,
57
- minStep: 1,
58
- minValue: 0,
59
- unit: 'unitless', // This is actually from HAP for Bluetooth LE Specification, but fits
60
- })
68
+ .setProps(this.speedAsPercent
69
+ ? { maxValue: 100, minStep: 33, minValue: 0, unit: 'percentage' }
70
+ : {
71
+ maxValue: 3,
72
+ minStep: 1,
73
+ minValue: 0,
74
+ unit: 'unitless', // This is actually from HAP for Bluetooth LE Specification, but fits
75
+ })
61
76
  .onSet(async value => this.internalSpeedUpdate(value))
62
77
 
63
78
  // Check to see if the user has hidden the light channel
@@ -116,6 +131,7 @@ export default class {
116
131
  const opts = JSON.stringify({
117
132
  hideLight: this.hideLight,
118
133
  logging: this.enableDebugLogging ? 'debug' : normalLogging,
134
+ speedAsPercent: this.speedAsPercent,
119
135
  })
120
136
  this.log('[%s] %s %s.', this.name, this.lang.devInitOpts, opts)
121
137
  }
@@ -170,13 +186,7 @@ export default class {
170
186
  switches: [],
171
187
  }
172
188
  const newState = value >= 1 ? 'on' : 'off'
173
- let newSpeed
174
-
175
- if (value < 0 || value > 3) {
176
- newSpeed = 0
177
- } else {
178
- newSpeed = value
179
- }
189
+ const newSpeed = this.levelFromHK(value)
180
190
  if (newSpeed === this.cacheSpeed) {
181
191
  return
182
192
  }
@@ -202,7 +212,7 @@ export default class {
202
212
  if (newSpeed === 0) {
203
213
  // Update the rotation speed back to the previous value (with the fan still off)
204
214
  setTimeout(() => {
205
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
215
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.levelToHK(this.cacheSpeed))
206
216
  }, 2000)
207
217
  return
208
218
  }
@@ -213,7 +223,7 @@ export default class {
213
223
  } catch (err) {
214
224
  this.platform.deviceUpdateError(this.accessory, err, true)
215
225
  setTimeout(() => {
216
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
226
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.levelToHK(this.cacheSpeed))
217
227
  }, 2000)
218
228
  throw new this.hapErr(-70402)
219
229
  }
@@ -294,7 +304,7 @@ export default class {
294
304
  }
295
305
  if (speed !== this.cacheSpeed && this.cacheState === 'on') {
296
306
  this.cacheSpeed = speed
297
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
307
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.levelToHK(this.cacheSpeed))
298
308
  if (params.updateSource && this.enableLogging) {
299
309
  this.log(
300
310
  '[%s] %s [%s].',
@@ -318,17 +328,8 @@ export default class {
318
328
 
319
329
  currentState() {
320
330
  const toReturn = {}
321
- let speedLabel
322
- const speed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
323
- if (speed === 0) {
324
- speedLabel = 'off'
325
- } else if (speed <= 1) {
326
- speedLabel = 'low'
327
- } else if (speed <= 2) {
328
- speedLabel = 'medium'
329
- } else {
330
- speedLabel = 'high'
331
- }
331
+ const level = this.levelFromHK(this.service.getCharacteristic(this.hapChar.RotationSpeed).value)
332
+ const speedLabel = level === 0 ? 'off' : this.speed2label[level]
332
333
  toReturn.services = ['fan']
333
334
  toReturn.fan = {
334
335
  state: this.service.getCharacteristic(this.hapChar.On).value ? 'on' : 'off',
@@ -343,6 +344,23 @@ export default class {
343
344
  return toReturn
344
345
  }
345
346
 
347
+ // A HomeKit rotation speed as a fan level, 0 (off) to 3. On the percentage
348
+ // scale 1-33 is low, 34-66 medium and 67-100 high
349
+ levelFromHK(value) {
350
+ if (!this.speedAsPercent) {
351
+ return value < 0 || value > 3 ? 0 : value
352
+ }
353
+ if (value <= 0) {
354
+ return 0
355
+ }
356
+ return value <= 33 ? 1 : value <= 66 ? 2 : 3
357
+ }
358
+
359
+ // A fan level as a HomeKit rotation speed, on whichever scale is in use
360
+ levelToHK(level) {
361
+ return this.speedAsPercent && level ? level * 33 : level
362
+ }
363
+
346
364
  markStatus(isOnline) {
347
365
  this.isOnline = isOnline
348
366
  }
package/lib/platform.js CHANGED
@@ -321,6 +321,7 @@ export default class {
321
321
  case 'resetOnStartup':
322
322
  case 'scaleBattery':
323
323
  case 'showHeatCool':
324
+ case 'speedAsPercent':
324
325
  if (typeof v === 'string') {
325
326
  logQuotes(`${key}.${id}.${k}`)
326
327
  }
@@ -164,6 +164,7 @@ export default {
164
164
  'ignoreDevice',
165
165
  'deviceModel',
166
166
  'hideLight',
167
+ 'speedAsPercent',
167
168
  'ipAddress',
168
169
  ],
169
170
  sensorDevices: [
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "displayName": "Homebridge eWeLink",
4
4
  "alias": "eWeLink",
5
5
  "type": "module",
6
- "version": "12.19.0",
6
+ "version": "12.19.1-beta.1",
7
7
  "description": "Homebridge plugin to integrate eWeLink devices into HomeKit.",
8
8
  "author": {
9
9
  "name": "bwp91",