@homebridge-plugins/homebridge-govee 11.32.2-beta.6 → 11.32.2-beta.7

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
@@ -19,6 +19,7 @@ All notable changes to `@homebridge-plugins/homebridge-govee` will be documented
19
19
  - feat: add 24 more light models, three thermo-hygrometers and a leak detector
20
20
  - feat: add the H7103 and H7112 fans and the H5085 plug, with unknown controls saying so instead of guessing
21
21
  - feat: give every appliance type a starting handler, so a new model works for on and off and asks for the rest
22
+ - refactor: point models that behave identically at one shared handler instead of copies of the same file
22
23
 
23
24
  ## v11.32.1 (2026-08-02)
24
25
 
@@ -0,0 +1,66 @@
1
+ # How this plugin is put together, and how to extend it
2
+
3
+ This is the working reference for adding models and functionality. It exists so
4
+ that the next change follows the same shape as the last, whoever makes it.
5
+
6
+ ## The three ways a model gets support
7
+
8
+ **1. It shares behaviour with a model already handled — alias it.**
9
+ Nothing new is written. `lib/device/index.js` maps the new model's key to the
10
+ existing handler:
11
+
12
+ ```text
13
+ deviceHumidifierH7147: deviceHumidifierH7143,
14
+ ```
15
+
16
+ This is the common case. Several Govee models are the same device with a
17
+ different name, and a handler copied per model is a handler that has to be fixed
18
+ per model.
19
+
20
+ **2. It is a new model of a known type, but the details are unknown — let it
21
+ say so.** Add it to the model list in `lib/utils/constants.js` and nothing else.
22
+ The dispatch falls back to `appliance-basic.js`, which offers on and off (that
23
+ command is identical across every Govee appliance) and makes every other control
24
+ report what it needs and then fail, so the owner sees it in the Home app, checks
25
+ the log, and finds a line to paste into an issue.
26
+
27
+ **3. It genuinely behaves differently — write a handler.**
28
+ Only when the controls or their commands really differ.
29
+
30
+ ## Where model knowledge lives
31
+
32
+ Per-model differences belong in data, not in a copied file:
33
+
34
+ | File | Holds |
35
+ |---|---|
36
+ | `lib/utils/constants.js` | which models are which type |
37
+ | `lib/utils/device-capabilities.js` | protocol quirks — colour command, brightness scale, and firmware-dependent behaviour |
38
+ | `lib/utils/appliance-capabilities.js` | which controls a model actually has |
39
+
40
+ If a change means copying a file and editing two lines, it belongs in one of
41
+ these instead.
42
+
43
+ ## Reporting anything unknown
44
+
45
+ Never log an unrecognised payload by hand. Use `lib/utils/report-unknown.js`:
46
+
47
+ - `logUnknownData` — something arrived that is not understood
48
+ - `reportUnsupportedControl` — the user asked for something not worked out yet
49
+
50
+ Both produce one line carrying the model, firmware, hardware, connection and
51
+ payload, so a single copy-paste is enough to act on. Both warn once and then
52
+ drop to debug, so a device repeating itself does not flood the log.
53
+
54
+ ## Things worth knowing before changing appliance code
55
+
56
+ - **The power command is universal.** `33 01 01` on, `33 01 00` off, on every
57
+ appliance. Safe to send for an untested model.
58
+ - **Frames are 20 bytes with an XOR checksum** — byte 20 is the XOR of bytes
59
+ 1–19. All 407 shipped appliance commands have been verified against this.
60
+ - **A control that silently does nothing is worse than one that fails.** Failing
61
+ shows up in the Home app and sends the owner to the log.
62
+ - **Do not remove a control a model has offered for a long time** on evidence
63
+ alone. Someone may be using it. Confirm with an owner first.
64
+ - **HomeKit tiles have requirements.** A humidifier must report a current
65
+ humidity, which many Govee humidifiers never send — hence the plain fan tile
66
+ by default, and the richer one only when the device says it has the sensor.
@@ -10,33 +10,23 @@ import deviceDehumidifierH7150 from './dehumidifier-H7150.js'
10
10
  import deviceDehumidifierH7151 from './dehumidifier-H7151.js'
