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

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
@@ -30,6 +30,11 @@ All notable changes to `@homebridge-plugins/homebridge-govee` will be documented
30
30
  - refactor: point the H7151 and H7111 at the handlers they were byte-for-byte copies of
31
31
  - refactor: build the ice maker size codes from the model rather than writing both orderings out by hand
32
32
  - refactor: build the switch and outlet tiles of a multi-socket device from one handler instead of two copies of it
33
+ - fix: move the H7152 dehumidifier's speed slider when it reports a new speed, which until now only the H7150 and H7151 did
34
+ - refactor: point the H7101 and H7111 fans at the H7100 handler they matched command for command
35
+ - feat: give a diffuser model that has not been worked out yet the same starting handler the other appliances have, so it works for on and off
36
+ - refactor: choose a device handler from a table, so adding a device type no longer means editing the platform
37
+ - refactor: move the settings parsing into its own file, away from the code that talks to devices
33
38
 
34
39
  ## v11.32.1 (2026-08-02)
35
40
 
@@ -356,6 +356,16 @@ export const deviceDehumidifierBasic = makeBasicAppliance({
356
356
  currentStateOff: 0, // inactive
357
357
  })
358
358
 
359
+ // Diffusers are power only, so there is no variable control to offer. They use
360
+ // the same tile as the models already handled, so a new one looks the same
361
+ export const deviceDiffuserBasic = makeBasicAppliance({
362
+ family: 'diffuser',
363
+ service: 'AirPurifier',
364
+ currentStateChar: 'CurrentAirPurifierState',
365
+ currentStateOn: 2, // purifying air
366
+ currentStateOff: 0, // inactive
367
+ })
368
+
359
369
  export const devicePurifierBasic = makeBasicAppliance({
360
370
  family: 'purifier',
361
371
  service: 'AirPurifier',
@@ -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 and H7151
11
+ H7150, H7151 and H7152
12
12
  {
13
13
  "mode": {
14
14
  "options": [
@@ -10,6 +10,16 @@ import {
10
10
  import platformLang from '../utils/lang-en.js'
11
11
  import { logUnknownData } from '../utils/report-unknown.js'
12
12
 
13
+ /*
14
+ The models on this handler and the modes each offers:
15
+
16
+ H7100, H7102, H7106 -> Sleep, Nature, Custom, Auto
17
+ H7101 -> Sleep, Nature, Turbo, Custom, Auto
18
+ H7111 -> Sleep, Nature, Custom, Auto
19
+ H1310, R1310 -> as the H7100, but its oscillation status message has
20
+ never been confirmed, so it is not read back
21
+ */
22
+
13
23
  export default class GoveeFanH7100 {
14
24
  constructor(platform, accessory) {
15
25
  // Set up variables from the platform
@@ -267,7 +277,7 @@ export default class GoveeFanH7100 {
267
277
  this.cacheSpeed = this.autoSpeed
268
278
  // Cache auto so we can tell it apart from "user asked for max speed"
269
279
  this.service.updateCharacteristic(this.hapChar.RotationSpeed, 100)
270
- this.accessory.log(`${platformLang.curSpeed} [auto]`)
280
+ this.accessory.log(`${platformLang.curSpeed} [auto -> 100%]`)
271
281
  }
272
282
  break
273
283
  }
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  deviceDehumidifierBasic,
3
+ deviceDiffuserBasic,
3
4
  deviceFanBasic,
4
5
  deviceHumidifierBasic,
5
6
  deviceIceMakerBasic,
@@ -7,11 +8,9 @@ import {
7
8
  } from './appliance-basic.js'
8
9
  import deviceCoolerSingle from './cooler-single.js'
9
10
  import deviceDehumidifierH7150 from './dehumidifier-H7150.js'
10
- import deviceDehumidifierH7152 from './dehumidifier-H7152.js'
11
11
  import deviceDiffuserH7161 from './diffuser-H7161.js'
12
12
  import deviceFanH1370 from './fan-H1370.js'
13
13
  import deviceFanH7100 from './fan-H7100.js'
14
- import deviceFanH7101 from './fan-H7101.js'
15
14
  import deviceFanH7105 from './fan-H7105.js'
16
15
  import deviceFanH7107 from './fan-H7107.js'
17
16
  import deviceHeater1A from './heater1a.js'
@@ -53,10 +52,11 @@ export default {
53
52
  deviceCoolerSingle,
54
53
  deviceDehumidifierH7150,
55
54
  deviceDehumidifierH7151: deviceDehumidifierH7150, // identical to the H7150
56
- deviceDehumidifierH7152,
55
+ deviceDehumidifierH7152: deviceDehumidifierH7150, // same device, and this gives it proper speed feedback
57
56
  deviceDiffuserH7161,
58
57
  deviceDiffuserH7162: deviceDiffuserH7161, // identical to the H7161
59
58
  deviceDehumidifierBasic,
59
+ deviceDiffuserBasic,
60
60
  deviceFanBasic,
61
61
  deviceFanH1310: deviceFanH7100, // same fan, minus a confirmed swing status message
62
62
  deviceFanH1370,
@@ -64,10 +64,10 @@ export default {
64
64
  deviceFanH7100,
65
65
  deviceFanH7102: deviceFanH7100, // identical to the H7100
66
66
  deviceFanH7106: deviceFanH7100, // identical to the H7100
67
- deviceFanH7101,
67
+ deviceFanH7101: deviceFanH7100, // identical to the H7100, with a turbo mode it is never sent
68
68
  deviceFanH7105,
69
69
  deviceFanH7107,
70
- deviceFanH7111: deviceFanH7101, // same fan without the turbo mode, which is not sent
70
+ deviceFanH7111: deviceFanH7100, // same fan without the turbo mode, which is not sent
71
71
  deviceHeaterSingle,
72
72
  deviceHeater1A,
73
73
  deviceHeater1B,
package/lib/platform.js CHANGED
@@ -11,18 +11,18 @@ import awsClient from './connection/aws.js'
11
11
  import httpClient from './connection/http.js'
12
12
  import lanClient from './connection/lan.js'
13
13
  import openApiClient from './connection/openapi.js'
14
- import deviceTypes from './device/index.js'
15
14
  import eveService from './fakegato/fakegato-history.js'
16
15
  import { BLE_UPDATE_TIMEOUT } from './utils/ble-protocol.js'
17
16
  import { buildCommand } from './utils/command-builder.js'
17
+ import { applyUserConfig } from './utils/config-parser.js'
18
18
  import platformConsts from './utils/constants.js'
19
19
  import platformChars from './utils/custom-chars.js'
20
20
  import { getDeviceCapabilities } from './utils/device-capabilities.js'
21
+ import { resolveDeviceType } from './utils/dispatch.js'
21
22
  import eveChars from './utils/eve-chars.js'
22
23
  import {
23
24
  base64ToHex,
24
25
  hasProperty,
25
- parseDeviceId,
26
26
  parseError,
27
27
  pfxToCertAndKey,
28
28
  updateFailureError,
@@ -86,7 +86,7 @@ export default class {
86
86
 
87
87
  // Apply the user's configuration
88
88
  this.config = platformConsts.defaultConfig
89
- this.applyUserConfig(config)
89
+ applyUserConfig(this, config)
90
90
 
91
91
  // Set up empty clients
92
92
  this.bleClient = false
@@ -104,269 +104,6 @@ export default class {
104
104
  }
105
105
  }
106
106
 
107
- applyUserConfig(config) {
108
- // These shorthand functions save line space during config parsing
109
- const logDefault = (k, def) => {
110
- this.log.warn('%s [%s] %s %s.', platformLang.cfgItem, k, platformLang.cfgDef, def)
111
- }
112
- const logDuplicate = (k) => {
113
- this.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgDup)
114
- }
115
- const logIgnore = (k) => {
116
- this.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgIgn)
117
- }
118
- const logIgnoreItem = (k) => {
119
- this.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgIgnItem)
120
- }
121
- const logIncrease = (k, min) => {
122
- this.log.warn('%s [%s] %s %s.', platformLang.cfgItem, k, platformLang.cfgLow, min)
123
- }
124
- const logQuotes = (k) => {
125
- this.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgQts)
126
- }
127
- const logRemove = (k) => {
128
- this.log.warn('%s [%s] %s.', platformLang.cfgItem, k, platformLang.cfgRmv)
129
- }
130
-
131
- // Begin applying the user's config
132
- Object.entries(config).forEach((entry) => {
133
- const [key, val] = entry
134
- switch (key) {
135
- case 'apiKey':
136
- case 'code':
137
- case 'password':
138
- case 'username':
139
- if (typeof val !== 'string' || val === '') {
140
- logIgnore(key)
141
- } else {
142
- this.config[key] = val
143
- }
144
- break
145
- case 'lanScanSubnets': {
146
- // Comma-separated list of CIDR subnets to scan directly (for devices
147
- // on a different subnet from Homebridge, which multicast can't reach)
148
- if (typeof val !== 'string' || val.trim() === '') {
149
- break
150
- }
151
- const subnets = val
152
- .split(',')
153
- .map(s => s.trim())
154
- .filter(s => /^\d{1,3}(?:\.\d{1,3}){3}\/\d{1,2}$/.test(s))
155
- if (subnets.length === 0) {
156
- logIgnore(key)
157
- } else {
158
- this.config[key] = subnets
159
- }
160
- break
161
- }
162
- case 'awsDisable':
163
- case 'bleDisable':
164
- case 'colourSafeMode':
165
- case 'disableDeviceLogging':
166
- case 'ignoreMatter':
167
- case 'lanDisable':
168
- if (typeof val === 'string') {
169
- logQuotes(key)
170
- }
171
- this.config[key] = val === 'false' ? false : !!val
172
- break
173
- case 'bleControlInterval':
174
- case 'bleRefreshTime':
175
- case 'httpRefreshTime':
176
- case 'lanRefreshTime':
177
- case 'lanScanInterval': {
178
- if (typeof val === 'string') {
179
- logQuotes(key)
180
- }
181
- const intVal = Number.parseInt(val, 10)
182
- if (Number.isNaN(intVal)) {
183
- logDefault(key, platformConsts.defaultValues[key])
184
- this.config[key] = platformConsts.defaultValues[key]
185
- } else if (intVal < platformConsts.minValues[key]) {
186
- logIncrease(key, platformConsts.minValues[key])
187
- this.config[key] = platformConsts.minValues[key]
188
- } else {
189
- this.config[key] = intVal
190
- }
191
- break
192
- }
193
- case 'dehumidifierDevices':
194
- case 'fanDevices':
195
- case 'heaterDevices':
196
- case 'humidifierDevices':
197
- case 'iceMakerDevices':
198
- case 'kettleDevices':
199
- case 'leakDevices':
200
- case 'lightDevices':
201
- case 'purifierDevices':
202
- case 'diffuserDevices':
203
- case 'switchDevices':
204
- case 'thermoDevices':
205
- if (Array.isArray(val) && val.length > 0) {
206
- val.forEach((x) => {
207
- if (!x.deviceId) {
208
- logIgnoreItem(key)
209
- return
210
- }
211
- const id = parseDeviceId(x.deviceId)
212
- if (Object.keys(this.deviceConf).includes(id)) {
213
- logDuplicate(`${key}.${id}`)
214
- return
215
- }
216
- const entries = Object.entries(x)
217
- if (entries.length === 1) {
218
- logRemove(`${key}.${id}`)
219
- return
220
- }
221
- this.deviceConf[id] = {}
222
- entries.forEach((subEntry) => {
223
- const [k, v] = subEntry
224
- switch (k) {
225
- case 'adaptiveLightingShift':
226
- case 'brightnessStep':
227
- case 'lowBattThreshold': {
228
- if (typeof v === 'string') {
229
- logQuotes(`${key}.${k}`)
230
- }
231
- const intVal = Number.parseInt(v, 10)
232
- if (Number.isNaN(intVal)) {
233
- logDefault(`${key}.${id}.${k}`, platformConsts.defaultValues[k])
234
- this.deviceConf[id][k] = platformConsts.defaultValues[k]
235
- } else if (intVal < platformConsts.minValues[k]) {
236
- logIncrease(`${key}.${id}.${k}`, platformConsts.minValues[k])
237
- this.deviceConf[id][k] = platformConsts.minValues[k]
238
- } else {
239
- this.deviceConf[id][k] = intVal
240
- }
241
- break
242
- }
243
- case 'awsBrightnessNoScale':
244
- case 'hideLight':
245
- case 'hideModeGreenTea':
246
- case 'hideModeOolongTea':
247
- case 'hideModeCoffee':
248
- case 'hideModeBlackTea':
249
- case 'showCustomMode1':
250
- case 'showCustomMode2':
251
- case 'showExtraSwitch':
252
- case 'tempReporting':
253
- if (typeof v === 'string') {
254
- logQuotes(`${key}.${id}.${k}`)
255
- }
256
- this.deviceConf[id][k] = v === 'false' ? false : !!v
257
- break
258
- case 'awsColourMode':
259
- case 'openApiTempUnit':
260
- case 'showAs': {
261
- if (typeof v !== 'string' || !platformConsts.allowed[k].includes(v)) {
262
- logIgnore(`${key}.${id}.${k}`)
263
- } else {
264
- this.deviceConf[id][k] = v
265
- }
266
- break
267
- }
268
- case 'customAddress':
269
- case 'customIPAddress':
270
- if (typeof v !== 'string' || v === '') {
271
- logIgnore(`${key}.${id}.${k}`)
272
- } else {
273
- this.deviceConf[id][k] = v.replace(WHITESPACE_REGEX, '')
274
- }
275
- break
276
- case 'deviceId':
277
- break
278
- case 'diyMode':
279
- case 'diyModeTwo':
280
- case 'diyModeThree':
281
- case 'diyModeFour':
282
- case 'musicMode':
283
- case 'musicModeTwo':
284
- case 'scene':
285
- case 'sceneTwo':
286
- case 'sceneThree':
287
- case 'sceneFour':
288
- case 'segmented':
289
- case 'segmentedTwo':
290
- case 'segmentedThree':
291
- case 'segmentedFour':
292
- case 'temperatureSource':
293
- case 'videoMode':
294
- case 'videoModeTwo': {
295
- if (typeof v === 'string') {
296
- this.log.warn(`${key}.${id}.${k} incorrectly configured - please use the config screen to reconfigure this item:`)
297
- this.log.warn(`${key}.${id}.${k}: ${v}`)
298
- }
299
- if (typeof v === 'object') {
300
- // object - only allowed keys are 'sceneCode', 'bleCode' and 'showAs'
301
- const subEntries = Object.entries(v)
302
- if (subEntries.length > 0) {
303
- this.deviceConf[id][k] = {}
304
- subEntries.forEach((subSubEntry) => {
305
- const [k1, v1] = subSubEntry
306
- switch (k1) {
307
- case 'bleCode':
308
- case 'sceneCode':
309
- if (typeof v1 !== 'string' || v1 === '') {
310
- logIgnore(`${key}.${id}.${k}.${k1}`)
311
- } else {
312
- this.deviceConf[id][k][k1] = v1
313
- }
314
- break
315
- case 'showAs': {
316
- if (typeof v1 !== 'string' || !['default', 'switch'].includes(v1)) {
317
- logIgnore(`${key}.${id}.${k}.${k1}`)
318
- } else {
319
- this.deviceConf[id][k][k1] = v1
320
- }
321
- break
322
- }
323
- default:
324
- logIgnore(`${key}.${id}.${k}.${k1}`)
325
- break
326
- }
327
- })
328
- } else {
329
- logIgnore(`${key}.${id}.${k}`)
330
- }
331
- } else {
332
- logIgnore(`${key}.${id}.${k}`)
333
- }
334
- break
335
- }
336
- case 'ignoreDevice':
337
- if (typeof v === 'string') {
338
- logQuotes(`${key}.${id}.${k}`)
339
- }
340
- if (!!v && v !== 'false') {
341
- this.ignoredDevices.push(id)
342
- }
343
- break
344
- case 'label':
345
- if (typeof v !== 'string' || v === '') {
346
- logIgnore(`${key}.${id}.${k}`)
347
- } else {
348
- this.deviceConf[id][k] = v
349
- }
350
- break
351
- default:
352
- logRemove(`${key}.${id}.${k}`)
353
- }
354
- })
355
- })
356
- } else {
357
- logIgnore(key)
358
- }
359
- break
360
- case 'name':
361
- case 'platform':
362
- break
363
- default:
364
- logRemove(key)
365
- break
366
- }
367
- })
368
- }
369
-
370
107
  async pluginSetup() {
371
108
  // Plugin has finished initialising so now onto setup
372
109
  try {
@@ -1095,215 +832,14 @@ export default class {
1095
832
  try {
1096
833
  const deviceConf = this.deviceConf[device.device.toUpperCase()] || {}
1097
834
  const uuid = this.api.hap.uuid.generate(device.device)
1098
- let accessory
1099
- let devInstance
1100
- let doAWSPolling = false
1101
- if (platformConsts.models.rgb.includes(device.model)) {
1102
- // Device is an LED strip/bulb
1103
- devInstance = deviceConf.showAs === 'switch'
1104
- ? deviceTypes.deviceLightSwitch
1105
- : deviceTypes.deviceLight
1106
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1107
- } else if (platformConsts.models.switchSingle.includes(device.model)) {
1108
- // Device is a cloud enabled Wi-Fi switch
1109
- switch (deviceConf.showAs || platformConsts.defaultValues.showAs) {
1110
- case 'audio': {
1111
- if (devicesInHB.get(uuid)) {
1112
- this.removeAccessory(devicesInHB.get(uuid))
1113
- }
1114
- devInstance = deviceTypes.deviceTVSingle
1115
- accessory = this.addExternalAccessory(device, 34)
1116
- break
1117
- }
1118
- case 'box': {
1119
- if (devicesInHB.get(uuid)) {
1120
- this.removeAccessory(devicesInHB.get(uuid))
1121
- }
1122
- devInstance = deviceTypes.deviceTVSingle
1123
- accessory = this.addExternalAccessory(device, 35)
1124
- break
1125
- }
1126
- case 'stick': {
1127
- if (devicesInHB.get(uuid)) {
1128
- this.removeAccessory(devicesInHB.get(uuid))
1129
- }
1130
- devInstance = deviceTypes.deviceTVSingle
1131
- accessory = this.addExternalAccessory(device, 36)
1132
- break
1133
- }
1134
- case 'cooler': {
1135
- if (!deviceConf.temperatureSource) {
1136
- this.log.warn('[%s] %s.', device.deviceName, platformLang.heaterSimNoSensor)
1137
- if (devicesInHB.has(uuid)) {
1138
- this.removeAccessory(devicesInHB.get(uuid))
1139
- }
1140
- return
1141
- }
1142
- devInstance = deviceTypes.deviceCoolerSingle
1143
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1144
- break
1145
- }
1146
- case 'heater': {
1147
- if (!deviceConf.temperatureSource) {
1148
- this.log.warn('[%s] %s.', device.deviceName, platformLang.heaterSimNoSensor)
1149
- if (devicesInHB.has(uuid)) {
1150
- this.removeAccessory(devicesInHB.get(uuid))
1151
- }
1152
- return
1153
- }
1154
- devInstance = deviceTypes.deviceHeater2Single
1155
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1156
- break
1157
- }
1158
- case 'purifier': {
1159
- devInstance = deviceTypes.devicePurifierSingle
1160
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1161
- break
1162
- }
1163
- case 'switch': {
1164
- devInstance = deviceTypes.deviceSwitchSingle
1165
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1166
- break
1167
- }
1168
- case 'tap': {
1169
- devInstance = deviceTypes.deviceTapSingle
1170
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1171
- break
1172
- }
1173
- case 'valve': {
1174
- devInstance = deviceTypes.deviceValveSingle
1175
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1176
- break
1177
- }
1178
- default:
1179
- devInstance = deviceTypes.deviceOutletSingle
1180
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1181
- break
1182
- }
1183
- } else if (platformConsts.models.switchDouble.includes(device.model)) {
1184
- // Device is an AWS enabled Wi-Fi double switch
1185
- switch (deviceConf.showAs || platformConsts.defaultValues.showAs) {
1186
- case 'switch': {
1187
- devInstance = deviceTypes.deviceSwitchDouble
1188
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1189
- break
1190
- }
1191
- default: {
1192
- devInstance = deviceTypes.deviceOutletDouble
1193
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1194
- break
1195
- }
1196
- }
1197
- } else if (platformConsts.models.switchTriple.includes(device.model)) {
1198
- // Device is an AWS enabled Wi-Fi double switch
1199
- switch (deviceConf.showAs || platformConsts.defaultValues.showAs) {
1200
- case 'switch': {
1201
- devInstance = deviceTypes.deviceSwitchTriple
1202
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1203
- break
1204
- }
1205
- default: {
1206
- devInstance = deviceTypes.deviceOutletTriple
1207
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1208
- break
1209
- }
1210
- }
1211
- } else if (platformConsts.models.sensorLeak.includes(device.model)) {
1212
- // Device is a leak sensor
1213
- devInstance = deviceTypes.deviceSensorLeak
1214
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1215
- } else if (platformConsts.models.sensorPresence.includes(device.model)) {
1216
- // Device is a presence sensor
1217
- devInstance = deviceTypes.deviceSensorPresence
1218
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1219
- } else if (platformConsts.models.sensorThermo.includes(device.model)) {
1220
- // Device is a thermo-hygrometer sensor
1221
- devInstance = deviceConf.showExtraSwitch
1222
- ? deviceTypes.deviceSensorThermoSwitch
1223
- : deviceTypes.deviceSensorThermo
1224
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1225
- } else if (platformConsts.models.sensorThermo4.includes(device.model)) {
1226
- // Device is a thermo-hygrometer sensor with 4 prongs and AWS support
1227
- devInstance = deviceTypes.deviceSensorThermo4
1228
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1229
- } else if (platformConsts.models.sensorMonitor.includes(device.model)) {
1230
- devInstance = deviceTypes.deviceSensorMonitor
1231
- doAWSPolling = true
1232
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1233
- } else if (platformConsts.models.sensorCO2.includes(device.model)) {
1234
- // Device is a CO2 + temperature + humidity monitor (H5140)
1235
- devInstance = deviceTypes.deviceSensorCO2
1236
- doAWSPolling = true
1237
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1238
- } else if (platformConsts.models.fan.includes(device.model)) {
1239
- // Device is a fan. Models whose speeds have not been worked out yet fall
1240
- // back to the basic handler, which offers on and off and asks for what
1241
- // it needs rather than leaving the device unsupported altogether
1242
- devInstance = deviceTypes[`deviceFan${device.model}`] || deviceTypes.deviceFanBasic
1243
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1244
- } else if (platformConsts.models.heater1.includes(device.model)) {
1245
- // Some of these heaters report the room temperature and some do not,
1246
- // and the two need different tiles in HomeKit - a plain fan against a
1247
- // heater with a temperature readout. Rather than leave the owner to
1248
- // work that out and tick a box, take Govee's own word for it where the
1249
- // device says so, and otherwise use what a previous run learnt from the
1250
- // readings themselves. The setting still wins if it has been set, so
1251
- // nobody who has already configured this sees their tile change.
1252
- const reportsTemperature = device.openApiInfo?.byInstance?.sensorTemperature
1253
- ? true
1254
- : devicesInHB.get(uuid)?.context?.supportsAmbientTemp
1255
- devInstance = (deviceConf.tempReporting ?? reportsTemperature)
1256
- ? deviceTypes.deviceHeater1B
1257
- : deviceTypes.deviceHeater1A
1258
- doAWSPolling = true
1259
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1260
- } else if (platformConsts.models.heater2.includes(device.model)) {
1261
- // Device is a H7131/H7132
1262
- devInstance = deviceTypes.deviceHeater2
1263
- doAWSPolling = true
1264
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1265
- } else if (platformConsts.models.humidifier.includes(device.model)) {
1266
- // Device is a humidifier
1267
- doAWSPolling = true
1268
- devInstance = deviceTypes[`deviceHumidifier${device.model}`] || deviceTypes.deviceHumidifierBasic
1269
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1270
- } else if (platformConsts.models.dehumidifier.includes(device.model)) {
1271
- // Device is a dehumidifier
1272
- devInstance = deviceTypes[`deviceDehumidifier${device.model}`] || deviceTypes.deviceDehumidifierBasic
1273
- doAWSPolling = true
1274
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1275
- } else if (platformConsts.models.purifier.includes(device.model)) {
1276
- // Device is a purifier
1277
- devInstance = deviceTypes[`devicePurifier${device.model}`] || deviceTypes.devicePurifierBasic
1278
- doAWSPolling = true
1279
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1280
- } else if (platformConsts.models.diffuser.includes(device.model)) {
1281
- // Device is a diffuser
1282
- devInstance = deviceTypes[`deviceDiffuser${device.model}`]
1283
- doAWSPolling = true
1284
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1285
- } else if (platformConsts.models.sensorButton.includes(device.model)) {
1286
- // Device is a button
1287
- devInstance = deviceTypes.deviceSensorButton
1288
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1289
- } else if (platformConsts.models.sensorContact.includes(device.model)) {
1290
- // Device is a contact sensor
1291
- devInstance = deviceTypes.deviceSensorContact
1292
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1293
- } else if (platformConsts.models.kettle.includes(device.model)) {
1294
- // Device is a kettle
1295
- devInstance = deviceTypes.deviceKettle
1296
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1297
- } else if (platformConsts.models.iceMaker.includes(device.model)) {
1298
- // Device is an ice maker
1299
- devInstance = deviceTypes[`deviceIceMaker${device.model}`] || deviceTypes.deviceIceMakerBasic
1300
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1301
- } else if (platformConsts.models.template.includes(device.model)) {
1302
- // Device is a work-in-progress
1303
- devInstance = deviceTypes.deviceTemplate
1304
- accessory = devicesInHB.get(uuid) || this.addAccessory(device)
1305
- } else {
1306
- // Device is not in any supported model list but could be implemented into the plugin
835
+ const cached = devicesInHB.get(uuid)
836
+
837
+ // Which handler this device gets is decided by the dispatch table, so
838
+ // adding a device type does not mean editing this file
839
+ const route = resolveDeviceType(device, deviceConf, cached?.context)
840
+
841
+ if (!route) {
842
+ // Not in any supported model list, but it could be added to the plugin
1307
843
  this.log.warn(
1308
844
  '[%s] %s:\n%s',
1309
845
  device.deviceName,
@@ -1313,6 +849,31 @@ export default class {
1313
849
  return
1314
850
  }
1315
851
 
852
+ if (route.skip === 'needsTemperatureSource') {
853
+ // Standing in for a heater or cooler needs a temperature from somewhere
854
+ // else, and without one there is nothing to show
855
+ this.log.warn('[%s] %s.', device.deviceName, platformLang.heaterSimNoSensor)
856
+ if (cached) {
857
+ this.removeAccessory(cached)
858
+ }
859
+ return
860
+ }
861
+
862
+ const devInstance = route.handler
863
+ const doAWSPolling = route.awsPolling
864
+
865
+ let accessory
866
+ if (route.externalCategory) {
867
+ // A television has to be published as its own accessory rather than as
868
+ // a tile on the bridge, so any tile from a previous setting goes first
869
+ if (cached) {
870
+ this.removeAccessory(cached)
871
+ }
872
+ accessory = this.addExternalAccessory(device, route.externalCategory)
873
+ } else {
874
+ accessory = cached || this.addAccessory(device)
875
+ }
876
+
1316
877
  // Final check the accessory now exists in Homebridge
1317
878
  if (!accessory) {
1318
879
  throw new Error(platformLang.accNotFound)