@homebridge-plugins/homebridge-govee 11.32.2-beta.1 → 11.32.2-beta.3
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 +2 -0
- package/lib/device/fan-H7107.js +9 -6
- package/lib/device/ice-maker-H8121.js +222 -0
- package/lib/device/ice-maker-H8122.js +222 -0
- package/lib/device/index.js +4 -0
- package/lib/utils/constants.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ All notable changes to `@homebridge-plugins/homebridge-govee` will be documented
|
|
|
8
8
|
|
|
9
9
|
- fix: implement the H7107's oscillation toggle, which threw a not-implemented error when tapped (#1334)
|
|
10
10
|
- fix: also send the H7107's oscillation command as the multiSync frame the fan itself uses, since the first shape only turned it off sometimes (#1334)
|
|
11
|
+
- fix: send the H7107's oscillation-off command without the swing-range bytes, matching the zero-tailed frame the fan echoes back (#1334)
|
|
12
|
+
- feat: add support for the H8121 and H8122 ice makers, with ice size derived from the govee app (pending owner confirmation)
|
|
11
13
|
|
|
12
14
|
## v11.32.1 (2026-08-02)
|
|
13
15
|
|
package/lib/device/fan-H7107.js
CHANGED
|
@@ -210,12 +210,15 @@ export default class {
|
|
|
210
210
|
return
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
// The AWS code mirrors the device's own `aa1d..` oscillation report
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
|
|
213
|
+
// The AWS code mirrors the device's own `aa1d..` oscillation report.
|
|
214
|
+
// Turning ON carries over the swing-range bytes from the last report
|
|
215
|
+
// seen, which hardware-confirmed works. Turning OFF must NOT carry
|
|
216
|
+
// them: the fan answered a parameterised off command by echoing back a
|
|
217
|
+
// zero-tailed `331d00..` frame while continuing to oscillate — its own
|
|
218
|
+
// way of showing the shape it expects (#1334). Mirroring a status code
|
|
219
|
+
// is not guaranteed on every model, so the documented OpenAPI
|
|
220
|
+
// oscillation toggle is also sent when the device advertises it
|
|
221
|
+
const tailBytes = value && this.cacheSwingCode
|
|
219
222
|
? this.cacheSwingCode.slice(6, 14).match(/.{1,2}/g).map(byte => Number.parseInt(byte, 16))
|
|
220
223
|
: []
|
|
221
224
|
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { base64ToHex, getTwoItemPosition, hexToTwoItems, 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
|
+
// Ice size codes: small=1, medium=2, large=3, matching the OpenAPI workMode
|
|
16
|
+
// values. Unlike the H8120, the H8121's BLE size byte runs in the SAME
|
|
17
|
+
// direction as workMode (small=0x05 01 .. large=0x05 03) - taken from the
|
|
18
|
+
// app's ModeActionParserH8121, so the two paths agree here.
|
|
19
|
+
this.sizeCodes = {
|
|
20
|
+
1: 'MwUBAAAAAAAAAAAAAAAAAAAAADc=', // small -> 33 05 01
|
|
21
|
+
2: 'MwUCAAAAAAAAAAAAAAAAAAAAADQ=', // medium -> 33 05 02
|
|
22
|
+
3: 'MwUDAAAAAAAAAAAAAAAAAAAAADU=', // large -> 33 05 03
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.sizeLabels = {
|
|
26
|
+
1: 'small',
|
|
27
|
+
2: 'medium',
|
|
28
|
+
3: 'large',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Remove old switch service if migrating
|
|
32
|
+
if (this.accessory.getService(this.hapServ.Switch)) {
|
|
33
|
+
this.accessory.removeService(this.accessory.getService(this.hapServ.Switch))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Add the fan service for ice size control
|
|
37
|
+
this.service = this.accessory.getService(this.hapServ.Fanv2)
|
|
38
|
+
|| this.accessory.addService(this.hapServ.Fanv2)
|
|
39
|
+
|
|
40
|
+
// Add the set handler to the on/off characteristic
|
|
41
|
+
this.service.getCharacteristic(this.hapChar.Active).onSet(async (value) => {
|
|
42
|
+
await this.internalStateUpdate(value)
|
|
43
|
+
})
|
|
44
|
+
this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
|
|
45
|
+
|
|
46
|
+
// Add the set handler to the rotation speed characteristic (ice size)
|
|
47
|
+
this.service
|
|
48
|
+
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
49
|
+
.setProps({
|
|
50
|
+
maxValue: 3,
|
|
51
|
+
minStep: 1,
|
|
52
|
+
minValue: 0,
|
|
53
|
+
unit: 'unitless',
|
|
54
|
+
})
|
|
55
|
+
.onSet(async value => this.internalSizeUpdate(value))
|
|
56
|
+
this.cacheSize = this.service.getCharacteristic(this.hapChar.RotationSpeed).value || 2
|
|
57
|
+
|
|
58
|
+
// Keep the characteristic in step with the cache. Without this an accessory
|
|
59
|
+
// that has never had a size set reports Active=1 with RotationSpeed=0, which
|
|
60
|
+
// is contradictory and which the Home app renders as "0".
|
|
61
|
+
if (this.service.getCharacteristic(this.hapChar.RotationSpeed).value !== this.cacheSize) {
|
|
62
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Output the customised options to the log
|
|
66
|
+
const opts = JSON.stringify({})
|
|
67
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async internalStateUpdate(value) {
|
|
71
|
+
try {
|
|
72
|
+
const newState = value ? 'on' : 'off'
|
|
73
|
+
|
|
74
|
+
// Don't continue if the new value is the same as before
|
|
75
|
+
if (newState === this.cacheState) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (value) {
|
|
80
|
+
// Turn on with current cached ice size
|
|
81
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
82
|
+
cmd: 'ptReal',
|
|
83
|
+
value: this.sizeCodes[this.cacheSize],
|
|
84
|
+
openApi: this.accessory.context.openApiCapabilities?.workMode
|
|
85
|
+
? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: { workMode: this.cacheSize, modeValue: 0 } }
|
|
86
|
+
: undefined,
|
|
87
|
+
})
|
|
88
|
+
} else {
|
|
89
|
+
// Turn off. The OpenAPI workMode=0 path is authoritative; the BLE frame
|
|
90
|
+
// mirrors the H8120 ice-maker-off command (33 19 00) as a best effort
|
|
91
|
+
// and is pending confirmation from an H8121 owner.
|
|
92
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
93
|
+
cmd: 'ptReal',
|
|
94
|
+
value: 'MxkAAAAAAAAAAAAAAAAAAAAAACo=',
|
|
95
|
+
openApi: this.accessory.context.openApiCapabilities?.workMode
|
|
96
|
+
? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: { workMode: 0, modeValue: 0 } }
|
|
97
|
+
: undefined,
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Cache the new state and log if appropriate
|
|
102
|
+
if (this.cacheState !== newState) {
|
|
103
|
+
this.cacheState = newState
|
|
104
|
+
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
105
|
+
}
|
|
106
|
+
} catch (err) {
|
|
107
|
+
// Catch any errors during the process
|
|
108
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
109
|
+
|
|
110
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
111
|
+
setTimeout(() => {
|
|
112
|
+
this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
|
|
113
|
+
}, 2000)
|
|
114
|
+
throw new this.hapErr(-70402)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async internalSizeUpdate(value) {
|
|
119
|
+
try {
|
|
120
|
+
// 0 is not a valid ice size. Put the slider back, otherwise HomeKit keeps 0
|
|
121
|
+
// while the device carries on at cacheSize - and that 0 is what gets written
|
|
122
|
+
// to the accessory cache, so it returns on the next restart. Deferred for the
|
|
123
|
+
// same reason as the error reverts below: an immediate update would be
|
|
124
|
+
// overwritten by the value HomeKit is currently writing.
|
|
125
|
+
if (value === 0) {
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
128
|
+
}, 1000)
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Don't continue if the new value is the same as before
|
|
133
|
+
if (value === this.cacheSize) {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
138
|
+
cmd: 'ptReal',
|
|
139
|
+
value: this.sizeCodes[value],
|
|
140
|
+
openApi: this.accessory.context.openApiCapabilities?.workMode
|
|
141
|
+
? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: { workMode: value, modeValue: 0 } }
|
|
142
|
+
: undefined,
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// Cache the new state and log if appropriate
|
|
146
|
+
this.cacheSize = value
|
|
147
|
+
this.cacheState = 'on'
|
|
148
|
+
this.service.updateCharacteristic(this.hapChar.Active, 1)
|
|
149
|
+
this.accessory.log(`${platformLang.curSpeed} [${this.sizeLabels[value]}]`)
|
|
150
|
+
} catch (err) {
|
|
151
|
+
// Catch any errors during the process
|
|
152
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
153
|
+
|
|
154
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
157
|
+
}, 2000)
|
|
158
|
+
throw new this.hapErr(-70402)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
externalUpdate(params) {
|
|
163
|
+
// Handle OpenAPI workMode
|
|
164
|
+
if (params.workMode) {
|
|
165
|
+
const mode = params.workMode.workMode
|
|
166
|
+
if (mode > 0 && mode <= 3) {
|
|
167
|
+
if (this.cacheState !== 'on') {
|
|
168
|
+
this.cacheState = 'on'
|
|
169
|
+
this.service.updateCharacteristic(this.hapChar.Active, 1)
|
|
170
|
+
this.accessory.log(`${platformLang.curState} [on]`)
|
|
171
|
+
}
|
|
172
|
+
if (this.cacheSize !== mode) {
|
|
173
|
+
this.cacheSize = mode
|
|
174
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
175
|
+
this.accessory.log(`${platformLang.curSpeed} [${this.sizeLabels[this.cacheSize]}]`)
|
|
176
|
+
}
|
|
177
|
+
} else if (mode === 0) {
|
|
178
|
+
if (this.cacheState !== 'off') {
|
|
179
|
+
this.cacheState = 'off'
|
|
180
|
+
this.service.updateCharacteristic(this.hapChar.Active, 0)
|
|
181
|
+
this.accessory.log(`${platformLang.curState} [off]`)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check for some other scene/mode change
|
|
187
|
+
(params.commands || []).forEach((command) => {
|
|
188
|
+
const hexString = base64ToHex(command)
|
|
189
|
+
const hexParts = hexToTwoItems(hexString)
|
|
190
|
+
|
|
191
|
+
// Return now if not a device query update code
|
|
192
|
+
if (getTwoItemPosition(hexParts, 1) !== 'aa') {
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const deviceFunction = `${getTwoItemPosition(hexParts, 1)}${getTwoItemPosition(hexParts, 2)}`
|
|
197
|
+
|
|
198
|
+
switch (deviceFunction) {
|
|
199
|
+
case 'aa19': {
|
|
200
|
+
// On/Off
|
|
201
|
+
const newState = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
|
|
202
|
+
if (this.cacheState !== newState) {
|
|
203
|
+
this.cacheState = newState
|
|
204
|
+
this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
|
|
205
|
+
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
206
|
+
}
|
|
207
|
+
break
|
|
208
|
+
}
|
|
209
|
+
case 'aa05': {
|
|
210
|
+
// Ice size report. Left undecoded for now: the OpenAPI workMode report
|
|
211
|
+
// above is the authoritative source of the current size, so decoding
|
|
212
|
+
// the BLE echo would only risk a mismatch. This case exists to stop a
|
|
213
|
+
// routine status report being logged as an unrecognised scene code.
|
|
214
|
+
break
|
|
215
|
+
}
|
|
216
|
+
default:
|
|
217
|
+
this.accessory.logWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
|
|
218
|
+
break
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { base64ToHex, getTwoItemPosition, hexToTwoItems, 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
|
+
// Ice size codes: small=1, medium=2, large=3, matching the OpenAPI workMode
|
|
16
|
+
// values. Unlike the H8120, the H8122's BLE size byte runs in the SAME
|
|
17
|
+
// direction as workMode (small=0x05 01 .. large=0x05 03) - taken from the
|
|
18
|
+
// app's ModeActionParserH8122, so the two paths agree here.
|
|
19
|
+
this.sizeCodes = {
|
|
20
|
+
1: 'MwUBAAAAAAAAAAAAAAAAAAAAADc=', // small -> 33 05 01
|
|
21
|
+
2: 'MwUCAAAAAAAAAAAAAAAAAAAAADQ=', // medium -> 33 05 02
|
|
22
|
+
3: 'MwUDAAAAAAAAAAAAAAAAAAAAADU=', // large -> 33 05 03
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.sizeLabels = {
|
|
26
|
+
1: 'small',
|
|
27
|
+
2: 'medium',
|
|
28
|
+
3: 'large',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Remove old switch service if migrating
|
|
32
|
+
if (this.accessory.getService(this.hapServ.Switch)) {
|
|
33
|
+
this.accessory.removeService(this.accessory.getService(this.hapServ.Switch))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Add the fan service for ice size control
|
|
37
|
+
this.service = this.accessory.getService(this.hapServ.Fanv2)
|
|
38
|
+
|| this.accessory.addService(this.hapServ.Fanv2)
|
|
39
|
+
|
|
40
|
+
// Add the set handler to the on/off characteristic
|
|
41
|
+
this.service.getCharacteristic(this.hapChar.Active).onSet(async (value) => {
|
|
42
|
+
await this.internalStateUpdate(value)
|
|
43
|
+
})
|
|
44
|
+
this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
|
|
45
|
+
|
|
46
|
+
// Add the set handler to the rotation speed characteristic (ice size)
|
|
47
|
+
this.service
|
|
48
|
+
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
49
|
+
.setProps({
|
|
50
|
+
maxValue: 3,
|
|
51
|
+
minStep: 1,
|
|
52
|
+
minValue: 0,
|
|
53
|
+
unit: 'unitless',
|
|
54
|
+
})
|
|
55
|
+
.onSet(async value => this.internalSizeUpdate(value))
|
|
56
|
+
this.cacheSize = this.service.getCharacteristic(this.hapChar.RotationSpeed).value || 2
|
|
57
|
+
|
|
58
|
+
// Keep the characteristic in step with the cache. Without this an accessory
|
|
59
|
+
// that has never had a size set reports Active=1 with RotationSpeed=0, which
|
|
60
|
+
// is contradictory and which the Home app renders as "0".
|
|
61
|
+
if (this.service.getCharacteristic(this.hapChar.RotationSpeed).value !== this.cacheSize) {
|
|
62
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Output the customised options to the log
|
|
66
|
+
const opts = JSON.stringify({})
|
|
67
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async internalStateUpdate(value) {
|
|
71
|
+
try {
|
|
72
|
+
const newState = value ? 'on' : 'off'
|
|
73
|
+
|
|
74
|
+
// Don't continue if the new value is the same as before
|
|
75
|
+
if (newState === this.cacheState) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (value) {
|
|
80
|
+
// Turn on with current cached ice size
|
|
81
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
82
|
+
cmd: 'ptReal',
|
|
83
|
+
value: this.sizeCodes[this.cacheSize],
|
|
84
|
+
openApi: this.accessory.context.openApiCapabilities?.workMode
|
|
85
|
+
? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: { workMode: this.cacheSize, modeValue: 0 } }
|
|
86
|
+
: undefined,
|
|
87
|
+
})
|
|
88
|
+
} else {
|
|
89
|
+
// Turn off. The OpenAPI workMode=0 path is authoritative; the BLE frame
|
|
90
|
+
// mirrors the H8120 ice-maker-off command (33 19 00) as a best effort
|
|
91
|
+
// and is pending confirmation from an H8122 owner.
|
|
92
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
93
|
+
cmd: 'ptReal',
|
|
94
|
+
value: 'MxkAAAAAAAAAAAAAAAAAAAAAACo=',
|
|
95
|
+
openApi: this.accessory.context.openApiCapabilities?.workMode
|
|
96
|
+
? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: { workMode: 0, modeValue: 0 } }
|
|
97
|
+
: undefined,
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Cache the new state and log if appropriate
|
|
102
|
+
if (this.cacheState !== newState) {
|
|
103
|
+
this.cacheState = newState
|
|
104
|
+
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
105
|
+
}
|
|
106
|
+
} catch (err) {
|
|
107
|
+
// Catch any errors during the process
|
|
108
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
109
|
+
|
|
110
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
111
|
+
setTimeout(() => {
|
|
112
|
+
this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
|
|
113
|
+
}, 2000)
|
|
114
|
+
throw new this.hapErr(-70402)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async internalSizeUpdate(value) {
|
|
119
|
+
try {
|
|
120
|
+
// 0 is not a valid ice size. Put the slider back, otherwise HomeKit keeps 0
|
|
121
|
+
// while the device carries on at cacheSize - and that 0 is what gets written
|
|
122
|
+
// to the accessory cache, so it returns on the next restart. Deferred for the
|
|
123
|
+
// same reason as the error reverts below: an immediate update would be
|
|
124
|
+
// overwritten by the value HomeKit is currently writing.
|
|
125
|
+
if (value === 0) {
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
128
|
+
}, 1000)
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Don't continue if the new value is the same as before
|
|
133
|
+
if (value === this.cacheSize) {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await this.platform.sendDeviceUpdate(this.accessory, {
|
|
138
|
+
cmd: 'ptReal',
|
|
139
|
+
value: this.sizeCodes[value],
|
|
140
|
+
openApi: this.accessory.context.openApiCapabilities?.workMode
|
|
141
|
+
? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: { workMode: value, modeValue: 0 } }
|
|
142
|
+
: undefined,
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// Cache the new state and log if appropriate
|
|
146
|
+
this.cacheSize = value
|
|
147
|
+
this.cacheState = 'on'
|
|
148
|
+
this.service.updateCharacteristic(this.hapChar.Active, 1)
|
|
149
|
+
this.accessory.log(`${platformLang.curSpeed} [${this.sizeLabels[value]}]`)
|
|
150
|
+
} catch (err) {
|
|
151
|
+
// Catch any errors during the process
|
|
152
|
+
this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
|
|
153
|
+
|
|
154
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
157
|
+
}, 2000)
|
|
158
|
+
throw new this.hapErr(-70402)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
externalUpdate(params) {
|
|
163
|
+
// Handle OpenAPI workMode
|
|
164
|
+
if (params.workMode) {
|
|
165
|
+
const mode = params.workMode.workMode
|
|
166
|
+
if (mode > 0 && mode <= 3) {
|
|
167
|
+
if (this.cacheState !== 'on') {
|
|
168
|
+
this.cacheState = 'on'
|
|
169
|
+
this.service.updateCharacteristic(this.hapChar.Active, 1)
|
|
170
|
+
this.accessory.log(`${platformLang.curState} [on]`)
|
|
171
|
+
}
|
|
172
|
+
if (this.cacheSize !== mode) {
|
|
173
|
+
this.cacheSize = mode
|
|
174
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSize)
|
|
175
|
+
this.accessory.log(`${platformLang.curSpeed} [${this.sizeLabels[this.cacheSize]}]`)
|
|
176
|
+
}
|
|
177
|
+
} else if (mode === 0) {
|
|
178
|
+
if (this.cacheState !== 'off') {
|
|
179
|
+
this.cacheState = 'off'
|
|
180
|
+
this.service.updateCharacteristic(this.hapChar.Active, 0)
|
|
181
|
+
this.accessory.log(`${platformLang.curState} [off]`)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check for some other scene/mode change
|
|
187
|
+
(params.commands || []).forEach((command) => {
|
|
188
|
+
const hexString = base64ToHex(command)
|
|
189
|
+
const hexParts = hexToTwoItems(hexString)
|
|
190
|
+
|
|
191
|
+
// Return now if not a device query update code
|
|
192
|
+
if (getTwoItemPosition(hexParts, 1) !== 'aa') {
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const deviceFunction = `${getTwoItemPosition(hexParts, 1)}${getTwoItemPosition(hexParts, 2)}`
|
|
197
|
+
|
|
198
|
+
switch (deviceFunction) {
|
|
199
|
+
case 'aa19': {
|
|
200
|
+
// On/Off
|
|
201
|
+
const newState = getTwoItemPosition(hexParts, 3) === '01' ? 'on' : 'off'
|
|
202
|
+
if (this.cacheState !== newState) {
|
|
203
|
+
this.cacheState = newState
|
|
204
|
+
this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
|
|
205
|
+
this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
|
|
206
|
+
}
|
|
207
|
+
break
|
|
208
|
+
}
|
|
209
|
+
case 'aa05': {
|
|
210
|
+
// Ice size report. Left undecoded for now: the OpenAPI workMode report
|
|
211
|
+
// above is the authoritative source of the current size, so decoding
|
|
212
|
+
// the BLE echo would only risk a mismatch. This case exists to stop a
|
|
213
|
+
// routine status report being logged as an unrecognised scene code.
|
|
214
|
+
break
|
|
215
|
+
}
|
|
216
|
+
default:
|
|
217
|
+
this.accessory.logWarn(`${platformLang.newScene}: [${command}] [${hexString}]`)
|
|
218
|
+
break
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
}
|
package/lib/device/index.js
CHANGED
|
@@ -30,6 +30,8 @@ import deviceHumidifierH7160 from './humidifier-H7160.js'
|
|
|
30
30
|
import deviceIceMakerH717D from './ice-maker-H717D.js'
|
|
31
31
|
import deviceIceMakerH7172 from './ice-maker-H7172.js'
|
|
32
32
|
import deviceIceMakerH8120 from './ice-maker-H8120.js'
|
|
33
|
+
import deviceIceMakerH8121 from './ice-maker-H8121.js'
|
|
34
|
+
import deviceIceMakerH8122 from './ice-maker-H8122.js'
|
|
33
35
|
import deviceKettle from './kettle.js'
|
|
34
36
|
import deviceLightSwitch from './light-switch.js'
|
|
35
37
|
import deviceLight from './light.js'
|
|
@@ -98,6 +100,8 @@ export default {
|
|
|
98
100
|
deviceIceMakerH7172,
|
|
99
101
|
deviceIceMakerH717D,
|
|
100
102
|
deviceIceMakerH8120,
|
|
103
|
+
deviceIceMakerH8121,
|
|
104
|
+
deviceIceMakerH8122,
|
|
101
105
|
deviceKettle,
|
|
102
106
|
deviceLight,
|
|
103
107
|
deviceLightSwitch,
|
package/lib/utils/constants.js
CHANGED
|
@@ -555,7 +555,7 @@ export default {
|
|
|
555
555
|
humidifier: ['H7140', 'H7141', 'H7142', 'H7143', 'H7145', 'H7147', 'H7148', 'H7149', 'H714E', 'H7160'],
|
|
556
556
|
purifier: ['H7120', 'H7121', 'H7122', 'H7123', 'H7124', 'H7126', 'H7127', 'H7128', 'H7129', 'H712C'],
|
|
557
557
|
diffuser: ['H7161', 'H7162'],
|
|
558
|
-
iceMaker: ['H7172', 'H717D', 'H8120'],
|
|
558
|
+
iceMaker: ['H7172', 'H717D', 'H8120', 'H8121', 'H8122'],
|
|
559
559
|
sensorButton: ['H5122'],
|
|
560
560
|
sensorContact: ['H5123'],
|
|
561
561
|
sensorPresence: ['H5127'],
|
package/package.json
CHANGED