@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 +6 -0
- package/config.schema.json +10 -0
- package/lib/connection/lan.js +45 -0
- package/lib/platform.js +17 -0
- package/package.json +1 -1
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
|
package/config.schema.json
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",
|
package/lib/connection/lan.js
CHANGED
|
@@ -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