11
11
  import deviceDehumidifierH7152 from './dehumidifier-H7152.js'
12
12
  import deviceDiffuserH7161 from './diffuser-H7161.js'
13
- import deviceDiffuserH7162 from './diffuser-H7162.js'
14
13
  import deviceFanH1310 from './fan-H1310.js'
15
14
  import deviceFanH1370 from './fan-H1370.js'
16
15
  import deviceFanH7100 from './fan-H7100.js'
17
16
  import deviceFanH7101 from './fan-H7101.js'
18
- import deviceFanH7102 from './fan-H7102.js'
19
17
  import deviceFanH7105 from './fan-H7105.js'
20
- import deviceFanH7106 from './fan-H7106.js'
21
18
  import deviceFanH7107 from './fan-H7107.js'
22
19
  import deviceFanH7111 from './fan-H7111.js'
23
20
  import deviceHeater1A from './heater1a.js'
24
21
  import deviceHeater1B from './heater1b.js'
25
22
  import deviceHeater2 from './heater2.js'
26
23
  import deviceHeaterSingle from './heater-single.js'
27
- import deviceHumidifierH714E from './humidifier-H714E.js'
28
24
  import deviceHumidifierH7140 from './humidifier-H7140.js'
29
25
  import deviceHumidifierH7141 from './humidifier-H7141.js'
30
26
  import deviceHumidifierH7142 from './humidifier-H7142.js'
31
27
  import deviceHumidifierH7143 from './humidifier-H7143.js'
32
- import deviceHumidifierH7145 from './humidifier-H7145.js'
33
- import deviceHumidifierH7147 from './humidifier-H7147.js'
34
- import deviceHumidifierH7148 from './humidifier-H7148.js'
35
- import deviceHumidifierH7149 from './humidifier-H7149.js'
36
28
  import deviceHumidifierH7160 from './humidifier-H7160.js'
37
- import deviceIceMakerH717D from './ice-maker-H717D.js'
38
29
  import deviceIceMakerH7172 from './ice-maker-H7172.js'
39
- import deviceIceMakerH8120 from './ice-maker-H8120.js'
40
30
  import deviceIceMakerH8121 from './ice-maker-H8121.js'
41
31
  import deviceIceMakerH8122 from './ice-maker-H8122.js'
42
32
  import deviceKettle from './kettle.js'
@@ -45,16 +35,12 @@ import deviceLight from './light.js'
45
35
  import deviceOutletDouble from './outlet-double.js'
46
36
  import deviceOutletSingle from './outlet-single.js'
47
37
  import deviceOutletTriple from './outlet-triple.js'
48
- import devicePurifierH712C from './purifier-H712C.js'
49
38
  import devicePurifierH7120 from './purifier-H7120.js'
50
39
  import devicePurifierH7121 from './purifier-H7121.js'
51
40
  import devicePurifierH7122 from './purifier-H7122.js'
52
41
  import devicePurifierH7123 from './purifier-H7123.js'
53
42
  import devicePurifierH7124 from './purifier-H7124.js'
54
43
  import devicePurifierH7126 from './purifier-H7126.js'
55
- import devicePurifierH7127 from './purifier-H7127.js'
56
- import devicePurifierH7128 from './purifier-H7128.js'
57
- import devicePurifierH7129 from './purifier-H7129.js'
58
44
  import devicePurifierSingle from './purifier-single.js'
59
45
  import deviceSensorButton from './sensor-button.js'
60
46
  import deviceSensorCO2 from './sensor-co2.js'
