@homebridge-plugins/homebridge-govee 11.30.1 → 11.31.0-beta.0

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,6 +2,12 @@
2
2
 
3
3
  All notable changes to `@homebridge-plugins/homebridge-govee` will be documented in this file.
4
4
 
5
+ ## v11.31.0 (Pending Release)
6
+
7
+ ### Added
8
+
9
+ - feat(lan): add a `lanScanSubnets` option to scan given subnets directly, so lan devices on a different subnet from homebridge can be found (#1323)
10
+
5
11
  ## v11.30.1 (2026-07-23)
6
12
 
7
13
  ### Changed
@@ -96,6 +96,15 @@
96
96
  "functionBody": "return (!model.lanDisable);"
97
97
  }
98
98
  },
99
+ "lanScanSubnets": {
100
+ "title": "LAN Scan Subnets",
101
+ "type": "string",
102
+ "description": "Optional. Only needed if your Govee devices are on a different subnet/VLAN from Homebridge. LAN discovery normally uses multicast, which routers do not pass between subnets. Enter one or more subnets in CIDR form (e.g. 192.168.20.0/24), separated by commas, and the plugin will also scan those directly. Requires the devices to have LAN control enabled.",
103
+ "placeholder": "192.168.20.0/24",
104
+ "condition": {
105
+ "functionBody": "return (!model.lanDisable);"
106
+ }
107
+ },
99
108
  "openApiDisable": {
100
109
  "title": "Disable OpenAPI",
101
110
  "type": "boolean",
@@ -1513,6 +1522,7 @@
1513
1522
  "lanDisable",
1514
1523
  "lanRefreshTime",
1515
1524
  "lanScanInterval",
1525
+ "lanScanSubnets",
1516
1526
  "openApiDisable",
1517
1527
  "openApiRefreshTime",
1518
1528
  "bleControlInterval",
@@ -69,6 +69,11 @@ export default class {
69
69
  // Keeps track of all devices that are found on the network
70
70
  this.lanDevices = []
71
71
 
72
+ // Expand any configured subnets (for devices on a different subnet than
73
+ // Homebridge, which the multicast scan can't reach) into a flat list of
74
+ // host IPs to scan directly. Capped so a wide mask can't flood the network.
75
+ this.scanHosts = this.expandSubnets(this.config.lanScanSubnets)
76
+
72
77
  // Add any devices that have a custom IP address in the config
73
78
  Object.keys(platform.deviceConf).forEach((device) => {
74
79
  if (platform.deviceConf[device].customIPAddress) {
@@ -191,11 +196,51 @@ export default class {
191
196
  })
192
197
  }
193
198
 
199
+ // Expand a comma-derived list of CIDR subnets into individual host IPs.
200
+ // Returns an empty array when nothing is configured or a subnet is invalid.
201
+ // The total is capped (a /24 is 254 hosts) so a wide mask can't flood.
202
+ expandSubnets(subnets) {
203
+ if (!Array.isArray(subnets) || subnets.length === 0) {
204
+ return []
205
+ }
206
+ const maxHosts = 1024
207
+ const hosts = []
208
+ subnets.forEach((cidr) => {
209
+ const [base, prefixStr] = cidr.split('/')
210
+ const prefix = Number.parseInt(prefixStr, 10)
211
+ const octets = base.split('.').map(o => Number.parseInt(o, 10))
212
+ if (prefix < 8 || prefix > 32 || octets.some(o => Number.isNaN(o) || o < 0 || o > 255)) {
213
+ this.log.warn('[LAN] ignoring invalid scan subnet [%s].', cidr)
214
+ return
215
+ }
216
+ const baseInt = ((octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]) >>> 0
217
+ const hostBits = 32 - prefix
218
+ const size = 2 ** hostBits
219
+ // Skip the network and broadcast addresses for a normal subnet
220
+ const first = hostBits > 1 ? baseInt + 1 : baseInt
221
+ const last = hostBits > 1 ? baseInt + size - 2 : baseInt + size - 1
222
+ for (let ip = first; ip <= last && hosts.length < maxHosts; ip += 1) {
223
+ hosts.push(`${(ip >>> 24) & 255}.${(ip >>> 16) & 255}.${(ip >>> 8) & 255}.${ip & 255}`)
224
+ }
225
+ })
226
+ if (hosts.length > 0) {
227
+ this.log.debug('[LAN] will also scan %s host(s) across configured subnet(s).', hosts.length)
228
+ }
229
+ return hosts
230
+ }
231
+
194
232
  // Send a request that asks for all devices in the multicast group
195
233
  sendScanCommand() {
196
234
  const scanCommand = JSON.stringify({ msg: { cmd: commands.scan, data: { account_topic: 'reserve' } } })
197
235
  this.log.debug('[LAN] scanning for devices over LAN...')
198
236
  this.sender.send(scanCommand, scanCommandPort, multicastIp)
237
+
238
+ // Also scan any configured subnets directly. Multicast doesn't cross
239
+ // between subnets, so devices on a different subnet than Homebridge are
240
+ // reached by sending the scan straight to each host (they reply on 4002).
241
+ this.scanHosts.forEach((host) => {
242
+ this.sender.send(scanCommand, scanCommandPort, host)
243
+ })
199
244
  }
200
245
 
201
246
  // Get all available LAN devices
package/lib/platform.js CHANGED
@@ -139,6 +139,23 @@ export default class {
139
139
  this.config[key] = val
140
140
  }
141
141
  break
142
+ case 'lanScanSubnets': {
143
+ // Comma-separated list of CIDR subnets to scan directly (for devices
144
+ // on a different subnet from Homebridge, which multicast can't reach)
145
+ if (typeof val !== 'string' || val.trim() === '') {
146
+ break
147
+ }
148
+ const subnets = val
149
+ .split(',')
150
+ .map(s => s.trim())
151
+ .filter(s => /^\d{1,3}(?:\.\d{1,3}){3}\/\d{1,2}$/.test(s))
152
+ if (subnets.length === 0) {
153
+ logIgnore(key)
154
+ } else {
155
+ this.config[key] = subnets
156
+ }
157
+ break
158
+ }
142
159
  case 'awsDisable':
143
160
  case 'bleDisable':
144
161
  case 'colourSafeMode':
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.30.1",
5
+ "version": "11.31.0-beta.0",
6
6
  "description": "Homebridge plugin to integrate Govee devices into HomeKit.",
7
7
  "author": {
8
8
  "name": "bwp91",