@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.
Files changed (78) hide show
  1. package/CHANGELOG.md +1937 -0
  2. package/LICENSE +21 -0
  3. package/README.md +72 -0
  4. package/config.schema.json +1727 -0
  5. package/eslint.config.js +49 -0
  6. package/lib/connection/aws.js +174 -0
  7. package/lib/connection/ble.js +208 -0
  8. package/lib/connection/cert/AmazonRootCA1.pem +20 -0
  9. package/lib/connection/http.js +240 -0
  10. package/lib/connection/lan.js +284 -0
  11. package/lib/device/cooler-single.js +300 -0
  12. package/lib/device/dehumidifier-H7150.js +182 -0
  13. package/lib/device/dehumidifier-H7151.js +157 -0
  14. package/lib/device/diffuser-H7161.js +117 -0
  15. package/lib/device/diffuser-H7162.js +117 -0
  16. package/lib/device/fan-H7100.js +274 -0
  17. package/lib/device/fan-H7101.js +330 -0
  18. package/lib/device/fan-H7102.js +274 -0
  19. package/lib/device/fan-H7105.js +503 -0
  20. package/lib/device/fan-H7106.js +274 -0
  21. package/lib/device/fan-H7111.js +335 -0
  22. package/lib/device/heater-single.js +300 -0
  23. package/lib/device/heater1a.js +353 -0
  24. package/lib/device/heater1b.js +616 -0
  25. package/lib/device/heater2.js +838 -0
  26. package/lib/device/humidifier-H7140.js +224 -0
  27. package/lib/device/humidifier-H7141.js +257 -0
  28. package/lib/device/humidifier-H7142.js +522 -0
  29. package/lib/device/humidifier-H7143.js +157 -0
  30. package/lib/device/humidifier-H7148.js +157 -0
  31. package/lib/device/humidifier-H7160.js +446 -0
  32. package/lib/device/ice-maker-H7162.js +46 -0
  33. package/lib/device/index.js +105 -0
  34. package/lib/device/kettle.js +269 -0
  35. package/lib/device/light-switch.js +86 -0
  36. package/lib/device/light.js +617 -0
  37. package/lib/device/outlet-double.js +121 -0
  38. package/lib/device/outlet-single.js +172 -0
  39. package/lib/device/outlet-triple.js +160 -0
  40. package/lib/device/purifier-H7120.js +336 -0
  41. package/lib/device/purifier-H7121.js +336 -0
  42. package/lib/device/purifier-H7122.js +449 -0
  43. package/lib/device/purifier-H7123.js +411 -0
  44. package/lib/device/purifier-H7124.js +411 -0
  45. package/lib/device/purifier-H7126.js +296 -0
  46. package/lib/device/purifier-H7127.js +296 -0
  47. package/lib/device/purifier-H712C.js +296 -0
  48. package/lib/device/purifier-single.js +119 -0
  49. package/lib/device/sensor-button.js +22 -0
  50. package/lib/device/sensor-contact.js +22 -0
  51. package/lib/device/sensor-leak.js +87 -0
  52. package/lib/device/sensor-monitor.js +190 -0
  53. package/lib/device/sensor-presence.js +53 -0
  54. package/lib/device/sensor-thermo.js +144 -0
  55. package/lib/device/sensor-thermo4.js +55 -0
  56. package/lib/device/switch-double.js +121 -0
  57. package/lib/device/switch-single.js +95 -0
  58. package/lib/device/switch-triple.js +160 -0
  59. package/lib/device/tap-single.js +108 -0
  60. package/lib/device/template.js +43 -0
  61. package/lib/device/tv-single.js +84 -0
  62. package/lib/device/valve-single.js +155 -0
  63. package/lib/fakegato/LICENSE +21 -0
  64. package/lib/fakegato/fakegato-history.js +814 -0
  65. package/lib/fakegato/fakegato-storage.js +108 -0
  66. package/lib/fakegato/fakegato-timer.js +125 -0
  67. package/lib/fakegato/uuid.js +27 -0
  68. package/lib/homebridge-ui/public/index.html +433 -0
  69. package/lib/homebridge-ui/server.js +10 -0
  70. package/lib/index.js +8 -0
  71. package/lib/platform.js +1967 -0
  72. package/lib/utils/colour.js +564 -0
  73. package/lib/utils/constants.js +579 -0
  74. package/lib/utils/custom-chars.js +225 -0
  75. package/lib/utils/eve-chars.js +68 -0
  76. package/lib/utils/functions.js +117 -0
  77. package/lib/utils/lang-en.js +131 -0
  78. 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
+ }