@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 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
 
@@ -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
- // carrying over the extra bytes from the last report seen (#1334).
215
- // Mirroring a status code is not guaranteed on every model, so the
216
- // documented OpenAPI oscillation toggle is also sent when the device
217
- // advertises it
218
- const tailBytes = this.cacheSwingCode
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
+ }
@@ -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,
@@ -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
@@ -2,7 +2,7 @@
2
2
  "name": "@homebridge-plugins/homebridge-govee",
3
3
  "alias": "Govee",
4
4
  "type": "module",
5
- "version": "11.32.2-beta.1",
5
+ "version": "11.32.2-beta.3",
6
6
  "description": "Homebridge plugin to integrate Govee devices into HomeKit.",
7
7
  "author": {
8
8
  "name": "bwp91",