@homebridge-plugins/homebridge-ewelink 12.6.2 → 12.7.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
@@ -1,10 +1,27 @@
1
1
  # Change Log
2
2
 
3
- All notable changes to homebridge-ewelink will be documented in this file.
3
+ All notable changes to `@homebridge-plugins/homebridge-ewelink` will be documented in this file.
4
+
5
+ ## v12.8.0 (2025-12-05)
6
+
7
+ ### Changes
8
+
9
+ - feat: add support for Virtual Switch (UIID 264) and Virtual Button (UIID 265)
10
+ - support more devices
11
+ - update readme badges (use `shields.io`)
12
+ - update workflows and npm publish via oidc
13
+ - updated dependencies
14
+
15
+ ## v12.7.0 (2025-09-19)
16
+
17
+ ### Changes
18
+
19
+ - dependency updates
20
+ - added new devices
4
21
 
5
22
  ## v12.6.2 (2025-07-24)
6
23
 
7
- ### Other Changes
24
+ ### Changes
8
25
 
9
26
  - dependency updates
10
27
 
package/CLAUDE.md ADDED
@@ -0,0 +1,144 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ ### Development
8
+ - **Lint code**: `npm run lint`
9
+ - **Fix lint issues**: `npm run lint:fix`
10
+
11
+ ## Architecture
12
+
13
+ ### Core Structure
14
+ This is a Homebridge plugin that integrates eWeLink devices into HomeKit. The main entry point is `lib/index.js` which registers the platform with Homebridge.
15
+
16
+ ### Key Components
17
+
18
+ **Platform Layer** (`lib/platform.js`)
19
+ - Main platform class that coordinates all plugin operations
20
+ - Manages device discovery, initialization, and lifecycle
21
+ - Handles configuration and storage via node-persist
22
+ - Implements request queue with p-queue for rate limiting
23
+ - Routes devices to appropriate handlers based on UIID and configuration
24
+
25
+ **Connection Layer** (`lib/connection/`)
26
+ - `api.js`: Internal HTTP API server for local control
27
+ - `http.js`: eWeLink cloud API client
28
+ - `lan.js`: Local network device control
29
+ - `ws.js`: WebSocket client for real-time updates
30
+
31
+ **Device Layer** (`lib/device/`)
32
+ - Individual device type implementations (switches, lights, sensors, etc.)
33
+ - Each device type extends base functionality with specific HomeKit characteristics
34
+ - Supports various Sonoff/eWeLink device models
35
+ - Device types include:
36
+ - Single/Multi switches with optional power monitoring
37
+ - RGB/CCT/Dimmable lights
38
+ - Sensors (ambient, contact, motion, water)
39
+ - Thermostats, fans, curtains
40
+ - RF Bridge devices
41
+ - Zigbee devices (via bridge)
42
+ - Groups and simulations
43
+
44
+ **Utilities** (`lib/utils/`)
45
+ - `constants.js`: Device configurations and UIID mappings
46
+ - `functions.js`: Shared utility functions
47
+ - `custom-chars.js`: Custom HomeKit characteristics
48
+ - `eve-chars.js`: Eve Home app specific characteristics
49
+ - Language files for internationalization
50
+
51
+ ### Device Identification (UIID)
52
+ Every eWeLink device has a UIID (unique interface ID) that determines its capabilities:
53
+
54
+ **Key UIID Categories** (defined in `lib/utils/constants.js`):
55
+ - `switchSingle`: [1, 6, 14, 24, 27, etc.] - Basic on/off switches
56
+ - `switchSinglePower`: [5, 32] - Switches with power monitoring
57
+ - `switchMulti`: [2, 3, 4, 7, 8, 9, etc.] - Multi-channel switches
58
+ - `lightRGB`: [22] - RGB color lights
59
+ - `lightCCT`: [103] - Color temperature lights
60
+ - `lightRGBCCT`: [33, 59, 104, etc.] - Full color + temperature lights
61
+ - `lightDimmer`: [36, 44, 57] - Dimmable lights
62
+ - `sensorContact`: [102, 154] - Door/window sensors
63
+ - `sensorAmbient`: [15, 181] - Temperature/humidity sensors
64
+ - `thermostat`: [127] - Smart thermostats
65
+ - `rfBridge`: [28, 98] - RF 433MHz bridges
66
+ - `zbBridge`: [66, 128, 168] - Zigbee bridges
67
+ - Zigbee prefixed devices (1000-7000 range)
68
+
69
+ **Device Routing Logic** (in `lib/platform.js:initialiseDevice()`):
70
+ 1. Checks device UIID against category arrays in constants
71
+ 2. Considers user configuration (showAs property for simulations)
72
+ 3. Creates appropriate device handler instance
73
+ 4. Special handling for:
74
+ - Power monitoring devices (different decimal places)
75
+ - Multi-channel devices (individual switches/outlets)
76
+ - Bridge devices (RF/Zigbee sub-devices)
77
+ - Simulated accessories (garage doors, valves, etc.)
78
+
79
+ ### Communication Flow
80
+ 1. Plugin authenticates with eWeLink cloud to fetch device list
81
+ 2. Attempts local (LAN) control first for supported devices
82
+ 3. Falls back to cloud control via HTTP/WebSocket if local fails
83
+ 4. Maintains real-time sync through WebSocket connection
84
+ 5. Exposes optional internal API for external control
85
+
86
+ ### Command & Payload Structure
87
+
88
+ **HTTP Connection** (`lib/connection/http.js`)
89
+ - **Login**: POST to `/v2/user/login`
90
+ - Request: `{email/phoneNumber, password, countryCode}`
91
+ - Response: Returns `at` (access token) and `apikey`
92
+ - Uses HMAC-SHA256 signature with app secret
93
+ - **Get Homes**: GET to `/v2/family`
94
+ - Headers: Bearer token authentication
95
+ - **Get Devices**: GET to `/v2/device` and `/v2/group`
96
+ - Fetches device list per home with full params
97
+
98
+ **WebSocket Connection** (`lib/connection/ws.js`)
99
+ - **Authentication**: Action `userOnline`
100
+ - Payload: `{action: 'userOnline', apikey, appid, at, nonce, sequence, ts, version: 8}`
101
+ - **Device Update**: Action `update`
102
+ - Payload: `{action: 'update', apikey, deviceid, params, sequence, ts: 0, userAgent: 'app'}`
103
+ - Params vary by device type (see below)
104
+ - **Query State**: Action `query`
105
+ - Payload: `{action: 'query', apikey, deviceid, params: [], sequence}`
106
+ - **Heartbeat**: Sends `'ping'` at intervals to keep connection alive
107
+
108
+ **LAN Connection** (`lib/connection/lan.js`)
109
+ - Uses mDNS discovery for `_ewelink._tcp.local` services
110
+ - **Encryption**: AES-128-CBC with MD5 hashed device key
111
+ - **Update Request**: POST to `http://{device_ip}:8081/zeroconf/{suffix}`
112
+ - Suffixes: `switch`, `switches`, `dimmable`, `light`, `fan`, `transmit`, `location`, `monitor`
113
+ - Encrypted payload with IV
114
+ - Data structure: `{data, deviceid, encrypt: true, iv, selfApikey: '123', sequence}`
115
+ - **DNS Monitoring**: Listens for TXT records with encrypted device updates
116
+
117
+ **Common Device Parameters**
118
+ - **Single Switch**: `{switch: 'on'/'off'}`
119
+ - **Multi Switch**: `{switches: [{outlet: 0-3, switch: 'on'/'off'}]}`
120
+ - **SCM Devices**: Uses switches array even for single switch (UIID 77/78/81/107/112/138/160)
121
+ - **Dimmable**: `{brightness: 0-100, mode: 0}`
122
+ - **RGB Light**: `{ltype: 'color'/'white', color: {br, r, g, b}, white: {br, ct}}`
123
+ - **Power Monitor**: `{uiActive: 120}` to request power updates
124
+ - **Curtain/Motor**: `{location: 0-100}` for position
125
+ - **RF Bridge**: `{cmd: 'transmit', rfChl: channel}`
126
+ - **TH10/16**: `{switch, mainSwitch, deviceType: 'normal'}`
127
+ - **Fan (iFan)**: `{light: 'on'/'off', fan: 'on'/'off', speed: 1-3}`
128
+
129
+ **Update Priority**
130
+ 1. Try LAN if device supports it (check `constants.devices.lan` array)
131
+ 2. Fall back to WebSocket if LAN fails
132
+ 3. Queue updates with 250ms intervals to prevent rate limiting
133
+ 4. Power monitoring devices poll every 120 seconds
134
+
135
+ ### State Management
136
+ - Device states cached using node-persist
137
+ - Queue system (p-queue) prevents API rate limiting
138
+ - Fakegato history service for Eve Home app integration
139
+
140
+ ## Important Notes
141
+ - ES modules with Node.js 20/22/24 support required
142
+ - Uses @antfu/eslint-config for code style
143
+ - No test suite - manual testing required
144
+ - Verified Homebridge plugin following official standards
package/README.md CHANGED
@@ -8,11 +8,9 @@
8
8
  Homebridge plugin to integrate eWeLink devices into HomeKit
