@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 +1 -0
- package/lib/platform.js +6 -156
- package/lib/utils/device-context.js +179 -0
- package/package.json +1 -1
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 {
|
|
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
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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