@homebridge-plugins/homebridge-govee 11.32.2-beta.16 → 11.32.2-beta.17

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
@@ -35,6 +35,7 @@ All notable changes to `@homebridge-plugins/homebridge-govee` will be documented
35
35
  - feat: give a diffuser model that has not been worked out yet the same starting handler the other appliances have, so it works for on and off
36
36
  - refactor: choose a device handler from a table, so adding a device type no longer means editing the platform
37
37
  - refactor: move the settings parsing into its own file, away from the code that talks to devices
38
+ - refactor: move the work of describing a device to its handler into its own file
38
39
 
39
40
  ## v11.32.1 (2026-08-02)
40
41
 
package/lib/platform.js CHANGED
@@ -17,7 +17,7 @@ import { buildCommand } from './utils/command-builder.js'
17
17
  import { applyUserConfig } from './utils/config-parser.js'
18
18
  import platformConsts from './utils/constants.js'
19
19
  import platformChars from './utils/custom-chars.js'
20
- import { getDeviceCapabilities } from './utils/device-capabilities.js'
20
+ import { applyDeviceContext } from './utils/device-context.js'
21
21
  import { resolveDeviceType } from './utils/dispatch.js'
22
22
  import eveChars from './utils/eve-chars.js'
23
23
  import {
@@ -860,7 +860,6 @@ export default class {
860
860
  }
861
861
 
862
862
  const devInstance = route.handler
863
- const doAWSPolling = route.awsPolling
864
863
 
865
864
  let accessory
866
865
  if (route.externalCategory) {
@@ -882,161 +881,12 @@ export default class {
882
881
  // Set the logging level for this device
883
882
  this.applyAccessoryLogging(accessory)
884
883
 
885
- // Add the temperatureSource config to the context if exists
886
- if (deviceConf.temperatureSource) {
887
- accessory.context.temperatureSource = deviceConf.temperatureSource
888
- }
889
-
890
- // Add the openApiTempUnit config to the context if set to something other than auto
891
- if (deviceConf.openApiTempUnit && deviceConf.openApiTempUnit !== 'auto') {
892
- accessory.context.openApiTempUnit = deviceConf.openApiTempUnit
893
- } else {
894
- delete accessory.context.openApiTempUnit
895
- }
896
-
897
- // Get a supported command list if provided, with their options
898
- if (device.supportCmds && Array.isArray(device.supportCmds)) {
899
- accessory.context.supportedCmds = device.supportCmds
900
- accessory.context.supportedCmdsOpts = {}
901
-
902
- device.supportCmds.forEach((cmd) => {
903
- if (device?.properties?.[cmd]) {
904
- accessory.context.supportedCmdsOpts[cmd] = device.properties[cmd]
905
- }
906
- })
907
- }
908
-
909
- // Add some initial context information which is changed later
910
- accessory.context.hasAwsControl = false
911
- accessory.context.useAwsControl = false
912
- accessory.context.hasOpenApiControl = false
913
- accessory.context.useOpenApiControl = false
914
- accessory.context.hasBleControl = false
915
- accessory.context.useBleControl = false
916
- accessory.context.hasLanControl = device.isLanDevice
917
- accessory.context.useLanControl = accessory.context.hasLanControl
918
- accessory.context.firmware = false
919
- accessory.context.hardware = false
920
- accessory.context.image = false
921
-
922
- // Overrides for when a custom IP is provided, for a light which is not BLE only
923
- if (
924
- deviceConf.customIPAddress
925
- && accessory.context.hasLanControl
926
- && accessory.context.hasAwsControl
927
- && platformConsts.models.rgb.includes(device.model)
928
- ) {
929
- accessory.context.hasLanControl = true
930
- accessory.context.useLanControl = true
931
- }
932
-
933
- // If the device is LAN-only, then sync the display name with the label in the configuration
934
- if (device.isLanOnly) {
935
- accessory.displayName = device.deviceName
936
- }
937
-
938
- // See if we have extra HTTP client info for this device
939
- if (device.httpInfo) {
940
- // Save the hardware and firmware versions
941
- accessory.context.firmware = device.httpInfo.versionSoft
942
- accessory.context.hardware = device.httpInfo.versionHard
943
-
944
- // It's possible to show a nice little icon of the device in the Homebridge UI
945
- if (device.httpInfo.deviceExt && device.httpInfo.deviceExt.extResources) {
946
- try {
947
- const parsed = JSON.parse(device.httpInfo.deviceExt.extResources)
948
- if (parsed && parsed.skuUrl) {
949
- accessory.context.image = parsed.skuUrl
950
- }
951
- } catch {
952
- // Ignore malformed extResources
953
- }
954
- }
955
-
956
- // HTTP info lets us see if AWS/BLE connection methods are available
957
- if (device.httpInfo.deviceExt && device.httpInfo.deviceExt.deviceSettings) {
958
- let parsed
959
- try {
960
- parsed = JSON.parse(device.httpInfo.deviceExt.deviceSettings)
961
- } catch {
962
- this.log.debugWarn('[%s] failed to parse deviceSettings, skipping AWS/BLE setup.', accessory.displayName)
963
- }
964
-
965
- // Check to see if AWS is possible
966
- if (parsed) {
967
- if (parsed.topic) {
968
- accessory.context.hasAwsControl = true
969
- accessory.context.awsTopic = parsed.topic
970
-
971
- if (this.awsClient) {
972
- accessory.context.useAwsControl = true
973
- accessory.context.awsBrightnessNoScale = deviceConf.awsBrightnessNoScale
974
- accessory.context.awsColourMode = deviceConf.awsColourMode || platformConsts.defaultValues.awsColourMode
975
- awsDevices.push(device.device)
976
-
977
- // Certain models need AWS polling
978
- if (doAWSPolling) {
979
- awsDevicesToPoll.push(device.device)
980
- }
981
- }
982
- }
983
-
984
- // Check to see if BLE is possible
985
- if (parsed.bleName) {
986
- const providedBle = parsed.address ? parsed.address.toLowerCase() : device.device.substring(6).toLowerCase()
987
- accessory.context.hasBleControl = !!parsed.bleName
988
- accessory.context.bleAddress = deviceConf.customAddress
989
- ? deviceConf.customAddress.toLowerCase()
990
- : providedBle
991
- accessory.context.bleName = parsed.bleName
992
- accessory.context.bleWriteUuid = getDeviceCapabilities(accessory.context.gvModel).bleWriteUuid
993
- if (this.bleClient) {
994
- accessory.context.useBleControl = true
995
- }
996
- }
997
-
998
- // Gateway subdevices (e.g. leak sensors paired to an H5044) include
999
- // their gateway id and slot number, which we need to match real-time
1000
- // leak events relayed by the gateway over AWS (#1276)
1001
- if (hasProperty(parsed, 'sno') && parsed.gatewayInfo?.device) {
1002
- accessory.context.gatewaySno = parsed.sno
1003
- accessory.context.gatewayDevice = parsed.gatewayInfo.device
1004
- }
1005
-
1006
- // Get a min and max temperature/humidity range to show in the homebridge-ui
1007
- if (hasProperty(parsed, 'temCali')) {
1008
- accessory.context.minTemp = parsed.temMin / 100
1009
- accessory.context.maxTemp = parsed.temMax / 100
1010
- accessory.context.offTemp = parsed.temCali
1011
- }
1012
- if (hasProperty(parsed, 'humCali')) {
1013
- accessory.context.minHumi = parsed.humMin / 100
1014
- accessory.context.maxHumi = parsed.humMax / 100
1015
- accessory.context.offHumi = parsed.humCali
1016
- }
1017
- }
1018
- }
1019
- }
1020
-
1021
- if (device.openApiInfo) {
1022
- accessory.context.hasOpenApiControl = true
1023
- accessory.context.useOpenApiControl = !!this.openApiClient
1024
- accessory.context.openApiCapabilities = device.openApiInfo.byInstance || {}
1025
-
1026
- // Log what Govee says this device can do. Working out whether a model
1027
- // exposes something like a night light otherwise means reverse
1028
- // engineering its status messages, when the answer is right here
1029
- const instances = Object.entries(accessory.context.openApiCapabilities)
1030
- .map(([instance, capability]) => `${instance} (${capability?.type ?? 'unknown type'})`)
1031
- this.log.debug(
1032
- '[%s] openapi category [%s] capabilities [%s].',
1033
- accessory.displayName,
1034
- device.openApiInfo.category || 'unknown',
1035
- instances.join(', ') || 'none',
1036
- )
1037
- }
884
+ applyDeviceContext(this, accessory, device, deviceConf, {
885
+ awsDevices,
886
+ awsDevicesToPoll,
887
+ doAWSPolling: route.awsPolling,
888
+ })
1038
889
 
1039
- // Create the instance for this device type
1040
890
  accessory.control = new devInstance(this, accessory)
1041
891
 
1042
892
  // Log the device initialisation
@@ -0,0 +1,179 @@
1
+ import platformConsts from './constants.js'
2
+ import { getDeviceCapabilities } from './device-capabilities.js'
3
+ import { hasProperty } from './functions.js'
4
+
5
+ /**
6
+ * Fills in everything a handler needs to know about a device before it is
7
+ * built: which connections are available, what firmware it reports, the limits
8
+ * it works within, and what Govee's api says it can do.
9
+ *
10
+ * Lifted out of the platform unchanged. It is data assembly rather than
11
+ * anything to do with Homebridge, and it was making the one method that sets a
12
+ * device up twice as long as it needed to be.
13
+ *
14
+ * @param {object} platform the platform, for logging and settings
15
+ * @param {object} accessory the accessory being set up
16
+ * @param {object} device the device as Govee describes it
17
+ * @param {object} deviceConf this device's settings
18
+ * @param {object} joins where to record the connections this device can use -
19
+ * `awsDevices`, `awsDevicesToPoll`, and whether this kind of device needs its
20
+ * status asking for over AWS
21
+ */
22
+ export function applyDeviceContext(platform, accessory, device, deviceConf, joins) {
23
+ const { awsDevices, awsDevicesToPoll, doAWSPolling } = joins
24
+ // Add the temperatureSource config to the context if exists
25
+ if (deviceConf.temperatureSource) {
26
+ accessory.context.temperatureSource = deviceConf.temperatureSource
27
+ }
28
+
29
+ // Add the openApiTempUnit config to the context if set to something other than auto
30
+ if (deviceConf.openApiTempUnit && deviceConf.openApiTempUnit !== 'auto') {
31
+ accessory.context.openApiTempUnit = deviceConf.openApiTempUnit
32
+ } else {
33
+ delete accessory.context.openApiTempUnit
34
+ }
35
+
36
+ // Get a supported command list if provided, with their options
37
+ if (device.supportCmds && Array.isArray(device.supportCmds)) {
38
+ accessory.context.supportedCmds = device.supportCmds
39
+ accessory.context.supportedCmdsOpts = {}
40
+
41
+ device.supportCmds.forEach((cmd) => {
42
+ if (device?.properties?.[cmd]) {
43
+ accessory.context.supportedCmdsOpts[cmd] = device.properties[cmd]
44
+ }
45
+ })
46
+ }
47
+
48
+ // Add some initial context information which is changed later
49
+ accessory.context.hasAwsControl = false
50
+ accessory.context.useAwsControl = false
51
+ accessory.context.hasOpenApiControl = false
52
+ accessory.context.useOpenApiControl = false
53
+ accessory.context.hasBleControl = false
54
+ accessory.context.useBleControl = false
55
+ accessory.context.hasLanControl = device.isLanDevice
56
+ accessory.context.useLanControl = accessory.context.hasLanControl
57
+ accessory.context.firmware = false
58
+ accessory.context.hardware = false
59
+ accessory.context.image = false
60
+
61
+ // Overrides for when a custom IP is provided, for a light which is not BLE only
62
+ if (
63
+ deviceConf.customIPAddress
64
+ && accessory.context.hasLanControl
65
+ && accessory.context.hasAwsControl
66
+ && platformConsts.models.rgb.includes(device.model)
67
+ ) {
68
+ accessory.context.hasLanControl = true
69
+ accessory.context.useLanControl = true
70
+ }
71
+
72
+ // If the device is LAN-only, then sync the display name with the label in the configuration
73
+ if (device.isLanOnly) {
74
+ accessory.displayName = device.deviceName
75
+ }
76
+
77
+ // See if we have extra HTTP client info for this device
78
+ if (device.httpInfo) {
79
+ // Save the hardware and firmware versions
80
+ accessory.context.firmware = device.httpInfo.versionSoft
81
+ accessory.context.hardware = device.httpInfo.versionHard
82
+
83
+ // It's possible to show a nice little icon of the device in the Homebridge UI
84
+ if (device.httpInfo.deviceExt && device.httpInfo.deviceExt.extResources) {
85
+ try {
86
+ const parsed = JSON.parse(device.httpInfo.deviceExt.extResources)
87
+ if (parsed && parsed.skuUrl) {
88
+ accessory.context.image = parsed.skuUrl
89
+ }
90
+ } catch {
91
+ // Ignore malformed extResources
92
+ }
93
+ }
94
+
95
+ // HTTP info lets us see if AWS/BLE connection methods are available
96
+ if (device.httpInfo.deviceExt && device.httpInfo.deviceExt.deviceSettings) {
97
+ let parsed
98
+ try {
99
+ parsed = JSON.parse(device.httpInfo.deviceExt.deviceSettings)
100
+ } catch {
101
+ platform.log.debugWarn('[%s] failed to parse deviceSettings, skipping AWS/BLE setup.', accessory.displayName)
102
+ }
103
+
104
+ // Check to see if AWS is possible
105
+ if (parsed) {
106
+ if (parsed.topic) {
107
+ accessory.context.hasAwsControl = true
108
+ accessory.context.awsTopic = parsed.topic
109
+
110
+ if (platform.awsClient) {
111
+ accessory.context.useAwsControl = true
112
+ accessory.context.awsBrightnessNoScale = deviceConf.awsBrightnessNoScale
113
+ accessory.context.awsColourMode = deviceConf.awsColourMode || platformConsts.defaultValues.awsColourMode
114
+ awsDevices.push(device.device)
115
+
116
+ // Certain models need AWS polling
117
+ if (doAWSPolling) {
118
+ awsDevicesToPoll.push(device.device)
119
+ }
120
+ }
121
+ }
122
+
123
+ // Check to see if BLE is possible
124
+ if (parsed.bleName) {
125
+ const providedBle = parsed.address ? parsed.address.toLowerCase() : device.device.substring(6).toLowerCase()
126
+ accessory.context.hasBleControl = !!parsed.bleName
127
+ accessory.context.bleAddress = deviceConf.customAddress
128
+ ? deviceConf.customAddress.toLowerCase()
129
+ : providedBle
130
+ accessory.context.bleName = parsed.bleName
131
+ accessory.context.bleWriteUuid = getDeviceCapabilities(accessory.context.gvModel).bleWriteUuid
132
+ if (platform.bleClient) {
133
+ accessory.context.useBleControl = true
134
+ }
135
+ }
136
+
137
+ // Gateway subdevices (e.g. leak sensors paired to an H5044) include
138
+ // their gateway id and slot number, which we need to match real-time
139
+ // leak events relayed by the gateway over AWS (#1276)
140
+ if (hasProperty(parsed, 'sno') && parsed.gatewayInfo?.device) {
141
+ accessory.context.gatewaySno = parsed.sno
142
+ accessory.context.gatewayDevice = parsed.gatewayInfo.device
143
+ }
144
+
145
+ // Get a min and max temperature/humidity range to show in the homebridge-ui
146
+ if (hasProperty(parsed, 'temCali')) {
147
+ accessory.context.minTemp = parsed.temMin / 100
148
+ accessory.context.maxTemp = parsed.temMax / 100
149
+ accessory.context.offTemp = parsed.temCali
150
+ }
151
+ if (hasProperty(parsed, 'humCali')) {
152
+ accessory.context.minHumi = parsed.humMin / 100
153
+ accessory.context.maxHumi = parsed.humMax / 100
154
+ accessory.context.offHumi = parsed.humCali
155
+ }
156
+ }
157
+ }
158
+ }
159
+
160
+ if (device.openApiInfo) {
161
+ accessory.context.hasOpenApiControl = true
162
+ accessory.context.useOpenApiControl = !!platform.openApiClient
163
+ accessory.context.openApiCapabilities = device.openApiInfo.byInstance || {}
164
+
165
+ // Log what Govee says this device can do. Working out whether a model
166
+ // exposes something like a night light otherwise means reverse
167
+ // engineering its status messages, when the answer is right here
168
+ const instances = Object.entries(accessory.context.openApiCapabilities)
169
+ .map(([instance, capability]) => `${instance} (${capability?.type ?? 'unknown type'})`)
170
+ platform.log.debug(
171
+ '[%s] openapi category [%s] capabilities [%s].',
172
+ accessory.displayName,
173
+ device.openApiInfo.category || 'unknown',
174
+ instances.join(', ') || 'none',
175
+ )
176
+ }
177
+
178
+ // Create the instance for this device type
179
+ }
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.32.2-beta.16",
5
+ "version": "11.32.2-beta.17",
6
6
  "description": "Homebridge plugin to integrate Govee devices into HomeKit.",
7
7
  "author": {
8
8
  "name": "bwp91",