@homebridge-plugins/homebridge-govee 11.32.0-beta.9 → 11.32.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 +38 -1
- package/README.md +1 -1
- package/lib/connection/ble.js +8 -5
- package/lib/connection/openapi.js +14 -2
- package/lib/device/fan-H1310.js +22 -10
- package/lib/device/fan-H1370.js +24 -5
- package/lib/device/fan-H7100.js +22 -10
- package/lib/device/fan-H7101.js +22 -10
- package/lib/device/fan-H7102.js +22 -10
- package/lib/device/fan-H7105.js +24 -8
- package/lib/device/fan-H7106.js +22 -10
- package/lib/device/fan-H7107.js +24 -8
- package/lib/device/fan-H7111.js +21 -22
- package/lib/device/ice-maker-H7172.js +28 -3
- package/lib/device/ice-maker-H717D.js +28 -3
- package/lib/device/ice-maker-H8120.js +28 -3
- package/lib/device/outlet-double.js +84 -17
- package/lib/device/sensor-monitor.js +34 -0
- package/lib/device/switch-double.js +84 -17
- package/lib/homebridge-ui/public/index.html +1 -1
- package/lib/homebridge-ui/server.js +7 -0
- package/lib/platform.js +74 -5
- package/lib/utils/ble-protocol.js +32 -0
- package/lib/utils/ble-timeouts.test.js +44 -0
- package/lib/utils/command-builder.js +13 -2
- package/lib/utils/constants.js +8 -3
- package/lib/utils/device-capabilities.js +8 -0
- package/lib/utils/functions.js +77 -0
- package/lib/utils/functions.test.js +292 -0
- package/lib/utils/lang-en.js +1 -0
- package/package.json +19 -11
package/CHANGELOG.md
CHANGED
|
@@ -2,16 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@homebridge-plugins/homebridge-govee` will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## v11.32.
|
|
5
|
+
## v11.32.1 (2026-08-02)
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- docs: add node 26 to the supported node versions
|
|
10
|
+
- fix: read the H5106 monitor's temperature and humidity from the http device list too, so accounts whose aws channel never delivers still get readings (#1322)
|
|
11
|
+
|
|
12
|
+
## v11.32.0 (2026-08-02)
|
|
6
13
|
|
|
7
14
|
### Changed
|
|
8
15
|
|
|
9
16
|
- chore(github): use the shared homebridge action to deprecate past pre-releases
|
|
17
|
+
- chore(github): allow the pre-release keep count to be set when running the deprecation workflow
|
|
10
18
|
- feat(ui): hide or show a device in homekit from the my devices tab
|
|
11
19
|
- chore(deps): dependency updates
|
|
12
20
|
- style(ui): standardise the custom ui layout and sync the support tab with the readme
|
|
13
21
|
- chore(deps): override help-me to v5 to clear the high brace-expansion security advisory from the aws-iot dependency chain
|
|
14
22
|
- feat(ui): add a remove all devices action to the my devices tab (#1323)
|
|
23
|
+
- feat: add support for the H5089 smart plug, exposing its outlet and built-in light as separate switches (#1323)
|
|
24
|
+
- chore: declare the supports-hap transport keyword for the homebridge ui
|
|
25
|
+
- feat: add support for the H60B3 uplighter as an rgb light (#1325)
|
|
26
|
+
- feat: add support for the H5901 water timer through the govee openapi (#1324)
|
|
27
|
+
- fix(fans): map rotation speed as a percentage on every fan model, so homekit stops showing speeds like 7% and the top of the slider means full power (#1326) (@cwhett)
|
|
28
|
+
- chore: log what each device can do according to the govee openapi, in debug mode, to make working out new device support quicker
|
|
29
|
+
- chore: decode the status codes a device reports into readable hex in debug mode, so support for new device features can be worked out from a normal log
|
|
30
|
+
- feat: add the H5089 night light as a homekit light tile with on, off and brightness (#1323)
|
|
31
|
+
- fix: send the H5089 night light commands as the newer ptIot type the plug listens for, instead of being silently ignored (#1323)
|
|
32
|
+
- fix: also send the H5089 night light the command shape the supported govee purifiers use, since mirroring the plug's own status code was not enough on its own (#1323)
|
|
33
|
+
- fix: stop sending a per-outlet value to the govee api, which rejected every press with a parameter out of range error on two-outlet plugs (#1323)
|
|
34
|
+
- fix: hide the H5089 night light tile until its commands are worked out, rather than showing a control that does nothing (#1323)
|
|
35
|
+
- feat: add support for the H3001 solar string lights as an rgb light (#1328)
|
|
36
|
+
- fix(fans): rebuild the fan service once when it was cached under the old speed scale, so the top of the slider stops setting the slowest speed (#1326) (@cwhett)
|
|
37
|
+
- fix: show the H5089 night light tile again, but refuse control commands, so it reports the real state instead of quietly ignoring taps (#1323)
|
|
38
|
+
- fix(ice-makers): stop the home app showing an ice size of 0 when one has never been set (#1329) (@cwhett)
|
|
39
|
+
- chore: add a test setup with tests for the shared helper functions
|
|
40
|
+
- chore(github): run the build and tests in ci, on node 22, 24 and 26
|
|
41
|
+
- chore: use the same lint setup across every plugin
|
|
42
|
+
- chore: add a changelog:sync script to populate the pending section from the commits
|
|
43
|
+
- chore(deps): refresh the lock file for the eslint config bump
|
|
44
|
+
- fix(ble): size the update queue timeout from the connection stages, so a slow device still has time to send its command (#1328)
|
|
45
|
+
- chore: count a repeated commit subject once when syncing the changelog
|
|
46
|
+
- chore(github): check the changelog against the commits in ci
|
|
47
|
+
- fix(openapi): report govee's own reason when it refuses a command, instead of the command appearing to succeed (#1324)
|
|
48
|
+
- fix: report why a command failed instead of claiming no connection method was available (#1324)
|
|
49
|
+
- fix(ble): use the segment-based colour command for the H617A, whose LEDs ignore the simple RGB colour commands (#1332)
|
|
50
|
+
- feat: add support for the H1250 ceiling light pro as an rgb light (#1333)
|
|
51
|
+
- fix: stop rescaling the H6022's aws brightness reports, which are already a percentage, so its brightness stops bouncing to a wrong value after every change
|
|
15
52
|
|
|
16
53
|
## v11.31.0 (2026-07-24)
|
|
17
54
|
|
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ Homebridge plugin to integrate Govee devices into HomeKit
|
|
|
27
27
|
### Prerequisites
|
|
28
28
|
|
|
29
29
|
- To use this plugin, you will need to already have:
|
|
30
|
-
- [Node](https://nodejs.org): latest version of `v22` or `
|
|
30
|
+
- [Node](https://nodejs.org): latest version of `v22`, `v24` or `v26` - any other major version is not supported.
|
|
31
31
|
- [Homebridge](https://homebridge.io): `v1.6` or above - refer to link for more information and installation instructions.
|
|
32
32
|
- For bluetooth connectivity, it may be necessary to install extra packages on your system, see [Bluetooth Control](https://github.com/homebridge-plugins/homebridge-govee/wiki/Bluetooth-Control). Bluetooth works best when using a Raspberry Pi, not been tested on Windows, and Mac devices are unsupported.
|
|
33
33
|
|
package/lib/connection/ble.js
CHANGED
|
@@ -3,7 +3,7 @@ import process from 'node:process'
|
|
|
3
3
|
|
|
4
4
|
import btClient from '@stoprocent/noble'
|
|
5
5
|
|
|
6
|
-
import { UUID } from '../utils/ble-protocol.js'
|
|
6
|
+
import { BLE_STAGE_TIMEOUTS, UUID } from '../utils/ble-protocol.js'
|
|
7
7
|
import { decodeAny } from '../utils/decode.js'
|
|
8
8
|
import { base64ToHex, generateCodeFromHexValues, hexToTwoItems, sleep } from '../utils/functions.js'
|
|
9
9
|
import platformLang from '../utils/lang-en.js'
|
|
@@ -15,8 +15,11 @@ process.env.NOBLE_REPORT_ALL_HCI_EVENTS = '1' // needed on Linux including Raspb
|
|
|
15
15
|
|
|
16
16
|
const H5075_UUID = 'ec88'
|
|
17
17
|
const H5101_UUID = '0001'
|
|
18
|
-
|
|
19
|
-
const
|
|
18
|
+
|
|
19
|
+
const POWER_ON_TIMEOUT = BLE_STAGE_TIMEOUTS.powerOn
|
|
20
|
+
const CONNECTION_TIMEOUT = BLE_STAGE_TIMEOUTS.connect
|
|
21
|
+
const WRITE_TIMEOUT = BLE_STAGE_TIMEOUTS.write
|
|
22
|
+
const SETTLE_DELAY = BLE_STAGE_TIMEOUTS.settle
|
|
20
23
|
|
|
21
24
|
/*
|
|
22
25
|
The necessary commands to send and functions are taken from and credit to:
|
|
@@ -169,7 +172,7 @@ export default class BLEConnection {
|
|
|
169
172
|
}
|
|
170
173
|
}
|
|
171
174
|
|
|
172
|
-
async waitForPowerOn(timeout =
|
|
175
|
+
async waitForPowerOn(timeout = POWER_ON_TIMEOUT) {
|
|
173
176
|
if (this.btState === 'poweredOn') {
|
|
174
177
|
return true
|
|
175
178
|
}
|
|
@@ -240,7 +243,7 @@ export default class BLEConnection {
|
|
|
240
243
|
// Write without response then allow time for the BLE controller to transmit before disconnecting
|
|
241
244
|
await this.writeWithTimeout(characteristic, finalBuffer, WRITE_TIMEOUT)
|
|
242
245
|
accessory.logDebug('command sent successfully')
|
|
243
|
-
await sleep(
|
|
246
|
+
await sleep(SETTLE_DELAY)
|
|
244
247
|
} catch (err) {
|
|
245
248
|
accessory.logWarn(`BLE update failed: ${err.message}`)
|
|
246
249
|
throw err
|
|
@@ -2,7 +2,7 @@ import axios from 'axios'
|
|
|
2
2
|
import mqtt from 'mqtt'
|
|
3
3
|
|
|
4
4
|
import { k2rgb } from '../utils/colour.js'
|
|
5
|
-
import { parseError } from '../utils/functions.js'
|
|
5
|
+
import { describeControlFailure, parseError } from '../utils/functions.js'
|
|
6
6
|
|
|
7
7
|
const BASE_URL = 'https://openapi.api.govee.com'
|
|
8
8
|
|
|
@@ -500,7 +500,7 @@ export default class {
|
|
|
500
500
|
async updateDevice(accessory, params) {
|
|
501
501
|
const capability = this.buildCapabilityPayload(accessory, params)
|
|
502
502
|
|
|
503
|
-
await this.request({
|
|
503
|
+
const response = await this.request({
|
|
504
504
|
method: 'post',
|
|
505
505
|
path: '/router/api/v1/device/control',
|
|
506
506
|
data: {
|
|
@@ -512,6 +512,18 @@ export default class {
|
|
|
512
512
|
},
|
|
513
513
|
},
|
|
514
514
|
})
|
|
515
|
+
|
|
516
|
+
// Govee can accept the request and still refuse the command, reporting it
|
|
517
|
+
// only inside `capability.state`. Without this the plugin treated that as
|
|
518
|
+
// success and the device quietly ignored the command (#1324).
|
|
519
|
+
const refusal = describeControlFailure(response)
|
|
520
|
+
if (refusal) {
|
|
521
|
+
const error = new Error(refusal)
|
|
522
|
+
// Their wording is aimed at the user and describes the device's state, so
|
|
523
|
+
// log it on its own — a stack trace here suggests a fault in the plugin.
|
|
524
|
+
error.deviceRefusal = true
|
|
525
|
+
throw error
|
|
526
|
+
}
|
|
515
527
|
}
|
|
516
528
|
|
|
517
529
|
async connectMQTT() {
|
package/lib/device/fan-H1310.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
base64ToHex,
|
|
3
|
+
fanSpeedToHkPercent,
|
|
3
4
|
getTwoItemPosition,
|
|
4
5
|
hexToTwoItems,
|
|
6
|
+
hkPercentToFanSpeed,
|
|
5
7
|
parseError,
|
|
6
8
|
} from '../utils/functions.js'
|
|
7
9
|
import platformLang from '../utils/lang-en.js'
|
|
@@ -30,16 +32,20 @@ export default class {
|
|
|
30
32
|
9: 'MwUAAwAAAAAAAAAAAAAAAAAAADU=', // auto mode
|
|
31
33
|
}
|
|
32
34
|
this.autoSpeed = 9
|
|
35
|
+
// Real speed steps, excluding auto - HomeKit percentages map onto these
|
|
36
|
+
this.speedSteps = 8
|
|
33
37
|
|
|
34
38
|
// Remove any old original Fan services
|
|
35
39
|
if (this.accessory.getService(this.hapServ.Fan)) {
|
|
36
40
|
this.accessory.removeService(this.accessory.getService(this.hapServ.Fan))
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
//
|
|
43
|
+
// Rebuild the fan service if it was cached under an older speed scale, as
|
|
44
|
+
// HomeKit keeps the old props until the service itself is replaced
|
|
40
45
|
const existingService = this.accessory.getService(this.hapServ.Fanv2)
|
|
41
46
|
if (existingService) {
|
|
42
|
-
|
|
47
|
+
const { props } = existingService.getCharacteristic(this.hapChar.RotationSpeed)
|
|
48
|
+
if (props.unit === 'percentage' || props.maxValue !== 100) {
|
|
43
49
|
this.accessory.removeService(existingService)
|
|
44
50
|
}
|
|
45
51
|
}
|
|
@@ -57,7 +63,7 @@ export default class {
|
|
|
57
63
|
this.service
|
|
58
64
|
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
59
65
|
.setProps({
|
|
60
|
-
maxValue:
|
|
66
|
+
maxValue: 100,
|
|
61
67
|
minStep: 1,
|
|
62
68
|
minValue: 0,
|
|
63
69
|
unit: 'unitless',
|
|
@@ -115,6 +121,9 @@ export default class {
|
|
|
115
121
|
return
|
|
116
122
|
}
|
|
117
123
|
|
|
124
|
+
// HomeKit sends a percentage - map it onto this model's real speed steps
|
|
125
|
+
value = hkPercentToFanSpeed(value, this.speedSteps)
|
|
126
|
+
|
|
118
127
|
// Don't continue if the new value is the same as before
|
|
119
128
|
if (this.cacheSpeed === value) {
|
|
120
129
|
return
|
|
@@ -142,7 +151,7 @@ export default class {
|
|
|
142
151
|
|
|
143
152
|
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
144
153
|
setTimeout(() => {
|
|
145
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
|
|
154
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, fanSpeedToHkPercent(this.cacheSpeed, this.speedSteps, this.autoSpeed))
|
|
146
155
|
}, 2000)
|
|
147
156
|
throw new this.hapErr(-70402)
|
|
148
157
|
}
|
|
@@ -199,8 +208,9 @@ export default class {
|
|
|
199
208
|
}
|
|
200
209
|
if (Number.isFinite(newSpeed) && newSpeed >= 1 && newSpeed <= this.autoSpeed && this.cacheSpeed !== newSpeed) {
|
|
201
210
|
this.cacheSpeed = newSpeed
|
|
202
|
-
|
|
203
|
-
this.
|
|
211
|
+
const hkSpeed = fanSpeedToHkPercent(newSpeed, this.speedSteps, this.autoSpeed)
|
|
212
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, hkSpeed)
|
|
213
|
+
this.accessory.log(`${platformLang.curSpeed} [${newSpeed} -> ${hkSpeed}%]`)
|
|
204
214
|
}
|
|
205
215
|
}
|
|
206
216
|
|
|
@@ -237,10 +247,11 @@ export default class {
|
|
|
237
247
|
case '0501': {
|
|
238
248
|
// Fan speed
|
|
239
249
|
const newSpeed = Number.parseInt(getTwoItemPosition(hexParts, 4), 16)
|
|
240
|
-
if (newSpeed >= 1 && newSpeed <=
|
|
250
|
+
if (newSpeed >= 1 && newSpeed <= this.speedSteps && this.cacheSpeed !== newSpeed) {
|
|
241
251
|
this.cacheSpeed = newSpeed
|
|
242
|
-
|
|
243
|
-
this.
|
|
252
|
+
const hkSpeed = fanSpeedToHkPercent(newSpeed, this.speedSteps, this.autoSpeed)
|
|
253
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, hkSpeed)
|
|
254
|
+
this.accessory.log(`${platformLang.curSpeed} [${newSpeed} -> ${hkSpeed}%]`)
|
|
244
255
|
}
|
|
245
256
|
break
|
|
246
257
|
}
|
|
@@ -248,7 +259,8 @@ export default class {
|
|
|
248
259
|
// Mode change (auto = 0x03)
|
|
249
260
|
if (getTwoItemPosition(hexParts, 4) === '03' && this.cacheSpeed !== this.autoSpeed) {
|
|
250
261
|
this.cacheSpeed = this.autoSpeed
|
|
251
|
-
|
|
262
|
+
// Cache auto so we can tell it apart from "user asked for max speed"
|
|
263
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, 100)
|
|
252
264
|
this.accessory.log(`${platformLang.curSpeed} [auto]`)
|
|
253
265
|
}
|
|
254
266
|
break
|
package/lib/device/fan-H1370.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
base64ToHex,
|
|
3
|
+
fanSpeedToHkPercent,
|
|
3
4
|
generateCodeFromHexValues,
|
|
4
5
|
generateRandomString,
|
|
5
6
|
getTwoItemPosition,
|
|
6
7
|
hexToTwoItems,
|
|
8
|
+
hkPercentToFanSpeed,
|
|
7
9
|
parseError,
|
|
8
10
|
sleep,
|
|
9
11
|
} from '../utils/functions.js'
|
|
@@ -32,6 +34,16 @@ export default class {
|
|
|
32
34
|
const deviceConf = platform.deviceConf[accessory.context.gvDeviceId]
|
|
33
35
|
this.hideLight = deviceConf && deviceConf.hideLight
|
|
34
36
|
|
|
37
|
+
// Rebuild the fan service if it was cached under an older speed scale, as
|
|
38
|
+
// HomeKit keeps the old props until the service itself is replaced
|
|
39
|
+
const existingService = this.accessory.getService(this.hapServ.Fanv2)
|
|
40
|
+
if (existingService) {
|
|
41
|
+
const { props } = existingService.getCharacteristic(this.hapChar.RotationSpeed)
|
|
42
|
+
if (props.unit === 'percentage' || props.maxValue !== 100) {
|
|
43
|
+
this.accessory.removeService(existingService)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
// Add the fan service if it doesn't already exist
|
|
36
48
|
this.service = this.accessory.getService(this.hapServ.Fanv2) || this.accessory.addService(this.hapServ.Fanv2)
|
|
37
49
|
|
|
@@ -41,11 +53,14 @@ export default class {
|
|
|
41
53
|
.onSet(async value => this.internalStateUpdate(value))
|
|
42
54
|
this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value ? 'on' : 'off'
|
|
43
55
|
|
|
56
|
+
// Real speed steps - HomeKit percentages map onto these
|
|
57
|
+
this.speedSteps = 12
|
|
58
|
+
|
|
44
59
|
// Add the set handler to the fan rotation speed characteristic
|
|
45
60
|
this.service
|
|
46
61
|
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
47
62
|
.setProps({
|
|
48
|
-
maxValue:
|
|
63
|
+
maxValue: 100,
|
|
49
64
|
minStep: 1,
|
|
50
65
|
minValue: 0,
|
|
51
66
|
unit: 'unitless',
|
|
@@ -140,6 +155,9 @@ export default class {
|
|
|
140
155
|
return
|
|
141
156
|
}
|
|
142
157
|
|
|
158
|
+
// HomeKit sends a percentage - map it onto this model's real speed steps
|
|
159
|
+
value = hkPercentToFanSpeed(value, this.speedSteps)
|
|
160
|
+
|
|
143
161
|
// Don't continue if the new value is the same as before
|
|
144
162
|
if (this.cacheSpeed === value) {
|
|
145
163
|
return
|
|
@@ -162,7 +180,7 @@ export default class {
|
|
|
162
180
|
|
|
163
181
|
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
164
182
|
setTimeout(() => {
|
|
165
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
|
|
183
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, fanSpeedToHkPercent(this.cacheSpeed, this.speedSteps))
|
|
166
184
|
}, 2000)
|
|
167
185
|
throw new this.hapErr(-70402)
|
|
168
186
|
}
|
|
@@ -320,10 +338,11 @@ export default class {
|
|
|
320
338
|
case '3101': {
|
|
321
339
|
// Fan speed
|
|
322
340
|
const newSpeed = Number.parseInt(getTwoItemPosition(hexParts, 4), 16)
|
|
323
|
-
if (newSpeed >= 1 && newSpeed <=
|
|
341
|
+
if (newSpeed >= 1 && newSpeed <= this.speedSteps && this.cacheSpeed !== newSpeed) {
|
|
324
342
|
this.cacheSpeed = newSpeed
|
|
325
|
-
|
|
326
|
-
this.
|
|
343
|
+
const hkSpeed = fanSpeedToHkPercent(newSpeed, this.speedSteps)
|
|
344
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, hkSpeed)
|
|
345
|
+
this.accessory.log(`${platformLang.curSpeed} [${newSpeed} -> ${hkSpeed}%]`)
|
|
327
346
|
}
|
|
328
347
|
break
|
|
329
348
|
}
|
package/lib/device/fan-H7100.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
base64ToHex,
|
|
3
|
+
fanSpeedToHkPercent,
|
|
3
4
|
getTwoItemPosition,
|
|
4
5
|
hexToTwoItems,
|
|
6
|
+
hkPercentToFanSpeed,
|
|
5
7
|
parseError,
|
|
6
8
|
} from '../utils/functions.js'
|
|
7
9
|
import platformLang from '../utils/lang-en.js'
|
|
@@ -30,16 +32,20 @@ export default class {
|
|
|
30
32
|
9: 'MwUAAwAAAAAAAAAAAAAAAAAAADU=', // auto mode
|
|
31
33
|
}
|
|
32
34
|
this.autoSpeed = 9
|
|
35
|
+
// Real speed steps, excluding auto - HomeKit percentages map onto these
|
|
36
|
+
this.speedSteps = 8
|
|
33
37
|
|
|
34
38
|
// Remove any old original Fan services
|
|
35
39
|
if (this.accessory.getService(this.hapServ.Fan)) {
|
|
36
40
|
this.accessory.removeService(this.accessory.getService(this.hapServ.Fan))
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
//
|
|
43
|
+
// Rebuild the fan service if it was cached under an older speed scale, as
|
|
44
|
+
// HomeKit keeps the old props until the service itself is replaced
|
|
40
45
|
const existingService = this.accessory.getService(this.hapServ.Fanv2)
|
|
41
46
|
if (existingService) {
|
|
42
|
-
|
|
47
|
+
const { props } = existingService.getCharacteristic(this.hapChar.RotationSpeed)
|
|
48
|
+
if (props.unit === 'percentage' || props.maxValue !== 100) {
|
|
43
49
|
this.accessory.removeService(existingService)
|
|
44
50
|
}
|
|
45
51
|
}
|
|
@@ -57,7 +63,7 @@ export default class {
|
|
|
57
63
|
this.service
|
|
58
64
|
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
59
65
|
.setProps({
|
|
60
|
-
maxValue:
|
|
66
|
+
maxValue: 100,
|
|
61
67
|
minStep: 1,
|
|
62
68
|
minValue: 0,
|
|
63
69
|
unit: 'unitless',
|
|
@@ -115,6 +121,9 @@ export default class {
|
|
|
115
121
|
return
|
|
116
122
|
}
|
|
117
123
|
|
|
124
|
+
// HomeKit sends a percentage - map it onto this model's real speed steps
|
|
125
|
+
value = hkPercentToFanSpeed(value, this.speedSteps)
|
|
126
|
+
|
|
118
127
|
// Don't continue if the new value is the same as before
|
|
119
128
|
if (this.cacheSpeed === value) {
|
|
120
129
|
return
|
|
@@ -142,7 +151,7 @@ export default class {
|
|
|
142
151
|
|
|
143
152
|
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
144
153
|
setTimeout(() => {
|
|
145
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
|
|
154
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, fanSpeedToHkPercent(this.cacheSpeed, this.speedSteps, this.autoSpeed))
|
|
146
155
|
}, 2000)
|
|
147
156
|
throw new this.hapErr(-70402)
|
|
148
157
|
}
|
|
@@ -199,8 +208,9 @@ export default class {
|
|
|
199
208
|
}
|
|
200
209
|
if (Number.isFinite(newSpeed) && newSpeed >= 1 && newSpeed <= this.autoSpeed && this.cacheSpeed !== newSpeed) {
|
|
201
210
|
this.cacheSpeed = newSpeed
|
|
202
|
-
|
|
203
|
-
this.
|
|
211
|
+
const hkSpeed = fanSpeedToHkPercent(newSpeed, this.speedSteps, this.autoSpeed)
|
|
212
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, hkSpeed)
|
|
213
|
+
this.accessory.log(`${platformLang.curSpeed} [${newSpeed} -> ${hkSpeed}%]`)
|
|
204
214
|
}
|
|
205
215
|
}
|
|
206
216
|
|
|
@@ -237,10 +247,11 @@ export default class {
|
|
|
237
247
|
case '0501': {
|
|
238
248
|
// Fan speed
|
|
239
249
|
const newSpeed = Number.parseInt(getTwoItemPosition(hexParts, 4), 16)
|
|
240
|
-
if (newSpeed >= 1 && newSpeed <=
|
|
250
|
+
if (newSpeed >= 1 && newSpeed <= this.speedSteps && this.cacheSpeed !== newSpeed) {
|
|
241
251
|
this.cacheSpeed = newSpeed
|
|
242
|
-
|
|
243
|
-
this.
|
|
252
|
+
const hkSpeed = fanSpeedToHkPercent(newSpeed, this.speedSteps, this.autoSpeed)
|
|
253
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, hkSpeed)
|
|
254
|
+
this.accessory.log(`${platformLang.curSpeed} [${newSpeed} -> ${hkSpeed}%]`)
|
|
244
255
|
}
|
|
245
256
|
break
|
|
246
257
|
}
|
|
@@ -248,7 +259,8 @@ export default class {
|
|
|
248
259
|
// Mode change (auto = 0x03)
|
|
249
260
|
if (getTwoItemPosition(hexParts, 4) === '03' && this.cacheSpeed !== this.autoSpeed) {
|
|
250
261
|
this.cacheSpeed = this.autoSpeed
|
|
251
|
-
|
|
262
|
+
// Cache auto so we can tell it apart from "user asked for max speed"
|
|
263
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, 100)
|
|
252
264
|
this.accessory.log(`${platformLang.curSpeed} [auto]`)
|
|
253
265
|
}
|
|
254
266
|
break
|
package/lib/device/fan-H7101.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
base64ToHex,
|
|
3
|
+
fanSpeedToHkPercent,
|
|
3
4
|
getTwoItemPosition,
|
|
4
5
|
hexToTwoItems,
|
|
6
|
+
hkPercentToFanSpeed,
|
|
5
7
|
parseError,
|
|
6
8
|
} from '../utils/functions.js'
|
|
7
9
|
import platformLang from '../utils/lang-en.js'
|
|
@@ -86,16 +88,20 @@ export default class {
|
|
|
86
88
|
9: 'MwUAAwAAAAAAAAAAAAAAAAAAADU=', // auto mode
|
|
87
89
|
}
|
|
88
90
|
this.autoSpeed = 9
|
|
91
|
+
// Real speed steps, excluding auto - HomeKit percentages map onto these
|
|
92
|
+
this.speedSteps = 8
|
|
89
93
|
|
|
90
94
|
// Remove any old original Fan services
|
|
91
95
|
if (this.accessory.getService(this.hapServ.Fan)) {
|
|
92
96
|
this.accessory.removeService(this.accessory.getService(this.hapServ.Fan))
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
//
|
|
99
|
+
// Rebuild the fan service if it was cached under an older speed scale, as
|
|
100
|
+
// HomeKit keeps the old props until the service itself is replaced
|
|
96
101
|
const existingService = this.accessory.getService(this.hapServ.Fanv2)
|
|
97
102
|
if (existingService) {
|
|
98
|
-
|
|
103
|
+
const { props } = existingService.getCharacteristic(this.hapChar.RotationSpeed)
|
|
104
|
+
if (props.unit === 'percentage' || props.maxValue !== 100) {
|
|
99
105
|
this.accessory.removeService(existingService)
|
|
100
106
|
}
|
|
101
107
|
}
|
|
@@ -113,7 +119,7 @@ export default class {
|
|
|
113
119
|
this.service
|
|
114
120
|
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
115
121
|
.setProps({
|
|
116
|
-
maxValue:
|
|
122
|
+
maxValue: 100,
|
|
117
123
|
minStep: 1,
|
|
118
124
|
minValue: 0,
|
|
119
125
|
unit: 'unitless',
|
|
@@ -171,6 +177,9 @@ export default class {
|
|
|
171
177
|
return
|
|
172
178
|
}
|
|
173
179
|
|
|
180
|
+
// HomeKit sends a percentage - map it onto this model's real speed steps
|
|
181
|
+
value = hkPercentToFanSpeed(value, this.speedSteps)
|
|
182
|
+
|
|
174
183
|
// Don't continue if the new value is the same as before
|
|
175
184
|
if (this.cacheSpeed === value) {
|
|
176
185
|
return
|
|
@@ -198,7 +207,7 @@ export default class {
|
|
|
198
207
|
|
|
199
208
|
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
200
209
|
setTimeout(() => {
|
|
201
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
|
|
210
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, fanSpeedToHkPercent(this.cacheSpeed, this.speedSteps, this.autoSpeed))
|
|
202
211
|
}, 2000)
|
|
203
212
|
throw new this.hapErr(-70402)
|
|
204
213
|
}
|
|
@@ -255,8 +264,9 @@ export default class {
|
|
|
255
264
|
}
|
|
256
265
|
if (Number.isFinite(newSpeed) && newSpeed >= 1 && newSpeed <= this.autoSpeed && this.cacheSpeed !== newSpeed) {
|
|
257
266
|
this.cacheSpeed = newSpeed
|
|
258
|
-
|
|
259
|
-
this.
|
|
267
|
+
const hkSpeed = fanSpeedToHkPercent(newSpeed, this.speedSteps, this.autoSpeed)
|
|
268
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, hkSpeed)
|
|
269
|
+
this.accessory.log(`${platformLang.curSpeed} [${newSpeed} -> ${hkSpeed}%]`)
|
|
260
270
|
}
|
|
261
271
|
}
|
|
262
272
|
|
|
@@ -293,10 +303,11 @@ export default class {
|
|
|
293
303
|
case '0501': {
|
|
294
304
|
// Fan speed
|
|
295
305
|
const newSpeed = Number.parseInt(getTwoItemPosition(hexParts, 4), 16)
|
|
296
|
-
if (newSpeed >= 1 && newSpeed <=
|
|
306
|
+
if (newSpeed >= 1 && newSpeed <= this.speedSteps && this.cacheSpeed !== newSpeed) {
|
|
297
307
|
this.cacheSpeed = newSpeed
|
|
298
|
-
|
|
299
|
-
this.
|
|
308
|
+
const hkSpeed = fanSpeedToHkPercent(newSpeed, this.speedSteps, this.autoSpeed)
|
|
309
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, hkSpeed)
|
|
310
|
+
this.accessory.log(`${platformLang.curSpeed} [${newSpeed} -> ${hkSpeed}%]`)
|
|
300
311
|
}
|
|
301
312
|
break
|
|
302
313
|
}
|
|
@@ -304,7 +315,8 @@ export default class {
|
|
|
304
315
|
// Mode change (auto = 0x03)
|
|
305
316
|
if (getTwoItemPosition(hexParts, 4) === '03' && this.cacheSpeed !== this.autoSpeed) {
|
|
306
317
|
this.cacheSpeed = this.autoSpeed
|
|
307
|
-
|
|
318
|
+
// Cache auto so we can tell it apart from "user asked for max speed"
|
|
319
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, 100)
|
|
308
320
|
this.accessory.log(`${platformLang.curSpeed} [auto]`)
|
|
309
321
|
}
|
|
310
322
|
break
|
package/lib/device/fan-H7102.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
base64ToHex,
|
|
3
|
+
fanSpeedToHkPercent,
|
|
3
4
|
getTwoItemPosition,
|
|
4
5
|
hexToTwoItems,
|
|
6
|
+
hkPercentToFanSpeed,
|
|
5
7
|
parseError,
|
|
6
8
|
} from '../utils/functions.js'
|
|
7
9
|
import platformLang from '../utils/lang-en.js'
|
|
@@ -30,16 +32,20 @@ export default class {
|
|
|
30
32
|
9: 'MwUAAwAAAAAAAAAAAAAAAAAAADU=', // auto mode
|
|
31
33
|
}
|
|
32
34
|
this.autoSpeed = 9
|
|
35
|
+
// Real speed steps, excluding auto - HomeKit percentages map onto these
|
|
36
|
+
this.speedSteps = 8
|
|
33
37
|
|
|
34
38
|
// Remove any old original Fan services
|
|
35
39
|
if (this.accessory.getService(this.hapServ.Fan)) {
|
|
36
40
|
this.accessory.removeService(this.accessory.getService(this.hapServ.Fan))
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
//
|
|
43
|
+
// Rebuild the fan service if it was cached under an older speed scale, as
|
|
44
|
+
// HomeKit keeps the old props until the service itself is replaced
|
|
40
45
|
const existingService = this.accessory.getService(this.hapServ.Fanv2)
|
|
41
46
|
if (existingService) {
|
|
42
|
-
|
|
47
|
+
const { props } = existingService.getCharacteristic(this.hapChar.RotationSpeed)
|
|
48
|
+
if (props.unit === 'percentage' || props.maxValue !== 100) {
|
|
43
49
|
this.accessory.removeService(existingService)
|
|
44
50
|
}
|
|
45
51
|
}
|
|
@@ -57,7 +63,7 @@ export default class {
|
|
|
57
63
|
this.service
|
|
58
64
|
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
59
65
|
.setProps({
|
|
60
|
-
maxValue:
|
|
66
|
+
maxValue: 100,
|
|
61
67
|
minStep: 1,
|
|
62
68
|
minValue: 0,
|
|
63
69
|
unit: 'unitless',
|
|
@@ -115,6 +121,9 @@ export default class {
|
|
|
115
121
|
return
|
|
116
122
|
}
|
|
117
123
|
|
|
124
|
+
// HomeKit sends a percentage - map it onto this model's real speed steps
|
|
125
|
+
value = hkPercentToFanSpeed(value, this.speedSteps)
|
|
126
|
+
|
|
118
127
|
// Don't continue if the new value is the same as before
|
|
119
128
|
if (this.cacheSpeed === value) {
|
|
120
129
|
return
|
|
@@ -142,7 +151,7 @@ export default class {
|
|
|
142
151
|
|
|
143
152
|
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
144
153
|
setTimeout(() => {
|
|
145
|
-
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
|
|
154
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, fanSpeedToHkPercent(this.cacheSpeed, this.speedSteps, this.autoSpeed))
|
|
146
155
|
}, 2000)
|
|
147
156
|
throw new this.hapErr(-70402)
|
|
148
157
|
}
|
|
@@ -199,8 +208,9 @@ export default class {
|
|
|
199
208
|
}
|
|
200
209
|
if (Number.isFinite(newSpeed) && newSpeed >= 1 && newSpeed <= this.autoSpeed && this.cacheSpeed !== newSpeed) {
|
|
201
210
|
this.cacheSpeed = newSpeed
|
|
202
|
-
|
|
203
|
-
this.
|
|
211
|
+
const hkSpeed = fanSpeedToHkPercent(newSpeed, this.speedSteps, this.autoSpeed)
|
|
212
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, hkSpeed)
|
|
213
|
+
this.accessory.log(`${platformLang.curSpeed} [${newSpeed} -> ${hkSpeed}%]`)
|
|
204
214
|
}
|
|
205
215
|
}
|
|
206
216
|
|
|
@@ -237,10 +247,11 @@ export default class {
|
|
|
237
247
|
case '0501': {
|
|
238
248
|
// Fan speed
|
|
239
249
|
const newSpeed = Number.parseInt(getTwoItemPosition(hexParts, 4), 16)
|
|
240
|
-
if (newSpeed >= 1 && newSpeed <=
|
|
250
|
+
if (newSpeed >= 1 && newSpeed <= this.speedSteps && this.cacheSpeed !== newSpeed) {
|
|
241
251
|
this.cacheSpeed = newSpeed
|
|
242
|
-
|
|
243
|
-
this.
|
|
252
|
+
const hkSpeed = fanSpeedToHkPercent(newSpeed, this.speedSteps, this.autoSpeed)
|
|
253
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, hkSpeed)
|
|
254
|
+
this.accessory.log(`${platformLang.curSpeed} [${newSpeed} -> ${hkSpeed}%]`)
|
|
244
255
|
}
|
|
245
256
|
break
|
|
246
257
|
}
|
|
@@ -248,7 +259,8 @@ export default class {
|
|
|
248
259
|
// Mode change (auto = 0x03)
|
|
249
260
|
if (getTwoItemPosition(hexParts, 4) === '03' && this.cacheSpeed !== this.autoSpeed) {
|
|
250
261
|
this.cacheSpeed = this.autoSpeed
|
|
251
|
-
|
|
262
|
+
// Cache auto so we can tell it apart from "user asked for max speed"
|
|
263
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, 100)
|
|
252
264
|
this.accessory.log(`${platformLang.curSpeed} [auto]`)
|
|
253
265
|
}
|
|
254
266
|
break
|