9
9
 
10
10
  [![npm](https://img.shields.io/npm/v/@homebridge-plugins/homebridge-ewelink/latest?label=latest)](https://www.npmjs.com/package/@homebridge-plugins/homebridge-ewelink)
11
- [![npm](https://img.shields.io/npm/v/@homebridge-plugins/homebridge-ewelink/beta?label=beta)](https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Beta-Version)
12
-
13
- [![verified-by-homebridge](https://badgen.net/badge/homebridge/verified/purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
14
- [![hoobs-certified](https://badgen.net/badge/HOOBS/certified/yellow?label=hoobs)](https://plugins.hoobs.org/plugin/homebridge-ewelink)
15
-
11
+ [![npm](https://img.shields.io/npm/v/@homebridge-plugins/homebridge-ewelink/beta?label=beta)](https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Beta-Version)<br>
12
+ [![verified-by-homebridge](https://img.shields.io/badge/homebridge-verified-blueviolet?color=%23491F59&style=flat)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
13
+ [![hoobs-certified](https://img.shields.io/badge/hoobs-certified-yellow)](https://plugins.hoobs.org/plugin/homebridge-ewelink)<br>
16
14
  [![npm](https://img.shields.io/npm/dt/@homebridge-plugins/homebridge-ewelink)](https://www.npmjs.com/package/@homebridge-plugins/homebridge-ewelink)
17
15
  [![Discord](https://img.shields.io/discord/432663330281226270?color=728ED5&logo=discord&label=hb-discord)](https://discord.com/channels/432663330281226270/742733745743855627)
18
16
 
@@ -242,6 +242,9 @@ export default class {
242
242
  } else if (json.params.uiActive && deviceInfo.uiid === 32) {
243
243
  params.uiActive = json.params.uiActive
244
244
  suffix = 'monitor'
245
+ } else if (json.params.uiActive && [126, 165].includes(deviceInfo.uiid)) {
246
+ // DualR3 uses statistics endpoint in LAN mode
247
+ suffix = 'statistics'
245
248
  } else if (json.params.cmd) {
246
249
  params.cmd = 'transmit'
247
250
  params.rfChl = json.params.rfChl
@@ -0,0 +1,512 @@
1
+ import platformConsts from '../utils/constants.js'
2
+ import { hasProperty, parseError } from '../utils/functions.js'
3
+
4
+ export default class {
5
+ constructor(platform, accessory) {
6
+ // Set up variables from the platform
7
+ this.hapChar = platform.api.hap.Characteristic
8
+ this.hapServ = platform.api.hap.Service
9
+ this.hapErr = platform.api.hap.HapStatusError
10
+ this.lang = platform.lang
11
+ this.log = platform.log
12
+ this.platform = platform
13
+
14
+ // Set up variables from the accessory
15
+ this.name = accessory.displayName
16
+ this.accessory = accessory
17
+
18
+ // Initially set the online flag as true (to be then updated as false if necessary)
19
+ this.isOnline = true
20
+
21
+ // Set up custom variables for this device type
22
+ const deviceConf = platform.deviceConf[accessory.context.eweDeviceId] || {}
23
+ this.tempOffset = deviceConf.offset || platformConsts.defaultValues.offset
24
+ this.tempOffsetFactor = deviceConf.offsetFactor
25
+
26
+ // Set the correct logging variables for this accessory
27
+ switch (deviceConf.overrideLogging) {
28
+ case 'standard':
29
+ this.enableLogging = true
30
+ this.enableDebugLogging = false
31
+ break
32
+ case 'debug':
33
+ this.enableLogging = true
34
+ this.enableDebugLogging = true
35
+ break
36
+ case 'disable':
37
+ this.enableLogging = false
38
+ this.enableDebugLogging = false
39
+ break
40
+ default:
41
+ this.enableLogging = !platform.config.disableDeviceLogging
42
+ this.enableDebugLogging = platform.config.debug
43
+ break
44
+ }
45
+
46
+ // Add the heater cooler service if it doesn't already exist
47
+ this.service = this.accessory.getService(this.hapServ.HeaterCooler)
48
+ || this.accessory.addService(this.hapServ.HeaterCooler)
49
+
50
+ // Set custom properties of the current temperature characteristic
51
+ this.service.getCharacteristic(this.hapChar.CurrentTemperature).setProps({
52
+ minStep: 0.1,
53
+ })
54
+ this.cacheTemp = this.service.getCharacteristic(this.hapChar.CurrentTemperature).value
55
+
56
+ // Add the set handler to the active characteristic
57
+ this.service
58
+ .getCharacteristic(this.hapChar.Active)
59
+ .onSet(async value => this.internalActiveUpdate(value))
60
+
61
+ // Add the set handler to the target heater cooler state characteristic
62
+ this.service
63
+ .getCharacteristic(this.hapChar.TargetHeaterCoolerState)
64
+ .setProps({
65
+ minValue: 0,
66
+ maxValue: 2,
67
+ validValues: [0, 1, 2], // Auto, Heat, Cool
68
+ })
69
+ .onSet(async value => this.internalModeUpdate(value))
70
+
71
+ // Add the set handler to the cooling threshold temperature characteristic
72
+ this.service
73
+ .getCharacteristic(this.hapChar.CoolingThresholdTemperature)
74
+ .setProps({
75
+ minValue: 16,
76
+ maxValue: 32,
77
+ minStep: 1,
78
+ })
79
+ .onSet(async value => this.internalTempUpdate(value, 'cool'))
80
+
81
+ // Add the set handler to the heating threshold temperature characteristic
82
+ this.service
83
+ .getCharacteristic(this.hapChar.HeatingThresholdTemperature)
84
+ .setProps({
85
+ minValue: 16,
86
+ maxValue: 32,
87
+ minStep: 1,
88
+ })
89
+ .onSet(async value => this.internalTempUpdate(value, 'heat'))
90
+
91
+ // Add the set handler to the rotation speed characteristic (for fan speed)
92
+ this.service
93
+ .getCharacteristic(this.hapChar.RotationSpeed)
94
+ .setProps({
95
+ minValue: 0,
96
+ maxValue: 100,
97
+ minStep: 25,
98
+ })
99
+ .onSet(async value => this.internalSpeedUpdate(value))
100
+
101
+ // Add the get handlers only if the user hasn't disabled the disableNoResponse setting
102
+ if (!platform.config.disableNoResponse) {
103
+ this.service.getCharacteristic(this.hapChar.Active).onGet(() => {
104
+ if (!this.isOnline) {
105
+ throw new this.hapErr(-70402)
106
+ }
107
+ return this.service.getCharacteristic(this.hapChar.Active).value
108
+ })
109
+ this.service.getCharacteristic(this.hapChar.TargetHeaterCoolerState).onGet(() => {
110
+ if (!this.isOnline) {
111
+ throw new this.hapErr(-70402)
112
+ }
113
+ return this.service.getCharacteristic(this.hapChar.TargetHeaterCoolerState).value
114
+ })
115
+ this.service.getCharacteristic(this.hapChar.CurrentHeaterCoolerState).onGet(() => {
116
+ if (!this.isOnline) {
117
+ throw new this.hapErr(-70402)
118
+ }
119
+ return this.service.getCharacteristic(this.hapChar.CurrentHeaterCoolerState).value
120
+ })
121
+ this.service.getCharacteristic(this.hapChar.CurrentTemperature).onGet(() => {
122
+ if (!this.isOnline) {
123
+ throw new this.hapErr(-70402)
124
+ }
125
+ return this.service.getCharacteristic(this.hapChar.CurrentTemperature).value
126
+ })
127
+ this.service.getCharacteristic(this.hapChar.CoolingThresholdTemperature).onGet(() => {
128
+ if (!this.isOnline) {
129
+ throw new this.hapErr(-70402)
130
+ }
131
+ return this.service.getCharacteristic(this.hapChar.CoolingThresholdTemperature).value
132
+ })
133
+ this.service.getCharacteristic(this.hapChar.HeatingThresholdTemperature).onGet(() => {
134
+ if (!this.isOnline) {
135
+ throw new this.hapErr(-70402)
136
+ }
137
+ return this.service.getCharacteristic(this.hapChar.HeatingThresholdTemperature).value
138
+ })
139
+ this.service.getCharacteristic(this.hapChar.RotationSpeed).onGet(() => {
140
+ if (!this.isOnline) {
141
+ throw new this.hapErr(-70402)
142
+ }
143
+ return this.service.getCharacteristic(this.hapChar.RotationSpeed).value
144
+ })
145
+ }
146
+
147
+ // Initialise these caches now since they aren't determined by the initial externalUpdate()
148
+ this.cacheTarg = this.service.getCharacteristic(this.hapChar.CoolingThresholdTemperature).value
149
+ this.cacheMode = 'cool'
150
+ this.cacheSpeed = 50
151
+
152
+ // Pass the accessory to Fakegato to set up with Eve
153
+ this.accessory.eveService = new platform.eveService('custom', this.accessory, {
154
+ log: () => {},
155
+ })
156
+
157
+ // Output the customised options to the log
158
+ const normalLogging = this.enableLogging ? 'standard' : 'disable'
159
+ const opts = JSON.stringify({
160
+ logging: this.enableDebugLogging ? 'debug' : normalLogging,
161
+ offset: this.tempOffset,
162
+ offsetFactor: this.tempOffsetFactor,
163
+ })
164
+ this.log('[%s] %s %s.', this.name, this.lang.devInitOpts, opts)
165
+ }
166
+
167
+ async internalActiveUpdate(value) {
168
+ try {
169
+ const newValue = value === 1 ? 'on' : 'off'
170
+
171
+ // Don't continue if the value is the same as before
172
+ const prevState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
173
+ if (prevState === newValue) {
174
+ return
175
+ }
176
+
177
+ // Send the request to the platform sender function
178
+ await this.platform.sendDeviceUpdate(this.accessory, {
179
+ power: newValue,
180
+ })
181
+
182
+ // Update the current state characteristic
183
+ this.service.updateCharacteristic(
184
+ this.hapChar.CurrentHeaterCoolerState,
185
+ value === 1
186
+ ? (this.cacheMode === 'heat'
187
+ ? this.hapChar.CurrentHeaterCoolerState.HEATING
188
+ : this.hapChar.CurrentHeaterCoolerState.COOLING)
189
+ : this.hapChar.CurrentHeaterCoolerState.INACTIVE,
190
+ )
191
+
192
+ // Cache the new state and log if appropriate
193
+ if (this.enableLogging) {
194
+ this.log('[%s] %s [%s].', this.name, this.lang.curState, newValue)
195
+ }
196
+ } catch (err) {
197
+ // Catch any errors during the process
198
+ const eText = parseError(err)
199
+ this.log.warn('[%s] %s %s.', this.name, this.lang.cantCtl, eText)
200
+
201
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
202
+ const prevStateValue = this.service.getCharacteristic(this.hapChar.Active).value
203
+ setTimeout(() => {
204
+ this.service.updateCharacteristic(this.hapChar.Active, prevStateValue)
205
+ }, 2000)
206
+ throw new this.hapErr(-70402)
207
+ }
208
+ }
209
+
210
+ async internalModeUpdate(value) {
211
+ try {
212
+ let newMode
213
+ switch (value) {
214
+ case 0: // Auto
215
+ newMode = 'auto'
216
+ break
217
+ case 1: // Heat
218
+ newMode = 'heat'
219
+ break
220
+ case 2: // Cool
221
+ newMode = 'cool'
222
+ break
223
+ default:
224
+ return
225
+ }
226
+
227
+ // Don't continue if the value is the same as before
228
+ if (this.cacheMode === newMode) {
229
+ return
230
+ }
231
+
232
+ // Send the request to the platform sender function
233
+ await this.platform.sendDeviceUpdate(this.accessory, {
234
+ mode: newMode,
235
+ })
236
+
237
+ // Update the current state characteristic if device is on
238
+ if (this.service.getCharacteristic(this.hapChar.Active).value === 1) {
239
+ this.service.updateCharacteristic(
240
+ this.hapChar.CurrentHeaterCoolerState,
241
+ value === 1
242
+ ? this.hapChar.CurrentHeaterCoolerState.HEATING
243
+ : value === 2
244
+ ? this.hapChar.CurrentHeaterCoolerState.COOLING
245
+ : this.hapChar.CurrentHeaterCoolerState.IDLE,
246
+ )
247
+ }
248
+
249
+ // Cache the new mode and log if appropriate
250
+ this.cacheMode = newMode
251
+ if (this.enableLogging) {
252
+ this.log('[%s] %s [%s].', this.name, this.lang.curMode, newMode)
253
+ }
254
+ } catch (err) {
255
+ // Catch any errors during the process
256
+ const eText = parseError(err)
257
+ this.log.warn('[%s] %s %s.', this.name, this.lang.cantCtl, eText)
258
+
259
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
260
+ setTimeout(() => {
261
+ const prevValue = this.cacheMode === 'heat' ? 1 : this.cacheMode === 'cool' ? 2 : 0
262
+ this.service.updateCharacteristic(this.hapChar.TargetHeaterCoolerState, prevValue)
263
+ }, 2000)
264
+ throw new this.hapErr(-70402)
265
+ }
266
+ }
267
+
268
+ async internalTempUpdate(value, mode) {
269
+ try {
270
+ // Only update if this is the active mode or auto mode
271
+ if (this.cacheMode !== mode && this.cacheMode !== 'auto') {
272
+ return
273
+ }
274
+
275
+ // Don't continue if the value is the same as before
276
+ if (this.cacheTarg === value) {
277
+ return
278
+ }
279
+
280
+ // Send the request to the platform sender function
281
+ await this.platform.sendDeviceUpdate(this.accessory, {
282
+ temperature: value,
283
+ })
284
+
285
+ // Cache the new target temperature and log if appropriate
286
+ this.cacheTarg = value
287
+ if (this.enableLogging) {
288
+ this.log('[%s] %s [%s°C].', this.name, this.lang.tarTemp, value)
289
+ }
290
+ } catch (err) {
291
+ // Catch any errors during the process
292
+ const eText = parseError(err)
293
+ this.log.warn('[%s] %s %s.', this.name, this.lang.cantCtl, eText)
294
+
295
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
296
+ setTimeout(() => {
297
+ if (mode === 'cool') {
298
+ this.service.updateCharacteristic(this.hapChar.CoolingThresholdTemperature, this.cacheTarg)
299
+ } else {
300
+ this.service.updateCharacteristic(this.hapChar.HeatingThresholdTemperature, this.cacheTarg)
301
+ }
302
+ }, 2000)
303
+ throw new this.hapErr(-70402)
304
+ }
305
+ }
306
+
307
+ async internalSpeedUpdate(value) {
308
+ try {
309
+ // Map HomeKit 0-100 to eWeLink wind speed values
310
+ // Assuming 101 = low, 102 = medium, 103 = high
311
+ let windSpeed
312
+ if (value === 0) {
313
+ windSpeed = 0
314
+ } else if (value <= 33) {
315
+ windSpeed = 101
316
+ } else if (value <= 66) {
317
+ windSpeed = 102
318
+ } else {
319
+ windSpeed = 103
320
+ }
321
+
322
+ // Don't continue if the value is the same as before
323
+ if (this.cacheSpeed === windSpeed) {
324
+ return
325
+ }
326
+
327
+ // Send the request to the platform sender function
328
+ await this.platform.sendDeviceUpdate(this.accessory, {
329
+ wind_speed: windSpeed,
330
+ })
331
+
332
+ // Cache the new speed and log if appropriate
333
+ this.cacheSpeed = windSpeed
334
+ if (this.enableLogging) {
335
+ this.log('[%s] %s [%s].', this.name, this.lang.curSpeed, value)
336
+ }
337
+ } catch (err) {
338
+ // Catch any errors during the process
339
+ const eText = parseError(err)
340
+ this.log.warn('[%s] %s %s.', this.name, this.lang.cantCtl, eText)
341
+
342
+ // Map cached speed back to HomeKit value
343
+ let prevValue = 50
344
+ if (this.cacheSpeed === 101) {
345
+ prevValue = 25
346
+ } else if (this.cacheSpeed === 102) {
347
+ prevValue = 50
348
+ } else if (this.cacheSpeed === 103) {
349
+ prevValue = 75
350
+ }
351
+
352
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
353
+ setTimeout(() => {
354
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, prevValue)
355
+ }, 2000)
356
+ throw new this.hapErr(-70402)
357
+ }
358
+ }
359
+
360
+ async externalUpdate(params) {
361
+ try {
362
+ // Check to see if the provided params are valid for this model
363
+ if (
364
+ !hasProperty(params, 'power')
365
+ && !hasProperty(params, 'mode')
366
+ && !hasProperty(params, 'temperature')
367
+ && !hasProperty(params, 'indoor_temperature')
368
+ && !hasProperty(params, 'wind_speed')
369
+ ) {
370
+ throw new Error(this.lang.paramsNotRecd)
371
+ }
372
+
373
+ // Update the active characteristic
374
+ if (hasProperty(params, 'power')) {
375
+ const newState = params.power === 'on' ? 1 : 0
376
+ this.service.updateCharacteristic(this.hapChar.Active, newState)
377
+
378
+ // Update the current state
379
+ this.service.updateCharacteristic(
380
+ this.hapChar.CurrentHeaterCoolerState,
381
+ newState === 1
382
+ ? (this.cacheMode === 'heat'
383
+ ? this.hapChar.CurrentHeaterCoolerState.HEATING
384
+ : this.hapChar.CurrentHeaterCoolerState.COOLING)
385
+ : this.hapChar.CurrentHeaterCoolerState.INACTIVE,
386
+ )
387
+
388
+ // Log the change if appropriate
389
+ if (params.updateSource && this.enableLogging) {
390
+ this.log('[%s] %s [%s].', this.name, this.lang.curState, params.power)
391
+ }
392
+ }
393
+
394
+ // Update the mode characteristic
395
+ if (hasProperty(params, 'mode')) {
396
+ let targetState
397
+ switch (params.mode) {
398
+ case 'auto':
399
+ targetState = 0
400
+ break
401
+ case 'heat':
402
+ targetState = 1
403
+ break
404
+ case 'cool':
405
+ targetState = 2
406
+ break
407
+ default:
408
+ return
409
+ }
410
+
411
+ this.cacheMode = params.mode
412
+ this.service.updateCharacteristic(this.hapChar.TargetHeaterCoolerState, targetState)
413
+
414
+ // Update current state if device is on
415
+ if (this.service.getCharacteristic(this.hapChar.Active).value === 1) {
416
+ this.service.updateCharacteristic(
417
+ this.hapChar.CurrentHeaterCoolerState,
418
+ targetState === 1
419
+ ? this.hapChar.CurrentHeaterCoolerState.HEATING
420
+ : targetState === 2
421
+ ? this.hapChar.CurrentHeaterCoolerState.COOLING
422
+ : this.hapChar.CurrentHeaterCoolerState.IDLE,
423
+ )
424
+ }
425
+
426
+ // Log the change if appropriate
427
+ if (params.updateSource && this.enableLogging) {
428
+ this.log('[%s] %s [%s].', this.name, this.lang.curMode, params.mode)
429
+ }
430
+ }
431
+
432
+ // Update the target temperature
433
+ if (hasProperty(params, 'temperature')) {
434
+ this.cacheTarg = params.temperature
435
+ this.service.updateCharacteristic(this.hapChar.CoolingThresholdTemperature, params.temperature)
436
+ this.service.updateCharacteristic(this.hapChar.HeatingThresholdTemperature, params.temperature)
437
+
438
+ // Log the change if appropriate
439
+ if (params.updateSource && this.enableLogging) {
440
+ this.log('[%s] %s [%s°C].', this.name, this.lang.tarTemp, params.temperature)
441
+ }
442
+ }
443
+
444
+ // Update the current temperature
445
+ if (hasProperty(params, 'indoor_temperature')) {
446
+ let newTemp = Number(params.indoor_temperature)
447
+
448
+ // Apply any configured offset
449
+ if (this.tempOffsetFactor) {
450
+ newTemp *= this.tempOffsetFactor
451
+ }
452
+ newTemp += this.tempOffset
453
+ newTemp = Math.round(newTemp * 10) / 10
454
+
455
+ this.cacheTemp = newTemp
456
+ this.service.updateCharacteristic(this.hapChar.CurrentTemperature, newTemp)
457
+ this.accessory.eveService.addEntry({
458
+ time: Math.round(Date.now() / 1000),
459
+ temp: newTemp,
460
+ })
461
+
462
+ // Log the change if appropriate
463
+ if (params.updateSource && this.enableLogging) {
464
+ this.log('[%s] %s [%s°C].', this.name, this.lang.curTemp, newTemp)
465
+ }
466
+ }
467
+
468
+ // Update the fan speed
469
+ if (hasProperty(params, 'wind_speed')) {
470
+ // Map eWeLink wind speed to HomeKit rotation speed
471
+ let rotationSpeed = 50
472
+ if (params.wind_speed === 0) {
473
+ rotationSpeed = 0
474
+ } else if (params.wind_speed === 101) {
475
+ rotationSpeed = 25
476
+ } else if (params.wind_speed === 102) {
477
+ rotationSpeed = 50
478
+ } else if (params.wind_speed === 103) {
479
+ rotationSpeed = 75
480
+ }
481
+
482
+ this.cacheSpeed = params.wind_speed
483
+ this.service.updateCharacteristic(this.hapChar.RotationSpeed, rotationSpeed)
484
+
485
+ // Log the change if appropriate
486
+ if (params.updateSource && this.enableLogging) {
487
+ this.log('[%s] %s [%s].', this.name, this.lang.curSpeed, rotationSpeed)
488
+ }
489
+ }
490
+ } catch (err) {
491
+ // Catch any errors during the process
492
+ const eText = parseError(err)
493
+ this.log.warn('[%s] %s %s.', this.name, this.lang.cantUpd, eText)
494
+ }
495
+ }
496
+
497
+ async currentState() {
498
+ const toReturn = {}
499
+ toReturn.services = ['heater-cooler']
500
+ toReturn.heater_cooler = {
501
+ state: this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off',
502
+ mode: this.cacheMode,
503
+ target_temp: this.cacheTarg,
504
+ current_temp: this.cacheTemp,
505
+ }
506
+ return toReturn
507
+ }
508
+
509
+ markStatus(isOnline) {
510
+ this.isOnline = isOnline
511
+ }
512
+ }
@@ -1,3 +1,4 @@
1
+ import deviceAirConditioner from './air-conditioner.js'
1
2
  import deviceCurtain from './curtain.js'
2
3
  import deviceDiffuser from './diffuser.js'
3
4
  import deviceFan from './fan.js'
@@ -26,9 +27,11 @@ import deviceSwitchSingleInched from './switch-single-inched.js'
26
27
  import deviceSwitchSingle from './switch-single.js'
27
28
  import deviceTemplate from './template.js'
28
29
  import deviceThermostat from './thermostat.js'
30
+ import deviceVirtual from './virtual.js'
29
31
  import zb from './zigbee/index.js'
30
32
 
31
33
  export default {
34
+ deviceAirConditioner,
32
35
  deviceCurtain,
33
36
  deviceDiffuser,
34
37
  deviceFan,
@@ -56,6 +59,7 @@ export default {
56
59
  deviceSwitchSingleInched,
57
60
  deviceTemplate,
58
61
  deviceThermostat,
62
+ deviceVirtual,
59
63
  sim,
60
64
  zb,
61
65
  }
@@ -0,0 +1,270 @@
1
+ import { hasProperty, parseError } from '../utils/functions.js'
2
+
3
+ export default class {
4
+ constructor(platform, accessory) {
5
+ // Set up variables from the platform
6
+ this.hapChar = platform.api.hap.Characteristic
7
+ this.hapServ = platform.api.hap.Service
8
+ this.hapErr = platform.api.hap.HapStatusError
9
+ this.lang = platform.lang
10
+ this.log = platform.log
11
+ this.platform = platform
12
+
13
+ // Set up variables from the accessory
14
+ this.name = accessory.displayName
15
+ this.accessory = accessory
16
+
17
+ // Initially set the online flag as true (to be then updated as false if necessary)
18
+ this.isOnline = true
19
+
20
+ // Set up custom variables for this device type
21
+ const deviceConf = platform.deviceConf[accessory.context.eweDeviceId] || {}
22
+
23
+ // Set the correct logging variables for this accessory
24
+ switch (deviceConf.overrideLogging) {
25
+ case 'standard':
26
+ this.enableLogging = true
27
+ this.enableDebugLogging = false
28
+ break
29
+ case 'debug':
30
+ this.enableLogging = true
31
+ this.enableDebugLogging = true
32
+ break
33
+ case 'disable':
34
+ this.enableLogging = false
35
+ this.enableDebugLogging = false
36
+ break
37
+ default:
38
+ this.enableLogging = !platform.config.disableDeviceLogging
39
+ this.enableDebugLogging = platform.config.debug
40
+ break
41
+ }
42
+
43
+ // Check device params to determine if it's a button or switch
44
+ this.isButton = hasProperty(accessory.context.eweParams, 'key')
45
+
46
+ if (this.isButton) {
47
+ // Virtual Button - use StatelessProgrammableSwitch
48
+ this.service = this.accessory.getService(this.hapServ.StatelessProgrammableSwitch)
49
+ || this.accessory.addService(this.hapServ.StatelessProgrammableSwitch)
50
+
51
+ // Set up the button with single, double, and long press
52
+ this.service.getCharacteristic(this.hapChar.ProgrammableSwitchEvent).setProps({
53
+ minValue: 0,
54
+ maxValue: 2,
55
+ validValues: [0, 1, 2], // Single, Double, Long press
56
+ })
57
+
58
+ // Remove switch service if it exists from previous setup
59
+ if (this.accessory.getService(this.hapServ.Switch)) {
60
+ this.accessory.removeService(this.accessory.getService(this.hapServ.Switch))
61
+ }
62
+ } else {
63
+ // Virtual Switch - use standard Switch service
64
+ this.service = this.accessory.getService(this.hapServ.Switch)
65
+ || this.accessory.addService(this.hapServ.Switch)
66
+
67
+ // Add the set handler to the switch on/off characteristic
68
+ this.service
69
+ .getCharacteristic(this.hapChar.On)
70
+ .onSet(async value => this.internalStateUpdate(value))
71
+
72
+ // Add the get handler only if the user hasn't disabled the disableNoResponse setting
73
+ if (!platform.config.disableNoResponse) {
74
+ this.service.getCharacteristic(this.hapChar.On).onGet(() => {
75
+ if (!this.isOnline) {
76
+ throw new this.hapErr(-70402)
77
+ }
78
+ return this.service.getCharacteristic(this.hapChar.On).value
79
+ })
80
+ }
81
+
82
+ // Remove button service if it exists from previous setup
83
+ if (this.accessory.getService(this.hapServ.StatelessProgrammableSwitch)) {
84
+ this.accessory.removeService(this.accessory.getService(this.hapServ.StatelessProgrammableSwitch))
85
+ }
86
+ }
87
+
88
+ // Pass the accessory to Fakegato to set up with Eve
89
+ this.accessory.eveService = new platform.eveService('switch', this.accessory, {
90
+ log: () => {},
91
+ })
92
+
93
+ // Output the customised options to the log
94
+ const normalLogging = this.enableLogging ? 'standard' : 'disable'
95
+ const opts = JSON.stringify({
96
+ logging: this.enableDebugLogging ? 'debug' : normalLogging,
97
+ type: this.isButton ? 'button' : 'switch',
98
+ })
99
+ this.log('[%s] %s %s.', this.name, this.lang.devInitOpts, opts)
100
+ }
101
+
102
+ async internalStateUpdate(value) {
103
+ try {
104
+ // This is only for virtual switches, not buttons
105
+ if (this.isButton) {
106
+ return
107
+ }
108
+
109
+ const newValue = value ? 'on' : 'off'
110
+
111
+ // Don't continue if the value is the same as before
112
+ const prevState = this.service.getCharacteristic(this.hapChar.On).value
113
+ if (prevState === value) {
114
+ return
115
+ }
116
+
117
+ // Send the request to the platform sender function
118
+ await this.platform.sendDeviceUpdate(this.accessory, {
119
+ switches: [
120
+ {
121
+ outlet: 0,
122
+ switch: newValue,
123
+ },
124
+ ],
125
+ })
126
+
127
+ // Cache the new state and log if appropriate
128
+ if (this.enableLogging) {
129
+ this.accessory.eveService.addEntry({
130
+ time: Math.round(Date.now() / 1000),
131
+ status: value ? 1 : 0,
132
+ })
133
+ this.log('[%s] %s [%s].', this.name, this.lang.curState, newValue)
134
+ }
135
+ } catch (err) {
136
+ // Catch any errors during the process
137
+ const eText = parseError(err)
138
+ this.log.warn('[%s] %s %s.', this.name, this.lang.cantCtl, eText)
139
+
140
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
141
+ setTimeout(() => {
142
+ this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
143
+ }, 2000)
144
+ throw new this.hapErr(-70402)
145
+ }
146
+ }
147
+
148
+ async externalUpdate(params) {
149
+ try {
150
+ if (this.isButton) {
151
+ // Handle button press events
152
+ if (hasProperty(params, 'key')) {
153
+ // Map key values to HomeKit button events
154
+ let buttonEvent
155
+ switch (params.key) {
156
+ case 0:
157
+ buttonEvent = this.hapChar.ProgrammableSwitchEvent.SINGLE_PRESS
158
+ break
159
+ case 1:
160
+ buttonEvent = this.hapChar.ProgrammableSwitchEvent.DOUBLE_PRESS
161
+ break
162
+ case 2:
163
+ buttonEvent = this.hapChar.ProgrammableSwitchEvent.LONG_PRESS
164
+ break
165
+ default:
166
+ // Unknown key value, default to single press
167
+ buttonEvent = this.hapChar.ProgrammableSwitchEvent.SINGLE_PRESS
168
+ }
169
+
170
+ // Update the button event characteristic
171
+ this.service.updateCharacteristic(this.hapChar.ProgrammableSwitchEvent, buttonEvent)
172
+
173
+ // Log the button press if appropriate
174
+ if (params.updateSource && this.enableLogging) {
175
+ const pressType = params.key === 0 ? 'single' : params.key === 1 ? 'double' : 'long'
176
+ this.log('[%s] %s [%s press].', this.name, this.lang.curState, pressType)
177
+ }
178
+ }
179
+
180
+ // Handle trigger time if provided
181
+ if (hasProperty(params, 'trigTime')) {
182
+ this.lastTriggerTime = params.trigTime
183
+ if (this.enableDebugLogging) {
184
+ this.log('[%s] Button triggered at %s.', this.name, new Date(Number.parseInt(params.trigTime)).toLocaleString())
185
+ }
186
+ }
187
+ } else {
188
+ // Handle virtual switch updates
189
+ if (hasProperty(params, 'switches') && Array.isArray(params.switches)) {
190
+ params.switches.forEach((outlet) => {
191
+ // Check this is the primary outlet
192
+ if (outlet.outlet !== 0) {
193
+ return
194
+ }
195
+
196
+ // Check we have a valid switch value
197
+ if (!hasProperty(outlet, 'switch')) {
198
+ return
199
+ }
200
+
201
+ const newState = outlet.switch === 'on'
202
+ const prevState = this.service.getCharacteristic(this.hapChar.On).value
203
+ if (prevState === newState) {
204
+ return
205
+ }
206
+
207
+ this.service.updateCharacteristic(this.hapChar.On, newState)
208
+ this.cacheState = outlet.switch
209
+
210
+ // Add Eve history entry
211
+ this.accessory.eveService.addEntry({
212
+ time: Math.round(Date.now() / 1000),
213
+ status: newState ? 1 : 0,
214
+ })
215
+
216
+ // Log the change if appropriate
217
+ if (params.updateSource && this.enableLogging) {
218
+ this.log('[%s] %s [%s].', this.name, this.lang.curState, outlet.switch)
219
+ }
220
+ })
221
+ }
222
+
223
+ // Also handle single switch param for compatibility
224
+ if (hasProperty(params, 'switch')) {
225
+ const newState = params.switch === 'on'
226
+ const prevState = this.service.getCharacteristic(this.hapChar.On).value
227
+ if (prevState !== newState) {
228
+ this.service.updateCharacteristic(this.hapChar.On, newState)
229
+ this.cacheState = params.switch
230
+
231
+ // Add Eve history entry
232
+ this.accessory.eveService.addEntry({
233
+ time: Math.round(Date.now() / 1000),
234
+ status: newState ? 1 : 0,
235
+ })
236
+
237
+ // Log the change if appropriate
238
+ if (params.updateSource && this.enableLogging) {
239
+ this.log('[%s] %s [%s].', this.name, this.lang.curState, params.switch)
240
+ }
241
+ }
242
+ }
243
+ }
244
+ } catch (err) {
245
+ // Catch any errors during the process
246
+ const eText = parseError(err)
247
+ this.log.warn('[%s] %s %s.', this.name, this.lang.cantUpd, eText)
248
+ }
249
+ }
250
+
251
+ async currentState() {
252
+ const toReturn = {}
253
+ if (this.isButton) {
254
+ toReturn.services = ['button']
255
+ toReturn.button = {
256
+ last_trigger: this.lastTriggerTime || 'none',
257
+ }
258
+ } else {
259
+ toReturn.services = ['switch']
260
+ toReturn.switch = {
261
+ state: this.cacheState || 'off',
262
+ }
263
+ }
264
+ return toReturn
265
+ }
266
+
267
+ markStatus(isOnline) {
268
+ this.isOnline = isOnline
269
+ }
270
+ }
package/lib/platform.js CHANGED
@@ -1106,6 +1106,26 @@ export default class {
1106
1106
  this.applyAccessoryLogging(accessory)
1107
1107
  accessory.control = new deviceTypes.deviceThermostat(this, accessory)
1108
1108
  /** */
1109
+ } else if (platformConsts.devices.airConditioner.includes(uiid)) {
1110
+ /**
1111
+ ***************
1112
+ AIR CONDITIONERS
1113
+ ****************
1114
+ */
1115
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1116
+ this.applyAccessoryLogging(accessory)
1117
+ accessory.control = new deviceTypes.deviceAirConditioner(this, accessory)
1118
+ /** */
1119
+ } else if (platformConsts.devices.virtual.includes(uiid)) {
1120
+ /**
1121
+ ***************
1122
+ VIRTUAL DEVICES
1123
+ ****************
1124
+ */
1125
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1126
+ this.applyAccessoryLogging(accessory)
1127
+ accessory.control = new deviceTypes.deviceVirtual(this, accessory)
1128
+ /** */
1109
1129
  } else if (
1110
1130
  device.showAs === 'thermostat'
1111
1131
  && platformConsts.devices.sensorAmbient.includes(uiid)
@@ -284,8 +284,8 @@ export default {
284
284
  211,
285
285
  212,
286
286
  ],
287
- switchSingle: [1, 6, 14, 24, 27, 226, 1009, 1256, 7004, 7005, 7010],
288
- switchSCM: [77, 78, 81, 107, 112, 138, 160, 191, 209],
287
+ switchSingle: [1, 6, 14, 24, 27, 225, 226, 1009, 1256, 7004, 7005, 7010, 7032],
288
+ switchSCM: [77, 78, 81, 107, 112, 138, 160, 191, 209, 264],
289
289
  switchSCMPower: [182, 190],
290
290
  switchSinglePower: [5, 32],
291
291
  switchMulti: [
@@ -312,23 +312,26 @@ export default {
312
312
  210,
313
313
  211,
314
314
  212,
315
+ 262,
315
316
  2256,
316
317
  3256,
317
318
  4256,
318
319
  ],
319
- switchMultiPower: [126, 165],
320
+ switchMultiPower: [126, 165, 262],
320
321
  switchMate: [177],
321
322
  switchMan: [174],
322
323
  lightDimmer: [36, 44, 57],
323
324
  lightRGB: [22],
324
325
  lightCCT: [103],
325
326
  lightRGBCCT: [33, 59, 104, 135, 136, 137, 173],
326
- curtain: [11, 67],
327
+ curtain: [11, 67, 91, 258],
327
328
  sensorContact: [102, 154],
328
329
  sensorAmbient: [15, 181],
329
330
  sensorTempHumi: [18],
330
331
  thermostat: [127],
332
+ airConditioner: [151],
331
333
  panel: [133, 195],
334
+ virtual: [265],
332
335
  fan: [34],
333
336
  humidifier: [19],
334
337
  diffuser: [25],
@@ -344,7 +347,7 @@ export default {
344
347
  zbSensorMotion: [2026, 7002],
345
348
  zbSensorOccupancy: [7016],
346
349
  zbSensorContact: [3026, 7003],
347
- zbSensorWater: [4026],
350
+ zbSensorWater: [4026, 7019],
348
351
  zbSensorSmoke: [5026],
349
352
  zbThermostat: [7017],
350
353
  zbWaterValve: [7027],
@@ -468,6 +471,7 @@ export default {
468
471
  83: 3, // "GSM_SOCKET_3_NO_FLOW"
469
472
  84: 4, // "GSM_SOCKET_4_NO_FLOW"
470
473
  87: 1, // "EWELINK_IOT_CAMERA" (GK-200MP2B)
474
+ 91: 1, // "ROLLING_DOOR" (Cover/Curtain device)
471
475
  98: 1, // "DOORBELL_RFBRIDGE"
472
476
  102: 1, // "DOOR_MAGNETIC" (OPL-DMA, DW2)
473
477
  103: 1, // "WOTEWODE_TEM_LIGHT" (B02-F)
@@ -506,8 +510,11 @@ export default {
506
510
  210: 2, // Sonoff Ultimate Switch T5-2C-86
507
511
  211: 3, // Sonoff Ultimate Switch T5-3C-86
508
512
  212: 4, // Sonoff Ultimate Switch T5-4C-86
513
+ 225: 1, // CK-BL602-PCSW-01 (Single switch with power monitoring)
509
514
  226: 1, // https://github.com/homebridge-plugins/homebridge-ewelink/issues/636
510
515
  243: 1, // Zigbee Bridge Ultra
516
+ 258: 1, // Additional Cover device
517
+ 262: 4, // CK-BL602-SWP1-02 (4-channel switch with power monitoring)
511
518
  1000: 1, // "zigbee_ON_OFF_SWITCH_1000" (button device)
512
519
  1009: 1, // "" (Some sort of single switch device)
513
520
  1256: 1, // "ZIGBEE_SINGLE_SWITCH"
@@ -535,7 +542,9 @@ export default {
535
542
  7014: 1, // some sensor https://github.com/homebridge-plugins/homebridge-ewelink/issues/494
536
543
  7016: 1, // https://sonoff.tech/product/gateway-and-sensors/snzb-06p/
537
544
  7017: 1, // https://github.com/homebridge-plugins/homebridge-ewelink/issues/518
545
+ 7019: 1, // Zigbee Water Sensor
538
546
  7027: 1, // Zigbee Smart Water Valve
547
+ 7032: 1, // Zigbee Power Monitoring Switch
539
548
  },
540
549
 
541
550
  cannotSupportDevices: {
@@ -582,7 +591,6 @@ export default {
582
591
  88: 0, // "YK_INFRARED"
583
592
  89: 0, // "SMART_OPEN_MACHINE"
584
593
  90: 0, // "GSM_RFBridge"
585
- 91: 0, // "ROLLING_DOOR_91"
586
594
  93: 0, // "HTHD_AIR_CLEANER"
587
595
  94: 0, // "YIAN_ELECTRIC_PROTECT"
588
596
  109: 0, // "YK_INFRARED_2"
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "displayName": "Homebridge eWeLink",
4
4
  "alias": "eWeLink",
5
5
  "type": "module",
6
- "version": "12.6.2",
6
+ "version": "12.7.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.1.0",
65
- "axios": "^1.11.0",
64
+ "@homebridge/plugin-ui-utils": "^2.1.2",
65
+ "axios": "^1.13.2",
66
66
  "node-persist": "^4.0.4",
67
- "p-queue": "^8.1.0",
67
+ "p-queue": "^9.0.1",
68
68
  "websocket-as-promised": "^3.0.1",
69
69
  "ws": "^8.18.3"
70
70
  },
71
71
  "devDependencies": {
72
- "@antfu/eslint-config": "^4.19.0"
72
+ "@antfu/eslint-config": "^6.4.2"
73
73
  }
74
74
  }