@homebridge-plugins/homebridge-ewelink 12.14.0 → 12.14.1-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 CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to `@homebridge-plugins/homebridge-ewelink` will be documented in this file.
4
4
 
5
+ ## v12.14.1 (Pending Release)
6
+
7
+ - fix(garage-one): send single-outlet payload on switchSCM devices (#774) (@nico81)
8
+ - fix(ws): do not retry sendUpdate on timeout (#775) (@nico81)
9
+ - fix(platform): do not serialize hidden accessories to the cache
10
+ - chore: dependency updates
11
+
5
12
  ## v12.14.0 (2026-05-05)
6
13
 
7
14
  ### Changed
@@ -322,7 +322,7 @@ export default class {
322
322
  }
323
323
  }
324
324
 
325
- async sendUpdate(json, retries = 0) {
325
+ async sendUpdate(json) {
326
326
  // Generate the payload to send
327
327
  const toSend = {
328
328
  ...json,
@@ -368,13 +368,11 @@ export default class {
368
368
  throw new Error(`${this.lang.wsUnkRes} [${res.error}]`)
369
369
  }
370
370
  } catch (err) {
371
- // Retry up to 3 times on timeout errors only
372
- const errMsg = parseError(err)
373
- if (retries < 3 && (errMsg.includes('504') || errMsg.includes('timeout') || errMsg.includes('Timeout'))) {
374
- await sleep(1000)
375
- return this.sendUpdate(json, retries + 1)
376
- }
377
- throw new Error(errMsg)
371
+ // Do not retry on timeout: the original command may have already been
372
+ // applied by the device but the cloud failed to deliver a formal ACK.
373
+ // Retrying non-idempotent writes (e.g. inching/momentary actuators)
374
+ // produces additional physical pulses, causing actuators to bounce.
375
+ throw new Error(parseError(err))
378
376
  }
379
377
  } else {
380
378
  // The web socket isn't currently open so log this
@@ -205,21 +205,33 @@ export default class {
205
205
  await sleep(delay)
206
206
  this.service.updateCharacteristic(this.hapChar.TargetDoorState, newPos)
207
207
  this.service.updateCharacteristic(this.hapChar.CurrentDoorState, newPos + 2)
208
+ // SCM (single-channel multi-format) devices use the switches[] array
209
+ // but only expose outlet 0. True dual-channel garage controllers
210
+ // (e.g. DUALR3) use separate outlets for open/close relay action.
211
+ const isScm = platformConsts.devices.switchSCM.includes(this.accessory.context.eweUIID)
212
+ || platformConsts.devices.switchSCMPower.includes(this.accessory.context.eweUIID)
213
+
208
214
  switch (this.setup) {
209
215
  case 'switchSingle':
210
216
  params.switch = 'on'
211
217
  break
212
218
  case 'switchMulti':
213
- params.switches = [
214
- {
215
- switch: newPos === 0 ? 'on' : 'off',
216
- outlet: 0,
217
- },
218
- {
219
- switch: newPos === 1 ? 'on' : 'off',
220
- outlet: 1,
221
- },
222
- ]
219
+ if (isScm) {
220
+ // SCM device: send a single momentary pulse on outlet 0
221
+ params.switches = [{ switch: 'on', outlet: 0 }]
222
+ } else {
223
+ // Dual-channel device with separate open/close relays
224
+ params.switches = [
225
+ {
226
+ switch: newPos === 0 ? 'on' : 'off',
227
+ outlet: 0,
228
+ },
229
+ {
230
+ switch: newPos === 1 ? 'on' : 'off',
231
+ outlet: 1,
232
+ },
233
+ ]
234
+ }
223
235
  break
224
236
  default:
225
237
  return
package/lib/platform.js CHANGED
@@ -1427,8 +1427,7 @@ export default class {
1427
1427
  }
1428
1428
 
1429
1429
  // Update any changes to the sub accessory to the platform
1430
- this.api.updatePlatformAccessories([subAccessory])
1431
- devicesInHB.set(subAccessory.UUID, subAccessory)
1430
+ this.saveAccessoryUpdate(subAccessory)
1432
1431
  if (i === 0) {
1433
1432
  accessory = subAccessory
1434
1433
  }
@@ -1567,8 +1566,7 @@ export default class {
1567
1566
  }
1568
1567
 
1569
1568
  // Update any changes to the sub accessory to the platform
1570
- this.api.updatePlatformAccessories([subAccessory])
1571
- devicesInHB.set(subAccessory.UUID, subAccessory)
1569
+ this.saveAccessoryUpdate(subAccessory)
1572
1570
 
1573
1571
  // Increment the counter
1574
1572
  rfChlCounter += Object.keys(subDevice.buttons || {}).length
@@ -1826,8 +1824,7 @@ export default class {
1826
1824
  }
1827
1825
 
1828
1826
  // Update any changes to the device into our devicesInHB map
1829
- this.api.updatePlatformAccessories([accessory])
1830
- devicesInHB.set(accessory.UUID, accessory)
1827
+ this.saveAccessoryUpdate(accessory)
1831
1828
  if (uiid === 5000) {
1832
1829
  this.log(
1833
1830
  '[%s] %s [%s] [%s].',
@@ -1947,6 +1944,20 @@ export default class {
1947
1944
  }
1948
1945
  }
1949
1946
 
1947
+ saveAccessoryUpdate(accessory) {
1948
+ // Persist any context/characteristic changes to Homebridge, then keep the
1949
+ // accessory in our in-memory map. Hidden accessories are deliberately never
1950
+ // registered (see addAccessory), so they have no associated plugin and would
1951
+ // make Homebridge throw when it serialises its accessory cache to disk
1952
+ // ('Cannot serialize accessory ... - missing associated plugin'). We still
1953
+ // keep them in devicesInHB so lookups (e.g. the SW0 master of a multi-channel
1954
+ // device) continue to work.
1955
+ if (!accessory.context.hidden) {
1956
+ this.api.updatePlatformAccessories([accessory])
1957
+ }
1958
+ devicesInHB.set(accessory.UUID, accessory)
1959
+ }
1960
+
1950
1961
  addExternalAccessory(device, hbDeviceId, category) {
1951
1962
  try {
1952
1963
  // Add the new accessory to Homebridge
@@ -2092,8 +2103,7 @@ export default class {
2092
2103
  // The update is from WS so update the WS online/offline status
2093
2104
  if (device.params.online !== accessory.context.reachableWAN) {
2094
2105
  accessory.context.reachableWAN = device.params.online
2095
- this.api.updatePlatformAccessories([accessory])
2096
- devicesInHB.set(accessory.UUID, accessory)
2106
+ this.saveAccessoryUpdate(accessory)
2097
2107
 
2098
2108
  // Flag this true to update the sub accessories later
2099
2109
  reachableChange = true
@@ -2154,8 +2164,7 @@ export default class {
2154
2164
 
2155
2165
  // Update the accessory context if the device is back online or the IP changed
2156
2166
  if (reachableChange) {
2157
- this.api.updatePlatformAccessories([accessory])
2158
- devicesInHB.set(accessory.UUID, accessory)
2167
+ this.saveAccessoryUpdate(accessory)
2159
2168
  }
2160
2169
  }
2161
2170
 
@@ -2183,8 +2192,7 @@ export default class {
2183
2192
  }
2184
2193
 
2185
2194
  // Save the sub accessory updates to the platform
2186
- this.api.updatePlatformAccessories([subAccessory])
2187
- devicesInHB.set(subAccessory.UUID, subAccessory)
2195
+ this.saveAccessoryUpdate(subAccessory)
2188
2196
  }
2189
2197
  }
2190
2198
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "displayName": "Homebridge eWeLink",
4
4
  "alias": "eWeLink",
5
5
  "type": "module",
6
- "version": "12.14.0",
6
+ "version": "12.14.1-beta.0",
7
7
  "description": "Homebridge plugin to integrate eWeLink devices into HomeKit.",
8
8
  "author": {
9
9
  "name": "bwp91",
@@ -61,14 +61,14 @@
61
61
  "lint:fix": "npm run lint -- --fix"
62
62
  },
63
63
  "dependencies": {
64
- "@homebridge/plugin-ui-utils": "^2.2.3",
65
- "axios": "^1.16.0",
64
+ "@homebridge/plugin-ui-utils": "^2.2.4",
65
+ "axios": "^1.17.0",
66
66
  "node-persist": "^4.0.4",
67
- "p-queue": "^9.2.0",
67
+ "p-queue": "^9.3.0",
68
68
  "websocket-as-promised": "^3.0.1",
69
- "ws": "^8.20.0"
69
+ "ws": "^8.21.0"
70
70
  },
71
71
  "devDependencies": {
72
- "@antfu/eslint-config": "^8.2.0"
72
+ "@antfu/eslint-config": "^9.0.0"
73
73
  }
74
74
  }