@homebridge-plugins/homebridge-ewelink 12.7.1-beta.0 → 12.8.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,15 @@
2
2
 
3
3
  All notable changes to `@homebridge-plugins/homebridge-ewelink` will be documented in this file.
4
4
 
5
+ ## v12.8.1 (Unreleased)
6
+
7
+ ### Changes
8
+
9
+ - determine debug mode from `-D` flag
10
+ - updated dependencies
11
+ - feat: adds support for SAWF-08P (#729) (@say4n)
12
+ - updated dependencies + lint rules
13
+
5
14
  ## v12.8.0 (2025-12-05)
6
15
 
7
16
  ### Changes
package/eslint.config.js CHANGED
@@ -18,19 +18,18 @@ export default antfu(
18
18
  'error',
19
19
  {
20
20
  groups: [
21
- 'type',
22
- 'internal-type',
21
+ ['type-builtin', 'type-external', 'type-internal'],
22
+ ['type-parent', 'type-sibling', 'type-index'],
23
23
  'builtin',
24
24
  'external',
25
25
  'internal',
26
- ['parent-type', 'sibling-type', 'index-type'],
27
26
  ['parent', 'sibling', 'index'],
28
- 'object',
27
+ 'side-effect',
29
28
  'unknown',
30
29
  ],
31
30
  order: 'asc',
32
31
  type: 'natural',
33
- newlinesBetween: 'always',
32
+ newlinesBetween: 1,
34
33
  },
35
34
  ],
36
35
  'perfectionist/sort-named-exports': 'error',
@@ -16,6 +16,7 @@ import devicePanel from './panel.js'
16
16
  import deviceRfBridge from './rf-bridge.js'
17
17
  import deviceRfButton from './rf-button.js'
18
18
  import deviceRfSensor from './rf-sensor.js'
19
+ import deviceSensorAirQuality from './sensor-air-quality.js'
19
20
  import deviceSensorAmbient from './sensor-ambient.js'
20
21
  import deviceSensorContact from './sensor-contact.js'
21
22
  import deviceSensorTempHumi from './sensor-temp-humi.js'
@@ -49,6 +50,7 @@ export default {
49
50
  deviceRfBridge,
50
51
  deviceRfButton,
51
52
  deviceRfSensor,
53
+ deviceSensorAirQuality,
52
54
  deviceSensorAmbient,
53
55
  deviceSensorContact,
54
56
  deviceSensorTempHumi,
@@ -0,0 +1,252 @@
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
+ this.humiOffset = deviceConf && deviceConf.humidityOffset
26
+ ? Number.parseInt(deviceConf.humidityOffset, 10)
27
+ : platformConsts.defaultValues.humidityOffset
28
+ this.humiOffsetFactor = deviceConf.humidityOffsetFactor
29
+
30
+ // Set the correct logging variables for this accessory
31
+ switch (deviceConf.overrideLogging) {
32
+ case 'standard':
33
+ this.enableLogging = true
34
+ this.enableDebugLogging = false
35
+ break
36
+ case 'debug':
37
+ this.enableLogging = true
38
+ this.enableDebugLogging = true
39
+ break
40
+ case 'disable':
41
+ this.enableLogging = false
42
+ this.enableDebugLogging = false
43
+ break
44
+ default:
45
+ this.enableLogging = !platform.config.disableDeviceLogging
46
+ this.enableDebugLogging = platform.config.debug
47
+ break
48
+ }
49
+
50
+ // Add the temperature sensor service if it doesn't already exist
51
+ this.tempService = this.accessory.getService(this.hapServ.TemperatureSensor)
52
+ || this.accessory.addService(this.hapServ.TemperatureSensor)
53
+
54
+ // Set custom properties of the current temperature characteristic
55
+ this.tempService.getCharacteristic(this.hapChar.CurrentTemperature).setProps({
56
+ minStep: 0.1,
57
+ })
58
+ this.cacheTemp = this.tempService.getCharacteristic(this.hapChar.CurrentTemperature).value
59
+ this.updateCache()
60
+
61
+ // Add the humidity sensor service if it doesn't already exist
62
+ this.humiService = this.accessory.getService(this.hapServ.HumiditySensor)
63
+ || this.accessory.addService(this.hapServ.HumiditySensor)
64
+ this.cacheHumi = this.humiService.getCharacteristic(this.hapChar.CurrentRelativeHumidity).value
65
+
66
+ // Add the air quality sensor service if it doesn't already exist
67
+ this.airQualityService = this.accessory.getService(this.hapServ.AirQualitySensor)
68
+ || this.accessory.addService(this.hapServ.AirQualitySensor)
69
+
70
+ // Add the carbon dioxide sensor service if it doesn't already exist
71
+ this.co2Service = this.accessory.getService(this.hapServ.CarbonDioxideSensor)
72
+ || this.accessory.addService(this.hapServ.CarbonDioxideSensor)
73
+ this.cacheCO2 = this.co2Service.getCharacteristic(this.hapChar.CarbonDioxideLevel).value
74
+
75
+ // Add the get handlers only if the user hasn't disabled the disableNoResponse setting
76
+ if (!platform.config.disableNoResponse) {
77
+ this.tempService
78
+ .getCharacteristic(this.hapChar.CurrentTemperature)
79
+ .setProps({
80
+ minStep: 0.1,
81
+ })
82
+ .onGet(() => {
83
+ if (!this.isOnline) {
84
+ throw new this.hapErr(-70402)
85
+ }
86
+ return this.tempService.getCharacteristic(this.hapChar.CurrentTemperature).value
87
+ })
88
+ this.humiService.getCharacteristic(this.hapChar.CurrentRelativeHumidity).onGet(() => {
89
+ if (!this.isOnline) {
90
+ throw new this.hapErr(-70402)
91
+ }
92
+ return this.humiService.getCharacteristic(this.hapChar.CurrentRelativeHumidity).value
93
+ })
94
+ this.airQualityService.getCharacteristic(this.hapChar.AirQuality).onGet(() => {
95
+ if (!this.isOnline) {
96
+ throw new this.hapErr(-70402)
97
+ }
98
+ return this.airQualityService.getCharacteristic(this.hapChar.AirQuality).value
99
+ })
100
+ this.co2Service.getCharacteristic(this.hapChar.CarbonDioxideDetected).onGet(() => {
101
+ if (!this.isOnline) {
102
+ throw new this.hapErr(-70402)
103
+ }
104
+ return this.co2Service.getCharacteristic(this.hapChar.CarbonDioxideDetected).value
105
+ })
106
+ }
107
+
108
+ // Pass the accessory to Fakegato to set up with Eve
109
+ this.accessory.eveService = new platform.eveService('custom', this.accessory, {
110
+ log: () => {},
111
+ })
112
+
113
+ // Output the customised options to the log
114
+ const normalLogging = this.enableLogging ? 'standard' : 'disable'
115
+ const opts = JSON.stringify({
116
+ humidityOffset: this.humiOffset,
117
+ humidityOffsetFactor: this.humiOffsetFactor,
118
+ logging: this.enableDebugLogging ? 'debug' : normalLogging,
119
+ offset: this.tempOffset,
120
+ offsetFactor: this.tempOffsetFactor,
121
+ })
122
+ this.log('[%s] %s %s.', this.name, this.lang.devInitOpts, opts)
123
+ }
124
+
125
+ // Convert CO2 ppm to HomeKit AirQuality enum
126
+ // 0: Unknown, 1: Excellent, 2: Good, 3: Fair, 4: Inferior, 5: Poor
127
+ co2ToAirQuality(co2) {
128
+ if (co2 <= 400) {
129
+ return 1 // Excellent
130
+ }
131
+ if (co2 <= 700) {
132
+ return 2 // Good
133
+ }
134
+ if (co2 <= 1000) {
135
+ return 3 // Fair
136
+ }
137
+ if (co2 <= 2000) {
138
+ return 4 // Inferior
139
+ }
140
+ return 5 // Poor
141
+ }
142
+
143
+ async externalUpdate(params) {
144
+ try {
145
+ if (hasProperty(params, 'temperature')) {
146
+ let newTemp = Number(params.temperature)
147
+ if (this.tempOffsetFactor) {
148
+ newTemp *= this.tempOffset
149
+ } else {
150
+ newTemp += this.tempOffset
151
+ }
152
+ if (newTemp !== this.cacheTemp) {
153
+ this.cacheTemp = newTemp
154
+ this.tempService.updateCharacteristic(this.hapChar.CurrentTemperature, this.cacheTemp)
155
+ this.accessory.eveService.addEntry({ temp: this.cacheTemp })
156
+ if (params.updateSource && this.enableLogging) {
157
+ this.log('[%s] %s [%s°C].', this.name, this.lang.curTemp, this.cacheTemp)
158
+ }
159
+
160
+ // Update the cache file with the new temperature
161
+ this.updateCache()
162
+ }
163
+ }
164
+ if (hasProperty(params, 'humidity')) {
165
+ let newHumi = Number.parseFloat(params.humidity)
166
+ if (this.humiOffsetFactor) {
167
+ newHumi *= this.humiOffset
168
+ } else {
169
+ newHumi += this.humiOffset
170
+ }
171
+ newHumi = Math.max(Math.min(newHumi, 100), 0)
172
+ if (newHumi !== this.cacheHumi) {
173
+ this.cacheHumi = newHumi
174
+ this.humiService.updateCharacteristic(
175
+ this.hapChar.CurrentRelativeHumidity,
176
+ this.cacheHumi,
177
+ )
178
+ this.accessory.eveService.addEntry({ humidity: this.cacheHumi })
179
+ if (params.updateSource && this.enableLogging) {
180
+ this.log('[%s] %s [%s%].', this.name, this.lang.curHumi, this.cacheHumi)
181
+ }
182
+ }
183
+ }
184
+ if (hasProperty(params, 'co2')) {
185
+ const newCO2 = Number.parseInt(params.co2, 10)
186
+ if (newCO2 !== this.cacheCO2) {
187
+ this.cacheCO2 = newCO2
188
+
189
+ // Update CO2 level on the carbon dioxide sensor
190
+ this.co2Service.updateCharacteristic(this.hapChar.CarbonDioxideLevel, this.cacheCO2)
191
+
192
+ // CO2 detected if above 1000 ppm (typical threshold)
193
+ const co2Detected = this.cacheCO2 > 1000 ? 1 : 0
194
+ this.co2Service.updateCharacteristic(this.hapChar.CarbonDioxideDetected, co2Detected)
195
+
196
+ // Update air quality based on CO2 levels
197
+ const airQuality = this.co2ToAirQuality(this.cacheCO2)
198
+ this.airQualityService.updateCharacteristic(this.hapChar.AirQuality, airQuality)
199
+ this.airQualityService.updateCharacteristic(this.hapChar.CarbonDioxideLevel, this.cacheCO2)
200
+
201
+ if (params.updateSource && this.enableLogging) {
202
+ this.log('[%s] %s [%s ppm].', this.name, this.lang.curCO2, this.cacheCO2)
203
+ }
204
+ }
205
+ }
206
+ } catch (err) {
207
+ this.platform.deviceUpdateError(this.accessory, err, false)
208
+ }
209
+ }
210
+
211
+ async updateCache() {
212
+ // Don't continue if the storage client hasn't initialised properly
213
+ if (!this.platform.storageClientData) {
214
+ return
215
+ }
216
+
217
+ // Attempt to save the new temperature to the cache
218
+ try {
219
+ await this.platform.storageData.setItem(
220
+ `${this.accessory.context.eweDeviceId}_temp`,
221
+ this.cacheTemp,
222
+ )
223
+ } catch (err) {
224
+ if (this.enableLogging) {
225
+ this.log.warn('[%s] %s %s.', this.name, this.lang.storageWriteErr, parseError(err))
226
+ }
227
+ }
228
+ }
229
+
230
+ currentState() {
231
+ const toReturn = {}
232
+ toReturn.services = ['temperature', 'humidity', 'airQuality', 'carbonDioxide']
233
+ toReturn.temperature = {
234
+ current: this.tempService.getCharacteristic(this.hapChar.CurrentTemperature).value,
235
+ }
236
+ toReturn.humidity = {
237
+ current: this.humiService.getCharacteristic(this.hapChar.CurrentRelativeHumidity).value,
238
+ }
239
+ toReturn.airQuality = {
240
+ current: this.airQualityService.getCharacteristic(this.hapChar.AirQuality).value,
241
+ }
242
+ toReturn.carbonDioxide = {
243
+ level: this.co2Service.getCharacteristic(this.hapChar.CarbonDioxideLevel).value,
244
+ detected: this.co2Service.getCharacteristic(this.hapChar.CarbonDioxideDetected).value,
245
+ }
246
+ return toReturn
247
+ }
248
+
249
+ markStatus(isOnline) {
250
+ this.isOnline = isOnline
251
+ }
252
+ }
package/lib/platform.js CHANGED
@@ -38,7 +38,7 @@ export default class {
38
38
  try {
39
39
  this.api = api
40
40
  this.log = log
41
- this.isBeta = plugin.version.includes('beta')
41
+ this.isBeta = process.argv.includes('-D')
42
42
  this.apiClient = false
43
43
  this.apiServer = false
44
44
  this.httpClient = false
@@ -449,14 +449,6 @@ export default class {
449
449
  if (this.isBeta) {
450
450
  this.log.debug = this.log
451
451
  this.log.debugWarn = this.log.warn
452
-
453
- // Log that using a beta will generate a lot of debug logs
454
- if (this.isBeta) {
455
- const divide = '*'.repeat(this.lang.beta.length + 1) // don't forget the full stop (+1!)
456
- this.log.warn(divide)
457
- this.log.warn(`${this.lang.beta}.`)
458
- this.log.warn(divide)
459
- }
460
452
  } else {
461
453
  this.log.debug = () => {}
462
454
  this.log.debugWarn = () => {}
@@ -1222,6 +1214,16 @@ export default class {
1222
1214
  this.applyAccessoryLogging(accessory)
1223
1215
  accessory.control = new deviceTypes.deviceSensorTempHumi(this, accessory)
1224
1216
  /** */
1217
+ } else if (platformConsts.devices.sensorAirQuality.includes(uiid)) {
1218
+ /**
1219
+ *************************
1220
+ SENSOR [AIR QUALITY-SAWF-08P]
1221
+ **************************
1222
+ */
1223
+ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`)
1224
+ this.applyAccessoryLogging(accessory)
1225
+ accessory.control = new deviceTypes.deviceSensorAirQuality(this, accessory)
1226
+ /** */
1225
1227
  } else if (device.showAs === 'heater') {
1226
1228
  /**
1227
1229
  *****************
@@ -328,6 +328,7 @@ export default {
328
328
  sensorContact: [102, 154],
329
329
  sensorAmbient: [15, 181],
330
330
  sensorTempHumi: [18],
331
+ sensorAirQuality: [266],
331
332
  thermostat: [127],
332
333
  airConditioner: [151],
333
334
  panel: [133, 195],
@@ -367,6 +368,7 @@ export default {
367
368
  'cctBrightness',
368
369
  'channel',
369
370
  'cmd',
371
+ 'co2',
370
372
  'color',
371
373
  'colorB',
372
374
  'colorG',
@@ -515,6 +517,7 @@ export default {
515
517
  243: 1, // Zigbee Bridge Ultra
516
518
  258: 1, // Additional Cover device
517
519
  262: 4, // CK-BL602-SWP1-02 (4-channel switch with power monitoring)
520
+ 266: 1, // SAWF-08P Air Quality Sensor (CO2, Temperature, Humidity)
518
521
  1000: 1, // "zigbee_ON_OFF_SWITCH_1000" (button device)
519
522
  1009: 1, // "" (Some sort of single switch device)
520
523
  1256: 1, // "ZIGBEE_SINGLE_SWITCH"
@@ -2,7 +2,6 @@ export default {
2
2
  apiListenErr: 'Internal API server error',
3
3
  apiListening: 'Internal API listening on port',
4
4
  apiShutdown: 'Internal API gracefully shutdown',
5
- beta: 'You are using a beta version of the plugin - you will experience more logging than normal',
6
5
  buttonDouble: 'double press',
7
6
  buttonLong: 'long press',
8
7
  buttonNotFound: 'rf button not found (restart Homebridge)',
@@ -23,6 +22,7 @@ export default {
23
22
  contactYes: 'contact detected',
24
23
  curBatt: 'current battery',
25
24
  curBright: 'current brightness',
25
+ curCO2: 'current CO2',
26
26
  curColour: 'current colour',
27
27
  curCool: 'current cooling',
28
28
  curCurr: 'current current',
@@ -2,7 +2,6 @@ export default {
2
2
  apiListenErr: 'Erreur de serveur d\'API interne',
3
3
  apiListening: 'API interne à l\'écoute sur le port',
4
4
  apiShutdown: 'L\'API interne s\'arrête normalement',
5
- beta: 'You are using a beta version of the plugin - you will experience more logging than normal',
6
5
  buttonDouble: 'appui double',
7
6
  buttonLong: 'appui long',
8
7
  buttonNotFound: 'rf button non trouvé (veuillez redémarrer Homebridge)',
@@ -23,6 +22,7 @@ export default {
23
22
  contactYes: 'contact detecté',
24
23
  curBatt: 'batterie actuelle',
25
24
  curBright: 'luminosité actuelle',
25
+ curCO2: 'CO2 actuel',
26
26
  curColour: 'couleur actuelle',
27
27
  curCool: 'refroidissement actuel',
28
28
  curCurr: 'actuel actuel',
@@ -2,7 +2,6 @@ export default {
2
2
  apiListenErr: 'ข้อผิดพลาดเซิร์ฟเวอร์ API ภายใน',
3
3
  apiListening: 'การฟัง API ภายในบนพอร์ต',
4
4
  apiShutdown: 'API ภายในปิดได้อย่างสมบูรณ์',
5
- beta: 'You are using a beta version of the plugin - you will experience more logging than normal',
6
5
  buttonDouble: 'กดสองครั้ง',
7
6
  buttonLong: 'กดค้าง',
8
7
  buttonNotFound: 'ไม่พบปุ่ม rf (รีสตาร์ท Homebridge)',
@@ -23,6 +22,7 @@ export default {
23
22
  contactYes: 'ตรวจพบการติดต่อแล้ว',
24
23
  curBatt: 'สถานะแบตเตอรี่',
25
24
  curBright: 'ความสว่างในตอนนี้',
25
+ curCO2: 'CO2 ตอนนี้',
26
26
  curColour: 'สีตอนนี้',
27
27
  curCool: 'ความเย็นในตอนนี้',
28
28
  curCurr: 'กระแสในตอนนี้',
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "displayName": "Homebridge eWeLink",
4
4
  "alias": "eWeLink",
5
5
  "type": "module",
6
- "version": "12.7.1-beta.0",
6
+ "version": "12.8.1-beta.0",
7
7
  "description": "Homebridge plugin to integrate eWeLink devices into HomeKit.",
8
8
  "author": {
9
9
  "name": "bwp91",
@@ -64,11 +64,11 @@
64
64
  "@homebridge/plugin-ui-utils": "^2.1.2",
65
65
  "axios": "^1.13.2",
66
66
  "node-persist": "^4.0.4",
67
- "p-queue": "^9.0.1",
67
+ "p-queue": "^9.1.0",
68
68
  "websocket-as-promised": "^3.0.1",
69
- "ws": "^8.18.3"
69
+ "ws": "^8.19.0"
70
70
  },
71
71
  "devDependencies": {
72
- "@antfu/eslint-config": "^6.4.2"
72
+ "@antfu/eslint-config": "^7.2.0"
73
73
  }
74
74
  }