@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,119 @@
|
|
|
1
|
+
import { generateRandomString, parseError } from '../utils/functions.js'
|
|
2
|
+
import platformLang from '../utils/lang-en.js'
|
|
3
|
+
|
|
4
|
+
export default class {
|
|
5
|
+
constructor(platform, accessory) {
|
|
6
|
+
// Set up variables from the platform
|
|
7
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
8
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
9
|
+
this.hapServ = platform.api.hap.Service
|
|
10
|
+
this.platform = platform
|
|
11
|
+
|
|
12
|
+
// Set up variables from the accessory
|
|
13
|
+
this.accessory = accessory;
|
|
14
|
+
|
|
15
|
+
// Remove any old services from simulations
|
|
16
|
+
['HeaterCooler', 'Lightbulb', 'Outlet', 'Switch', 'Valve'].forEach((service) => {
|
|
17
|
+
if (this.accessory.getService(this.hapServ[service])) {
|
|
18
|
+
this.accessory.removeService(this.accessory.getService(this.hapServ[service]))
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// Add the purifier service if it doesn't already exist
|
|
23
|
+
this.service = this.accessory.getService(this.hapServ.AirPurifier)
|
|
24
|
+
|| this.accessory.addService(this.hapServ.AirPurifier)
|
|
25
|
+
|
|
26
|
+
// Add the set handler to the switch on/off characteristic
|
|
27
|
+
this.service.getCharacteristic(this.hapChar.Active).onSet(async (value) => {
|
|
28
|
+
await this.internalStateUpdate(value)
|
|
29
|
+
})
|
|
30
|
+
this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
|
|
31
|
+
|
|
32
|
+
// Add options to the purifier target state characteristic
|
|
33
|
+
this.service
|
|
34
|
+
.getCharacteristic(this.hapChar.TargetAirPurifierState)
|
|
35
|
+
.updateValue(1)
|
|
36
|
+
.setProps({
|
|
37
|
+
minValue: 1,
|
|
38
|
+
maxValue: 1,
|
|
39
|
+
validValues: [1],
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Pass the accessory to Fakegato to set up with Eve
|
|
43
|
+
this.accessory.eveService = new platform.eveService('switch', this.accessory, {
|
|
44
|
+
log: () => {},
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// Output the customised options to the log
|
|
48
|
+
const opts = JSON.stringify({
|
|
49
|
+
showAs: 'purifier',
|
|
50
|
+
})
|
|
51
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async internalStateUpdate(value) {
|
|
55
|
+
try {
|
|
56
|
+
const newValue = value === 1 ? 'on' : 'off'
|
|
57
|
+
|
|
58
|
+
// Don't continue if the new value is the same as before
|
|
59
|
+
if (newValue === this.cacheState) {
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Set up a one-minute timeout for the plugin to ignore incoming updates
|
|
64
|
+
const timerKey = generateRandomString(5)
|
|
65
|
+
this.updateTimeout = timerKey
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
if (this.updateTimeout === timerKey) {
|
|
68
|
+
this.updateTimeout = false
|
|
69
|
+
}
|
|
70
|
+
}, 60000)
|
|
71
|
+
|
|
72
|
+
// Send the request to the platform sender function
|
|
73
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
74
|
+
cmd: 'stateOutlet',
|
|
75
|
+
value: newValue,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// Cache the new state and log if appropriate
|
|
79
|
+
if (this.cacheState !== newValue) {
|
|
80
|
+
this.cacheState = newValue
|
|
81
|
+
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Add the entry to the Eve history service
|
|
85
|
+
this.accessory.eveService.addEntry({ status: value ? 1 : 0 })
|
|
86
|
+
this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, value === 1 ? 2 : 0)
|
|
87
|
+
} catch (err) {
|
|
88
|
+
// Catch any errors during the process
|
|
89
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
90
|
+
|
|
91
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
|
|
94
|
+
}, 2000)
|
|
95
|
+
throw new this.hapErr(-70402)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
externalUpdate(params) {
|
|
100
|
+
// Check to see if the provided state is different from the cached state
|
|
101
|
+
if (params.state && params.state !== this.cacheState) {
|
|
102
|
+
// State is different so update Homebridge with new values
|
|
103
|
+
this.cacheState = params.state
|
|
104
|
+
this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
|
|
105
|
+
this.service.updateCharacteristic(
|
|
106
|
+
this.hapChar.CurrentAirPurifierState,
|
|
107
|
+
this.cacheState === 'on' ? 2 : 0,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
// Log the change
|
|
111
|
+
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
112
|
+
|
|
113
|
+
// Add the entry to the Eve history service
|
|
114
|
+
this.accessory.eveService.addEntry({
|
|
115
|
+
status: this.cacheState === 'on' ? 1 : 0,
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import platformLang from '../utils/lang-en.js'
|
|
2
|
+
|
|
3
|
+
export default class {
|
|
4
|
+
constructor(platform, accessory) {
|
|
5
|
+
// Set up variables from the platform
|
|
6
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
7
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
8
|
+
this.hapServ = platform.api.hap.Service
|
|
9
|
+
this.platform = platform
|
|
10
|
+
|
|
11
|
+
// Set up variables from the accessory
|
|
12
|
+
this.accessory = accessory
|
|
13
|
+
|
|
14
|
+
// Output the customised options to the log
|
|
15
|
+
const opts = JSON.stringify({})
|
|
16
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
externalUpdate(params) {
|
|
20
|
+
this.accessory.logWarn(`${platformLang.newScene}: [${params.scene}]`)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import platformLang from '../utils/lang-en.js'
|
|
2
|
+
|
|
3
|
+
export default class {
|
|
4
|
+
constructor(platform, accessory) {
|
|
5
|
+
// Set up variables from the platform
|
|
6
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
7
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
8
|
+
this.hapServ = platform.api.hap.Service
|
|
9
|
+
this.platform = platform
|
|
10
|
+
|
|
11
|
+
// Set up variables from the accessory
|
|
12
|
+
this.accessory = accessory
|
|
13
|
+
|
|
14
|
+
// Output the customised options to the log
|
|
15
|
+
const opts = JSON.stringify({})
|
|
16
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
externalUpdate(params) {
|
|
20
|
+
this.accessory.logWarn(`${platformLang.newScene}: [${params.scene}]`)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import platformConsts from '../utils/constants.js'
|
|
2
|
+
import { hasProperty } from '../utils/functions.js'
|
|
3
|
+
import platformLang from '../utils/lang-en.js'
|
|
4
|
+
|
|
5
|
+
export default class {
|
|
6
|
+
constructor(platform, accessory) {
|
|
7
|
+
// Set up variables from the platform
|
|
8
|
+
this.eveChar = platform.eveChar
|
|
9
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
10
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
11
|
+
this.hapServ = platform.api.hap.Service
|
|
12
|
+
this.platform = platform
|
|
13
|
+
|
|
14
|
+
// Set up variables from the accessory
|
|
15
|
+
this.accessory = accessory
|
|
16
|
+
|
|
17
|
+
// Set up custom variables for this device type
|
|
18
|
+
const deviceConf = platform.deviceConf[accessory.context.gvDeviceId]
|
|
19
|
+
this.lowBattThreshold = deviceConf && deviceConf.lowBattThreshold
|
|
20
|
+
? Math.min(deviceConf.lowBattThreshold, 100)
|
|
21
|
+
: platformConsts.defaultValues.lowBattThreshold
|
|
22
|
+
|
|
23
|
+
// Add the leak sensor service if it doesn't already exist
|
|
24
|
+
this.service = this.accessory.getService(this.hapServ.LeakSensor)
|
|
25
|
+
if (!this.service) {
|
|
26
|
+
this.service = this.accessory.addService(this.hapServ.LeakSensor)
|
|
27
|
+
|
|
28
|
+
// Adding the characteristic here avoids Homebridge characteristic warnings
|
|
29
|
+
this.service.addCharacteristic(this.eveChar.LastActivation)
|
|
30
|
+
}
|
|
31
|
+
this.cacheLeak = !!this.service.getCharacteristic(this.hapChar.LeakDetected).value
|
|
32
|
+
|
|
33
|
+
// Add the battery service if it doesn't already exist
|
|
34
|
+
this.battService = this.accessory.getService(this.hapServ.Battery)
|
|
35
|
+
|| this.accessory.addService(this.hapServ.Battery)
|
|
36
|
+
this.cacheBatt = this.battService.getCharacteristic(this.hapChar.BatteryLevel).value
|
|
37
|
+
|
|
38
|
+
// Pass the accessory to Fakegato to set up with Eve
|
|
39
|
+
this.accessory.eveService = new platform.eveService('motion', this.accessory, {
|
|
40
|
+
log: () => {},
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// Output the customised options to the log
|
|
44
|
+
const opts = JSON.stringify({})
|
|
45
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
externalUpdate(params) {
|
|
49
|
+
// Check to see if the provided online status is different from the cache value
|
|
50
|
+
if (hasProperty(params, 'online') && this.cacheOnline !== params.online) {
|
|
51
|
+
this.cacheOnline = params.online
|
|
52
|
+
this.platform.updateAccessoryStatus(this.accessory, this.cacheOnline)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check to see if the provided battery is different from the cached state
|
|
56
|
+
if (params.battery !== this.cacheBatt) {
|
|
57
|
+
// Battery is different so update Homebridge with new values
|
|
58
|
+
this.cacheBatt = params.battery
|
|
59
|
+
this.battService.updateCharacteristic(this.hapChar.BatteryLevel, this.cacheBatt)
|
|
60
|
+
this.battService.updateCharacteristic(
|
|
61
|
+
this.hapChar.StatusLowBattery,
|
|
62
|
+
this.cacheBatt < this.lowBattThreshold ? 1 : 0,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
// Log the change
|
|
66
|
+
this.accessory.log(`${platformLang.curBatt} [${this.cacheBatt}%]`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check to see if the provided leak status is different from the cached state
|
|
70
|
+
if (params.leakDetected !== this.cacheLeak) {
|
|
71
|
+
// Leak status is different so update Homebridge with new values
|
|
72
|
+
this.cacheLeak = params.leakDetected
|
|
73
|
+
this.service.updateCharacteristic(this.hapChar.LeakDetected, this.cacheLeak ? 1 : 0)
|
|
74
|
+
|
|
75
|
+
// Add the alert to Eve if a leak has been detected
|
|
76
|
+
if (this.cacheLeak) {
|
|
77
|
+
this.service.updateCharacteristic(
|
|
78
|
+
this.eveChar.LastActivation,
|
|
79
|
+
Math.round(new Date().valueOf() / 1000) - this.accessory.eveService.getInitialTime(),
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Log the change
|
|
84
|
+
this.accessory.log(`${platformLang.curLeak} [${this.cacheLeak ? platformLang.labelYes : platformLang.labelNo}]`)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import platformConsts from '../utils/constants.js'
|
|
2
|
+
import {
|
|
3
|
+
base64ToHex,
|
|
4
|
+
cenToFar,
|
|
5
|
+
getTwoItemPosition,
|
|
6
|
+
hexToDecimal,
|
|
7
|
+
hexToTwoItems,
|
|
8
|
+
parseError,
|
|
9
|
+
} from '../utils/functions.js'
|
|
10
|
+
import platformLang from '../utils/lang-en.js'
|
|
11
|
+
|
|
12
|
+
export default class {
|
|
13
|
+
constructor(platform, accessory) {
|
|
14
|
+
// Set up variables from the platform
|
|
15
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
16
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
17
|
+
this.hapServ = platform.api.hap.Service
|
|
18
|
+
this.platform = platform
|
|
19
|
+
|
|
20
|
+
// Set up variables from the accessory
|
|
21
|
+
this.accessory = accessory
|
|
22
|
+
|
|
23
|
+
// Set up custom variables for this device type
|
|
24
|
+
const deviceConf = platform.deviceConf[accessory.context.gvDeviceId]
|
|
25
|
+
this.lowBattThreshold = deviceConf && deviceConf.lowBattThreshold
|
|
26
|
+
? Math.min(deviceConf.lowBattThreshold, 100)
|
|
27
|
+
: platformConsts.defaultValues.lowBattThreshold
|
|
28
|
+
|
|
29
|
+
// Add the temperature service if it doesn't already exist
|
|
30
|
+
this.tempService = this.accessory.getService(this.hapServ.TemperatureSensor)
|
|
31
|
+
|| this.accessory.addService(this.hapServ.TemperatureSensor)
|
|
32
|
+
this.cacheTemp = this.tempService.getCharacteristic(this.hapChar.CurrentTemperature).value
|
|
33
|
+
this.updateCache()
|
|
34
|
+
|
|
35
|
+
// Add the humidity service if it doesn't already exist
|
|
36
|
+
this.humiService = this.accessory.getService(this.hapServ.HumiditySensor)
|
|
37
|
+
|| this.accessory.addService(this.hapServ.HumiditySensor)
|
|
38
|
+
this.cacheHumi = this.humiService.getCharacteristic(this.hapChar.CurrentRelativeHumidity).value
|
|
39
|
+
|
|
40
|
+
// Add the air quality service if it doesn't already exist
|
|
41
|
+
this.airService = this.accessory.getService(this.hapServ.AirQualitySensor)
|
|
42
|
+
|
|
43
|
+
if (!this.airService) {
|
|
44
|
+
this.airService = this.accessory.addService(this.hapServ.AirQualitySensor)
|
|
45
|
+
this.airService.addCharacteristic(this.hapChar.PM2_5Density)
|
|
46
|
+
}
|
|
47
|
+
this.cacheAir = this.airService.getCharacteristic(this.hapChar.PM2_5Density).value
|
|
48
|
+
|
|
49
|
+
// Pass the accessory to Fakegato to set up with Eve
|
|
50
|
+
this.accessory.eveService = new platform.eveService('custom', this.accessory, {
|
|
51
|
+
log: () => {},
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// Output the customised options to the log
|
|
55
|
+
const opts = JSON.stringify({
|
|
56
|
+
lowBattThreshold: this.lowBattThreshold,
|
|
57
|
+
})
|
|
58
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async externalUpdate(params) {
|
|
62
|
+
// Check for some other scene/mode change
|
|
63
|
+
(params.commands || []).forEach((command) => {
|
|
64
|
+
const hexString = base64ToHex(command)
|
|
65
|
+
const hexParts = hexToTwoItems(hexString)
|
|
66
|
+
|
|
67
|
+
const deviceFunction = `${getTwoItemPosition(hexParts, 1)}${getTwoItemPosition(hexParts, 2)}`
|
|
68
|
+
|
|
69
|
+
switch (deviceFunction) {
|
|
70
|
+
case '0000':
|
|
71
|
+
case '0003':
|
|
72
|
+
case '0100':
|
|
73
|
+
case '0101':
|
|
74
|
+
case '0102':
|
|
75
|
+
case '0103':
|
|
76
|
+
case '331a':
|
|
77
|
+
case '3315':
|
|
78
|
+
case 'aa0d':
|
|
79
|
+
case 'aa0e':
|
|
80
|
+
break
|
|
81
|
+
default: {
|
|
82
|
+
const tempInCen = Math.round((hexToDecimal(`0x${deviceFunction}`) + (this.accessory.context.offTemp / 100)) / 10) / 10
|
|
83
|
+
|
|
84
|
+
// Check to see if the provided temperature is different from the cached state
|
|
85
|
+
if (tempInCen !== this.cacheTemp) {
|
|
86
|
+
// Temperature is different so update Homebridge with new values
|
|
87
|
+
this.cacheTemp = tempInCen
|
|
88
|
+
this.tempService.updateCharacteristic(this.hapChar.CurrentTemperature, this.cacheTemp)
|
|
89
|
+
this.accessory.eveService.addEntry({ temp: this.cacheTemp })
|
|
90
|
+
|
|
91
|
+
// Log the change
|
|
92
|
+
this.accessory.log(`${platformLang.curTemp} [${this.cacheTemp}°C / ${cenToFar(tempInCen)}°F]`)
|
|
93
|
+
|
|
94
|
+
// Update the cache file with the new temperature
|
|
95
|
+
this.updateCache()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Check to see if the provided humidity is different from the cached state
|
|
99
|
+
const humiHex = `${getTwoItemPosition(hexParts, 10)}${getTwoItemPosition(hexParts, 11)}`
|
|
100
|
+
const humiDec = Math.round(`0x${humiHex}` / 100) + (this.accessory.context.offHumi / 100)
|
|
101
|
+
if (humiDec !== this.cacheHumi) {
|
|
102
|
+
// Humidity is different so update Homebridge with new values
|
|
103
|
+
this.cacheHumi = humiDec
|
|
104
|
+
this.humiService.updateCharacteristic(this.hapChar.CurrentRelativeHumidity, this.cacheHumi)
|
|
105
|
+
this.accessory.eveService.addEntry({ humidity: this.cacheHumi })
|
|
106
|
+
|
|
107
|
+
// Log the change
|
|
108
|
+
this.accessory.log(`${platformLang.curHumi} [${this.cacheHumi}%]`)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check air quality reading
|
|
112
|
+
const qualHex = `${getTwoItemPosition(hexParts, 19)}${getTwoItemPosition(hexParts, 20)}`
|
|
113
|
+
const qualDec = hexToDecimal(`0x${qualHex}`)
|
|
114
|
+
if (qualDec !== this.cacheAir) {
|
|
115
|
+
// Air quality is different so update Homebridge with new values
|
|
116
|
+
this.cacheAir = qualDec
|
|
117
|
+
this.airService.updateCharacteristic(this.hapChar.PM2_5Density, this.cacheAir)
|
|
118
|
+
|
|
119
|
+
// Log the change
|
|
120
|
+
this.accessory.log(`${platformLang.curPM25} [${qualDec}µg/m³]`)
|
|
121
|
+
|
|
122
|
+
// Check for any change to the main air quality characteristic
|
|
123
|
+
// PM2.5 has a range of 0-1000µg/m³
|
|
124
|
+
// HK characteristic ranges from 1-5 (excellent, good, fair, inferior, poor)
|
|
125
|
+
// Scales based on Govee manual
|
|
126
|
+
// 0-12.0µg/m³ = excellent
|
|
127
|
+
// 12-35µg/m³ = good
|
|
128
|
+
// 35-75µg/m³ = fair
|
|
129
|
+
// 75-115µg/m³ = inferior
|
|
130
|
+
// 115-500µg/m³ = poor (use 1000 for HK)
|
|
131
|
+
if (this.cacheAir <= 12) {
|
|
132
|
+
const newValue = 'excellent'
|
|
133
|
+
if (this.cacheAirQual !== newValue) {
|
|
134
|
+
this.cacheAirQual = newValue
|
|
135
|
+
this.airService.updateCharacteristic(this.hapChar.AirQuality, 1)
|
|
136
|
+
this.accessory.log(`${platformLang.curAirQual} [${newValue}]`)
|
|
137
|
+
}
|
|
138
|
+
} else if (this.cacheAir <= 35) {
|
|
139
|
+
const newValue = 'good'
|
|
140
|
+
if (this.cacheAirQual !== newValue) {
|
|
141
|
+
this.cacheAirQual = newValue
|
|
142
|
+
this.airService.updateCharacteristic(this.hapChar.AirQuality, 2)
|
|
143
|
+
this.accessory.log(`${platformLang.curAirQual} [${newValue}]`)
|
|
144
|
+
}
|
|
145
|
+
} else if (this.cacheAir <= 75) {
|
|
146
|
+
const newValue = 'fair'
|
|
147
|
+
if (this.cacheAirQual !== newValue) {
|
|
148
|
+
this.cacheAirQual = newValue
|
|
149
|
+
this.airService.updateCharacteristic(this.hapChar.AirQuality, 3)
|
|
150
|
+
this.accessory.log(`${platformLang.curAirQual} [${newValue}]`)
|
|
151
|
+
}
|
|
152
|
+
} else if (this.cacheAir <= 115) {
|
|
153
|
+
const newValue = 'inferior'
|
|
154
|
+
if (this.cacheAirQual !== newValue) {
|
|
155
|
+
this.cacheAirQual = newValue
|
|
156
|
+
this.airService.updateCharacteristic(this.hapChar.AirQuality, 4)
|
|
157
|
+
this.accessory.log(`${platformLang.curAirQual} [${newValue}]`)
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
const newValue = 'poor'
|
|
161
|
+
if (this.cacheAirQual !== newValue) {
|
|
162
|
+
this.cacheAirQual = newValue
|
|
163
|
+
this.airService.updateCharacteristic(this.hapChar.AirQuality, 5)
|
|
164
|
+
this.accessory.log(`${platformLang.curAirQual} [${newValue}]`)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
break
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async updateCache() {
|
|
175
|
+
// Don't continue if the storage client hasn't initialised properly
|
|
176
|
+
if (!this.platform.storageClientData) {
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Attempt to save the new temperature to the cache
|
|
181
|
+
try {
|
|
182
|
+
await this.platform.storageData.setItem(
|
|
183
|
+
`${this.accessory.context.gvDeviceId}_temp`,
|
|
184
|
+
this.cacheTemp,
|
|
185
|
+
)
|
|
186
|
+
} catch (err) {
|
|
187
|
+
this.accessory.logWarn(`${platformLang.storageWriteErr} ${parseError(err)}`)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { base64ToHex, getTwoItemPosition, hexToTwoItems } from '../utils/functions.js'
|
|
2
|
+
import platformLang from '../utils/lang-en.js'
|
|
3
|
+
|
|
4
|
+
export default class {
|
|
5
|
+
constructor(platform, accessory) {
|
|
6
|
+
// Set up variables from the platform
|
|
7
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
8
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
9
|
+
this.hapServ = platform.api.hap.Service
|
|
10
|
+
this.platform = platform
|
|
11
|
+
|
|
12
|
+
// Set up variables from the accessory
|
|
13
|
+
this.accessory = accessory
|
|
14
|
+
|
|
15
|
+
// Add the occupancy sensor if it does not already exist
|
|
16
|
+
this.service = this.accessory.getService(this.hapServ.OccupancySensor) || this.accessory.addService(this.hapServ.OccupancySensor)
|
|
17
|
+
this.cacheState = this.service.getCharacteristic(this.hapChar.OccupancyDetected).value
|
|
18
|
+
|
|
19
|
+
// Output the customised options to the log
|
|
20
|
+
const opts = JSON.stringify({})
|
|
21
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
externalUpdate(params) {
|
|
25
|
+
// Check for some other scene/mode change
|
|
26
|
+
(params.commands || []).forEach((command) => {
|
|
27
|
+
const hexString = base64ToHex(command)
|
|
28
|
+
const hexParts = hexToTwoItems(hexString)
|
|
29
|
+
|
|
30
|
+
// Return now if not a device query update code
|
|
31
|
+
if (getTwoItemPosition(hexParts, 1) !== 'aa') {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const deviceFunction = `${getTwoItemPosition(hexParts, 1)}${getTwoItemPosition(hexParts, 2)}`
|
|
36
|
+
|
|
37
|
+
switch (deviceFunction) {
|
|
38
|
+
case 'aa01': { // lock
|
|
39
|
+
const newState = getTwoItemPosition(hexParts, 3) === '01' ? 1 : 0
|
|
40
|
+
if (newState !== this.cacheState) {
|
|
41
|
+
this.cacheState = newState
|
|
42
|
+
this.service.updateCharacteristic(this.hapChar.OccupancyDetected, this.cacheState)
|
|
43
|
+
this.accessory.log(`${platformLang.curOcc} [${this.cacheState === 1 ? 'yes' : 'no'}]`)
|
|
44
|
+
}
|
|
45
|
+
break
|
|
46
|
+
}
|
|
47
|
+
default:
|
|
48
|
+
this.accessory.logDebugWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
|
|
49
|
+
break
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import platformConsts from '../utils/constants.js'
|
|
2
|
+
import {
|
|
3
|
+
cenToFar,
|
|
4
|
+
generateRandomString,
|
|
5
|
+
hasProperty,
|
|
6
|
+
parseError,
|
|
7
|
+
} from '../utils/functions.js'
|
|
8
|
+
import platformLang from '../utils/lang-en.js'
|
|
9
|
+
|
|
10
|
+
export default class {
|
|
11
|
+
constructor(platform, accessory) {
|
|
12
|
+
// Set up variables from the platform
|
|
13
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
14
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
15
|
+
this.hapServ = platform.api.hap.Service
|
|
16
|
+
this.platform = platform
|
|
17
|
+
this.httpTimeout = platform.config.bleRefreshTime * 4.5 * 1000
|
|
18
|
+
|
|
19
|
+
// Set up variables from the accessory
|
|
20
|
+
this.accessory = accessory
|
|
21
|
+
|
|
22
|
+
// Set up custom variables for this device type
|
|
23
|
+
const deviceConf = platform.deviceConf[accessory.context.gvDeviceId]
|
|
24
|
+
this.lowBattThreshold = deviceConf && deviceConf.lowBattThreshold
|
|
25
|
+
? Math.min(deviceConf.lowBattThreshold, 100)
|
|
26
|
+
: platformConsts.defaultValues.lowBattThreshold
|
|
27
|
+
|
|
28
|
+
// Add the temperature service if it doesn't already exist
|
|
29
|
+
this.tempService = this.accessory.getService(this.hapServ.TemperatureSensor)
|
|
30
|
+
|| this.accessory.addService(this.hapServ.TemperatureSensor)
|
|
31
|
+
this.cacheTemp = this.tempService.getCharacteristic(this.hapChar.CurrentTemperature).value
|
|
32
|
+
this.updateCache()
|
|
33
|
+
|
|
34
|
+
// Add the humidity service if it doesn't already exist
|
|
35
|
+
this.humiService = this.accessory.getService(this.hapServ.HumiditySensor)
|
|
36
|
+
|| this.accessory.addService(this.hapServ.HumiditySensor)
|
|
37
|
+
this.cacheHumi = this.humiService.getCharacteristic(this.hapChar.CurrentRelativeHumidity).value
|
|
38
|
+
|
|
39
|
+
// Add the battery service if it doesn't already exist
|
|
40
|
+
this.battService = this.accessory.getService(this.hapServ.Battery)
|
|
41
|
+
|| this.accessory.addService(this.hapServ.Battery)
|
|
42
|
+
this.cacheBatt = this.battService.getCharacteristic(this.hapChar.BatteryLevel).value
|
|
43
|
+
|
|
44
|
+
// Pass the accessory to Fakegato to set up with Eve
|
|
45
|
+
this.accessory.eveService = new platform.eveService('custom', this.accessory, {
|
|
46
|
+
log: () => {},
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// Output the customised options to the log
|
|
50
|
+
const opts = JSON.stringify({
|
|
51
|
+
lowBattThreshold: this.lowBattThreshold,
|
|
52
|
+
})
|
|
53
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async externalUpdate(params) {
|
|
57
|
+
// Check to see if the provided online status is different from the cache value
|
|
58
|
+
if (hasProperty(params, 'online') && this.cacheOnline !== params.online) {
|
|
59
|
+
this.cacheOnline = params.online
|
|
60
|
+
this.platform.updateAccessoryStatus(this.accessory, this.cacheOnline)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (params.source === 'BLE') {
|
|
64
|
+
// If we have a BLE update then we should ignore HTTP updates for the next 4 BLE refresh cycles
|
|
65
|
+
// Since BLE will be more accurate and may not have updated with the cloud yet
|
|
66
|
+
// Generate a random key
|
|
67
|
+
const bleKey = generateRandomString(5)
|
|
68
|
+
this.bleKey = bleKey
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
if (this.bleKey === bleKey) {
|
|
71
|
+
this.bleKey = false
|
|
72
|
+
}
|
|
73
|
+
}, this.httpTimeout)
|
|
74
|
+
}
|
|
75
|
+
if (params.source === 'HTTP' && this.bleKey) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check to see if the provided battery is different from the cached state
|
|
80
|
+
if (params.battery !== this.cacheBatt && this.battService) {
|
|
81
|
+
// Battery is different so update Homebridge with new values
|
|
82
|
+
this.cacheBatt = params.battery
|
|
83
|
+
this.battService.updateCharacteristic(this.hapChar.BatteryLevel, this.cacheBatt)
|
|
84
|
+
this.battService.updateCharacteristic(
|
|
85
|
+
this.hapChar.StatusLowBattery,
|
|
86
|
+
this.cacheBatt < this.lowBattThreshold ? 1 : 0,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
// Log the change
|
|
90
|
+
this.accessory.log(`${platformLang.curBatt} [${this.cacheBatt}%]`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check to see if the provided temperature is different from the cached state
|
|
94
|
+
if (hasProperty(params, 'temperature')) {
|
|
95
|
+
let newTemp = Number.parseInt(params.temperature + this.accessory.context.offTemp, 10)
|
|
96
|
+
newTemp /= 100
|
|
97
|
+
if (newTemp !== this.cacheTemp) {
|
|
98
|
+
// Temperature is different so update Homebridge with new values
|
|
99
|
+
this.cacheTemp = newTemp
|
|
100
|
+
this.tempService.updateCharacteristic(this.hapChar.CurrentTemperature, this.cacheTemp)
|
|
101
|
+
this.accessory.eveService.addEntry({ temp: this.cacheTemp })
|
|
102
|
+
|
|
103
|
+
// Log the change
|
|
104
|
+
this.accessory.log(`${platformLang.curTemp} [${this.cacheTemp}°C / ${cenToFar(this.cacheTemp)}°F]`)
|
|
105
|
+
|
|
106
|
+
// Update the cache file with the new temperature
|
|
107
|
+
this.updateCache()
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check to see if the provided humidity is different from the cached state
|
|
112
|
+
if (hasProperty(params, 'humidity')) {
|
|
113
|
+
let newHumi = Number.parseInt(params.humidity + this.accessory.context.offHumi, 10)
|
|
114
|
+
newHumi /= 100
|
|
115
|
+
newHumi = Math.max(Math.min(newHumi, 100), 0)
|
|
116
|
+
if (newHumi !== this.cacheHumi) {
|
|
117
|
+
// Humidity is different so update Homebridge with new values
|
|
118
|
+
this.cacheHumi = newHumi
|
|
119
|
+
this.humiService.updateCharacteristic(this.hapChar.CurrentRelativeHumidity, this.cacheHumi)
|
|
120
|
+
this.accessory.eveService.addEntry({ humidity: this.cacheHumi })
|
|
121
|
+
|
|
122
|
+
// Log the change
|
|
123
|
+
this.accessory.log(`${platformLang.curHumi} [${this.cacheHumi}%]`)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async updateCache() {
|
|
129
|
+
// Don't continue if the storage client hasn't initialised properly
|
|
130
|
+
if (!this.platform.storageClientData) {
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Attempt to save the new temperature to the cache
|
|
135
|
+
try {
|
|
136
|
+
await this.platform.storageData.setItem(
|
|
137
|
+
`${this.accessory.context.gvDeviceId}_temp`,
|
|
138
|
+
this.cacheTemp,
|
|
139
|
+
)
|
|
140
|
+
} catch (err) {
|
|
141
|
+
this.accessory.logWarn(`${platformLang.storageWriteErr} ${parseError(err)}`)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|