@homebridge-plugins/homebridge-govee 11.2.0 → 11.2.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,12 @@
2
2
 
3
3
  All notable changes to homebridge-govee will be documented in this file.
4
4
 
5
+ ## v11.2.1 (Unreleased)
6
+
7
+ ### Other Changes
8
+
9
+ - fix permission in release workflow
10
+
5
11
  ## v11.2.0 (2025-07-12)
6
12
 
7
13
  ### Notable Changes
@@ -1153,6 +1153,14 @@
1153
1153
  "condition": {
1154
1154
  "functionBody": "return (model.fanDevices && model.fanDevices[arrayIndices] && model.fanDevices[arrayIndices].deviceId && model.fanDevices[arrayIndices].deviceId.length === 23);"
1155
1155
  }
1156
+ },
1157
+ "hideLight": {
1158
+ "type": "boolean",
1159
+ "title": "Hide Light",
1160
+ "description": "Enable this to not expose the light service in Homebridge/Homekit if your fan has one.",
1161
+ "condition": {
1162
+ "functionBody": "return (model.fanDevices && model.fanDevices[arrayIndices] && model.fanDevices[arrayIndices].deviceId && model.fanDevices[arrayIndices].deviceId.length === 23 && !model.fanDevices[arrayIndices].ignoreDevice);"
1163
+ }
1156
1164
  }
1157
1165
  }
1158
1166
  }
@@ -1604,7 +1612,8 @@
1604
1612
  "items": [
1605
1613
  "fanDevices[].label",
1606
1614
  "fanDevices[].deviceId",
1607
- "fanDevices[].ignoreDevice"
1615
+ "fanDevices[].ignoreDevice",
1616
+ "fanDevices[].hideLight"
1608
1617
  ]
1609
1618
  }
1610
1619
  ]
