@homebridge-plugins/homebridge-govee 11.32.2-beta.12 → 11.32.2-beta.14

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
@@ -25,6 +25,11 @@ All notable changes to `@homebridge-plugins/homebridge-govee` will be documented
25
25
  - test: record what every supported model turns into, so a change can be shown to have altered nothing
26
26
  - feat: work out what an appliance can do from what the device itself reports, so a model nobody has written down still gets the right controls
27
27
  - feat: offer oscillation and the child lock on any appliance that reports having them, driven through govee's api rather than needing that model's command codes
28
+ - refactor: point three more models at a shared handler instead of a near-copy of the same file, keeping each one's behaviour exactly as it was
29
+ - fix: give the H7124 the air quality handling the H7123 already had, so a poor reading is named properly and stops being logged over and over (#1261)
30
+ - refactor: point the H7151 and H7111 at the handlers they were byte-for-byte copies of
31
+ - refactor: build the ice maker size codes from the model rather than writing both orderings out by hand
32
+ - refactor: build the switch and outlet tiles of a multi-socket device from one handler instead of two copies of it
28
33
 
29
34
  ## v11.32.1 (2026-08-02)
30
35
 
@@ -8,7 +8,7 @@ import platformLang from '../utils/lang-en.js'
8
8
  import { logUnknownData } from '../utils/report-unknown.js'
9
9
 
10
10
  /*
11
- H7150
11
+ H7150 and H7151
12
12
  {
13
13
  "mode": {
14
14
  "options": [
@@ -32,7 +32,7 @@ import { logUnknownData } from '../utils/report-unknown.js'
32
32
  }
33
33
  }
34
34
  */
35
- export default class {
35
+ export default class GoveeDehumidifierH7150 {
36
36
  constructor(platform, accessory) {
37
37
  // Set up variables from the platform
38
38
  this.hapChar = platform.api.hap.Characteristic
@@ -1,3 +1,4 @@
1
+ import { getDeviceCapabilities } from '../utils/device-capabilities.js'
1
2
  import {
2
3
  base64ToHex,
3
4
  fanSpeedToHkPercent,
@@ -9,7 +10,7 @@ import {
9
10
  import platformLang from '../utils/lang-en.js'
10
11
  import { logUnknownData } from '../utils/report-unknown.js'
11
12
 
12
- export default class {
13
+ export default class GoveeFanH7100 {
13
14
  constructor(platform, accessory) {
14
15
  // Set up variables from the platform
15
16
  this.hapChar = platform.api.hap.Characteristic
@@ -20,6 +21,10 @@ export default class {
20
21
  // Set up variables from the accessory
21
22
  this.accessory = accessory
22
23
 
24
+ // Which of the models on this handler have a confirmed meaning for the
25
+ // oscillation status message
26
+ this.readsSwingStatus = getDeviceCapabilities(accessory.context.gvModel).readsSwingStatus
27
+
23
28
  // Codes etc
24
29
  this.speedCodes = {
25
30
  1: 'MwUBAQAAAAAAAAAAAAAAAAAAADY=',
@@ -267,7 +272,18 @@ export default class {
267
272
  break
268
273
  }
269
274
  case '1f01': {
270
- // Swing Mode
275
+ // Not every model on this handler is known to mean oscillation by
276
+ // this message, so one that has not been confirmed falls through and
277
+ // reports it instead - which is how it gets confirmed
278
+ if (!this.readsSwingStatus) {
279
+ logUnknownData(this.accessory, {
280
+ kind: 'scene',
281
+ source: params.source,
282
+ raw: command,
283
+ hex: hexString,
284
+ })
285
+ break
286
+ }
271
287
  const newSwing = getTwoItemPosition(hexParts, 4) === '01' ? 'on' : 'off'
272
288
  if (this.cacheSwing !== newSwing) {
273
289
  this.cacheSwing = newSwing
@@ -10,7 +10,7 @@ import platformLang from '../utils/lang-en.js'
10
10
  import { logUnknownData } from '../utils/report-unknown.js'
11
11
 
12
12
  /*
13
- H7101
13
+ H7101 and H7111
14
14
  {
15
15
  "mode": {
16
16
  "options": [
@@ -53,6 +53,7 @@ import { logUnknownData } from '../utils/report-unknown.js'
53
53
 
54
54
  // NOTES
55
55
  H7101 -> Sleep, Nature, Turbo, Custom, Auto
56
+ H7111 -> Sleep, Nature, Custom, Auto (no turbo)
56
57
 
57
58
  sleep
58
59
  [31/10/2022, 20:52:08] [Govee] [Device1] [AWS] receiving update {"source":"AWS","result":1,"commands":["qgUFAAAAAAAAAAAAAAAAAAAAAKo=","qgUABQAAAAAAAAAAAAAAAAAAAKo="]}.
@@ -65,7 +66,7 @@ import { logUnknownData } from '../utils/report-unknown.js'
65
66
  [31/10/2022, 20:52:43] [Govee] [Device1] new scene code: [qgUABgAAAAAAAAAAAAAAAAAAAKk=] [aa050006000000000000000000000000000000a9].
66
67
  */
67
68
 
68
- export default class {
69
+ export default class GoveeFanH7101 {
69
70
  constructor(platform, accessory) {
70
71
  // Set up variables from the platform
71
72
  this.hapChar = platform.api.hap.Characteristic
@@ -318,7 +319,7 @@ export default class {
318
319
  this.cacheSpeed = this.autoSpeed
319
320
  // Cache auto so we can tell it apart from "user asked for max speed"
320
321
  this.service.updateCharacteristic(this.hapChar.RotationSpeed, 100)
321
- this.accessory.log(`${platformLang.curSpeed} [auto]`)
322
+ this.accessory.log(`${platformLang.curSpeed} [auto -> 100%]`)
322
323
  }
323
324
  break
324
325
  }
@@ -1,8 +1,9 @@
1
- import { base64ToHex, getTwoItemPosition, hexToTwoItems, parseError } from '../utils/functions.js'
1
+ import { getDeviceCapabilities } from '../utils/device-capabilities.js'
2
+ import { base64ToHex, generateCodeFromHexValues, getTwoItemPosition, hexToTwoItems, parseError } from '../utils/functions.js'
2
3
  import platformLang from '../utils/lang-en.js'
3
4
  import { logUnknownData } from '../utils/report-unknown.js'
4
5
 
5
- export default class {
6
+ export default class GoveeIceMaker {
6
7
  constructor(platform, accessory) {
7
8
  // Set up variables from the platform
8
9
  this.hapChar = platform.api.hap.Characteristic
@@ -14,10 +15,15 @@ export default class {
14
15
  this.accessory = accessory
15
16
 
16
17
  // Ice size codes: small=1, medium=2, large=3 (matching OpenAPI workMode values)
18
+ // Small, medium and large are all `33 05 <size>`. Which byte means which
19
+ // size runs the opposite way on the older models, so the order comes from
20
+ // the model rather than being written out twice
21
+ const ascending = getDeviceCapabilities(accessory.context.gvModel).iceSizeAscending
22
+ const sizeBytes = ascending ? [0x01, 0x02, 0x03] : [0x03, 0x02, 0x01]
17
23
  this.sizeCodes = {
18
- 1: 'MwUDAAAAAAAAAAAAAAAAAAAAADU=', // small
19
- 2: 'MwUCAAAAAAAAAAAAAAAAAAAAADQ=', // medium
20
- 3: 'MwUBAAAAAAAAAAAAAAAAAAAAADc=', // large
24
+ 1: generateCodeFromHexValues([0x33, 0x05, sizeBytes[0]]), // small
25
+ 2: generateCodeFromHexValues([0x33, 0x05, sizeBytes[1]]), // medium
26
+ 3: generateCodeFromHexValues([0x33, 0x05, sizeBytes[2]]), // large
21
27
  }
22
28
 
23
29
  this.sizeLabels = {
@@ -204,7 +210,7 @@ export default class {
204
210
  }
205
211
  case 'aa05': {
206
212
  // Ice size report. The payload encoding is deliberately NOT decoded here:
207
- // sizeCodes runs small=0x03..large=0x01 while the OpenAPI path sends
213
+ // the size byte order differs by model while the OpenAPI path sends
208
214
  // workMode un-inverted, and which is authoritative is unresolved (#1329).
209
215
  // Claiming a mapping here could silently swap sizes, so this case exists
210
216
  // only to stop a routine status report being logged as a new scene code.
@@ -7,16 +7,13 @@ import {
7
7
  } from './appliance-basic.js'
8
8
  import deviceCoolerSingle from './cooler-single.js'
9
9
  import deviceDehumidifierH7150 from './dehumidifier-H7150.js'
10
- import deviceDehumidifierH7151 from './dehumidifier-H7151.js'
11
10
  import deviceDehumidifierH7152 from './dehumidifier-H7152.js'
12
11
  import deviceDiffuserH7161 from './diffuser-H7161.js'
13
- import deviceFanH1310 from './fan-H1310.js'
14
12
  import deviceFanH1370 from './fan-H1370.js'
15
13
  import deviceFanH7100 from './fan-H7100.js'
16
14
  import deviceFanH7101 from './fan-H7101.js'
17
15
  import deviceFanH7105 from './fan-H7105.js'
18
16
  import deviceFanH7107 from './fan-H7107.js'
19
- import deviceFanH7111 from './fan-H7111.js'
20
17
  import deviceHeater1A from './heater1a.js'
21
18
  import deviceHeater1B from './heater1b.js'
22
19
  import deviceHeater2 from './heater2.js'
@@ -27,33 +24,26 @@ import deviceHumidifierH7142 from './humidifier-H7142.js'
27
24
  import deviceHumidifierH7143 from './humidifier-H7143.js'
28
25
  import deviceHumidifierH7160 from './humidifier-H7160.js'
29
26
  import deviceIceMakerH7172 from './ice-maker-H7172.js'
30
- import deviceIceMakerH8121 from './ice-maker-H8121.js'
31
- import deviceIceMakerH8122 from './ice-maker-H8122.js'
32
27
  import deviceKettle from './kettle.js'
33
28
  import deviceLightSwitch from './light-switch.js'
34
29
  import deviceLight from './light.js'
35
- import deviceOutletDouble from './outlet-double.js'
36
30
  import deviceOutletSingle from './outlet-single.js'
37
- import deviceOutletTriple from './outlet-triple.js'
38
31
  import devicePurifierH7120 from './purifier-H7120.js'
39
- import devicePurifierH7121 from './purifier-H7121.js'
40
32
  import devicePurifierH7122 from './purifier-H7122.js'
41
33
  import devicePurifierH7123 from './purifier-H7123.js'
42
- import devicePurifierH7124 from './purifier-H7124.js'
43
34
  import devicePurifierH7126 from './purifier-H7126.js'
44
35
  import devicePurifierSingle from './purifier-single.js'
45
- import deviceSensorButton from './sensor-button.js'
36
+ import deviceSensorBasic from './sensor-basic.js'
46
37
  import deviceSensorCO2 from './sensor-co2.js'
47
- import deviceSensorContact from './sensor-contact.js'
48
38
  import deviceSensorLeak from './sensor-leak.js'
49
39
  import deviceSensorMonitor from './sensor-monitor.js'
50
40
  import deviceSensorPresence from './sensor-presence.js'
51
41
  import deviceSensorThermo4 from './sensor-thermo4.js'
52
42
  import deviceSensorThermoSwitch from './sensor-thermo-switch.js'
53
43
  import deviceSensorThermo from './sensor-thermo.js'
54
- import deviceSwitchDouble from './switch-double.js'
44
+ import deviceSwitchDouble, { asOutlet as deviceOutletDouble } from './switch-double.js'
55
45
  import deviceSwitchSingle from './switch-single.js'
56
- import deviceSwitchTriple from './switch-triple.js'
46
+ import deviceSwitchTriple, { asOutlet as deviceOutletTriple } from './switch-triple.js'
57
47
  import deviceTapSingle from './tap-single.js'
58
48
  import deviceTemplate from './template.js'
59
49
  import deviceTVSingle from './tv-single.js'
@@ -62,22 +52,22 @@ import deviceValveSingle from './valve-single.js'
62
52
  export default {
63
53
  deviceCoolerSingle,
64
54
  deviceDehumidifierH7150,
65
- deviceDehumidifierH7151,
55
+ deviceDehumidifierH7151: deviceDehumidifierH7150, // identical to the H7150
66
56
  deviceDehumidifierH7152,
67
57
  deviceDiffuserH7161,
68
58
  deviceDiffuserH7162: deviceDiffuserH7161, // identical to the H7161
69
59
  deviceDehumidifierBasic,
70
60
  deviceFanBasic,
71
- deviceFanH1310,
61
+ deviceFanH1310: deviceFanH7100, // same fan, minus a confirmed swing status message
72
62
  deviceFanH1370,
73
- deviceFanR1310: deviceFanH1310, // R1310 is the newer naming of the H1310
63
+ deviceFanR1310: deviceFanH7100, // R1310 is the newer naming of the H1310
74
64
  deviceFanH7100,
75
65
  deviceFanH7102: deviceFanH7100, // identical to the H7100
76
66
  deviceFanH7106: deviceFanH7100, // identical to the H7100
77
67
  deviceFanH7101,
78
68
  deviceFanH7105,
79
69
  deviceFanH7107,
80
- deviceFanH7111,
70
+ deviceFanH7111: deviceFanH7101, // same fan without the turbo mode, which is not sent
81
71
  deviceHeaterSingle,
82
72
  deviceHeater1A,
83
73
  deviceHeater1B,
@@ -97,8 +87,8 @@ export default {
97
87
  deviceIceMakerH7172,
98
88
  deviceIceMakerH717D: deviceIceMakerH7172, // identical to the H7172
99
89
  deviceIceMakerH8120: deviceIceMakerH7172, // identical to the H7172
100
- deviceIceMakerH8121,
101
- deviceIceMakerH8122,
90
+ deviceIceMakerH8121: deviceIceMakerH7172, // same handler; its size byte order comes from the model
91
+ deviceIceMakerH8122: deviceIceMakerH7172, // identical to the H8121
102
92
  deviceKettle,
103
93
  deviceLight,
104
94
  deviceLightSwitch,
@@ -107,19 +97,20 @@ export default {
107
97
  deviceOutletTriple,
108
98
  devicePurifierBasic,
109
99
  devicePurifierH7120,
110
- devicePurifierH7121,
100
+ devicePurifierH7121: devicePurifierH7120, // identical apart from its sleep mode value, which is not sent
111
101
  devicePurifierH7122,
112
102
  devicePurifierH7123,
113
- devicePurifierH7124,
103
+ devicePurifierH7124: devicePurifierH7123, // same device; this also gives it the #1261 air quality fix
114
104
  devicePurifierH7126,
115
105
  devicePurifierH7127: devicePurifierH7126, // identical to the H7126
116
106
  devicePurifierH7128: devicePurifierH7126, // identical to the H7126
117
107
  devicePurifierH7129: devicePurifierH7126, // identical to the H7126
118
108
  devicePurifierH712C: devicePurifierH7126, // identical to the H7126
119
109
  devicePurifierSingle,
120
- deviceSensorButton,
110
+ deviceSensorBasic,
111
+ deviceSensorButton: deviceSensorBasic, // nothing decoded yet, so it reports instead
121
112
  deviceSensorCO2,
122
- deviceSensorContact,
113
+ deviceSensorContact: deviceSensorBasic, // nothing decoded yet, so it reports instead
123
114
  deviceSensorLeak,
124
115
  deviceSensorMonitor,
125
116
  deviceSensorPresence,
@@ -8,32 +8,15 @@ import platformLang from '../utils/lang-en.js'
8
8
  import { logUnknownData } from '../utils/report-unknown.js'
9
9
 
10
10
  /*
11
- H7120
12
- {
13
- "mode": {
14
- "options": [
15
- {
16
- "name": "Low",
17
- "value": 1
18
- },
19
- {
20
- "name": "Medium",
21
- "value": 2
22
- },
23
- {
24
- "name": "High",
25
- "value": 3
26
- },
27
- {
28
- "name": "Sleep mode",
29
- "value": 5
30
- }
31
- ]
32
- }
33
- }
11
+ The modes these purifiers report over the api. Low, medium and high are the
12
+ gear speeds the plugin drives; sleep is listed for reference and is the one
13
+ place the two models differ, so it must not be sent as a shared value.
14
+
15
+ H7120: Low 1, Medium 2, High 3, "Sleep mode" 5
16
+ H7121: Low 1, Medium 2, High 3, "Sleep" 16
34
17
  */
35
18
 
36
- export default class {
19
+ export default class GoveePurifierH7120 {
37
20
  constructor(platform, accessory) {
38
21
  // Set up variables from the platform
39
22
  this.cusChar = platform.cusChar
@@ -10,7 +10,7 @@ import platformLang from '../utils/lang-en.js'
10
10
  import { logUnknownData } from '../utils/report-unknown.js'
11
11
 
12
12
  /*
13
- H7123
13
+ H7123 and H7124
14
14
  {
15
15
  "mode": {
16
16
  "options": [
@@ -39,7 +39,7 @@ import { logUnknownData } from '../utils/report-unknown.js'
39
39
  }
40
40
  */
41
41
 
42
- export default class {
42
+ export default class GoveePurifierH7123 {
43
43
  constructor(platform, accessory) {
44
44
  // Set up variables from the platform
45
45
  this.cusChar = platform.cusChar
@@ -1,7 +1,14 @@
1
1
  import platformLang from '../utils/lang-en.js'
2
2
  import { logUnknownData } from '../utils/report-unknown.js'
3
3
 
4
- export default class {
4
+ /**
5
+ * A sensor whose broadcasts are not decoded yet.
6
+ *
7
+ * It offers no tile, because a tile that never updates is worse than none. What
8
+ * it does is report every message it receives in one copy-paste line, so an
9
+ * owner can send in exactly what is needed to finish the model off.
10
+ */
11
+ export default class GoveeSensorBasic {
5
12
  constructor(platform, accessory) {
6
13
  // Set up variables from the platform
7
14
  this.hapChar = platform.api.hap.Characteristic
@@ -8,8 +8,8 @@ import platformLang from '../utils/lang-en.js'
8
8
  // outright rather than accepted and silently dropped (#1323).
9
9
  const nightLightModels = ['H5089']
10
10
 
11
- export default class {
12
- constructor(platform, accessory) {
11
+ class MultiSwitchDouble {
12
+ constructor(platform, accessory, config) {
13
13
  // Set up variables from the platform
14
14
  this.hapChar = platform.api.hap.Characteristic
15
15
  this.hapErr = platform.api.hap.HapStatusError
@@ -19,12 +19,13 @@ export default class {
19
19
  // Set up variables from the accessory
20
20
  this.accessory = accessory
21
21
 
22
- // Remove outlet services if they exist
23
- if (this.accessory.getService('Outlet 1')) {
24
- this.accessory.removeService(this.accessory.getService('Outlet 1'))
22
+ // Drop the tiles of the other kind, left behind if this device has been
23
+ // shown as the other one before
24
+ if (this.accessory.getService(`${config.other} 1`)) {
25
+ this.accessory.removeService(this.accessory.getService(`${config.other} 1`))
25
26
  }
26
- if (this.accessory.getService('Outlet 2')) {
27
- this.accessory.removeService(this.accessory.getService('Outlet 2'))
27
+ if (this.accessory.getService(`${config.other} 2`)) {
28
+ this.accessory.removeService(this.accessory.getService(`${config.other} 2`))
28
29
  }
29
30
 
30
31
  // A model that used to be treated as a single switch or outlet leaves an
@@ -35,15 +36,15 @@ export default class {
35
36
  && [this.hapServ.Switch.UUID, this.hapServ.Outlet.UUID].includes(service.UUID))
36
37
  .forEach(service => this.accessory.removeService(service))
37
38
 
38
- // Add the switch services if they don't already exist
39
- this.service1 = this.accessory.getService('Switch 1')
40
- || this.accessory.addService(this.hapServ.Switch, 'Switch 1', 'switch1')
41
- this.service2 = this.accessory.getService('Switch 2')
42
- || this.accessory.addService(this.hapServ.Switch, 'Switch 2', 'switch2')
39
+ // Add this device's tiles if they are not there already
40
+ this.service1 = this.accessory.getService(`${config.label} 1`)
41
+ || this.accessory.addService(this.hapServ[config.label], `${config.label} 1`, `${config.subtype}1`)
42
+ this.service2 = this.accessory.getService(`${config.label} 2`)
43
+ || this.accessory.addService(this.hapServ[config.label], `${config.label} 2`, `${config.subtype}2`)
43
44
 
44
45
  if (!this.service1.testCharacteristic(this.hapChar.ConfiguredName)) {
45
46
  this.service1.addCharacteristic(this.hapChar.ConfiguredName)
46
- this.service1.updateCharacteristic(this.hapChar.ConfiguredName, 'Switch 1')
47
+ this.service1.updateCharacteristic(this.hapChar.ConfiguredName, `${config.label} 1`)
47
48
  }
48
49
  if (!this.service1.testCharacteristic(this.hapChar.ServiceLabelIndex)) {
49
50
  this.service1.addCharacteristic(this.hapChar.ServiceLabelIndex)
@@ -51,7 +52,7 @@ export default class {
51
52
  }
52
53
  if (!this.service2.testCharacteristic(this.hapChar.ConfiguredName)) {
53
54
  this.service2.addCharacteristic(this.hapChar.ConfiguredName)
54
- this.service2.updateCharacteristic(this.hapChar.ConfiguredName, 'Switch 2')
55
+ this.service2.updateCharacteristic(this.hapChar.ConfiguredName, `${config.label} 2`)
55
56
  }
56
57
  if (!this.service2.testCharacteristic(this.hapChar.ServiceLabelIndex)) {
57
58
  this.service2.addCharacteristic(this.hapChar.ServiceLabelIndex)
@@ -86,7 +87,7 @@ export default class {
86
87
 
87
88
  // Output the customised options to the log
88
89
  const opts = JSON.stringify({
89
- showAs: 'switch',
90
+ showAs: config.showAs,
90
91
  })
91
92
  platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
92
93
 
@@ -186,3 +187,21 @@ export default class {
186
187
  }
187
188
  }
188
189
  }
190
+
191
+ /**
192
+ * A double switch and a double outlet are the same device with a different tile
193
+ * in the Home app, chosen by the owner's `showAs` setting. Only the service
194
+ * type, the tile names and the logged option differ, so they share this and are
195
+ * built from it rather than from a copy of it.
196
+ */
197
+ function make(config) {
198
+ return class extends MultiSwitchDouble {
199
+ constructor(platform, accessory) {
200
+ super(platform, accessory, config)
201
+ }
202
+ }
203
+ }
204
+
205
+ export default make({ label: 'Switch', subtype: 'switch', other: 'Outlet', showAs: 'switch' })
206
+
207
+ export const asOutlet = make({ label: 'Outlet', subtype: 'outlet', other: 'Switch', showAs: 'outlet' })
@@ -1,8 +1,8 @@
1
1
  import { parseError } from '../utils/functions.js'
2
2
  import platformLang from '../utils/lang-en.js'
3
3
 
4
- export default class {
5
- constructor(platform, accessory) {
4
+ class MultiSwitchTriple {
5
+ constructor(platform, accessory, config) {
6
6
  // Set up variables from the platform
7
7
  this.hapChar = platform.api.hap.Characteristic
8
8
  this.hapErr = platform.api.hap.HapStatusError
@@ -12,24 +12,25 @@ export default class {
12
12
  // Set up variables from the accessory
13
13
  this.accessory = accessory
14
14
 
15
- // Remove outlet services if they exist
16
- if (this.accessory.getService('Outlet 1')) {
17
- this.accessory.removeService(this.accessory.getService('Outlet 1'))
15
+ // Drop the tiles of the other kind, left behind if this device has been
16
+ // shown as the other one before
17
+ if (this.accessory.getService(`${config.other} 1`)) {
18
+ this.accessory.removeService(this.accessory.getService(`${config.other} 1`))
18
19
  }
19
- if (this.accessory.getService('Outlet 2')) {
20
- this.accessory.removeService(this.accessory.getService('Outlet 2'))
20
+ if (this.accessory.getService(`${config.other} 2`)) {
21
+ this.accessory.removeService(this.accessory.getService(`${config.other} 2`))
21
22
  }
22
- if (this.accessory.getService('Outlet 3')) {
23
- this.accessory.removeService(this.accessory.getService('Outlet 3'))
23
+ if (this.accessory.getService(`${config.other} 3`)) {
24
+ this.accessory.removeService(this.accessory.getService(`${config.other} 3`))
24
25
  }
25
26
 
26
- // Add the switch services if they don't already exist
27
- this.service1 = this.accessory.getService('Switch 1')
28
- || this.accessory.addService(this.hapServ.Switch, 'Switch 1', 'switch1')
29
- this.service2 = this.accessory.getService('Switch 2')
30
- || this.accessory.addService(this.hapServ.Switch, 'Switch 2', 'switch2')
31
- this.service3 = this.accessory.getService('Switch 3')
32
- || this.accessory.addService(this.hapServ.Switch, 'Switch 3', 'switch3')
27
+ // Add this device's tiles if they are not there already
28
+ this.service1 = this.accessory.getService(`${config.label} 1`)
29
+ || this.accessory.addService(this.hapServ[config.label], `${config.label} 1`, `${config.subtype}1`)
30
+ this.service2 = this.accessory.getService(`${config.label} 2`)
31
+ || this.accessory.addService(this.hapServ[config.label], `${config.label} 2`, `${config.subtype}2`)
32
+ this.service3 = this.accessory.getService(`${config.label} 3`)
33
+ || this.accessory.addService(this.hapServ[config.label], `${config.label} 3`, `${config.subtype}3`)
33
34
 
34
35
  // Add the set handler to the switch on/off characteristic
35
36
  this.service1.getCharacteristic(this.hapChar.On).onSet(async (value) => {
@@ -47,7 +48,7 @@ export default class {
47
48
 
48
49
  // Output the customised options to the log
49
50
  const opts = JSON.stringify({
50
- showAs: 'switch',
51
+ showAs: config.showAs,
51
52
  })
52
53
  platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
53
54
 
@@ -158,3 +159,21 @@ export default class {
158
159
  }
159
160
  }
160
161
  }
162
+
163
+ /**
164
+ * A triple switch and a triple outlet are the same device with a different tile
165
+ * in the Home app, chosen by the owner's `showAs` setting. Only the service
166
+ * type, the tile names and the logged option differ, so they share this and are
167
+ * built from it rather than from a copy of it.
168
+ */
169
+ function make(config) {
170
+ return class extends MultiSwitchTriple {
171
+ constructor(platform, accessory) {
172
+ super(platform, accessory, config)
173
+ }
174
+ }
175
+ }
176
+
177
+ export default make({ label: 'Switch', subtype: 'switch', other: 'Outlet', showAs: 'switch' })
178
+
179
+ export const asOutlet = make({ label: 'Outlet', subtype: 'outlet', other: 'Switch', showAs: 'outlet' })
@@ -34,6 +34,19 @@ const modelOverrides = {
34
34
  // and only the brightness scale is fixed here (#1332)
35
35
  H6102: { bleBrightnessScale: 0x64 },
36
36
 
37
+ // The H1310 ceiling fan shares the H7100's handler, but its oscillation
38
+ // status message has never been confirmed to mean the same thing, so reading
39
+ // it back stays off. While it is off, an owner's fan reporting one produces a
40
+ // line in the log saying so - which is the evidence needed to turn this on
41
+ H1310: { readsSwingStatus: false },
42
+ R1310: { readsSwingStatus: false },
43
+
44
+ // Later ice makers number their ice sizes the natural way round. The older
45
+ // ones run the other way, small being the highest byte, so the two families
46
+ // share a handler and differ only here
47
+ H8121: { iceSizeAscending: true },
48
+ H8122: { iceSizeAscending: true },
49
+
37
50
  // AWS outlet uses 17/16 for on/off instead of 1/0
38
51
  H5080: { awsPowerOn: 17, awsPowerOff: 16 },
39
52
  H5083: { awsPowerOn: 17, awsPowerOff: 16 },
@@ -101,6 +114,8 @@ const defaults = {
101
114
  bleWriteUuid: UUID.WRITE_DEFAULT,
102
115
  awsStatusCmdVersion: 2,
103
116
  awsBrightnessNoScale: false,
117
+ readsSwingStatus: true,
118
+ iceSizeAscending: false,
104
119
  }
105
120
 
106
121
  export function getDeviceCapabilities(model, firmware) {
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.12",
5
+ "version": "11.32.2-beta.14",
6
6
  "description": "Homebridge plugin to integrate Govee devices into HomeKit.",
7
7
  "author": {
8
8
  "name": "bwp91",