@homebridge-plugins/homebridge-govee 11.2.0 → 11.2.1-beta.1

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,43 +60,56 @@ 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
72
73
  .getCharacteristic(this.hapChar.SwingMode)
73
74
  .onSet(async value => this.internalSwingUpdate(value))
74
75
  this.cacheSwing = this.service.getCharacteristic(this.hapChar.SwingMode).value === 1 ? 'on' : 'off'
76
+ this.cacheSwingCode = ''
75
77
 
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'
78
+ if (this.hideLight) {
79
+ if (this.accessory.getService(this.hapServ.Lightbulb)) {
80
+ // Remove the light service if it exists
81
+ this.accessory.removeService(this.accessory.getService(this.hapServ.Lightbulb))
82
+ }
83
+ } else {
84
+ // Add the night light service if it doesn't already exist
85
+ this.lightService = this.accessory.getService(this.hapServ.Lightbulb) || this.accessory.addService(this.hapServ.Lightbulb)
81
86
 
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)
87
+ // Add the set handler to the lightbulb on/off characteristic
88
+ this.lightService.getCharacteristic(this.hapChar.On).onSet(async (value) => {
89
+ await this.internalLightStateUpdate(value)
87
90
  })
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
91
+ this.cacheLightState = this.lightService.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
92
+
93
+ // Add the set handler to the lightbulb brightness characteristic
94
+ this.lightService
95
+ .getCharacteristic(this.hapChar.Brightness)
96
+ .onSet(async (value) => {
97
+ await this.internalBrightnessUpdate(value)
98
+ })
99
+ this.cacheBright = this.lightService.getCharacteristic(this.hapChar.Brightness).value
100
+
101
+ // Add the set handler to the lightbulb hue characteristic
102
+ this.lightService.getCharacteristic(this.hapChar.Hue).onSet(async (value) => {
103
+ await this.internalColourUpdate(value)
104
+ })
105
+ this.cacheHue = this.lightService.getCharacteristic(this.hapChar.Hue).value
106
+ this.cacheSat = this.lightService.getCharacteristic(this.hapChar.Saturation).value
107
+ }
96
108
 
97
109
  // Output the customised options to the log
98
- const opts = JSON.stringify({})
110
+ const opts = JSON.stringify({
111
+ hideLight: this.hideLight,
112
+ })
99
113
  platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
100
114
  }
101
115
 
