@homebridge-plugins/homebridge-wemo 7.6.0 → 7.7.1-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 +26 -0
- package/README.md +1 -1
- package/config.schema.json +6 -0
- package/lib/device/link-bulb.js +42 -8
- package/lib/homebridge-ui/public/index.html +1 -1
- package/lib/platform.js +78 -58
- package/lib/utils/functions.js +10 -0
- package/lib/utils/lang-en.js +4 -1
- package/package.json +10 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@homebridge-plugins/homebridge-wemo` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## v7.7.1 (Pending Release)
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- chore: exclude test files and the test config from the published package
|
|
10
|
+
- fix: restore debug logging when the plugin runs in a child bridge
|
|
11
|
+
- fix: only turn adaptive lighting off when a colour is genuinely chosen elsewhere
|
|
12
|
+
- fix: send a colour change when only the saturation moves, and mend the al cleanup
|
|
13
|
+
- fix: explain a port clash on the notification listener instead of crashing the bridge
|
|
14
|
+
- fix: stop gating a few log lines on a debug setting that was never readable
|
|
15
|
+
|
|
16
|
+
## v7.7.0 (2026-08-02)
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- chore: add a test setup with tests for the shared helper functions
|
|
21
|
+
- chore(github): run the build and tests in ci, on node 22, 24 and 26
|
|
22
|
+
- chore: use the same lint setup across every plugin
|
|
23
|
+
- chore: add a changelog:sync script to populate the pending section from the commits
|
|
24
|
+
- chore: count a repeated commit subject once when syncing the changelog
|
|
25
|
+
- chore(github): check the changelog against the commits in ci
|
|
26
|
+
- chore(deps): dependency updates
|
|
27
|
+
- docs: add node 26 to the supported node versions
|
|
28
|
+
- feat: only accept a device status notification from the address the device was discovered on, with a disableNotifySourceCheck option for networks that rewrite addresses (#277)
|
|
29
|
+
- fix: replace the ip dependency with a local subnet check, clearing its unfixable npm advisory
|
|
30
|
+
|
|
5
31
|
## v7.6.0 (2026-07-27)
|
|
6
32
|
|
|
7
33
|
### Changed
|
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ Homebridge plugin to integrate Wemo devices into HomeKit
|
|
|
31
31
|
### Prerequisites
|
|
32
32
|
|
|
33
33
|
- To use this plugin, you will need to already have:
|
|
34
|
-
- [Node](https://nodejs.org): latest version of `v22` or `
|
|
34
|
+
- [Node](https://nodejs.org): latest version of `v22`, `v24` or `v26` - any other major version is not supported.
|
|
35
35
|
- [Homebridge](https://homebridge.io): `v1.6` or above - refer to link for more information and installation instructions.
|
|
36
36
|
- For the UPnP connection, make sure your Homebridge instance has an allocated IP from the same IP network or VLAN as your Wemo devices. Otherwise, you should disable the UPnP connection to avoid connection errors.
|
|
37
37
|
|
package/config.schema.json
CHANGED
|
@@ -106,6 +106,11 @@
|
|
|
106
106
|
"title": "Disable Device Logging",
|
|
107
107
|
"description": "Global logging setting for accessory status changes. If true then accessory status changes will not be logged. This can also be set per accessory later in the config."
|
|
108
108
|
},
|
|
109
|
+
"disableNotifySourceCheck": {
|
|
110
|
+
"type": "boolean",
|
|
111
|
+
"title": "Disable Notify Source Check",
|
|
112
|
+
"description": "By default, a device status notification is only accepted when it comes from the address the device was discovered on. Enable this if your network rewrites addresses (for example Docker bridge networking or NAT) and the log shows notifications being ignored."
|
|
113
|
+
},
|
|
109
114
|
"removeByName": {
|
|
110
115
|
"title": "Remove By Name",
|
|
111
116
|
"type": "string"
|
|
@@ -646,6 +651,7 @@
|
|
|
646
651
|
"expandable": true,
|
|
647
652
|
"items": [
|
|
648
653
|
"disableDeviceLogging",
|
|
654
|
+
"disableNotifySourceCheck",
|
|
649
655
|
"hideConnectionErrors",
|
|
650
656
|
"discoveryInterval",
|
|
651
657
|
"wemoClient.discover_opts.interfaces",
|
package/lib/device/link-bulb.js
CHANGED
|
@@ -57,10 +57,22 @@ export default class {
|
|
|
57
57
|
this.service = this.accessory.getService(this.hapServ.Lightbulb)
|
|
58
58
|
|| this.accessory.addService(this.hapServ.Lightbulb)
|
|
59
59
|
|
|
60
|
-
//
|
|
60
|
+
// Adaptive lighting has just been switched off, so take away the three
|
|
61
|
+
// characteristics that make Home offer it. Only these need to go: the whole
|
|
62
|
+
// Lightbulb service used to be thrown away and rebuilt here, which does hide
|
|
63
|
+
// the icon but presents Home with what looks like a different light, and an
|
|
64
|
+
// owner can lose that light's room and its automations as a result.
|
|
61
65
|
if (this.alShift === -1 && this.accessory.context.adaptiveLighting) {
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
[
|
|
67
|
+
'SupportedCharacteristicValueTransitionConfiguration',
|
|
68
|
+
'CharacteristicValueTransitionControl',
|
|
69
|
+
'CharacteristicValueActiveTransitionCount',
|
|
70
|
+
].forEach((charName) => {
|
|
71
|
+
const char = this.hapChar[charName]
|
|
72
|
+
if (char && this.service.testCharacteristic(char)) {
|
|
73
|
+
this.service.removeCharacteristic(this.service.getCharacteristic(char))
|
|
74
|
+
}
|
|
75
|
+
})
|
|
64
76
|
this.accessory.context.adaptiveLighting = false
|
|
65
77
|
}
|
|
66
78
|
|
|
@@ -85,6 +97,14 @@ export default class {
|
|
|
85
97
|
this.service.getCharacteristic(this.hapChar.Hue).onSet(async (value) => {
|
|
86
98
|
await this.internalColourUpdate(value)
|
|
87
99
|
})
|
|
100
|
+
// Saturation needs a handler of its own. HomeKit writes hue and saturation
|
|
101
|
+
// separately, so with only hue listened for, dragging saturation alone -
|
|
102
|
+
// the same colour made paler or richer - reached nothing and the bulb
|
|
103
|
+
// never changed. Both handlers share one update, and the debounce inside
|
|
104
|
+
// it collapses a hue-and-saturation pair into a single command.
|
|
105
|
+
this.service.getCharacteristic(this.hapChar.Saturation).onSet(async () => {
|
|
106
|
+
await this.internalColourUpdate(this.service.getCharacteristic(this.hapChar.Hue).value)
|
|
107
|
+
})
|
|
88
108
|
this.cacheHue = this.service.getCharacteristic(this.hapChar.Hue).value
|
|
89
109
|
this.cacheSat = this.service.getCharacteristic(this.hapChar.Saturation).value
|
|
90
110
|
} else {
|
|
@@ -109,6 +129,10 @@ export default class {
|
|
|
109
129
|
customTemperatureAdjustment: this.alShift,
|
|
110
130
|
})
|
|
111
131
|
this.accessory.configureController(this.alController)
|
|
132
|
+
// Without this the cleanup that removes the adaptive lighting
|
|
133
|
+
// characteristics can never run, because it is guarded on this flag -
|
|
134
|
+
// so switching adaptive lighting off left Home still offering it.
|
|
135
|
+
this.accessory.context.adaptiveLighting = true
|
|
112
136
|
}
|
|
113
137
|
}
|
|
114
138
|
|
|
@@ -289,13 +313,15 @@ export default class {
|
|
|
289
313
|
return
|
|
290
314
|
}
|
|
291
315
|
|
|
292
|
-
// Don't continue if this
|
|
293
|
-
|
|
316
|
+
// Don't continue if this colour is the same as before. Saturation is part
|
|
317
|
+
// of that question: comparing hue alone meant a change of saturation at
|
|
318
|
+
// the same hue looked like no change at all, and nothing was sent.
|
|
319
|
+
const currentSat = this.service.getCharacteristic(this.hapChar.Saturation).value
|
|
320
|
+
if (this.cacheHue === value && this.cacheSat === currentSat) {
|
|
294
321
|
return
|
|
295
322
|
}
|
|
296
323
|
|
|
297
324
|
// First convert to RGB
|
|
298
|
-
const currentSat = this.service.getCharacteristic(this.hapChar.Saturation).value
|
|
299
325
|
const [r, g, b] = hs2rgb(value, currentSat)
|
|
300
326
|
|
|
301
327
|
// Then convert the RGB to the values needed for Wemo
|
|
@@ -424,7 +450,10 @@ export default class {
|
|
|
424
450
|
// Log the change if appropriate
|
|
425
451
|
this.accessory.log(`${platformLang.curColour} [X:${valueX} Y:${valueY}]`)
|
|
426
452
|
|
|
427
|
-
//
|
|
453
|
+
// Somebody chose this colour outside HomeKit - in the Wemo app, on a
|
|
454
|
+
// remote, or at the wall. Homebridge catches changes made in the Home
|
|
455
|
+
// app itself, but nothing else sees these, so adaptive lighting would
|
|
456
|
+
// carry on and move the bulb off that colour within minutes.
|
|
428
457
|
if (this.alController?.isAdaptiveLightingActive()) {
|
|
429
458
|
this.alController.disableAdaptiveLighting()
|
|
430
459
|
this.accessory.log(platformLang.alDisabled)
|
|
@@ -449,7 +478,12 @@ export default class {
|
|
|
449
478
|
const mToK = Math.round(1000000 / value)
|
|
450
479
|
this.accessory.log(`${platformLang.curCCT} [${mToK}K / ${value}M]`)
|
|
451
480
|
|
|
452
|
-
//
|
|
481
|
+
// A bulb repeating back the temperature adaptive lighting just set can
|
|
482
|
+
// land a few mireds off through the conversion, and treating that as the
|
|
483
|
+
// owner's doing would switch adaptive lighting off moments after it was
|
|
484
|
+
// switched on. Further apart than that and somebody set this temperature
|
|
485
|
+
// themselves, outside HomeKit - which nothing else would notice, so
|
|
486
|
+
// adaptive lighting has to be stopped here.
|
|
453
487
|
if (!Number.isNaN(this.cacheMired)) {
|
|
454
488
|
const diff = Math.abs(value - this.cacheMired) > 20
|
|
455
489
|
if (this.alController?.isAdaptiveLightingActive() && diff) {
|
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
<ul>
|
|
193
193
|
<li>To use this plugin, you will need to already have:</li>
|
|
194
194
|
<ul>
|
|
195
|
-
<li><a href="https://nodejs.org" target="_blank">Node</a>: latest version of <code>v22</code> or <code>
|
|
195
|
+
<li><a href="https://nodejs.org" target="_blank">Node</a>: latest version of <code>v22</code>, <code>v24</code> or <code>v26</code> - any other major version is not supported.</li>
|
|
196
196
|
<li><a href="https://homebridge.io" target="_blank">Homebridge</a>: <code>v1.6</code> or above - refer to link for more information and installation instructions.</li>
|
|
197
197
|
<li>For the UPnP connection, make sure your Homebridge instance has an allocated IP from the same IP network or VLAN as your Wemo devices. Otherwise, you should disable the UPnP connection to avoid connection errors.</li>
|
|
198
198
|
</ul>
|
package/lib/platform.js
CHANGED
|
@@ -5,7 +5,6 @@ import process from 'node:process'
|
|
|
5
5
|
import { URL as url } from 'node:url'
|
|
6
6
|
|
|
7
7
|
import axios from 'axios'
|
|
8
|
-
import ip from 'ip'
|
|
9
8
|
import nodeSSDP from 'node-ssdp'
|
|
10
9
|
import { parseStringPromise } from 'xml2js'
|
|
11
10
|
import { create as xmlCreate } from 'xmlbuilder'
|
|
@@ -16,7 +15,7 @@ import deviceTypes from './device/index.js'
|
|
|
16
15
|
import eveService from './fakegato/fakegato-history.js'
|
|
17
16
|
import platformConsts from './utils/constants.js'
|
|
18
17
|
import eveChars from './utils/eve-chars.js'
|
|
19
|
-
import { parseError, parseSerialNumber } from './utils/functions.js'
|
|
18
|
+
import { parseError, parseSerialNumber, sameIpv4Subnet } from './utils/functions.js'
|
|
20
19
|
import platformLang from './utils/lang-en.js'
|
|
21
20
|
|
|
22
21
|
const { Client: ssdp } = nodeSSDP
|
|
@@ -24,6 +23,7 @@ const require = createRequire(import.meta.url)
|
|
|
24
23
|
const plugin = require('../package.json')
|
|
25
24
|
|
|
26
25
|
const devicesInHB = new Map()
|
|
26
|
+
const notifySourceWarned = new Set()
|
|
27
27
|
const trailingSlashRegex = /\/\s*$/
|
|
28
28
|
|
|
29
29
|
let listenerServer
|
|
@@ -41,7 +41,6 @@ export default class {
|
|
|
41
41
|
try {
|
|
42
42
|
this.api = api
|
|
43
43
|
this.log = log
|
|
44
|
-
this.isBeta = process.argv.includes('-D')
|
|
45
44
|
|
|
46
45
|
// Configuration objects for accessories
|
|
47
46
|
this.ignoredDevices = []
|
|
@@ -113,6 +112,7 @@ export default class {
|
|
|
113
112
|
const [key, val] = entry
|
|
114
113
|
switch (key) {
|
|
115
114
|
case 'disableDeviceLogging':
|
|
115
|
+
case 'disableNotifySourceCheck':
|
|
116
116
|
case 'disableUPNP':
|
|
117
117
|
case 'hideConnectionErrors':
|
|
118
118
|
if (typeof val === 'string') {
|
|
@@ -343,13 +343,16 @@ export default class {
|
|
|
343
343
|
this.log('%s.', platformLang.initialised)
|
|
344
344
|
|
|
345
345
|
// Sort out some logging functions
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
346
|
+
// Homebridge already decides whether debug lines are shown, and it gets it
|
|
347
|
+
// right in a child bridge too. This used to work it out for itself by
|
|
348
|
+
// looking for `-D` in the plugin's own process arguments, which a child
|
|
349
|
+
// bridge does not receive unless that bridge has its own debug setting
|
|
350
|
+
// turned on - so debug logging silently disappeared for anyone running the
|
|
351
|
+
// plugin in a child bridge with debug enabled globally.
|
|
352
|
+
//
|
|
353
|
+
// `debugWarn` has no equivalent on Homebridge's logger, so it goes out as
|
|
354
|
+
// a debug line too rather than being invented here.
|
|
355
|
+
this.log.debugWarn = (...args) => this.log.debug(...args)
|
|
353
356
|
|
|
354
357
|
// Set up the discovery run counter
|
|
355
358
|
this.discoveryRun = -1
|
|
@@ -423,44 +426,68 @@ export default class {
|
|
|
423
426
|
let body = ''
|
|
424
427
|
const accessory = devicesInHB.get(req.url.substring(1))
|
|
425
428
|
if (req.method === 'NOTIFY' && accessory) {
|
|
426
|
-
//
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
429
|
+
// Only accept a notification from the address the device was discovered on.
|
|
430
|
+
// Anything on the network can reach this listener, so without this check a
|
|
431
|
+
// notification for a known device could be forged from elsewhere. Networks
|
|
432
|
+
// which rewrite source addresses (docker bridge, some NAT) can turn this
|
|
433
|
+
// off with the disableNotifySourceCheck setting.
|
|
434
|
+
const remoteIp = (req.socket.remoteAddress || '').replace('::ffff:', '')
|
|
435
|
+
if (this.config.disableNotifySourceCheck || remoteIp === accessory.context.ipAddress) {
|
|
436
|
+
// A notification from a known device
|
|
437
|
+
req.on('data', (chunk) => {
|
|
438
|
+
body += chunk.toString()
|
|
439
|
+
})
|
|
440
|
+
req.on('end', () => this.httpClient.receiveDeviceUpdate(accessory, body))
|
|
441
|
+
} else if (!notifySourceWarned.has(accessory.UUID)) {
|
|
442
|
+
// Warn once per accessory, so a misconfigured network does not flood the log
|
|
443
|
+
notifySourceWarned.add(accessory.UUID)
|
|
444
|
+
this.log.warn(
|
|
445
|
+
'[%s] %s [%s] %s [%s] - %s.',
|
|
446
|
+
accessory.displayName,
|
|
447
|
+
platformLang.notifyIgnored,
|
|
448
|
+
remoteIp,
|
|
449
|
+
platformLang.notifyIgnoredAs,
|
|
450
|
+
accessory.context.ipAddress,
|
|
451
|
+
platformLang.notifyIgnoredOpt,
|
|
452
|
+
)
|
|
453
|
+
}
|
|
431
454
|
}
|
|
432
455
|
res.writeHead(200)
|
|
433
456
|
res.end()
|
|
434
457
|
})
|
|
435
458
|
|
|
459
|
+
// A failure to listen - the configured port already taken, or a privileged
|
|
460
|
+
// port without the rights to use it - arrives as an 'error' event, not
|
|
461
|
+
// through the listen callback. Node throws an 'error' event that nobody is
|
|
462
|
+
// listening for, so without this the bridge dies on a port clash and
|
|
463
|
+
// restarts into the same clash. The callback below only ever runs on
|
|
464
|
+
// success, and node calls it with no arguments.
|
|
465
|
+
listenerServer.on('error', (err) => {
|
|
466
|
+
this.log.warn('%s: %s.', platformLang.listenerError, parseError(err))
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
const logListenerPort = () => {
|
|
470
|
+
this.log.debug('%s [%s].', platformLang.listenerPort, listenerServer.address().port)
|
|
471
|
+
}
|
|
472
|
+
|
|
436
473
|
// Start listening on the above created server
|
|
437
474
|
if (this.config.wemoClient.listen_interface) {
|
|
438
475
|
// User has defined a specific network interface to listen on
|
|
439
|
-
listenerServer.listen(this.config.wemoClient.port, this.getLocalInterfaceAddress(),
|
|
440
|
-
if (err) {
|
|
441
|
-
this.log.warn('%s: %s.', platformLang.listenerError, err)
|
|
442
|
-
} else if (this.config.debug) {
|
|
443
|
-
// Log the port of the listener server in debug mode
|
|
444
|
-
this.log('%s [%s].', platformLang.listenerPort, listenerServer.address().port)
|
|
445
|
-
}
|
|
446
|
-
})
|
|
476
|
+
listenerServer.listen(this.config.wemoClient.port, this.getLocalInterfaceAddress(), logListenerPort)
|
|
447
477
|
} else {
|
|
448
478
|
// User has not defined a specific network interface to listen on
|
|
449
|
-
listenerServer.listen(this.config.wemoClient.port,
|
|
450
|
-
if (err) {
|
|
451
|
-
this.log.warn('%s: %s', platformLang.listenerError, err)
|
|
452
|
-
} else if (this.config.debug) {
|
|
453
|
-
// Log the port of the listener server in debug mode
|
|
454
|
-
this.log('%s [%s].', platformLang.listenerPort, listenerServer.address().port)
|
|
455
|
-
}
|
|
456
|
-
})
|
|
479
|
+
listenerServer.listen(this.config.wemoClient.port, logListenerPort)
|
|
457
480
|
}
|
|
458
481
|
|
|
459
482
|
// Set up the SSDP client if the user has not specified manual devices only
|
|
460
483
|
if (this.config.mode !== 'manual') {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
484
|
+
// The discovery client's own chatter goes to the debug log, where
|
|
485
|
+
// Homebridge decides whether it is shown. It used to be handed the
|
|
486
|
+
// normal logger only when the plugin thought debug was on, which it
|
|
487
|
+
// could not tell in a child bridge. The wrapper matters: Homebridge's
|
|
488
|
+
// `debug` is not bound to its logger, so passing it bare would throw
|
|
489
|
+
// the first time the discovery client tried to log anything.
|
|
490
|
+
this.config.wemoClient.discover_opts.customLogger = (...args) => this.log.debug(...args)
|
|
464
491
|
ssdpClient = new ssdp(this.config.wemoClient.discover_opts)
|
|
465
492
|
}
|
|
466
493
|
|
|
@@ -490,19 +517,13 @@ export default class {
|
|
|
490
517
|
|
|
491
518
|
// Shutdown the listener server if it's running
|
|
492
519
|
if (listenerServer) {
|
|
493
|
-
listenerServer.close(() =>
|
|
494
|
-
if (this.config.debug) {
|
|
495
|
-
this.log('%s.', platformLang.listenerClosed)
|
|
496
|
-
}
|
|
497
|
-
})
|
|
520
|
+
listenerServer.close(() => this.log.debug('%s.', platformLang.listenerClosed))
|
|
498
521
|
}
|
|
499
522
|
|
|
500
523
|
// Stop the SSDP client if it's running
|
|
501
524
|
if (ssdpClient) {
|
|
502
525
|
ssdpClient.stop()
|
|
503
|
-
|
|
504
|
-
this.log('%s.', platformLang.ssdpStopped)
|
|
505
|
-
}
|
|
526
|
+
this.log.debug('%s.', platformLang.ssdpStopped)
|
|
506
527
|
}
|
|
507
528
|
|
|
508
529
|
// Close accessory subscriptions
|
|
@@ -757,22 +778,21 @@ export default class {
|
|
|
757
778
|
}
|
|
758
779
|
|
|
759
780
|
applyAccessoryLogging(accessory) {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
accessory.logWarn = msg => this.log.warn('[%s] %s.', accessory.displayName, msg)
|
|
772
|
-
}
|
|
773
|
-
accessory.logDebug = () => {}
|
|
774
|
-
accessory.logDebugWarn = () => {}
|
|
781
|
+
// Debug lines always go to Homebridge's debug logger, which prints them only
|
|
782
|
+
// when debug is on - so there is nothing to decide here. Working it out from
|
|
783
|
+
// the process arguments instead is what lost debug logging entirely for
|
|
784
|
+
// anyone running the plugin in a child bridge.
|
|
785
|
+
accessory.logDebug = msg => this.log.debug('[%s] %s.', accessory.displayName, msg)
|
|
786
|
+
accessory.logDebugWarn = accessory.logDebug
|
|
787
|
+
|
|
788
|
+
if (this.config.disableDeviceLogging) {
|
|
789
|
+
accessory.log = () => {}
|
|
790
|
+
accessory.logWarn = () => {}
|
|
791
|
+
return
|
|
775
792
|
}
|
|
793
|
+
|
|
794
|
+
accessory.log = msg => this.log('[%s] %s.', accessory.displayName, msg)
|
|
795
|
+
accessory.logWarn = msg => this.log.warn('[%s] %s.', accessory.displayName, msg)
|
|
776
796
|
}
|
|
777
797
|
|
|
778
798
|
async initialiseDevice(device) {
|
|
@@ -1419,7 +1439,7 @@ export default class {
|
|
|
1419
1439
|
const [k2] = subEntry
|
|
1420
1440
|
const address = interfaces[k][k2]
|
|
1421
1441
|
if ((address.family === 'IPv4' || address.family === 4) && !address.internal) {
|
|
1422
|
-
if (targetNetwork &&
|
|
1442
|
+
if (targetNetwork && sameIpv4Subnet(address.address, targetNetwork, address.netmask)) {
|
|
1423
1443
|
// Try to find IP address on the same IP network as the device's location
|
|
1424
1444
|
addresses.unshift(address.address)
|
|
1425
1445
|
} else {
|
package/lib/utils/functions.js
CHANGED
|
@@ -29,6 +29,15 @@ function parseError(err, hideStack = []) {
|
|
|
29
29
|
return toReturn
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// Whether two IPv4 addresses sit in the same subnet for the given netmask.
|
|
33
|
+
// Replaces the single ip.subnet().contains() call this plugin used from the
|
|
34
|
+
// `ip` package, which carries an unfixed npm advisory (GHSA in its isPublic).
|
|
35
|
+
function sameIpv4Subnet(addressA, addressB, netmask) {
|
|
36
|
+
const toInt = addr => addr.split('.').reduce((acc, octet) => (acc * 256) + Number.parseInt(octet, 10), 0)
|
|
37
|
+
const mask = toInt(netmask)
|
|
38
|
+
return ((toInt(addressA) & mask) >>> 0) === ((toInt(addressB) & mask) >>> 0)
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
function parseSerialNumber(input) {
|
|
33
42
|
return input
|
|
34
43
|
.toString()
|
|
@@ -48,5 +57,6 @@ export {
|
|
|
48
57
|
hasProperty,
|
|
49
58
|
parseError,
|
|
50
59
|
parseSerialNumber,
|
|
60
|
+
sameIpv4Subnet,
|
|
51
61
|
sleep,
|
|
52
62
|
}
|
package/lib/utils/lang-en.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
accNotFound: 'accessory not found',
|
|
3
3
|
accNotReady: 'has not been discovered yet so commands will fail',
|
|
4
|
-
alDisabled: 'adaptive lighting
|
|
4
|
+
alDisabled: 'adaptive lighting turned off, as the colour was changed outside HomeKit',
|
|
5
5
|
awaiting: 'The following devices have still not been initially found',
|
|
6
6
|
brand: 'Belkin Wemo',
|
|
7
7
|
brightnessFail: 'could not obtain updated brightness as',
|
|
@@ -90,6 +90,9 @@ export default {
|
|
|
90
90
|
noInterface: 'Unable to find interface',
|
|
91
91
|
noPort: 'could not find correct port for device',
|
|
92
92
|
noService: 'device does not have this service',
|
|
93
|
+
notifyIgnored: 'ignoring a status notification from',
|
|
94
|
+
notifyIgnoredAs: 'as it does not match the device address',
|
|
95
|
+
notifyIgnoredOpt: 'if your network rewrites addresses (docker/nat), enable the Disable Notify Source Check setting',
|
|
93
96
|
noServices: 'device does not have any upnp services',
|
|
94
97
|
noSockets: 'NodeSSDP error [no sockets], if this continues to happen try restarting Homebridge',
|
|
95
98
|
notConfigured: 'Plugin has not been configured',
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@homebridge-plugins/homebridge-wemo",
|
|
3
3
|
"alias": "Wemo",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "7.
|
|
5
|
+
"version": "7.7.1-beta.0",
|
|
6
6
|
"description": "Homebridge plugin to integrate Wemo devices into HomeKit.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "bwp91",
|
|
@@ -49,22 +49,27 @@
|
|
|
49
49
|
"main": "lib/index.js",
|
|
50
50
|
"engines": {
|
|
51
51
|
"homebridge": "^1.6.0 || ^2.0.0",
|
|
52
|
-
"node": "^22.12.0 || ^24.0.0"
|
|
52
|
+
"node": "^22.12.0 || ^24.0.0 || ^26.0.0"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"lint": "eslint . --max-warnings=0",
|
|
56
|
-
"lint:fix": "npm run lint -- --fix"
|
|
56
|
+
"lint:fix": "npm run lint -- --fix",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"test:watch": "vitest watch",
|
|
59
|
+
"test-coverage": "npm run test -- --coverage",
|
|
60
|
+
"changelog:sync": "node .github/scripts/sync-changelog.mjs"
|
|
57
61
|
},
|
|
58
62
|
"dependencies": {
|
|
59
63
|
"@homebridge/plugin-ui-utils": "^2.2.5",
|
|
60
64
|
"axios": "^1.18.1",
|
|
61
|
-
"ip": "^2.0.1",
|
|
62
65
|
"node-ssdp": "^4.0.1",
|
|
63
66
|
"p-queue": "^9.3.3",
|
|
64
67
|
"xml2js": "^0.6.2",
|
|
65
68
|
"xmlbuilder": "^15.1.1"
|
|
66
69
|
},
|
|
67
70
|
"devDependencies": {
|
|
68
|
-
"@antfu/eslint-config": "^9.2.0"
|
|
71
|
+
"@antfu/eslint-config": "^9.2.0",
|
|
72
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
73
|
+
"vitest": "^4.1.10"
|
|
69
74
|
}
|
|
70
75
|
}
|