@@ -22,20 +22,24 @@ export default class {
22
22
  // Set up variables from the accessory
23
23
  this.accessory = accessory
24
24
 
25
+ // Set up custom variables for this device type
26
+ const deviceConf = platform.deviceConf[accessory.context.gvDeviceId]
27
+ this.hideLight = deviceConf && deviceConf.hideLight
28
+
25
29
  // Codes etc
26
30
  this.speedCodes = {
27
- 7: 'MwUBAQAAAAAAAAAAAAAAAAAAADY=',
28
- 14: 'MwUBAgAAAAAAAAAAAAAAAAAAADU=',
29
- 21: 'MwUBAwAAAAAAAAAAAAAAAAAAADQ=',
30
- 28: 'MwUBBAAAAAAAAAAAAAAAAAAAADM=',
31
- 35: 'MwUBBQAAAAAAAAAAAAAAAAAAADI=',
32
- 42: 'MwUBBgAAAAAAAAAAAAAAAAAAADE=',
33
- 49: 'MwUBBwAAAAAAAAAAAAAAAAAAADA=',
34
- 56: 'MwUBCAAAAAAAAAAAAAAAAAAAAD8=',
35
- 63: 'MwUBCQAAAAAAAAAAAAAAAAAAAD4=',
36
- 70: 'MwUBCgAAAAAAAAAAAAAAAAAAAD0=', // guessed
37
- 77: 'MwUBCwAAAAAAAAAAAAAAAAAAADw=', // guessed
38
- 84: 'MwUBDAAAAAAAAAAAAAAAAAAAADs=', // guessed
31
+ 1: 'MwUBAQAAAAAAAAAAAAAAAAAAADY=',
32
+ 2: 'MwUBAgAAAAAAAAAAAAAAAAAAADU=',
33
+ 3: 'MwUBAwAAAAAAAAAAAAAAAAAAADQ=',
34
+ 4: 'MwUBBAAAAAAAAAAAAAAAAAAAADM=',
35
+ 5: 'MwUBBQAAAAAAAAAAAAAAAAAAADI=',
36
+ 6: 'MwUBBgAAAAAAAAAAAAAAAAAAADE=',
37
+ 7: 'MwUBBwAAAAAAAAAAAAAAAAAAADA=',
38
+ 8: 'MwUBCAAAAAAAAAAAAAAAAAAAAD8=',
39
+ 9: 'MwUBCQAAAAAAAAAAAAAAAAAAAD4=',
40
+ 10: 'MwUBCgAAAAAAAAAAAAAAAAAAAD0=',
41
+ 11: 'MwUBCwAAAAAAAAAAAAAAAAAAADw=',
42
+ 12: 'MwUBDAAAAAAAAAAAAAAAAAAAADs=',
39
43
  }
40
44
 
41
45
  // Remove any old original Fan services
@@ -46,9 +50,6 @@ export default class {
46
50
  // Add the fan service for the fan if it doesn't already exist
47
51
  this.service = this.accessory.getService(this.hapServ.Fanv2) || this.accessory.addService(this.hapServ.Fanv2)
48
52
 
49
- // Add the night light service if it doesn't already exist
50
- this.lightService = this.accessory.getService(this.hapServ.Lightbulb) || this.accessory.addService(this.hapServ.Lightbulb)
51
-
52
53
  // Add the set handler to the fan on/off characteristic
53
54
  this.service
54
55
  .getCharacteristic(this.hapChar.Active)
@@ -59,13 +60,13 @@ export default class {
59
60
  this.service
60
61
  .getCharacteristic(this.hapChar.RotationSpeed)
61
62
  .setProps({
62
- minStep: 7,
63
+ maxValue: 12,
64
+ minStep: 1,
63
65
  minValue: 0,
64
- validValues: [0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91],
66
+ unit: 'unitless', // This is actually from HAP for Bluetooth LE Specification, but fits
65
67
  })
66
68
  .onSet(async value => this.internalSpeedUpdate(value))
67
69
  this.cacheSpeed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
68
- this.cacheMode = this.cacheSpeed >= 91 ? 'auto' : 'manual'
69
70
 
70
71
  // Add the set handler to the fan swing mode
71
72
  this.service
@@ -73,29 +74,41 @@ export default class {
73
74
  .onSet(async value => this.internalSwingUpdate(value))
74
75
  this.cacheSwing = this.service.getCharacteristic(this.hapChar.SwingMode).value === 1 ? 'on' : 'off'
75
76
 
76
- // Add the set handler to the lightbulb on/off characteristic
77
- this.lightService.getCharacteristic(this.hapChar.On).onSet(async (value) => {
78
- await this.internalLightStateUpdate(value)
79
- })
80
- this.cacheLightState = this.lightService.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
77
+ if (this.hideLight) {
78
+ if (this.accessory.getService(this.hapServ.Lightbulb)) {
79
+ // Remove the light service if it exists
80
+ this.accessory.removeService(this.accessory.getService(this.hapServ.Lightbulb))
81
+ }
82
+ } else {
83
+ // Add the night light service if it doesn't already exist
84
+ this.lightService = this.accessory.getService(this.hapServ.Lightbulb) || this.accessory.addService(this.hapServ.Lightbulb)
81
85
 
82
- // Add the set handler to the lightbulb brightness characteristic
83
- this.lightService
84
- .getCharacteristic(this.hapChar.Brightness)
85
- .onSet(async (value) => {
86
- await this.internalBrightnessUpdate(value)
86
+ // Add the set handler to the lightbulb on/off characteristic
87
+ this.lightService.getCharacteristic(this.hapChar.On).onSet(async (value) => {
88
+ await this.internalLightStateUpdate(value)
87
89
  })
88
- this.cacheBright = this.lightService.getCharacteristic(this.hapChar.Brightness).value
89
-
90
- // Add the set handler to the lightbulb hue characteristic
91
- this.lightService.getCharacteristic(this.hapChar.Hue).onSet(async (value) => {
92
- await this.internalColourUpdate(value)
93
- })
94
- this.cacheHue = this.lightService.getCharacteristic(this.hapChar.Hue).value
95
- this.cacheSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
90
+ this.cacheLightState = this.lightService.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
91
+
92
+ // Add the set handler to the lightbulb brightness characteristic
93
+ this.lightService
94
+ .getCharacteristic(this.hapChar.Brightness)
95
+ .onSet(async (value) => {
96
+ await this.internalBrightnessUpdate(value)
97
+ })
98
+ this.cacheBright = this.lightService.getCharacteristic(this.hapChar.Brightness).value
99
+
100
+ // Add the set handler to the lightbulb hue characteristic
101
+ this.lightService.getCharacteristic(this.hapChar.Hue).onSet(async (value) => {
102
+ await this.internalColourUpdate(value)
103
+ })
104
+ this.cacheHue = this.lightService.getCharacteristic(this.hapChar.Hue).value
105
+ this.cacheSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
106
+ }
96
107
 
97
108
  // Output the customised options to the log
98
- const opts = JSON.stringify({})
109
+ const opts = JSON.stringify({
110
+ hideLight: this.hideLight,
111
+ })
99
112
  platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
100
113
  }
101
114
 
@@ -133,74 +146,20 @@ export default class {
133
146
 
134
147
  async internalSpeedUpdate(value) {
135
148
  try {
136
- if (value < 3) {
137
- return
138
- }
139
-
140
- let newValue
141
- if (value < 10) {
142
- newValue = 7
143
- } else if (value < 17) {
144
- newValue = 14
145
- } else if (value < 24) {
146
- newValue = 21
147
- } else if (value < 31) {
148
- newValue = 28
149
- } else if (value < 38) {
150
- newValue = 35
151
- } else if (value < 45) {
152
- newValue = 42
153
- } else if (value < 52) {
154
- newValue = 49
155
- } else if (value < 59) {
156
- newValue = 56
157
- } else if (value < 66) {
158
- newValue = 63
159
- } else if (value < 73) {
160
- newValue = 70
161
- } else if (value < 80) {
162
- newValue = 77
163
- } else if (value < 87) {
164
- newValue = 84
165
- } else {
166
- newValue = 91
167
- }
168
-
169
- let newMode = value === 91 ? 'auto' : 'manual'
170
-
171
149
  // Don't continue if the new value is the same as before
172
- if (this.cacheSpeed === newValue) {
150
+ if (this.cacheSpeed === value || value === 0) {
173
151
  return
174
152
  }
175
153
 
176
- // Don't continue if trying to access auto mode but there is no sensor attached
177
- let codeToSend
178
- if (newMode === 'auto') {
179
- if (!this.accessory.context.sensorAttached || !this.cacheAutoCode) {
180
- this.accessory.logWarn('auto mode not supported without a linked sensor')
181
- codeToSend = this.speedCodes[84]
182
- newMode = 'manual'
183
- newValue = 84
184
- } else {
185
- codeToSend = this.cacheAutoCode
186
- }
187
- } else {
188
- codeToSend = this.speedCodes[newValue]
189
- }
190
-
191
154
  await this.platform.sendDeviceUpdate(this.accessory, {
192
155
  cmd: 'ptReal',
193
- value: codeToSend,
156
+ value: this.speedCodes[value],
194
157
  })
195
158
 
196
159
  // Cache the new state and log if appropriate
197
- if (this.cacheMode !== newMode) {
198
- this.cacheMode = newMode
199
- this.accessory.log(`${platformLang.curMode} [${this.cacheMode}]`)
200
- }
201
- if (this.cacheSpeed !== newValue) {
202
- this.cacheSpeed = newValue
203
- this.accessory.log(`${platformLang.curSpeed} [${newValue}%]`)
160
+ if (this.cacheSpeed !== value) {
161
+ this.cacheSpeed = value
162
+ this.accessory.log(`${platformLang.curSpeed} [${value}]`)
204
163
  }
205
164
  } catch (err) {
206
165
  // Catch any errors during the process
@@ -224,7 +183,7 @@ export default class {
224
183
 
225
184
  await this.platform.sendDeviceUpdate(this.accessory, {
226
185
  cmd: 'ptReal',
227
- value: value ? 'Mx8BAQAAAAAAAAAAAAAAAAAAACw=' : 'Mx8BAAAAAAAAAAAAAAAAAAAAAC0=',
186
+ value: value ? 'Mx0BAZYDhAAAAAAAAAAAAAAAAD8=' : 'Mx0AAZYDhAAAAAAAAAAAAAAAAD4=',
228
187
  })
229
188
 
230
189
  // Cache the new state and log if appropriate
@@ -408,16 +367,11 @@ export default class {
408
367
  case '0501': {
409
368
  // Fan speed
410
369
  const newSpeed = getTwoItemPosition(hexParts, 4)
411
- const newSpeedInt = Number.parseInt(newSpeed, 16) * 7
412
- const newMode = 'manual'
413
- if (this.cacheMode !== newMode) {
414
- this.cacheMode = newMode
415
- this.accessory.log(`${platformLang.curMode} [${this.cacheMode}]`)
416
- }
370
+ const newSpeedInt = Number.parseInt(newSpeed, 16)
417
371
  if (this.cacheSpeed !== newSpeedInt) {
418
372
  this.cacheSpeed = newSpeedInt
419
373
  this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
420
- this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}%]`)
374
+ this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}]`)
421
375
  }
422
376
  break
423
377
  }
@@ -430,66 +384,64 @@ export default class {
430
384
  // Sleep: 5
431
385
  // Nature: 6
432
386
  // Turbo: 7
433
- const newMode = getTwoItemPosition(hexParts, 4) === '03' ? 'auto' : 'manual'
434
- if (this.cacheMode !== newMode) {
435
- this.cacheMode = newMode
436
- this.accessory.log(`${platformLang.curMode} [${this.cacheMode}]`)
437
-
438
- if (this.cacheMode === 'auto' && this.cacheSpeed < 91) {
439
- this.cacheSpeed = 91
440
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
441
- this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}%]`)
442
- }
443
- }
444
- break
445
- }
446
- case '0503': {
447
- // Auto mode, we need to keep this code to send it back to the device
448
- const code = hexToTwoItems(`33${hexString.substring(2, hexString.length - 2)}`)
449
- this.cacheAutoCode = generateCodeFromHexValues(code.map(p => Number.parseInt(p, 16)))
450
387
  break
451
388
  }
452
389
  case '1b01': {
453
- const newLightState = getTwoItemPosition(hexParts, 4) === '01' ? 'on' : 'off'
454
- if (this.cacheLightState !== newLightState) {
455
- this.cacheLightState = newLightState
456
- this.lightService.updateCharacteristic(this.hapChar.On, this.cacheLightState === 'on')
457
- this.accessory.log(`${platformLang.curLight} [${this.cacheLightState}]`)
458
- }
459
- const newBrightness = hexToDecimal(getTwoItemPosition(hexParts, 5))
460
- if (this.cacheBright !== newBrightness) {
461
- this.cacheBright = newBrightness
462
- this.lightService.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
463
- this.accessory.log(`${platformLang.curBright} [${this.cacheBright}%]`)
390
+ // Night light on/off
391
+ if (!this.hideLight) {
392
+ const newLightState = getTwoItemPosition(hexParts, 4) === '01' ? 'on' : 'off'
393
+ if (this.cacheLightState !== newLightState) {
394
+ this.cacheLightState = newLightState
395
+ this.lightService.updateCharacteristic(this.hapChar.On, this.cacheLightState === 'on')
396
+ this.accessory.log(`${platformLang.curLight} [${this.cacheLightState}]`)
397
+ }
398
+ const newBrightness = hexToDecimal(getTwoItemPosition(hexParts, 5))
399
+ if (this.cacheBright !== newBrightness) {
400
+ this.cacheBright = newBrightness
401
+ this.lightService.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
402
+ this.accessory.log(`${platformLang.curBright} [${this.cacheBright}%]`)
403
+ }
464
404
  }
465
405
  break
466
406
  }
467
407
  case '1b05': {
468
408
  // Night light colour
469
- const newR = hexToDecimal(getTwoItemPosition(hexParts, 5))
470
- const newG = hexToDecimal(getTwoItemPosition(hexParts, 6))
471
- const newB = hexToDecimal(getTwoItemPosition(hexParts, 7))
472
-
473
- const hs = rgb2hs(newR, newG, newB)
474
-
475
- // Check for a colour change
476
- if (hs[0] !== this.cacheHue) {
477
- // Colour is different so update Homebridge with new values
478
- this.lightService.updateCharacteristic(this.hapChar.Hue, hs[0])
479
- this.lightService.updateCharacteristic(this.hapChar.Saturation, hs[1]);
480
- [this.cacheHue] = hs
481
-
482
- // Log the change
483
- this.accessory.log(`${platformLang.curColour} [rgb ${newR} ${newG} ${newB}]`)
409
+ if (!this.hideLight) {
410
+ const newR = hexToDecimal(getTwoItemPosition(hexParts, 5))
411
+ const newG = hexToDecimal(getTwoItemPosition(hexParts, 6))
412
+ const newB = hexToDecimal(getTwoItemPosition(hexParts, 7))
413
+
414
+ const hs = rgb2hs(newR, newG, newB)
415
+
416
+ // Check for a colour change
417
+ if (hs[0] !== this.cacheHue) {
418
+ // Colour is different so update Homebridge with new values
419
+ this.lightService.updateCharacteristic(this.hapChar.Hue, hs[0])
420
+ this.lightService.updateCharacteristic(this.hapChar.Saturation, hs[1]);
421
+ [this.cacheHue] = hs
422
+
423
+ // Log the change
424
+ this.accessory.log(`${platformLang.curColour} [rgb ${newR} ${newG} ${newB}]`)
425
+ }
426
+ }
427
+ break
428
+ }
429
+ case '1d00':{
430
+ // Swing Mode Off
431
+ const newSwing = 'off'
432
+ if (this.cacheSwing !== newSwing) {
433
+ this.cacheSwing = newSwing
434
+ this.service.updateCharacteristic(this.hapChar.SwingMode, 0)
435
+ this.accessory.log(`${platformLang.curSwing} [${this.cacheSwing}]`)
484
436
  }
485
437
  break
486
438
  }
487
- case '1f01': {
488
- // Swing Mode
489
- const newSwing = getTwoItemPosition(hexParts, 4) === '01' ? 'on' : 'off'
439
+ case '1d01':{
440
+ // Swing Mode On
441
+ const newSwing = 'on'
490
442
  if (this.cacheSwing !== newSwing) {
491
443
  this.cacheSwing = newSwing
492
- this.service.updateCharacteristic(this.hapChar.SwingMode, this.cacheSwing === 'on' ? 1 : 0)
444
+ this.service.updateCharacteristic(this.hapChar.SwingMode, 1)
493
445
  this.accessory.log(`${platformLang.curSwing} [${this.cacheSwing}]`)
494
446
  }
495
447
  break
package/lib/platform.js CHANGED
@@ -200,6 +200,7 @@ export default class {
200
200
  break
201
201
  }
202
202
  case 'awsBrightnessNoScale':
203
+ case 'hideLight':
203
204
  case 'hideModeGreenTea':
204
205
  case 'hideModeOolongTea':
205
206
  case 'hideModeCoffee':
@@ -91,7 +91,7 @@ export default {
91
91
  ],
92
92
  leakDevices: ['label', 'deviceId', 'ignoreDevice', 'lowBattThreshold'],
93
93
  thermoDevices: ['label', 'deviceId', 'ignoreDevice', 'lowBattThreshold', 'showExtraSwitch'],
94
- fanDevices: ['label', 'deviceId', 'ignoreDevice'],
94
+ fanDevices: ['label', 'deviceId', 'ignoreDevice', 'hideLight'],
95
95
  heaterDevices: ['label', 'deviceId', 'ignoreDevice', 'tempReporting'],
96
96
  humidifierDevices: ['label', 'deviceId', 'ignoreDevice'],
97
97
  dehumidifierDevices: ['label', 'deviceId', 'ignoreDevice'],
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@homebridge-plugins/homebridge-govee",
3
3
  "alias": "Govee",
4
4
  "type": "module",
5
- "version": "11.2.0",
5
+ "version": "11.2.1-beta.0",
6
6
  "description": "Homebridge plugin to integrate Govee devices into HomeKit.",
7
7
  "author": {
8
8
  "name": "bwp91",