@@ -133,74 +147,20 @@ export default class {
133
147
 
134
148
  async internalSpeedUpdate(value) {
135
149
  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
150
  // Don't continue if the new value is the same as before
172
- if (this.cacheSpeed === newValue) {
151
+ if (this.cacheSpeed === value || value === 0) {
173
152
  return
174
153
  }
175
154
 
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
155
  await this.platform.sendDeviceUpdate(this.accessory, {
192
156
  cmd: 'ptReal',
193
- value: codeToSend,
157
+ value: this.speedCodes[value],
194
158
  })
195
159
 
196
160
  // 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}%]`)
161
+ if (this.cacheSpeed !== value) {
162
+ this.cacheSpeed = value
163
+ this.accessory.log(`${platformLang.curSpeed} [${value}]`)
204
164
  }
205
165
  } catch (err) {
206
166
  // Catch any errors during the process
@@ -218,13 +178,22 @@ export default class {
218
178
  try {
219
179
  const newValue = value ? 'on' : 'off'
220
180
  // Don't continue if the new value is the same as before
221
- if (this.cacheSwing === value) {
181
+ if (this.cacheSwing === value || !this.cacheSwingCode) {
222
182
  return
223
183
  }
224
184
 
185
+ // The existing cacheSwingCode might be something like aa1d0101960384000000000000000000000000a6
186
+ // We need to change the third hex value to 00 for off or 01 for on
187
+ const hexValues = [
188
+ 0x3A,
189
+ 0x1D,
190
+ value ? 0x01 : 0x00,
191
+ ...this.cacheSwingCode.slice(6, 14).match(/.{1,2}/g).map(byte => Number.parseInt(byte, 16)),
192
+ ]
193
+
225
194
  await this.platform.sendDeviceUpdate(this.accessory, {
226
195
  cmd: 'ptReal',
227
- value: value ? 'Mx8BAQAAAAAAAAAAAAAAAAAAACw=' : 'Mx8BAAAAAAAAAAAAAAAAAAAAAC0=',
196
+ value: generateCodeFromHexValues(hexValues),
228
197
  })
229
198
 
230
199
  // Cache the new state and log if appropriate
@@ -408,16 +377,11 @@ export default class {
408
377
  case '0501': {
409
378
  // Fan speed
410
379
  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
- }
380
+ const newSpeedInt = Number.parseInt(newSpeed, 16)
417
381
  if (this.cacheSpeed !== newSpeedInt) {
418
382
  this.cacheSpeed = newSpeedInt
419
383
  this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
420
- this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}%]`)
384
+ this.accessory.log(`${platformLang.curSpeed} [${this.cacheSpeed}]`)
421
385
  }
422
386
  break
423
387
  }
@@ -430,66 +394,66 @@ export default class {
430
394
  // Sleep: 5
431
395
  // Nature: 6
432
396
  // 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
397
  break
451
398
  }
452
399
  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}%]`)
400
+ // Night light on/off
401
+ if (!this.hideLight) {
402
+ const newLightState = getTwoItemPosition(hexParts, 4) === '01' ? 'on' : 'off'
403
+ if (this.cacheLightState !== newLightState) {
404
+ this.cacheLightState = newLightState
405
+ this.lightService.updateCharacteristic(this.hapChar.On, this.cacheLightState === 'on')
406
+ this.accessory.log(`${platformLang.curLight} [${this.cacheLightState}]`)
407
+ }
408
+ const newBrightness = hexToDecimal(getTwoItemPosition(hexParts, 5))
409
+ if (this.cacheBright !== newBrightness) {
410
+ this.cacheBright = newBrightness
411
+ this.lightService.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
412
+ this.accessory.log(`${platformLang.curBright} [${this.cacheBright}%]`)
413
+ }
464
414
  }
465
415
  break
466
416
  }
467
417
  case '1b05': {
468
418
  // 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}]`)
419
+ if (!this.hideLight) {
420
+ const newR = hexToDecimal(getTwoItemPosition(hexParts, 5))
421
+ const newG = hexToDecimal(getTwoItemPosition(hexParts, 6))
422
+ const newB = hexToDecimal(getTwoItemPosition(hexParts, 7))
423
+
424
+ const hs = rgb2hs(newR, newG, newB)
425
+
426
+ // Check for a colour change
427
+ if (hs[0] !== this.cacheHue) {
428
+ // Colour is different so update Homebridge with new values
429
+ this.lightService.updateCharacteristic(this.hapChar.Hue, hs[0])
430
+ this.lightService.updateCharacteristic(this.hapChar.Saturation, hs[1]);
431
+ [this.cacheHue] = hs
432
+
433
+ // Log the change
434
+ this.accessory.log(`${platformLang.curColour} [rgb ${newR} ${newG} ${newB}]`)
435
+ }
436
+ }
437
+ break
438
+ }
439
+ case '1d00':{
440
+ // Swing Mode Off
441
+ const newSwing = 'off'
442
+ this.cacheSwingCode = hexParts
443
+ if (this.cacheSwing !== newSwing) {
444
+ this.cacheSwing = newSwing
445
+ this.service.updateCharacteristic(this.hapChar.SwingMode, 0)
446
+ this.accessory.log(`${platformLang.curSwing} [${this.cacheSwing}]`)
484
447
  }
485
448
  break
486
449
  }
487
- case '1f01': {
488
- // Swing Mode
489
- const newSwing = getTwoItemPosition(hexParts, 4) === '01' ? 'on' : 'off'
450
+ case '1d01':{
451
+ // Swing Mode On
452
+ const newSwing = 'on'
453
+ this.cacheSwingCode = hexParts
490
454
  if (this.cacheSwing !== newSwing) {
491
455
  this.cacheSwing = newSwing
492
- this.service.updateCharacteristic(this.hapChar.SwingMode, this.cacheSwing === 'on' ? 1 : 0)
456
+ this.service.updateCharacteristic(this.hapChar.SwingMode, 1)
493
457
  this.accessory.log(`${platformLang.curSwing} [${this.cacheSwing}]`)
494
458
  }
495
459
  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.1",
6
6
  "description": "Homebridge plugin to integrate Govee devices into HomeKit.",
7
7
  "author": {
8
8
  "name": "bwp91",