@homebridge-plugins/homebridge-ewelink 12.18.4-beta.0 → 12.19.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 CHANGED
@@ -2,13 +2,14 @@
2
2
 
3
3
  All notable changes to `@homebridge-plugins/homebridge-ewelink` will be documented in this file.
4
4
 
5
- ## v12.18.4 (Pending Release)
5
+ ## v12.19.0 (2026-10-18)
6
6
 
7
7
  ### Changed
8
8
 
9
9
  - feat(zigbee): add the snzb-03pr2 presence sensor, with occupancy and light level
10
10
  - feat(zigbee): add the snzb-09p indoor siren, as a switch that sounds the alarm
11
11
  - chore(deps): dependency updates
12
+ - fix: match multi-channel updates by outlet, so a partial update changes the right channel (#804)
12
13
 
13
14
  ## v12.18.3 (2026-08-11)
14
15
 
@@ -1,5 +1,5 @@
1
1
  import platformConsts from '../utils/constants.js'
2
- import { hasProperty, sleep } from '../utils/functions.js'
2
+ import { hasProperty, sleep, switchForChannel } from '../utils/functions.js'
3
3
 
4
4
  export default class {
5
5
  constructor(platform, accessory, devicesInHB) {
@@ -327,10 +327,13 @@ export default class {
327
327
  if (this.devicesInHB.has(uuid)) {
328
328
  const subAccessory = this.devicesInHB.get(uuid)
329
329
  const service = subAccessory.getService(this.hapServ.Outlet)
330
- if (params.switches && params.switches[i - 1]) {
330
+ // By `outlet` where the device gives it - a partial update holds only
331
+ // the channel that changed, at position 0 (#804)
332
+ const channelEntry = switchForChannel(params.switches, i - 1)
333
+ if (channelEntry) {
331
334
  const currentState = service.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
332
335
  if (subAccessory.context.isInched) {
333
- if (params.switches[i - 1].switch === 'on' && !subAccessory.context.ignoreUpdates) {
336
+ if (channelEntry.switch === 'on' && !subAccessory.context.ignoreUpdates) {
334
337
  subAccessory.context.ignoreUpdates = true
335
338
  setTimeout(() => {
336
339
  subAccessory.context.ignoreUpdates = false
@@ -349,28 +352,33 @@ export default class {
349
352
  }
350
353
  }
351
354
  } else {
352
- if (params.switches[i - 1].switch === 'on') {
355
+ if (channelEntry.switch === 'on') {
353
356
  primaryState = true
354
357
  }
355
- if (!params.updateSource || params.switches[i - 1].switch !== currentState) {
358
+ if (!params.updateSource || channelEntry.switch !== currentState) {
356
359
  service.updateCharacteristic(
357
360
  this.hapChar.On,
358
- params.switches[i - 1].switch === 'on',
361
+ channelEntry.switch === 'on',
359
362
  )
360
363
  subAccessory.eveService.addEntry({
361
- status: params.switches[i - 1].switch === 'on' ? 1 : 0,
364
+ status: channelEntry.switch === 'on' ? 1 : 0,
362
365
  })
363
366
  if (params.updateSource && this.enableLogging) {
364
367
  this.log(
365
368
  '[%s] %s [%s].',
366
369
  subAccessory.displayName,
367
370
  this.lang.curState,
368
- params.switches[i - 1].switch,
371
+ channelEntry.switch,
369
372
  )
370
373
  }
371
374
  }
372
375
  }
373
376
  }
377
+ // The master tile is on when ANY channel is on, so it has to read every
378
+ // channel's resulting state - a partial update names only one (#804)
379
+ if (service.getCharacteristic(this.hapChar.On).value) {
380
+ primaryState = true
381
+ }
374
382
  if (this.powerReadings) {
375
383
  let logger = false
376
384
  let power
@@ -1,5 +1,5 @@
1
1
  import platformConsts from '../utils/constants.js'
2
- import { hasProperty, sleep } from '../utils/functions.js'
2
+ import { hasProperty, sleep, switchForChannel } from '../utils/functions.js'
3
3
 
4
4
  export default class {
5
5
  constructor(platform, accessory, devicesInHB) {
@@ -322,10 +322,13 @@ export default class {
322
322
  if (this.devicesInHB.has(uuid)) {
323
323
  const subAccessory = this.devicesInHB.get(uuid)
324
324
  const service = subAccessory.getService(this.hapServ.Switch)
325
- if (params.switches && params.switches[i - 1]) {
325
+ // By `outlet` where the device gives it - a partial update holds only
326
+ // the channel that changed, at position 0 (#804)
327
+ const channelEntry = switchForChannel(params.switches, i - 1)
328
+ if (channelEntry) {
326
329
  const currentState = service.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
327
330
  if (subAccessory.context.isInched) {
328
- if (params.switches[i - 1].switch === 'on' && !subAccessory.context.ignoreUpdates) {
331
+ if (channelEntry.switch === 'on' && !subAccessory.context.ignoreUpdates) {
329
332
  subAccessory.context.ignoreUpdates = true
330
333
  setTimeout(() => {
331
334
  subAccessory.context.ignoreUpdates = false
@@ -344,28 +347,33 @@ export default class {
344
347
  }
345
348
  }
346
349
  } else {
347
- if (params.switches[i - 1].switch === 'on') {
350
+ if (channelEntry.switch === 'on') {
348
351
  primaryState = true
349
352
  }
350
- if (!params.updateSource || params.switches[i - 1].switch !== currentState) {
353
+ if (!params.updateSource || channelEntry.switch !== currentState) {
351
354
  service.updateCharacteristic(
352
355
  this.hapChar.On,
353
- params.switches[i - 1].switch === 'on',
356
+ channelEntry.switch === 'on',
354
357
  )
355
358
  subAccessory.eveService.addEntry({
356
- status: params.switches[i - 1].switch === 'on' ? 1 : 0,
359
+ status: channelEntry.switch === 'on' ? 1 : 0,
357
360
  })
358
361
  if (params.updateSource && this.enableLogging) {
359
362
  this.log(
360
363
  '[%s] %s [%s].',
361
364
  subAccessory.displayName,
362
365
  this.lang.curState,
363
- params.switches[i - 1].switch,
366
+ channelEntry.switch,
364
367
  )
365
368
  }
366
369
  }
367
370
  }
368
371
  }
372
+ // The master tile is on when ANY channel is on, so it has to read every
373
+ // channel's resulting state - a partial update names only one (#804)
374
+ if (service.getCharacteristic(this.hapChar.On).value) {
375
+ primaryState = true
376
+ }
369
377
  if (this.powerReadings) {
370
378
  let logger = false
371
379
  let power
@@ -24,6 +24,30 @@ function parseError(err, hideStack = []) {
24
24
  return toReturn
25
25
  }
26
26
 
27
+ /**
28
+ * The entry for one channel in a device's `switches` array.
29
+ *
30
+ * A full report lists every channel in order, so position used to be enough.
31
+ * But some devices (SmartWise two-channel switches, for one) send a partial
32
+ * update holding only the channel that changed - always at position 0, with
33
+ * its real channel in `outlet`. Reading by position then applied channel 2's
34
+ * change to channel 1 (#804). When the entries carry `outlet`, match on it;
35
+ * only a list without `outlet` numbers falls back to position.
36
+ *
37
+ * @param {Array<{outlet?: number, switch: string}>} switches the reported array
38
+ * @param {number} index the zero-based channel wanted
39
+ * @returns {{outlet?: number, switch: string}|undefined} that channel's entry
40
+ */
41
+ function switchForChannel(switches, index) {
42
+ if (!Array.isArray(switches)) {
43
+ return undefined
44
+ }
45
+ if (switches.some(entry => typeof entry?.outlet === 'number')) {
46
+ return switches.find(entry => entry?.outlet === index)
47
+ }
48
+ return switches[index]
49
+ }
50
+
27
51
  function sleep(ms) {
28
52
  return new Promise((resolve) => {
29
53
  setTimeout(resolve, ms)
@@ -36,4 +60,5 @@ export {
36
60
  parseDeviceId,
37
61
  parseError,
38
62
  sleep,
63
+ switchForChannel,
39
64
  }
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.4-beta.0",
6
+ "version": "12.19.0",
7
7
  "description": "Homebridge plugin to integrate eWeLink devices into HomeKit.",
8
8
  "author": {
9
9
  "name": "bwp91",
@@ -69,13 +69,13 @@
69
69
  "@homebridge/plugin-ui-utils": "^2.2.6",
70
70
  "axios": "^1.20.0",
71
71
  "node-persist": "^4.0.4",
72
- "p-queue": "^9.3.3",
72
+ "p-queue": "^10.0.0",
73
73
  "websocket-as-promised": "^3.0.1",
74
- "ws": "^8.21.3"
74
+ "ws": "^8.22.0"
75
75
  },
76
76
  "devDependencies": {
77
- "@antfu/eslint-config": "^9.3.0",
78
- "@vitest/coverage-v8": "^4.1.11",
79
- "vitest": "^4.1.11"
77
+ "@antfu/eslint-config": "^9.5.1",
78
+ "@vitest/coverage-v8": "^5.0.5",
79
+ "vitest": "^5.0.5"
80
80
  }
81
81
  }