@homebridge-plugins/homebridge-govee 10.12.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 +1937 -0
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/config.schema.json +1727 -0
- package/eslint.config.js +49 -0
- package/lib/connection/aws.js +174 -0
- package/lib/connection/ble.js +208 -0
- package/lib/connection/cert/AmazonRootCA1.pem +20 -0
- package/lib/connection/http.js +240 -0
- package/lib/connection/lan.js +284 -0
- package/lib/device/cooler-single.js +300 -0
- package/lib/device/dehumidifier-H7150.js +182 -0
- package/lib/device/dehumidifier-H7151.js +157 -0
- package/lib/device/diffuser-H7161.js +117 -0
- package/lib/device/diffuser-H7162.js +117 -0
- package/lib/device/fan-H7100.js +274 -0
- package/lib/device/fan-H7101.js +330 -0
- package/lib/device/fan-H7102.js +274 -0
- package/lib/device/fan-H7105.js +503 -0
- package/lib/device/fan-H7106.js +274 -0
- package/lib/device/fan-H7111.js +335 -0
- package/lib/device/heater-single.js +300 -0
- package/lib/device/heater1a.js +353 -0
- package/lib/device/heater1b.js +616 -0
- package/lib/device/heater2.js +838 -0
- package/lib/device/humidifier-H7140.js +224 -0
- package/lib/device/humidifier-H7141.js +257 -0
- package/lib/device/humidifier-H7142.js +522 -0
- package/lib/device/humidifier-H7143.js +157 -0
- package/lib/device/humidifier-H7148.js +157 -0
- package/lib/device/humidifier-H7160.js +446 -0
- package/lib/device/ice-maker-H7162.js +46 -0
- package/lib/device/index.js +105 -0
- package/lib/device/kettle.js +269 -0
- package/lib/device/light-switch.js +86 -0
- package/lib/device/light.js +617 -0
- package/lib/device/outlet-double.js +121 -0
- package/lib/device/outlet-single.js +172 -0
- package/lib/device/outlet-triple.js +160 -0
- package/lib/device/purifier-H7120.js +336 -0
- package/lib/device/purifier-H7121.js +336 -0
- package/lib/device/purifier-H7122.js +449 -0
- package/lib/device/purifier-H7123.js +411 -0
- package/lib/device/purifier-H7124.js +411 -0
- package/lib/device/purifier-H7126.js +296 -0
- package/lib/device/purifier-H7127.js +296 -0
- package/lib/device/purifier-H712C.js +296 -0
- package/lib/device/purifier-single.js +119 -0
- package/lib/device/sensor-button.js +22 -0
- package/lib/device/sensor-contact.js +22 -0
- package/lib/device/sensor-leak.js +87 -0
- package/lib/device/sensor-monitor.js +190 -0
- package/lib/device/sensor-presence.js +53 -0
- package/lib/device/sensor-thermo.js +144 -0
- package/lib/device/sensor-thermo4.js +55 -0
- package/lib/device/switch-double.js +121 -0
- package/lib/device/switch-single.js +95 -0
- package/lib/device/switch-triple.js +160 -0
- package/lib/device/tap-single.js +108 -0
- package/lib/device/template.js +43 -0
- package/lib/device/tv-single.js +84 -0
- package/lib/device/valve-single.js +155 -0
- package/lib/fakegato/LICENSE +21 -0
- package/lib/fakegato/fakegato-history.js +814 -0
- package/lib/fakegato/fakegato-storage.js +108 -0
- package/lib/fakegato/fakegato-timer.js +125 -0
- package/lib/fakegato/uuid.js +27 -0
- package/lib/homebridge-ui/public/index.html +433 -0
- package/lib/homebridge-ui/server.js +10 -0
- package/lib/index.js +8 -0
- package/lib/platform.js +1967 -0
- package/lib/utils/colour.js +564 -0
- package/lib/utils/constants.js +579 -0
- package/lib/utils/custom-chars.js +225 -0
- package/lib/utils/eve-chars.js +68 -0
- package/lib/utils/functions.js +117 -0
- package/lib/utils/lang-en.js +131 -0
- package/package.json +75 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hs2rgb,
|
|
3
|
+
k2rgb,
|
|
4
|
+
m2hs,
|
|
5
|
+
rgb2hs,
|
|
6
|
+
} from '../utils/colour.js'
|
|
7
|
+
import platformConsts from '../utils/constants.js'
|
|
8
|
+
import {
|
|
9
|
+
generateRandomString,
|
|
10
|
+
hasProperty,
|
|
11
|
+
parseError,
|
|
12
|
+
sleep,
|
|
13
|
+
} from '../utils/functions.js'
|
|
14
|
+
import platformLang from '../utils/lang-en.js'
|
|
15
|
+
|
|
16
|
+
export default class {
|
|
17
|
+
constructor(platform, accessory) {
|
|
18
|
+
// Set up variables from the platform
|
|
19
|
+
this.cusChar = platform.cusChar
|
|
20
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
21
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
22
|
+
this.hapServ = platform.api.hap.Service
|
|
23
|
+
this.platform = platform
|
|
24
|
+
|
|
25
|
+
// Set up variables from the accessory
|
|
26
|
+
this.accessory = accessory
|
|
27
|
+
this.colourSafeMode = platform.config.colourSafeMode
|
|
28
|
+
this.minKelvin = accessory.context?.supportedCmdsOpts?.colorTem?.range?.min || 2000
|
|
29
|
+
this.maxKelvin = accessory.context?.supportedCmdsOpts?.colorTem?.range?.max || 9000
|
|
30
|
+
this.isBLEOnly = !accessory.context.useAWSControl && !accessory.context.useLANControl
|
|
31
|
+
|
|
32
|
+
// Set up custom variables for this device type
|
|
33
|
+
const deviceConf = platform.deviceConf[accessory.context.gvDeviceId] || {}
|
|
34
|
+
this.alShift = deviceConf.adaptiveLightingShift || platformConsts.defaultValues.adaptiveLightingShift
|
|
35
|
+
this.brightStep = deviceConf.brightnessStep
|
|
36
|
+
? Math.min(deviceConf.brightnessStep, 100)
|
|
37
|
+
: platformConsts.defaultValues.brightnessStep
|
|
38
|
+
|
|
39
|
+
// Remove any switch service if it exists
|
|
40
|
+
if (accessory.getService(this.hapServ.Switch)) {
|
|
41
|
+
accessory.removeService(accessory.getService(this.hapServ.Switch))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Add the main lightbulb service if it doesn't already exist
|
|
45
|
+
this.service = this.accessory.getService(this.hapServ.Lightbulb)
|
|
46
|
+
|| this.accessory.addService(this.hapServ.Lightbulb)
|
|
47
|
+
|
|
48
|
+
// If adaptive lighting has just been disabled then remove and re-add service to hide AL icon
|
|
49
|
+
if ((this.colourSafeMode || this.alShift === -1) && this.accessory.context.adaptiveLighting) {
|
|
50
|
+
this.accessory.removeService(this.service)
|
|
51
|
+
this.service = this.accessory.addService(this.hapServ.Lightbulb)
|
|
52
|
+
this.accessory.context.adaptiveLighting = false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Setup custom characteristics for different scenes and modes
|
|
56
|
+
this.usedCodes = [];
|
|
57
|
+
|
|
58
|
+
[
|
|
59
|
+
'DiyMode',
|
|
60
|
+
'DiyModeTwo',
|
|
61
|
+
'DiyModeThree',
|
|
62
|
+
'DiyModeFour',
|
|
63
|
+
'MusicMode',
|
|
64
|
+
'MusicModeTwo',
|
|
65
|
+
'Scene',
|
|
66
|
+
'SceneTwo',
|
|
67
|
+
'SceneThree',
|
|
68
|
+
'SceneFour',
|
|
69
|
+
'Segmented',
|
|
70
|
+
'SegmentedTwo',
|
|
71
|
+
'SegmentedThree',
|
|
72
|
+
'SegmentedFour',
|
|
73
|
+
'VideoMode',
|
|
74
|
+
'VideoModeTwo',
|
|
75
|
+
].forEach((charName) => {
|
|
76
|
+
const confName = charName.charAt(0).toLowerCase() + charName.slice(1)
|
|
77
|
+
const confCode = deviceConf[confName]
|
|
78
|
+
|
|
79
|
+
// Check if any code has been entered in the config by the user
|
|
80
|
+
if (confCode?.sceneCode) {
|
|
81
|
+
const { bleCode, sceneCode } = confCode
|
|
82
|
+
|
|
83
|
+
// Add to the global enabled scenes list
|
|
84
|
+
this.usedCodes.push(charName)
|
|
85
|
+
|
|
86
|
+
// Add the characteristic if not already
|
|
87
|
+
if (confCode?.showAs === 'switch') {
|
|
88
|
+
// Remove the Eve switch if exists
|
|
89
|
+
if (this.service.testCharacteristic(this.cusChar[charName])) {
|
|
90
|
+
this.service.removeCharacteristic(this.service.getCharacteristic(this.cusChar[charName]))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Add the accessory service switch
|
|
94
|
+
if (!this.accessory.getService(charName)) {
|
|
95
|
+
this.accessory.addService(this.hapServ.Switch, charName, charName)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Add the set handler and also mark all as off when initialising accessory
|
|
99
|
+
this.accessory.getService(charName)
|
|
100
|
+
.getCharacteristic(this.hapChar.On)
|
|
101
|
+
.onSet(async (value) => {
|
|
102
|
+
await this.internalSceneUpdate(charName, sceneCode, bleCode, value, true)
|
|
103
|
+
})
|
|
104
|
+
.updateValue(false)
|
|
105
|
+
} else {
|
|
106
|
+
// Remove the accessory service switch if exists
|
|
107
|
+
if (this.accessory.getService(charName)) {
|
|
108
|
+
this.accessory.removeService(this.accessory.getService(charName))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Add the Eve switch
|
|
112
|
+
if (!this.service.testCharacteristic(this.cusChar[charName])) {
|
|
113
|
+
this.service.addCharacteristic(this.cusChar[charName])
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Add the set handler and also mark all as off when initialising accessory
|
|
117
|
+
this.service
|
|
118
|
+
.getCharacteristic(this.cusChar[charName])
|
|
119
|
+
.onSet(async (value) => {
|
|
120
|
+
await this.internalSceneUpdate(charName, sceneCode, bleCode, value)
|
|
121
|
+
})
|
|
122
|
+
.updateValue(false)
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
// If here then either code is invalid or has been removed, so remove the characteristic
|
|
126
|
+
|
|
127
|
+
if (this.service.testCharacteristic(this.cusChar[charName])) {
|
|
128
|
+
this.service.removeCharacteristic(this.service.getCharacteristic(this.cusChar[charName]))
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
this.hasScenes = this.usedCodes.length > 0
|
|
134
|
+
|
|
135
|
+
// Add the colour mode characteristic if at least one other scene/mode is exposed
|
|
136
|
+
if (this.hasScenes) {
|
|
137
|
+
// Add the colour mode characteristic if not already
|
|
138
|
+
if (!this.service.testCharacteristic(this.cusChar.ColourMode)) {
|
|
139
|
+
this.service.addCharacteristic(this.cusChar.ColourMode)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Add the set handler and also mark as off when initialising accessory
|
|
143
|
+
this.service
|
|
144
|
+
.getCharacteristic(this.cusChar.ColourMode)
|
|
145
|
+
.onSet(async (value) => {
|
|
146
|
+
if (value) {
|
|
147
|
+
await this.internalColourUpdate(this.cacheHue, true)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
.updateValue(false)
|
|
151
|
+
} else if (this.service.testCharacteristic(this.cusChar.ColourMode)) {
|
|
152
|
+
// Remove the characteristic if it exists already (no need for it)
|
|
153
|
+
this.service.removeCharacteristic(this.service.getCharacteristic(this.cusChar.ColourMode))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Add the set handler to the lightbulb on/off characteristic
|
|
157
|
+
this.service.getCharacteristic(this.hapChar.On).onSet(async (value) => {
|
|
158
|
+
await this.internalStateUpdate(value)
|
|
159
|
+
})
|
|
160
|
+
this.cacheState = this.service.getCharacteristic(this.hapChar.On).value ? 'on' : 'off'
|
|
161
|
+
|
|
162
|
+
// Add the set handler to the lightbulb brightness characteristic
|
|
163
|
+
this.service
|
|
164
|
+
.getCharacteristic(this.hapChar.Brightness)
|
|
165
|
+
.setProps({ minStep: this.brightStep })
|
|
166
|
+
.onSet(async (value) => {
|
|
167
|
+
await this.internalBrightnessUpdate(value)
|
|
168
|
+
})
|
|
169
|
+
this.cacheBright = this.service.getCharacteristic(this.hapChar.Brightness).value
|
|
170
|
+
this.cacheBrightRaw = this.cacheBright
|
|
171
|
+
|
|
172
|
+
// Add the set handler to the lightbulb hue characteristic
|
|
173
|
+
this.service.getCharacteristic(this.hapChar.Hue).onSet(async (value) => {
|
|
174
|
+
await this.internalColourUpdate(value)
|
|
175
|
+
})
|
|
176
|
+
this.cacheHue = this.service.getCharacteristic(this.hapChar.Hue).value
|
|
177
|
+
this.cacheSat = this.service.getCharacteristic(this.hapChar.Saturation).value
|
|
178
|
+
|
|
179
|
+
// Add the set handler to the lightbulb cct characteristic
|
|
180
|
+
if (this.colourSafeMode) {
|
|
181
|
+
if (this.service.testCharacteristic(this.hapChar.ColorTemperature)) {
|
|
182
|
+
this.service.removeCharacteristic(this.service.getCharacteristic(this.hapChar.ColorTemperature))
|
|
183
|
+
}
|
|
184
|
+
this.cacheMired = 0
|
|
185
|
+
} else {
|
|
186
|
+
this.service.getCharacteristic(this.hapChar.ColorTemperature).onSet(async (value) => {
|
|
187
|
+
await this.internalCTUpdate(value)
|
|
188
|
+
})
|
|
189
|
+
this.cacheMired = this.service.getCharacteristic(this.hapChar.ColorTemperature).value
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Set up the adaptive lighting controller if not disabled by user
|
|
193
|
+
if (!this.colourSafeMode && this.alShift !== -1) {
|
|
194
|
+
this.alController = new platform.api.hap.AdaptiveLightingController(this.service, {
|
|
195
|
+
customTemperatureAdjustment: this.alShift,
|
|
196
|
+
})
|
|
197
|
+
this.accessory.configureController(this.alController)
|
|
198
|
+
this.accessory.context.adaptiveLighting = true
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Output the customised options to the log
|
|
202
|
+
const useAWSControl = accessory.context.useAWSControl ? 'enabled' : 'disabled'
|
|
203
|
+
const useBLEControl = accessory.context.useBLEControl ? 'enabled' : 'disabled'
|
|
204
|
+
const useLANControl = accessory.context.useLANControl ? 'enabled' : 'disabled'
|
|
205
|
+
const opts = JSON.stringify({
|
|
206
|
+
adaptiveLightingShift: this.alShift,
|
|
207
|
+
aws: accessory.context.hasAWSControl ? useAWSControl : 'unsupported',
|
|
208
|
+
ble: accessory.context.hasBLEControl ? useBLEControl : 'unsupported',
|
|
209
|
+
brightnessStep: this.brightStep,
|
|
210
|
+
colourSafeMode: this.colourSafeMode,
|
|
211
|
+
lan: accessory.context.hasLANControl ? useLANControl : 'unsupported',
|
|
212
|
+
})
|
|
213
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
214
|
+
this.initialised = true
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async internalStateUpdate(value) {
|
|
218
|
+
try {
|
|
219
|
+
const newValue = value ? 'on' : 'off'
|
|
220
|
+
|
|
221
|
+
// Don't continue if the new value is the same as before
|
|
222
|
+
if (newValue === this.cacheState) {
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Await slightly longer than brightness and colour so on/off is sent last
|
|
227
|
+
await sleep(400)
|
|
228
|
+
|
|
229
|
+
// Send the request to the platform sender function
|
|
230
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
231
|
+
cmd: 'state',
|
|
232
|
+
value: newValue,
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
// Cache the new state and log if appropriate
|
|
236
|
+
if (this.cacheState !== newValue) {
|
|
237
|
+
this.cacheState = newValue
|
|
238
|
+
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
239
|
+
}
|
|
240
|
+
} catch (err) {
|
|
241
|
+
// Catch any errors during the process
|
|
242
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
243
|
+
|
|
244
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
245
|
+
setTimeout(() => {
|
|
246
|
+
this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
|
|
247
|
+
}, 2000)
|
|
248
|
+
throw new this.hapErr(-70402)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async internalBrightnessUpdate(value) {
|
|
253
|
+
try {
|
|
254
|
+
// This acts like a debounce function when endlessly sliding the brightness scale
|
|
255
|
+
const updateKeyBright = generateRandomString(5)
|
|
256
|
+
this.updateKeyBright = updateKeyBright
|
|
257
|
+
await sleep(350)
|
|
258
|
+
if (updateKeyBright !== this.updateKeyBright) {
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Don't continue if the new value is the same as before
|
|
263
|
+
if (value === this.cacheBright) {
|
|
264
|
+
return
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Send the request to the platform sender function
|
|
268
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
269
|
+
cmd: 'brightness',
|
|
270
|
+
value,
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
// Govee considers 0% brightness to be off
|
|
274
|
+
if (value === 0) {
|
|
275
|
+
setTimeout(() => {
|
|
276
|
+
this.cacheState = 'off'
|
|
277
|
+
if (this.service.getCharacteristic(this.hapChar.On).value) {
|
|
278
|
+
this.service.updateCharacteristic(this.hapChar.On, false)
|
|
279
|
+
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
280
|
+
}
|
|
281
|
+
this.service.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
|
|
282
|
+
}, 1500)
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Cache the new state and log if appropriate
|
|
287
|
+
if (this.cacheBright !== value) {
|
|
288
|
+
this.cacheBright = value
|
|
289
|
+
this.accessory.log(`${platformLang.curBright} [${value}%]`)
|
|
290
|
+
}
|
|
291
|
+
} catch (err) {
|
|
292
|
+
// Catch any errors during the process
|
|
293
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
294
|
+
|
|
295
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
296
|
+
setTimeout(() => {
|
|
297
|
+
this.service.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
|
|
298
|
+
}, 2000)
|
|
299
|
+
throw new this.hapErr(-70402)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async internalColourUpdate(value, force = false) {
|
|
304
|
+
try {
|
|
305
|
+
// This acts like a debounce function when endlessly sliding the colour wheel
|
|
306
|
+
const updateKeyColour = generateRandomString(5)
|
|
307
|
+
this.updateKeyColour = updateKeyColour
|
|
308
|
+
await sleep(300)
|
|
309
|
+
if (updateKeyColour !== this.updateKeyColour) {
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (!this.colourSafeMode) {
|
|
314
|
+
// Updating the cct to the lowest value mimics native adaptive lighting
|
|
315
|
+
this.service.updateCharacteristic(this.hapChar.ColorTemperature, 140)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Don't continue if the new value is the same as before
|
|
319
|
+
const currentSat = this.service.getCharacteristic(this.hapChar.Saturation).value
|
|
320
|
+
const newRGB = hs2rgb(value, currentSat)
|
|
321
|
+
if (
|
|
322
|
+
!force
|
|
323
|
+
&& newRGB[0] === this.cacheR
|
|
324
|
+
&& newRGB[1] === this.cacheG
|
|
325
|
+
&& newRGB[2] === this.cacheB
|
|
326
|
+
) {
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Send the request to the platform sender function
|
|
331
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
332
|
+
cmd: 'color',
|
|
333
|
+
value: {
|
|
334
|
+
r: newRGB[0],
|
|
335
|
+
g: newRGB[1],
|
|
336
|
+
b: newRGB[2],
|
|
337
|
+
},
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
// Switch off any custom mode/scene characteristics and turn the on switch to on
|
|
341
|
+
if (this.hasScenes) {
|
|
342
|
+
setTimeout(() => {
|
|
343
|
+
this.service.updateCharacteristic(this.hapChar.On, true)
|
|
344
|
+
this.service.updateCharacteristic(this.cusChar.ColourMode, true)
|
|
345
|
+
this.usedCodes.forEach((thisCharName) => {
|
|
346
|
+
if (this.service.testCharacteristic(this.cusChar[thisCharName])) {
|
|
347
|
+
this.service.updateCharacteristic(this.cusChar[thisCharName], false)
|
|
348
|
+
}
|
|
349
|
+
if (this.accessory.getService(thisCharName)) {
|
|
350
|
+
this.accessory.getService(thisCharName).updateCharacteristic(this.hapChar.On, false)
|
|
351
|
+
}
|
|
352
|
+
})
|
|
353
|
+
}, 1000)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Cache the new state and log if appropriate
|
|
357
|
+
this.cacheHue = value
|
|
358
|
+
this.cacheKelvin = 0
|
|
359
|
+
this.cacheScene = ''
|
|
360
|
+
if (this.cacheR !== newRGB[0] || this.cacheG !== newRGB[1] || this.cacheB !== newRGB[2]) {
|
|
361
|
+
[this.cacheR, this.cacheG, this.cacheB] = newRGB
|
|
362
|
+
this.accessory.log(`${platformLang.curColour} [rgb ${this.cacheR} ${this.cacheG} ${this.cacheB}]`)
|
|
363
|
+
}
|
|
364
|
+
} catch (err) {
|
|
365
|
+
// Catch any errors during the process
|
|
366
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
367
|
+
|
|
368
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
369
|
+
setTimeout(() => {
|
|
370
|
+
this.service.updateCharacteristic(this.hapChar.Hue, this.cacheHue)
|
|
371
|
+
}, 2000)
|
|
372
|
+
throw new this.hapErr(-70402)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async internalCTUpdate(value) {
|
|
377
|
+
try {
|
|
378
|
+
// This acts like a debounce function when endlessly sliding the colour wheel
|
|
379
|
+
const updateKeyCT = generateRandomString(5)
|
|
380
|
+
this.updateKeyCT = updateKeyCT
|
|
381
|
+
await sleep(300)
|
|
382
|
+
if (updateKeyCT !== this.updateKeyCT) {
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Convert mired to kelvin to nearest 100 (Govee seems to need this)
|
|
387
|
+
const kelvin = Math.round(1000000 / value / 100) * 100
|
|
388
|
+
|
|
389
|
+
// Check and increase/decrease kelvin to range of device
|
|
390
|
+
const k = Math.min(Math.max(kelvin, this.minKelvin), this.maxKelvin)
|
|
391
|
+
|
|
392
|
+
// Don't continue if the new value is the same as before
|
|
393
|
+
if (this.cacheState !== 'on' || this.cacheKelvin === k) {
|
|
394
|
+
if (this.alController?.isAdaptiveLightingActive?.()) {
|
|
395
|
+
this.accessory.logDebug(`${platformLang.skippingAL} [${k}K /${value}M]`)
|
|
396
|
+
}
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Updating the hue/sat to the corresponding values mimics native adaptive lighting
|
|
401
|
+
const hs = m2hs(value)
|
|
402
|
+
this.service.updateCharacteristic(this.hapChar.Hue, hs[0])
|
|
403
|
+
this.service.updateCharacteristic(this.hapChar.Saturation, hs[1])
|
|
404
|
+
|
|
405
|
+
// Convert kelvin to rgb to use in case device doesn't support colour temperature
|
|
406
|
+
const rgb = k2rgb(k)
|
|
407
|
+
|
|
408
|
+
// Set up the params object to send
|
|
409
|
+
const objToSend = {}
|
|
410
|
+
|
|
411
|
+
// For BLE only models, convert to RGB, otherwise send kelvin value
|
|
412
|
+
// TODO we can look at this in the future
|
|
413
|
+
if (this.isBLEOnly) {
|
|
414
|
+
objToSend.cmd = 'color'
|
|
415
|
+
objToSend.value = { r: rgb[0], g: rgb[1], b: rgb[2] }
|
|
416
|
+
} else {
|
|
417
|
+
objToSend.cmd = 'colorTem'
|
|
418
|
+
objToSend.value = k
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Send the request to the platform sender function
|
|
422
|
+
await this.platform.sendDeviceUpdate(this.accessory, objToSend)
|
|
423
|
+
|
|
424
|
+
// Switch off any custom mode/scene characteristics and turn the on switch to on
|
|
425
|
+
if (this.hasScenes) {
|
|
426
|
+
setTimeout(() => {
|
|
427
|
+
this.service.updateCharacteristic(this.hapChar.On, true)
|
|
428
|
+
this.service.updateCharacteristic(this.cusChar.ColourMode, true)
|
|
429
|
+
this.usedCodes.forEach((thisCharName) => {
|
|
430
|
+
if (this.service.testCharacteristic(this.cusChar[thisCharName])) {
|
|
431
|
+
this.service.updateCharacteristic(this.cusChar[thisCharName], false)
|
|
432
|
+
}
|
|
433
|
+
if (this.accessory.getService(thisCharName)) {
|
|
434
|
+
this.accessory.getService(thisCharName).updateCharacteristic(this.hapChar.On, false)
|
|
435
|
+
}
|
|
436
|
+
})
|
|
437
|
+
}, 1000)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Cache the new state and log if appropriate
|
|
441
|
+
[this.cacheR, this.cacheG, this.cacheB] = rgb
|
|
442
|
+
this.cacheMired = value
|
|
443
|
+
this.cacheScene = ''
|
|
444
|
+
if (this.cacheKelvin !== k) {
|
|
445
|
+
this.cacheKelvin = k
|
|
446
|
+
if (this.alController?.isAdaptiveLightingActive?.()) {
|
|
447
|
+
this.accessory.log(`${platformLang.curColour} [${k}K / ${value}M] ${platformLang.viaAL}`)
|
|
448
|
+
} else {
|
|
449
|
+
this.accessory.log(`${platformLang.curColour} [${k}K / ${value}M]`)
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
} catch (err) {
|
|
453
|
+
// Catch any errors during the process
|
|
454
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
455
|
+
|
|
456
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
457
|
+
setTimeout(() => {
|
|
458
|
+
this.service.updateCharacteristic(this.hapChar.ColorTemperature, this.cacheMired)
|
|
459
|
+
}, 2000)
|
|
460
|
+
throw new this.hapErr(-70402)
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async internalSceneUpdate(charName, awsCode, bleCode, value, isService = false) {
|
|
465
|
+
try {
|
|
466
|
+
// Don't continue if command is to turn off - we should turn off by changing to a colour mode instead, or another scene
|
|
467
|
+
if (!value) {
|
|
468
|
+
return
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Send the request to the platform sender function
|
|
472
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
473
|
+
cmd: 'rgbScene',
|
|
474
|
+
value: [awsCode, bleCode],
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
// Disable adaptive lighting if it's on already
|
|
478
|
+
if (!this.colourSafeMode && this.alController?.isAdaptiveLightingActive?.()) {
|
|
479
|
+
this.alController.disableAdaptiveLighting()
|
|
480
|
+
this.accessory.log(platformLang.alDisabledScene)
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Log the scene change
|
|
484
|
+
if (this.cacheScene !== charName) {
|
|
485
|
+
this.cacheScene = charName
|
|
486
|
+
this.accessory.log(`${platformLang.curScene} [${this.cacheScene}]`)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Turn all the characteristics off and turn the on switch to on
|
|
490
|
+
setTimeout(() => {
|
|
491
|
+
this.service.updateCharacteristic(this.hapChar.On, true)
|
|
492
|
+
this.service.updateCharacteristic(this.cusChar.ColourMode, false)
|
|
493
|
+
this.usedCodes.forEach((thisCharName) => {
|
|
494
|
+
if (thisCharName !== charName) {
|
|
495
|
+
if (this.service.testCharacteristic(this.cusChar[thisCharName])) {
|
|
496
|
+
this.service.updateCharacteristic(this.cusChar[thisCharName], false)
|
|
497
|
+
}
|
|
498
|
+
if (this.accessory.getService(thisCharName)) {
|
|
499
|
+
this.accessory.getService(thisCharName).updateCharacteristic(this.hapChar.On, false)
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
})
|
|
503
|
+
}, 1000)
|
|
504
|
+
} catch (err) {
|
|
505
|
+
// Catch any errors during the process
|
|
506
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
507
|
+
|
|
508
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
509
|
+
setTimeout(() => {
|
|
510
|
+
if (isService) {
|
|
511
|
+
this.accessory.getService(charName).updateCharacteristic(this.hapChar.On, false)
|
|
512
|
+
} else {
|
|
513
|
+
this.service.updateCharacteristic(this.cusChar[charName], false)
|
|
514
|
+
}
|
|
515
|
+
}, 2000)
|
|
516
|
+
throw new this.hapErr(-70402)
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
externalUpdate(params) {
|
|
521
|
+
// Return if not initialised
|
|
522
|
+
if (!this.initialised) {
|
|
523
|
+
return
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Check to see if the provided state is different from the cached value
|
|
527
|
+
if (params.state && params.state !== this.cacheState) {
|
|
528
|
+
// State is different so update Homebridge with new values
|
|
529
|
+
this.cacheState = params.state
|
|
530
|
+
this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
|
|
531
|
+
|
|
532
|
+
// Log the change
|
|
533
|
+
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Check to see if the provided brightness is different from the cached value
|
|
537
|
+
if (hasProperty(params, 'brightness') && params.brightness !== this.cacheBrightRaw) {
|
|
538
|
+
// Brightness is different so update Homebridge with new values
|
|
539
|
+
this.cacheBrightRaw = params.brightness
|
|
540
|
+
|
|
541
|
+
// Govee considers brightness 0 as OFF so change brightness to 1 if light is on
|
|
542
|
+
this.cacheBright = this.cacheState === 'on' ? Math.max(this.cacheBrightRaw, 1) : this.cacheBrightRaw
|
|
543
|
+
this.service.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
|
|
544
|
+
|
|
545
|
+
// Log the change
|
|
546
|
+
this.accessory.log(`${platformLang.curBright} [${this.cacheBright}%]`)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Check to see if the provided colour is different from the cached state
|
|
550
|
+
if (params.kelvin || params.rgb) {
|
|
551
|
+
// Colour can be provided in rgb or kelvin so either way convert to hs for later
|
|
552
|
+
let hs
|
|
553
|
+
let rgb
|
|
554
|
+
let mired
|
|
555
|
+
let colourChange = false
|
|
556
|
+
let sigColourChange = false
|
|
557
|
+
if (params.kelvin) {
|
|
558
|
+
mired = Math.round(1000000 / params.kelvin)
|
|
559
|
+
hs = m2hs(mired)
|
|
560
|
+
rgb = hs2rgb(hs[0], hs[1])
|
|
561
|
+
|
|
562
|
+
// Check for a colour change
|
|
563
|
+
if (params.kelvin !== this.cacheKelvin) {
|
|
564
|
+
colourChange = true
|
|
565
|
+
|
|
566
|
+
// Check for a significant colour change
|
|
567
|
+
const kelvinDiff = Math.abs(params.kelvin - this.cacheKelvin)
|
|
568
|
+
if (kelvinDiff > 100) {
|
|
569
|
+
sigColourChange = true
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
} else {
|
|
573
|
+
rgb = [params.rgb.r, params.rgb.g, params.rgb.b]
|
|
574
|
+
hs = rgb2hs(rgb[0], rgb[1], rgb[2])
|
|
575
|
+
|
|
576
|
+
// Check for a colour change
|
|
577
|
+
if (hs[0] !== this.cacheHue) {
|
|
578
|
+
colourChange = true
|
|
579
|
+
|
|
580
|
+
// Check for a significant colour change
|
|
581
|
+
const rgbDiff = Math.abs(rgb[0] - this.cacheR)
|
|
582
|
+
+ Math.abs(rgb[1] - this.cacheG)
|
|
583
|
+
+ Math.abs(rgb[2] - this.cacheB)
|
|
584
|
+
if (rgbDiff > 50) {
|
|
585
|
+
sigColourChange = true
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Perform the check against the cache
|
|
591
|
+
if (colourChange) {
|
|
592
|
+
// Colour is different so update Homebridge with new values
|
|
593
|
+
this.service.updateCharacteristic(this.hapChar.Hue, hs[0])
|
|
594
|
+
this.service.updateCharacteristic(this.hapChar.Saturation, hs[1]);
|
|
595
|
+
[this.cacheR, this.cacheG, this.cacheB] = rgb;
|
|
596
|
+
[this.cacheHue] = hs
|
|
597
|
+
|
|
598
|
+
if (mired) {
|
|
599
|
+
if (!this.colourSafeMode) {
|
|
600
|
+
this.service.updateCharacteristic(this.hapChar.ColorTemperature, mired)
|
|
601
|
+
}
|
|
602
|
+
this.cacheMired = mired
|
|
603
|
+
this.cacheKelvin = params.kelvin
|
|
604
|
+
this.accessory.log(`${platformLang.curColour} [${params.kelvin}K / ${mired}M]`)
|
|
605
|
+
} else {
|
|
606
|
+
this.accessory.log(`${platformLang.curColour} [rgb ${this.cacheR} ${this.cacheG} ${this.cacheB}]`)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// If the difference is significant then disable adaptive lighting
|
|
610
|
+
if (!this.colourSafeMode && this.alController?.isAdaptiveLightingActive?.() && sigColourChange) {
|
|
611
|
+
this.alController.disableAdaptiveLighting()
|
|
612
|
+
this.accessory.log(platformLang.alDisabled)
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|