@homebridge-plugins/homebridge-govee 11.29.3 → 11.30.0-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,14 @@
2
2
 
3
3
  All notable changes to `@homebridge-plugins/homebridge-govee` will be documented in this file.
4
4
 
5
+ ## v11.30.0 (Pending Release)
6
+
7
+ ### Changed
8
+
9
+ - chore(ble): let noble apply its own adapter timeout instead of racing a second one
10
+ - fix: restore aws brightness scaling, so lights reach the brightness asked for (#1262) (@gradyg21)
11
+ - fix: skip a periodic sync when the previous one is still running, so slow responses stop stacking up (#1256) (@schwaggy)
12
+
5
13
  ## v11.29.3 (2026-07-21)
6
14
 
7
15
  ### Changed
@@ -174,20 +174,16 @@ export default class BLEConnection {
174
174
  return true
175
175
  }
176
176
 
177
- let timeoutId
178
177
  try {
179
- await Promise.race([
180
- btClient.waitForPoweredOnAsync(),
181
- new Promise((_, reject) => {
182
- timeoutId = setTimeout(() => reject(new Error('timeout waiting for bluetooth adapter')), timeout)
183
- }),
184
- ])
178
+ // noble applies the timeout itself and removes its own state listener when
179
+ // it expires. Racing it against a second, shorter timer here just meant
180
+ // noble kept waiting - and holding its listener - for another five seconds
181
+ // after we had already given up.
182
+ await btClient.waitForPoweredOnAsync(timeout)
185
183
  return true
186
184
  } catch (err) {
187
185
  this.log.warn('[BLE] failed to power on adapter: %s.', err.message)
188
186
  return false
189
- } finally {
190
- clearTimeout(timeoutId)
191
187
  }
192
188
  }
193
189
 
package/lib/platform.js CHANGED
@@ -57,6 +57,9 @@ export default class {
57
57
  this.deviceConf = {}
58
58
  this.ignoredDevices = []
59
59
 
60
+ // Periodic syncs currently running, so a slow one is not started twice
61
+ this.syncsInFlight = new Set()
62
+
60
63
  // Make sure user is running Homebridge v1.5 or above
61
64
  if (!api.versionGreaterOrEqual?.('1.5.0')) {
62
65
  throw new Error(platformLang.hbVersionFail)
@@ -217,6 +220,7 @@ export default class {
217
220
  }
218
221
  break
219
222
  }
223
+ case 'awsBrightnessNoScale':
220
224
  case 'hideLight':
221
225
  case 'hideModeGreenTea':
222
226
  case 'hideModeOolongTea':
@@ -776,7 +780,7 @@ export default class {
776
780
 
777
781
  this.refreshBLEInterval = setInterval(async () => {
778
782
  try {
779
- await this.goveeBLESync()
783
+ await this.runPeriodicSync('BLE', () => this.goveeBLESync())
780
784
  } catch (err) {
781
785
  this.log.warn('[BLE] sync failed: %s', parseError(err))
782
786
  }
@@ -790,7 +794,7 @@ export default class {
790
794
  if (this.httpClient && httpSyncNeeded) {
791
795
  this.goveeHTTPSync()
792
796
  this.refreshHTTPInterval = setInterval(
793
- () => this.goveeHTTPSync(),
797
+ () => this.runPeriodicSync('HTTP', () => this.goveeHTTPSync()),
794
798
  this.config.httpRefreshTime * 1000,
795
799
  )
796
800
  }
@@ -799,7 +803,7 @@ export default class {
799
803
  const openApiRefresh = (this.config.openApiRefreshTime || this.config.httpRefreshTime) * 1000
800
804
  this.goveeOpenApiSync()
801
805
  this.refreshOpenApiInterval = setInterval(
802
- () => this.goveeOpenApiSync(),
806
+ () => this.runPeriodicSync('OPENAPI', () => this.goveeOpenApiSync()),
803
807
  openApiRefresh,
804
808
  )
805
809
 
@@ -819,7 +823,7 @@ export default class {
819
823
  // No need for await as catches its own errors, we poll specific models that need it
820
824
  this.goveeAWSSync(true)
821
825
  this.refreshAWSInterval = setInterval(
822
- () => this.goveeAWSSync(),
826
+ () => this.runPeriodicSync('AWS', () => this.goveeAWSSync()),
823
827
  60000,
824
828
  )
825
829
  }
@@ -1363,6 +1367,7 @@ export default class {
1363
1367
 
1364
1368
  if (this.awsClient) {
1365
1369
  accessory.context.useAwsControl = true
1370
+ accessory.context.awsBrightnessNoScale = deviceConf.awsBrightnessNoScale
1366
1371
  accessory.context.awsColourMode = deviceConf.awsColourMode || platformConsts.defaultValues.awsColourMode
1367
1372
  awsDevices.push(device.device)
1368
1373
 
@@ -1441,6 +1446,28 @@ export default class {
1441
1446
  }
1442
1447
  }
1443
1448
 
1449
+ /**
1450
+ * Run a periodic sync, skipping this run if the previous one is still going.
1451
+ *
1452
+ * The intervals fire on a timer regardless of how long a sync takes, and the
1453
+ * http request timeout and the default refresh interval are both thirty
1454
+ * seconds. A single slow response therefore guaranteed the next sync started
1455
+ * before the last had finished, and those stacked up - piling more requests
1456
+ * onto an endpoint that was already struggling (#1256).
1457
+ */
1458
+ async runPeriodicSync(name, sync) {
1459
+ if (this.syncsInFlight.has(name)) {
1460
+ this.log.debugWarn('[%s] %s', name, platformLang.syncStillRunning)
1461
+ return
1462
+ }
1463
+ this.syncsInFlight.add(name)
1464
+ try {
1465
+ await sync()
1466
+ } finally {
1467
+ this.syncsInFlight.delete(name)
1468
+ }
1469
+ }
1470
+
1444
1471
  async goveeAWSSync(allDevices = false) {
1445
1472
  const pollList = allDevices ? awsDevices : awsDevicesToPoll
1446
1473
  if (pollList.length === 0) {
@@ -111,7 +111,15 @@ function buildBrightnessCommand(params, context) {
111
111
  return {
112
112
  awsParams: {
113
113
  cmd: 'brightness',
114
- data: { val: params.value },
114
+ // Most devices take 0-254 over AWS, so HomeKit's 0-100 has to be scaled
115
+ // up. A handful expect 0-100 already and are opted out per device with
116
+ // `awsBrightnessNoScale`. Dropping this scaling made every AWS device
117
+ // land at roughly a third of the requested brightness (#1262).
118
+ data: {
119
+ val: context.awsBrightnessNoScale
120
+ ? params.value
121
+ : Math.round(params.value * 2.54),
122
+ },
115
123
  },
116
124
  bleParams: {
117
125
  cmd: CMD.BRIGHTNESS,
@@ -130,6 +130,7 @@ export default {
130
130
  storageSetupErr: 'Could not setup storage client as',
131
131
  storageWriteErr: 'could not save accessory to file as',
132
132
  syncFail: 'sync failed as',
133
+ syncStillRunning: 'skipping this sync as the previous one has not finished yet',
133
134
  viaAL: 'via adaptive lighting',
134
135
  unknownCommand: 'unknown command in payload received',
135
136
  }
@@ -49,8 +49,18 @@ export function parseDeviceUpdate(params, context) {
49
49
 
50
50
  // BRIGHTNESS
51
51
  if (params.state && hasProperty(params.state, 'brightness')) {
52
- data.brightness = params.state.brightness
52
+ // AWS reports 0-254 on most devices, so it has to come back down to
53
+ // HomeKit's 0-100. Without this every AWS reading of 100 or more clamped
54
+ // to full brightness (#1262). The other sources already report 0-100.
55
+ if (params.source === 'AWS') {
56
+ data.brightness = context.awsBrightnessNoScale
57
+ ? params.state.brightness
58
+ : Math.round(params.state.brightness / 2.54)
59
+ } else {
60
+ data.brightness = params.state.brightness
61
+ }
53
62
  }
63
+ // Govee occasionally sends a value outside [0, 100] anyway
54
64
  if (hasProperty(data, 'brightness')) {
55
65
  data.brightness = Math.max(Math.min(data.brightness, 100), 0)
56
66
  }
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.29.3",
5
+ "version": "11.30.0-beta.1",
6
6
  "description": "Homebridge plugin to integrate Govee devices into HomeKit.",
7
7
  "author": {
8
8
  "name": "bwp91",