@@ -79,17 +65,17 @@ export default {
79
65
  deviceDehumidifierH7151,
80
66
  deviceDehumidifierH7152,
81
67
  deviceDiffuserH7161,
82
- deviceDiffuserH7162,
68
+ deviceDiffuserH7162: deviceDiffuserH7161, // identical to the H7161
83
69
  deviceDehumidifierBasic,
84
70
  deviceFanBasic,
85
71
  deviceFanH1310,
86
72
  deviceFanH1370,
87
73
  deviceFanR1310: deviceFanH1310, // R1310 is the newer naming of the H1310
88
74
  deviceFanH7100,
75
+ deviceFanH7102: deviceFanH7100, // identical to the H7100
76
+ deviceFanH7106: deviceFanH7100, // identical to the H7100
89
77
  deviceFanH7101,
90
- deviceFanH7102,
91
78
  deviceFanH7105,
92
- deviceFanH7106,
93
79
  deviceFanH7107,
94
80
  deviceFanH7111,
95
81
  deviceHeaterSingle,
@@ -97,20 +83,20 @@ export default {
97
83
  deviceHeater1B,
98
84
  deviceHeater2,
99
85
  deviceHumidifierBasic,
100
- deviceHumidifierH714E,
101
86
  deviceHumidifierH7140,
102
87
  deviceHumidifierH7141,
103
88
  deviceHumidifierH7142,
104
89
  deviceHumidifierH7143,
105
- deviceHumidifierH7145,
106
- deviceHumidifierH7147,
107
- deviceHumidifierH7148,
108
- deviceHumidifierH7149,
90
+ deviceHumidifierH7145: deviceHumidifierH7143, // identical to the H7143
91
+ deviceHumidifierH7147: deviceHumidifierH7143, // identical to the H7143
92
+ deviceHumidifierH7148: deviceHumidifierH7143, // identical to the H7143
93
+ deviceHumidifierH7149: deviceHumidifierH7143, // identical to the H7143
94
+ deviceHumidifierH714E: deviceHumidifierH7143, // identical to the H7143
109
95
  deviceHumidifierH7160,
110
96
  deviceIceMakerBasic,
111
97
  deviceIceMakerH7172,
112
- deviceIceMakerH717D,
113
- deviceIceMakerH8120,
98
+ deviceIceMakerH717D: deviceIceMakerH7172, // identical to the H7172
99
+ deviceIceMakerH8120: deviceIceMakerH7172, // identical to the H7172
114
100
  deviceIceMakerH8121,
115
101
  deviceIceMakerH8122,
116
102
  deviceKettle,
@@ -120,16 +106,16 @@ export default {
120
106
  deviceOutletSingle,
121
107
  deviceOutletTriple,
122
108
  devicePurifierBasic,
123
- devicePurifierH712C,
124
109
  devicePurifierH7120,
125
110
  devicePurifierH7121,
126
111
  devicePurifierH7122,
127
112
  devicePurifierH7123,
128
113
  devicePurifierH7124,
129
114
  devicePurifierH7126,
130
- devicePurifierH7127,
131
- devicePurifierH7128,
132
- devicePurifierH7129,
115
+ devicePurifierH7127: devicePurifierH7126, // identical to the H7126
116
+ devicePurifierH7128: devicePurifierH7126, // identical to the H7126
117
+ devicePurifierH7129: devicePurifierH7126, // identical to the H7126
118
+ devicePurifierH712C: devicePurifierH7126, // identical to the H7126
133
119
  devicePurifierSingle,
134
120
  deviceSensorButton,
135
121
  deviceSensorCO2,
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.6",
5
+ "version": "11.32.2-beta.7",
6
6
  "description": "Homebridge plugin to integrate Govee devices into HomeKit.",
7
7
  "author": {
8
8
  "name": "bwp91",
@@ -1,129 +0,0 @@
1
- import {
2
- base64ToHex,
3
- getTwoItemPosition,
4
- hexToTwoItems,
5
- parseError,
6
- } from '../utils/functions.js'
7
- import platformLang from '../utils/lang-en.js'
8
- import { logUnknownData } from '../utils/report-unknown.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
-
18
- // Set up variables from the accessory
19
- this.accessory = accessory
20
-
21
- // Remove fan service if exists
22
- if (this.accessory.getService(this.hapServ.Fan)) {
23
- this.accessory.removeService(this.accessory.getService(this.hapServ.Fan))
24
- }
25
-
26
- // Add the purifier service if it doesn't already exist
27
- this.service = this.accessory.getService(this.hapServ.AirPurifier)
28
- || this.accessory.addService(this.hapServ.AirPurifier)
29
-
30
- // Add the set handler to the switch on/off characteristic
31
- this.service.getCharacteristic(this.hapChar.Active).onSet(async (value) => {
32
- await this.internalStateUpdate(value)
33
- })
34
- this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value === 1 ? 'on' : 'off'
35
-
36
- // Add options to the purifier target state characteristic
37
- this.service
38
- .getCharacteristic(this.hapChar.TargetAirPurifierState)
39
- .updateValue(1)
40
- .setProps({
41
- minValue: 1,
42
- maxValue: 1,
43
- validValues: [1],
44
- })
45
-
46
- // Output the customised options to the log
47
- const opts = JSON.stringify({})
48
- platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
49
- }
50
-
51
- async internalStateUpdate(value) {
52
- try {
53
- const newValue = value === 1 ? 'on' : 'off'
54
-
55
- // Don't continue if the new value is the same as before
56
- if (this.cacheState === newValue) {
57
- return
58
- }
59
-
60
- // Send the request to the platform sender function
61
- await this.platform.sendDeviceUpdate(this.accessory, {
62
- cmd: 'stateHumi',
63
- value,
64
- })
65
-
66
- // Cache the new state and log if appropriate
67
- if (this.cacheState !== newValue) {
68
- this.cacheState = newValue
69
- this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
70
- }
71
-
72
- this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, value === 1 ? 2 : 0)
73
- } catch (err) {
74
- // Catch any errors during the process
75
- this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
76
-
77
- // Throw a 'no response' error and set a timeout to revert this after 2 seconds
78
- setTimeout(() => {
79
- this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
80
- }, 2000)
81
- throw new this.hapErr(-70402)
82
- }
83
- }
84
-
85
- externalUpdate(params) {
86
- // Check for an ON/OFF change
87
- if (params.state && params.state !== this.cacheState) {
88
- this.cacheState = params.state
89
- this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
90
- this.service.updateCharacteristic(
91
- this.hapChar.CurrentAirPurifierState,
92
- this.cacheState === 'on' ? 2 : 0,
93
- )
94
-
95
- // Log the change
96
- this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
97
- }
98
-
99
- // Handle OpenAPI workMode
100
- if (params.workMode) {
101
- const mode = params.workMode.modeValue
102
- this.accessory.log(`${platformLang.curMode} [${mode}]`)
103
- }
104
-
105
- // Check for some other scene/mode change
106
- (params.commands || []).forEach((command) => {
107
- const hexString = base64ToHex(command)
108
- const hexParts = hexToTwoItems(hexString)
109
-
110
- // Return now if not a device query update code
111
- if (getTwoItemPosition(hexParts, 1) !== 'aa') {
112
- return
113
- }
114
-
115
- const deviceFunction = `${getTwoItemPosition(hexParts, 1)}${getTwoItemPosition(hexParts, 2)}`
116
-
117
- switch (deviceFunction) {
118
- default:
119
- logUnknownData(this.accessory, {
120
- kind: 'scene',
121
- source: params.source,
122
- raw: command,
123
- hex: hexString,
124
- })
125
- break
126
- }
127
- })
128
- }
129
- }
@@ -1,290 +0,0 @@
1
- import {
2
- base64ToHex,
3
- fanSpeedToHkPercent,
4
- getTwoItemPosition,
5
- hexToTwoItems,
6
- hkPercentToFanSpeed,
7
- parseError,
8
- } from '../utils/functions.js'
9
- import platformLang from '../utils/lang-en.js'
10
- import { logUnknownData } from '../utils/report-unknown.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
- // Codes etc
24
- this.speedCodes = {
25
- 1: 'MwUBAQAAAAAAAAAAAAAAAAAAADY=',
26
- 2: 'MwUBAgAAAAAAAAAAAAAAAAAAADU=',
27
- 3: 'MwUBAwAAAAAAAAAAAAAAAAAAADQ=',
28
- 4: 'MwUBBAAAAAAAAAAAAAAAAAAAADM=',
29
- 5: 'MwUBBQAAAAAAAAAAAAAAAAAAADI=',
30
- 6: 'MwUBBgAAAAAAAAAAAAAAAAAAADE=',
31
- 7: 'MwUBBwAAAAAAAAAAAAAAAAAAADA=',
32
- 8: 'MwUBCAAAAAAAAAAAAAAAAAAAAD8=',
33
- 9: 'MwUAAwAAAAAAAAAAAAAAAAAAADU=', // auto mode
34
- }
35
- this.autoSpeed = 9
36
- // Real speed steps, excluding auto - HomeKit percentages map onto these
37
- this.speedSteps = 8
38
-
39
- // Remove any old original Fan services
40
- if (this.accessory.getService(this.hapServ.Fan)) {
41
- this.accessory.removeService(this.accessory.getService(this.hapServ.Fan))
42
- }
43
-
44
- // Rebuild the fan service if it was cached under an older speed scale, as
45
- // HomeKit keeps the old props until the service itself is replaced
46
- const existingService = this.accessory.getService(this.hapServ.Fanv2)
47
- if (existingService) {
48
- const { props } = existingService.getCharacteristic(this.hapChar.RotationSpeed)
49
- if (props.unit === 'percentage' || props.maxValue !== 100) {
50
- this.accessory.removeService(existingService)
51
- }
52
- }
53
-
54
- // Add the fan service for the fan if it doesn't already exist
55
- this.service = this.accessory.getService(this.hapServ.Fanv2) || this.accessory.addService(this.hapServ.Fanv2)
56
-
57
- // Add the set handler to the fan on/off characteristic
58
- this.service
59
- .getCharacteristic(this.hapChar.Active)
60
- .onSet(async value => this.internalStateUpdate(value))
61
- this.cacheState = this.service.getCharacteristic(this.hapChar.Active).value ? 'on' : 'off'
62
-
63
- // Add the set handler to the fan rotation speed characteristic
64
- this.service
65
- .getCharacteristic(this.hapChar.RotationSpeed)
66
- .setProps({
67
- maxValue: 100,
68
- minStep: 1,
69
- minValue: 0,
70
- unit: 'unitless',
71
- })
72
- .onSet(async value => this.internalSpeedUpdate(value))
73
- this.cacheSpeed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
74
-
75
- // Add the set handler to the fan swing mode
76
- this.service
77
- .getCharacteristic(this.hapChar.SwingMode)
78
- .onSet(async value => this.internalSwingUpdate(value))
79
- this.cacheSwing = this.service.getCharacteristic(this.hapChar.SwingMode).value === 1 ? 'on' : 'off'
80
-
81
- // Output the customised options to the log
82
- const opts = JSON.stringify({})
83
- platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
84
- }
85
-
86
- async internalStateUpdate(value) {
87
- try {
88
- const newValue = value ? 'on' : 'off'
89
-
90
- // Don't continue if the new value is the same as before
91
- if (this.cacheState === newValue) {
92
- return
93
- }
94
-
95
- // Send the request to the platform sender function
96
- await this.platform.sendDeviceUpdate(this.accessory, {
97
- cmd: 'ptReal',
98
- value: value ? 'MwEBAAAAAAAAAAAAAAAAAAAAADM=' : 'MwEAAAAAAAAAAAAAAAAAAAAAADI=',
99
- })
100
-
101
- // Cache the new state and log if appropriate
102
- if (this.cacheState !== newValue) {
103
- this.cacheState = newValue
104
- this.accessory.log(`${platformLang.curState} [${newValue}]`)
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 internalSpeedUpdate(value) {
119
- try {
120
- // Don't continue if the value is 0
121
- if (value === 0) {
122
- return
123
- }
124
-
125
- // HomeKit sends a percentage - map it onto this model's real speed steps
126
- value = hkPercentToFanSpeed(value, this.speedSteps)
127
-
128
- // Don't continue if the new value is the same as before
129
- if (this.cacheSpeed === value) {
130
- return
131
- }
132
-
133
- const codeToSend = this.speedCodes[value]
134
-
135
- const isAuto = value === this.autoSpeed
136
- await this.platform.sendDeviceUpdate(this.accessory, {
137
- cmd: 'ptReal',
138
- value: codeToSend,
139
- openApi: this.accessory.context.openApiCapabilities?.workMode
140
- ? { instance: 'workMode', capabilityType: 'devices.capabilities.work_mode', value: isAuto ? { workMode: 3 } : { workMode: 1, modeValue: value } }
141
- : undefined,
142
- })
143
-
144
- // Cache the new state and log if appropriate
145
- if (this.cacheSpeed !== value) {
146
- this.cacheSpeed = value
147
- this.accessory.log(`${platformLang.curSpeed} [${value}]`)
148
- }
149
- } catch (err) {
150
- // Catch any errors during the process
151
- this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
152
-
153
- // Throw a 'no response' error and set a timeout to revert this after 2 seconds
154
- setTimeout(() => {
155
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, fanSpeedToHkPercent(this.cacheSpeed, this.speedSteps, this.autoSpeed))
156
- }, 2000)
157
- throw new this.hapErr(-70402)
158
- }
159
- }
160
-
161
- async internalSwingUpdate(value) {
162
- try {
163
- const newValue = value ? 'on' : 'off'
164
- // Don't continue if the new value is the same as before
165
- if (this.cacheSwing === value) {
166
- return
167
- }
168
-
169
- await this.platform.sendDeviceUpdate(this.accessory, {
170
- cmd: 'ptReal',
171
- value: value ? 'Mx8BAQAAAAAAAAAAAAAAAAAAACw=' : 'Mx8BAAAAAAAAAAAAAAAAAAAAAC0=',
172
- openApi: this.accessory.context.openApiCapabilities?.oscillationToggle
173
- ? { instance: 'oscillationToggle', capabilityType: 'devices.capabilities.toggle', value: value ? 1 : 0 }
174
- : undefined,
175
- })
176
-
177
- // Cache the new state and log if appropriate
178
- if (this.cacheSwing !== newValue) {
179
- this.cacheSwing = newValue
180
- this.accessory.log(`${platformLang.curSwing} [${newValue}]`)
181
- }
182
- } catch (err) {
183
- // Catch any errors during the process
184
- this.accessory.logWarn(`${platformLang.devNotUpdated} ${parseError(err)}`)
185
-
186
- // Throw a 'no response' error and set a timeout to revert this after 2 seconds
187
- setTimeout(() => {
188
- this.service.updateCharacteristic(this.hapChar.SwingMode, this.cacheSwing === 'on' ? 1 : 0)
189
- }, 2000)
190
- throw new this.hapErr(-70402)
191
- }
192
- }
193
-
194
- externalUpdate(params) {
195
- // Update the active characteristic
196
- if (params.state && params.state !== this.cacheState) {
197
- this.cacheState = params.state
198
- this.service.updateCharacteristic(this.hapChar.Active, this.cacheState === 'on' ? 1 : 0)
199
- this.accessory.log(`${platformLang.curState} [${this.cacheState}]`)
200
- }
201
-
202
- // Handle OpenAPI workMode (speed or auto)
203
- if (params.workMode) {
204
- let newSpeed
205
- if (params.workMode.workMode === 3) {
206
- newSpeed = this.autoSpeed
207
- } else {
208
- newSpeed = params.workMode.modeValue
209
- }
210
- if (Number.isFinite(newSpeed) && newSpeed >= 1 && newSpeed <= this.autoSpeed && this.cacheSpeed !== newSpeed) {
211
- this.cacheSpeed = newSpeed
212
- const hkSpeed = fanSpeedToHkPercent(newSpeed, this.speedSteps, this.autoSpeed)
213
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, hkSpeed)
214
- this.accessory.log(`${platformLang.curSpeed} [${newSpeed} -> ${hkSpeed}%]`)
215
- }
216
- }
217
-
218
- // Handle OpenAPI toggles
219
- if (params.toggles?.oscillationToggle !== undefined) {
220
- const newSwing = params.toggles.oscillationToggle ? 'on' : 'off'
221
- if (this.cacheSwing !== newSwing) {
222
- this.cacheSwing = newSwing
223
- this.service.updateCharacteristic(this.hapChar.SwingMode, this.cacheSwing === 'on' ? 1 : 0)
224
- this.accessory.log(`${platformLang.curSwing} [${this.cacheSwing}]`)
225
- }
226
- }
227
-
228
- // Check for some other scene/mode change
229
- (params.commands || []).forEach((command) => {
230
- const hexString = base64ToHex(command)
231
- const hexParts = hexToTwoItems(hexString)
232
-
233
- // Return now if not a device query update code
234
- if (getTwoItemPosition(hexParts, 1) !== 'aa') {
235
- return
236
- }
237
-
238
- if (getTwoItemPosition(hexParts, 2) === '08') {
239
- // Sensor Attached?
240
- const dev = hexString.substring(4, hexString.length - 24)
241
- this.accessory.context.sensorAttached = dev !== '000000000000'
242
- return
243
- }
244
-
245
- const deviceFunction = `${getTwoItemPosition(hexParts, 2)}${getTwoItemPosition(hexParts, 3)}`
246
-
247
- switch (deviceFunction) {
248
- case '0501': {
249
- // Fan speed
250
- const newSpeed = Number.parseInt(getTwoItemPosition(hexParts, 4), 16)
251
- if (newSpeed >= 1 && newSpeed <= this.speedSteps && this.cacheSpeed !== newSpeed) {
252
- this.cacheSpeed = newSpeed
253
- const hkSpeed = fanSpeedToHkPercent(newSpeed, this.speedSteps, this.autoSpeed)
254
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, hkSpeed)
255
- this.accessory.log(`${platformLang.curSpeed} [${newSpeed} -> ${hkSpeed}%]`)
256
- }
257
- break
258
- }
259
- case '0500': {
260
- // Mode change (auto = 0x03)
261
- if (getTwoItemPosition(hexParts, 4) === '03' && this.cacheSpeed !== this.autoSpeed) {
262
- this.cacheSpeed = this.autoSpeed
263
- // Cache auto so we can tell it apart from "user asked for max speed"
264
- this.service.updateCharacteristic(this.hapChar.RotationSpeed, 100)
265
- this.accessory.log(`${platformLang.curSpeed} [auto]`)
266
- }
267
- break
268
- }
269
- case '1f01': {
270
- // Swing Mode
271
- const newSwing = getTwoItemPosition(hexParts, 4) === '01' ? 'on' : 'off'
272
- if (this.cacheSwing !== newSwing) {
273
- this.cacheSwing = newSwing
274
- this.service.updateCharacteristic(this.hapChar.SwingMode, this.cacheSwing === 'on' ? 1 : 0)
275
- this.accessory.log(`${platformLang.curSwing} [${this.cacheSwing}]`)
276
- }
277
- break
278
- }
279
- default:
280
- logUnknownData(this.accessory, {
281
- kind: 'scene',
282
- source: params.source,
283
- raw: command,
284
- hex: hexString,
285
- })
286
- break
287
- }
288
- })
289
- }
290
- }