@homebridge-plugins/homebridge-govee 11.32.2 → 11.33.1

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
@@ -2,7 +2,16 @@
2
2
 
3
3
  All notable changes to `@homebridge-plugins/homebridge-govee` will be documented in this file.
4
4
 
5
- ## v11.32.2 (2026-08-03)
5
+ ## v11.33.1 (2026-08-04)
6
+
7
+ ### Changed
8
+
9
+ - fix: stop the kettle reporting its startup status as unrecognised scenes (#1337)
10
+ - fix: stop reporting a bluetooth broadcast that carries no data to read (#1338)
11
+ - fix: report an unrecognised code once per kind, not once per value it carries (#1338)
12
+ - chore(deps): dependency updates
13
+
14
+ ## v11.33.0 (2026-08-03)
6
15
 
7
16
  ### Changed
8
17
 
@@ -129,10 +129,12 @@ export default class BLEConnection {
129
129
  // model and an owner has no other way to get hold of it.
130
130
  if (!isValidPeripheral(peripheral)) {
131
131
  const unknownName = advertisement?.localName
132
- if (unknownName && unknownName.toLowerCase().startsWith('govee')) {
133
- const raw = advertisement.manufacturerData
134
- ? advertisement.manufacturerData.toString('hex')
135
- : 'no manufacturer data'
132
+ // A device sends more than one kind of advertisement, and the ones
133
+ // carrying only its name have no manufacturer data at all. There is
134
+ // nothing in those to decode and nothing to be learnt from reporting
135
+ // them, so a working sensor was warning about itself (#1338)
136
+ if (unknownName && unknownName.toLowerCase().startsWith('govee') && advertisement.manufacturerData) {
137
+ const raw = advertisement.manufacturerData.toString('hex')
136
138
 
137
139
  // A device rebroadcasts constantly, so say this once and then stay
138
140
  // quiet about it for the rest of the session. This has to come first:
@@ -29,6 +29,9 @@ export default class extends GoveeDevice {
29
29
  // Set up variables from the accessory
30
30
  const deviceConf = platform.deviceConf[accessory.context.gvDeviceId] || {}
31
31
 
32
+ // What each preset was last reported as, so a repeat is not logged twice
33
+ this.reportedPresets = new Map()
34
+
32
35
  this.codes = {
33
36
  greenTea: 'MwUAAgAAAAAAAAAAAAAAAAAAADQ=',
34
37
  oolongTea: 'MwUAAwAAAAAAAAAAAAAAAAAAADU=',
@@ -251,6 +254,27 @@ export default class extends GoveeDevice {
251
254
 
252
255
  const deviceFunction = `${getTwoItemPosition(hexParts, 2)}${getTwoItemPosition(hexParts, 3)}`
253
256
 
257
+ // On startup the kettle lists the temperature each of its presets is set
258
+ // to, one frame per preset: aa 05 <slot> <degrees F>. That is a different
259
+ // thing from aa 05 00 <mode> below, which is a mode change - the slot
260
+ // byte is what tells them apart. Nothing here needs the values, so they
261
+ // are logged for reference rather than reported as unrecognised (#1337)
262
+ if (getTwoItemPosition(hexParts, 2) === '05' && getTwoItemPosition(hexParts, 3) !== '00') {
263
+ const slot = Number.parseInt(getTwoItemPosition(hexParts, 3), 16)
264
+ const tempInF = Number.parseInt(getTwoItemPosition(hexParts, 4), 16)
265
+ const line = `preset ${slot} is set to ${tempInF}°F [${Math.round(((tempInF - 32) * 5) / 9)}°C]`
266
+ // Worth seeing in a normal log, but only once each. The kettle sends
267
+ // these when it connects, and if a firmware ever repeats them on a
268
+ // timer this keeps five lines from becoming five every few minutes
269
+ if (this.reportedPresets.get(slot) === line) {
270
+ this.accessory.logDebug(line)
271
+ } else {
272
+ this.reportedPresets.set(slot, line)
273
+ this.accessory.log(line)
274
+ }
275
+ return
276
+ }
277
+
254
278
  switch (deviceFunction) {
255
279
  case '0500': { // current mode
256
280
  // switch to green tea_: aa050002000000000000000000000000000000ad
@@ -287,9 +311,19 @@ export default class extends GoveeDevice {
287
311
  case '2301': { // scheduled start on
288
312
  break
289
313
  }
314
+ // Also sent on startup, alongside the preset list above. What each one
315
+ // carries has not been worked out, and nothing here needs them, so they
316
+ // are known-and-ignored rather than reported every time (#1337)
317
+ case '1701': // a second form of the on-base report
318
+ case '1f06':
319
+ case '1f08':
320
+ case '1900': {
321
+ this.accessory.logDebug(`startup status [${deviceFunction}]: ${hexString}`)
322
+ break
323
+ }
290
324
  default:
291
325
  logUnknownData(this.accessory, {
292
- kind: 'scene',
326
+ kind: 'status',
293
327
  source: params.source,
294
328
  raw: command,
295
329
  hex: hexString,
@@ -14,10 +14,33 @@ import { base64ToHex } from './functions.js'
14
14
  */
15
15
 
16
16
  // Remembers what has already been reported, so a code the device repeats every
17
- // few seconds is only shouted about once. Keyed by device, kind and payload, so
18
- // a genuinely different unknown value is still surfaced.
17
+ // few seconds is only shouted about once. Keyed by device, kind and the shape
18
+ // of the frame rather than its whole payload - see frameShape below.
19
19
  const alreadyReported = new Set()
20
20
 
21
+ /**
22
+ * What kind of message a frame is, ignoring the values it carries.
23
+ *
24
+ * A frame is `<prefix> <command> <sub-command> <data...>`, and the first three
25
+ * bytes are what the device handlers themselves switch on to decide what a
26
+ * frame means. Keying on those means one report per kind of message.
27
+ *
28
+ * Keying on the whole payload instead looks equivalent but is not: a frame
29
+ * carrying a reading or a counter differs every time it is sent, so each one
30
+ * counts as new and gets reported again. One fan sent the same message every
31
+ * twenty minutes with a changing value, which worked out at around seventy
32
+ * warnings a day for a single message type, burying the frames worth seeing
33
+ * (#1338).
34
+ *
35
+ * @param {string} [hex] the frame as hex, where there is one
36
+ * @returns {string|undefined} the first three bytes, or undefined if not a frame
37
+ */
38
+ function frameShape(hex) {
39
+ return typeof hex === 'string' && /^[0-9a-f]{6}/i.test(hex)
40
+ ? hex.slice(0, 6).toLowerCase()
41
+ : undefined
42
+ }
43
+
21
44
  /**
22
45
  * Reset the record of what has been reported. Only needed by tests, and when
23
46
  * the platform restarts and wants a fresh session.
@@ -142,10 +165,14 @@ export function logUnknownData(accessory, {
142
165
  })
143
166
 
144
167
  const line = `unrecognised ${kind} - please include this line if you report it: ${JSON.stringify(report)}`
145
- const fingerprint = `${context.gvDeviceId || accessory.displayName}|${kind}|${report.raw}`
146
-
147
- // Say it once loudly enough to be noticed, then keep quiet about that exact
148
- // payload for the rest of the session.
168
+ // Only the callers that pass `hex` are handing over a real device frame. The
169
+ // rest report things like a settings object, and base64ToHex will happily
170
+ // turn any string into hex that looks like one - two different messages can
171
+ // then share a first three bytes and wrongly count as the same shape.
172
+ const fingerprint = `${context.gvDeviceId || accessory.displayName}|${kind}|${frameShape(hex) ?? report.raw}`
173
+
174
+ // Say it once loudly enough to be noticed, then keep quiet about that shape
175
+ // for the rest of the session.
149
176
  if (alreadyReported.has(fingerprint)) {
150
177
  accessory.logDebug?.(line)
151
178
  return
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",
5
+ "version": "11.33.1",
6
6
  "description": "Homebridge plugin to integrate Govee devices into HomeKit.",
7
7
  "author": {
8
8
  "name": "bwp91",