@homebridge-plugins/homebridge-govee 11.34.2-beta.5 → 11.34.2-beta.7

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
@@ -19,6 +19,8 @@ All notable changes to `@homebridge-plugins/homebridge-govee` will be documented
19
19
  - fix: apply the openapi disable and refresh settings instead of asking for them to be removed
20
20
  - fix: say once when a bluetooth reading arrives for an unknown device, not on every broadcast
21
21
  - fix: reject a refresh time so large it would make the plugin poll every millisecond
22
+ - fix(H5054): don't let a slow leak warning block other sensors (#1348)
23
+ - fix: mark sensor batteries low at the configured threshold
22
24
 
23
25
  ## v11.34.1 (2026-08-06)
24
26
 
@@ -53,6 +53,27 @@ export default class GoveeDevice {
53
53
  return !!existing
54
54
  }
55
55
 
56
+ updateBattery(battery) {
57
+ if (!this.battService || !Number.isFinite(battery) || battery < 0 || battery > 100) {
58
+ return
59
+ }
60
+
61
+ if (battery !== this.cacheBatt) {
62
+ this.cacheBatt = battery
63
+ this.battService.updateCharacteristic(this.hapChar.BatteryLevel, this.cacheBatt)
64
+ this.accessory.log(`${platformLang.curBatt} [${this.cacheBatt}%]`)
65
+ }
66
+
67
+ const lowBattery = battery <= this.lowBattThreshold ? 1 : 0
68
+ const currentLowBattery = Number(this.battService
69
+ .getCharacteristic(this.hapChar.StatusLowBattery)
70
+ .value)
71
+
72
+ if (lowBattery !== currentLowBattery) {
73
+ this.battService.updateCharacteristic(this.hapChar.StatusLowBattery, lowBattery)
74
+ }
75
+ }
76
+
56
77
  /**
57
78
  * Give up on a control, put it back the way it was, and tell HomeKit.
58
79
  *
@@ -47,19 +47,7 @@ export default class extends GoveeDevice {
47
47
  this.platform.updateAccessoryStatus(this.accessory, this.cacheOnline)
48
48
  }
49
49
 
50
- // Check to see if the provided battery is different from the cached state
51
- if (Number.isFinite(params.battery) && params.battery !== this.cacheBatt) {
52
- // Battery is different so update Homebridge with new values
53
- this.cacheBatt = params.battery
54
- this.battService.updateCharacteristic(this.hapChar.BatteryLevel, this.cacheBatt)
55
- this.battService.updateCharacteristic(
56
- this.hapChar.StatusLowBattery,
57
- this.cacheBatt < this.lowBattThreshold ? 1 : 0,
58
- )
59
-
60
- // Log the change
61
- this.accessory.log(`${platformLang.curBatt} [${this.cacheBatt}%]`)
62
- }
50
+ this.updateBattery(params.battery)
63
51
 
64
52
  // Check to see if the provided leak status is different from the cached state
65
53
  if (hasProperty(params, 'leakDetected') && params.leakDetected !== this.cacheLeak) {
@@ -104,19 +104,7 @@ export default class extends GoveeDevice {
104
104
  return
105
105
  }
106
106
 
107
- // Check to see if the provided battery is different from the cached state
108
- if (Number.isFinite(params.battery) && params.battery !== this.cacheBatt && this.battService) {
109
- // Battery is different so update Homebridge with new values
110
- this.cacheBatt = params.battery
111
- this.battService.updateCharacteristic(this.hapChar.BatteryLevel, this.cacheBatt)
112
- this.battService.updateCharacteristic(
113
- this.hapChar.StatusLowBattery,
114
- this.cacheBatt < this.lowBattThreshold ? 1 : 0,
115
- )
116
-
117
- // Log the change
118
- this.accessory.log(`${platformLang.curBatt} [${this.cacheBatt}%]`)
119
- }
107
+ this.updateBattery(params.battery)
120
108
 
121
109
  // Check to see if the provided temperature is different from the cached state
122
110
  if (hasProperty(params, 'temperature')) {
@@ -80,19 +80,7 @@ export default class extends GoveeDevice {
80
80
  return
81
81
  }
82
82
 
83
- // Check to see if the provided battery is different from the cached state
84
- if (Number.isFinite(params.battery) && params.battery !== this.cacheBatt && this.battService) {
85
- // Battery is different so update Homebridge with new values
86
- this.cacheBatt = params.battery
87
- this.battService.updateCharacteristic(this.hapChar.BatteryLevel, this.cacheBatt)
88
- this.battService.updateCharacteristic(
89
- this.hapChar.StatusLowBattery,
90
- this.cacheBatt < this.lowBattThreshold ? 1 : 0,
91
- )
92
-
93
- // Log the change
94
- this.accessory.log(`${platformLang.curBatt} [${this.cacheBatt}%]`)
95
- }
83
+ this.updateBattery(params.battery)
96
84
 
97
85
  // Check to see if the provided temperature is different from the cached state
98
86
  if (hasProperty(params, 'temperature')) {
package/lib/platform.js CHANGED
@@ -408,11 +408,7 @@ export default class {
408
408
 
409
409
  // Set up the http client sync needed for leak and thermo sensor devices
410
410
  if (this.httpClient && httpSyncNeeded) {
411
- this.goveeHTTPSync()
412
- this.refreshHTTPInterval = setInterval(
413
- () => this.runPeriodicSync('HTTP', () => this.goveeHTTPSync()),
414
- this.config.httpRefreshTime * 1000,
415
- )
411
+ this.setupHTTPSync()
416
412
  }
417
413
 
418
414
  if (this.openApiClient && (openApiDevicesWereInitialised || httpDevicesWereInitialised)) {
@@ -787,6 +783,14 @@ export default class {
787
783
  }
788
784
  }
789
785
 
786
+ setupHTTPSync() {
787
+ this.runPeriodicSync('HTTP', () => this.goveeHTTPSync())
788
+ this.refreshHTTPInterval = setInterval(
789
+ () => this.runPeriodicSync('HTTP', () => this.goveeHTTPSync()),
790
+ this.config.httpRefreshTime * 1000,
791
+ )
792
+ }
793
+
790
794
  async goveeAWSSync(allDevices = false) {
791
795
  const pollList = allDevices ? awsDevices : awsDevicesToPoll
792
796
  if (pollList.length === 0) {
@@ -864,123 +868,129 @@ export default class {
864
868
  // Obtain a refreshed device list
865
869
  const devices = await this.httpClient.getDevices(true)
866
870
 
867
- // Filter those which are leak sensors
868
- for (const device1 of devices
869
- .filter(device => [...platformConsts.models.sensorLeak, ...platformConsts.models.sensorThermo, ...platformConsts.models.sensorMonitor].includes(device.sku))) {
870
- try {
871
- // Reformat the device id
872
- if (!device1.device.includes(':')) {
873
- // Eg converts abcd1234abcd1234 to AB:CD:12:34:AB:CD:12:34
874
- // For sensors with an add-on sensor like H5178
875
- // Eg converts abcd1234abcd1234_1 to AB:CD:12:34:AB:CD:12:34_1
876
- device1.device = device1.device.replace(DEVICE_ID_FORMAT_REGEX, '$&:').toUpperCase()
877
- }
871
+ // A stalled leak-warning request must not hold every water sensor behind
872
+ // it. Queue only those cloud requests so thermometer and monitor updates
873
+ // still arrive immediately, while a poll stays gentle on Govee's API.
874
+ const leakWarningQueue = new PQueue({ concurrency: 2 })
875
+ await Promise.all(devices
876
+ .filter(device => [...platformConsts.models.sensorLeak, ...platformConsts.models.sensorThermo, ...platformConsts.models.sensorMonitor].includes(device.sku))
877
+ .map(async (device1) => {
878
+ try {
879
+ // Reformat the device id
880
+ if (!device1.device.includes(':')) {
881
+ // Eg converts abcd1234abcd1234 to AB:CD:12:34:AB:CD:12:34
882
+ // For sensors with an add-on sensor like H5178
883
+ // Eg converts abcd1234abcd1234_1 to AB:CD:12:34:AB:CD:12:34_1
884
+ device1.device = device1.device.replace(DEVICE_ID_FORMAT_REGEX, '$&:').toUpperCase()
885
+ }
878
886
 
879
- // Generate the UIID from which we can match our Homebridge accessory
880
- const uiid = this.api.hap.uuid.generate(device1.device)
887
+ // Generate the UIID from which we can match our Homebridge accessory
888
+ const uiid = this.api.hap.uuid.generate(device1.device)
881
889
 
882
- // Don't continue if the accessory doesn't exist
883
- if (!devicesInHB.has(uiid)) {
884
- continue
885
- }
890
+ // Don't continue if the accessory doesn't exist
891
+ if (!devicesInHB.has(uiid)) {
892
+ return
893
+ }
886
894
 
887
- // Retrieve the Homebridge accessory
888
- const accessory = devicesInHB.get(uiid)
895
+ // Retrieve the Homebridge accessory
896
+ const accessory = devicesInHB.get(uiid)
889
897
 
890
- // Make sure the data we need for the device exists
891
- if (!device1.deviceExt || !device1.deviceExt.deviceSettings || !device1.deviceExt.lastDeviceData) {
892
- continue
893
- }
898
+ // Make sure the data we need for the device exists
899
+ if (!device1.deviceExt || !device1.deviceExt.deviceSettings || !device1.deviceExt.lastDeviceData) {
900
+ return
901
+ }
894
902
 
895
- // Parse the data received
896
- let parsedSettings
897
- let parsedData
898
- try {
899
- parsedSettings = JSON.parse(device1.deviceExt.deviceSettings)
900
- parsedData = JSON.parse(device1.deviceExt.lastDeviceData)
901
- } catch {
902
- continue
903
- }
903
+ // Parse the data received
904
+ let parsedSettings
905
+ let parsedData
906
+ try {
907
+ parsedSettings = JSON.parse(device1.deviceExt.deviceSettings)
908
+ parsedData = JSON.parse(device1.deviceExt.lastDeviceData)
909
+ } catch {
910
+ return
911
+ }
904
912
 
905
- const toReturn = { source: 'HTTP' }
906
- if (platformConsts.models.sensorLeak.includes(device1.sku)) {
907
- accessory.logDebug(`raw data: ${JSON.stringify({ ...parsedData, ...parsedSettings })}`)
913
+ const toReturn = { source: 'HTTP' }
914
+ if (platformConsts.models.sensorLeak.includes(device1.sku)) {
915
+ accessory.logDebug(`raw data: ${JSON.stringify({ ...parsedData, ...parsedSettings })}`)
908
916
 
909
- // Keep the gateway details up to date for matching real-time leak
910
- // events relayed by the gateway over AWS (#1276)
911
- if (hasProperty(parsedSettings, 'sno') && parsedSettings.gatewayInfo?.device) {
912
- accessory.context.gatewaySno = parsedSettings.sno
913
- accessory.context.gatewayDevice = parsedSettings.gatewayInfo.device
914
- }
917
+ // Keep the gateway details up to date for matching real-time leak
918
+ // events relayed by the gateway over AWS (#1276)
919
+ if (hasProperty(parsedSettings, 'sno') && parsedSettings.gatewayInfo?.device) {
920
+ accessory.context.gatewaySno = parsedSettings.sno
921
+ accessory.context.gatewayDevice = parsedSettings.gatewayInfo.device
922
+ }
915
923
 
916
- // Leak Sensors - check to see of any warnings if the lastTime is above 0
917
- let hasUnreadLeak = false
918
- if (parsedData.lastTime > 0) {
919
- // Obtain the leak warning messages for this device
920
- const msgs = await this.httpClient.getLeakDeviceWarning(device1.device, device1.sku)
924
+ // Leak Sensors - check to see of any warnings if the lastTime is above 0
925
+ let hasUnreadLeak = false
926
+ if (parsedData.lastTime > 0) {
927
+ // Obtain the leak warning messages for this device
928
+ const msgs = await leakWarningQueue.add(
929
+ () => this.httpClient.getLeakDeviceWarning(device1.device, device1.sku),
930
+ )
921
931
 
922
- accessory.logDebug(`raw messages: ${JSON.stringify(msgs)}`)
932
+ accessory.logDebug(`raw messages: ${JSON.stringify(msgs)}`)
923
933
 
924
- // Check to see if unread messages exist
925
- const unreadCount = msgs.filter(msg => !msg.read && msg.message.toLowerCase().replace(WHITESPACE_REGEX, '').startsWith('leakagealert'))
926
- if (unreadCount.length > 0) {
927
- hasUnreadLeak = true
934
+ // Check to see if unread messages exist
935
+ const unreadCount = msgs.filter(msg => !msg.read && msg.message.toLowerCase().replace(WHITESPACE_REGEX, '').startsWith('leakagealert'))
936
+ if (unreadCount.length > 0) {
937
+ hasUnreadLeak = true
938
+ }
928
939
  }
929
- }
930
940
 
931
- // Generate the params to return
932
- toReturn.battery = parsedSettings.battery
933
- toReturn.leakDetected = hasUnreadLeak
934
- toReturn.online = parsedData.gwonline && parsedData.online
935
- } else if (platformConsts.models.sensorThermo.includes(device1.sku)) {
936
- // Some accounts report `tem` in hundredths of a degree Fahrenheit
937
- // rather than Celsius, which lands in HomeKit as a reading roughly
938
- // 90 degrees too high (#1269). The unit is not stated anywhere in
939
- // the response, so log the raw payload to work out what
940
- // distinguishes those accounts before converting anything.
941
- accessory.logDebug(`[HTTP] raw sensor data: ${JSON.stringify(parsedData)}`)
942
-
943
- if (hasProperty(parsedSettings, 'battery')) {
944
- toReturn.battery = parsedSettings.battery
945
- }
946
- if (hasProperty(parsedData, 'tem')) {
947
- toReturn.temperature = parsedData.tem
948
- }
949
- if (hasProperty(parsedData, 'hum')) {
950
- toReturn.humidity = parsedData.hum
951
- }
952
- if (hasProperty(parsedData, 'online')) {
953
- toReturn.online = parsedData.online
954
- }
955
- } else if (platformConsts.models.sensorMonitor.includes(device1.sku)) {
956
- // The H5106 normally reports over AWS, but some accounts never
957
- // receive those messages, leaving the readings at zero (#1322).
958
- // The HTTP device list carries the same readings, so use it as a
959
- // second source. Every field is guarded and the raw payload is
960
- // logged, since the format for this model is not confirmed on
961
- // every account
962
- accessory.logDebug(`[HTTP] raw sensor data: ${JSON.stringify(parsedData)}`)
963
-
964
- if (hasProperty(parsedSettings, 'battery')) {
941
+ // Generate the params to return
965
942
  toReturn.battery = parsedSettings.battery
943
+ toReturn.leakDetected = hasUnreadLeak
944
+ toReturn.online = parsedData.gwonline && parsedData.online
945
+ } else if (platformConsts.models.sensorThermo.includes(device1.sku)) {
946
+ // Some accounts report `tem` in hundredths of a degree Fahrenheit
947
+ // rather than Celsius, which lands in HomeKit as a reading roughly
948
+ // 90 degrees too high (#1269). The unit is not stated anywhere in
949
+ // the response, so log the raw payload to work out what
950
+ // distinguishes those accounts before converting anything.
951
+ accessory.logDebug(`[HTTP] raw sensor data: ${JSON.stringify(parsedData)}`)
952
+
953
+ if (hasProperty(parsedSettings, 'battery')) {
954
+ toReturn.battery = parsedSettings.battery
955
+ }
956
+ if (hasProperty(parsedData, 'tem')) {
957
+ toReturn.temperature = parsedData.tem
958
+ }
959
+ if (hasProperty(parsedData, 'hum')) {
960
+ toReturn.humidity = parsedData.hum
961
+ }
962
+ if (hasProperty(parsedData, 'online')) {
963
+ toReturn.online = parsedData.online
964
+ }
965
+ } else if (platformConsts.models.sensorMonitor.includes(device1.sku)) {
966
+ // The H5106 normally reports over AWS, but some accounts never
967
+ // receive those messages, leaving the readings at zero (#1322).
968
+ // The HTTP device list carries the same readings, so use it as a
969
+ // second source. Every field is guarded and the raw payload is
970
+ // logged, since the format for this model is not confirmed on
971
+ // every account
972
+ accessory.logDebug(`[HTTP] raw sensor data: ${JSON.stringify(parsedData)}`)
973
+
974
+ if (hasProperty(parsedSettings, 'battery')) {
975
+ toReturn.battery = parsedSettings.battery
976
+ }
977
+ if (hasProperty(parsedData, 'tem')) {
978
+ toReturn.temperature = parsedData.tem
979
+ }
980
+ if (hasProperty(parsedData, 'hum')) {
981
+ toReturn.humidity = parsedData.hum
982
+ }
983
+ if (hasProperty(parsedData, 'online')) {
984
+ toReturn.online = parsedData.online
985
+ }
966
986
  }
967
- if (hasProperty(parsedData, 'tem')) {
968
- toReturn.temperature = parsedData.tem
969
- }
970
- if (hasProperty(parsedData, 'hum')) {
971
- toReturn.humidity = parsedData.hum
972
- }
973
- if (hasProperty(parsedData, 'online')) {
974
- toReturn.online = parsedData.online
975
- }
976
- }
977
987
 
978
- // Send the information to the update receiver function
979
- this.receiveDeviceUpdate(accessory, toReturn)
980
- } catch (err) {
981
- this.log.warn('[%s] %s %s.', device1.deviceName, platformLang.devNotRef, parseError(err))
982
- }
983
- }
988
+ // Send the information to the update receiver function
989
+ this.receiveDeviceUpdate(accessory, toReturn)
990
+ } catch (err) {
991
+ this.log.warn('[%s] %s %s.', device1.deviceName, platformLang.devNotRef, parseError(err))
992
+ }
993
+ }))
984
994
  } catch (err) {
985
995
  this.log.warn('[HTTP] %s %s.', platformLang.syncFail, parseError(err))
986
996
  }
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.34.2-beta.5",
5
+ "version": "11.34.2-beta.7",
6
6
  "description": "Homebridge plugin to integrate Govee devices into HomeKit.",
7
7
  "author": {
8
8
  "name": "bwp91",