homebridge-lib 6.3.7 → 6.3.8
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/README.md +1 -6
- package/cli/hap.js +2 -2
- package/cli/json.js +2 -2
- package/cli/sysinfo.js +2 -2
- package/cli/upnp.js +2 -2
- package/index.js +41 -105
- package/lib/AccessoryDelegate.js +1 -1
- package/lib/AdaptiveLighting.js +3 -3
- package/lib/Delegate.js +2 -1
- package/lib/EveHomeKitTypes.js +2 -2
- package/lib/MyHomeKitTypes.js +2 -2
- package/lib/Platform.js +7 -5
- package/lib/ServiceDelegate/AccessoryInformation.js +1 -3
- package/lib/ServiceDelegate/Battery.js +1 -3
- package/lib/ServiceDelegate/Dummy.js +1 -3
- package/lib/ServiceDelegate/History.js +6 -11
- package/lib/ServiceDelegate/ServiceLabel.js +1 -3
- package/lib/UiServer.js +1 -2
- package/package.json +4 -6
- package/lib/Colour.js +0 -323
- package/lib/CommandLineParser.js +0 -311
- package/lib/CommandLineTool.js +0 -328
- package/lib/HapTool.js +0 -92
- package/lib/HttpClient.js +0 -478
- package/lib/JsonFormatter.js +0 -200
- package/lib/JsonTool.js +0 -166
- package/lib/OptionParser.js +0 -886
- package/lib/SysinfoTool.js +0 -79
- package/lib/SystemInfo.js +0 -522
- package/lib/UpnpClient.js +0 -217
- package/lib/UpnpTool.js +0 -148
package/lib/UpnpClient.js
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
// homebridge-lib/lib/UpnpClient.js
|
|
2
|
-
//
|
|
3
|
-
// Library for Homebridge plugins.
|
|
4
|
-
// Copyright © 2018-2023 Erik Baauw. All rights reserved.
|
|
5
|
-
|
|
6
|
-
'use strict'
|
|
7
|
-
|
|
8
|
-
const homebridgeLib = require('../index')
|
|
9
|
-
|
|
10
|
-
const dgram = require('dgram')
|
|
11
|
-
const events = require('events')
|
|
12
|
-
|
|
13
|
-
const listener = {}
|
|
14
|
-
|
|
15
|
-
// Convert raw UPnP message to message object.
|
|
16
|
-
function convert (rawMessage) {
|
|
17
|
-
const message = {}
|
|
18
|
-
const lines = rawMessage.toString().trim().split('\r\n')
|
|
19
|
-
if (lines && lines[0]) {
|
|
20
|
-
message.status = lines[0]
|
|
21
|
-
for (const line of lines) {
|
|
22
|
-
const fields = line.split(': ')
|
|
23
|
-
if (fields.length === 2) {
|
|
24
|
-
message[fields[0].toLowerCase()] = fields[1]
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return message
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Universal Plug and Play client.
|
|
32
|
-
* @extends EventEmitter
|
|
33
|
-
*/
|
|
34
|
-
class UpnpClient extends events.EventEmitter {
|
|
35
|
-
/** Create a new instance of a Universal Plug and Play client.
|
|
36
|
-
* @param {object} params - Paramters.
|
|
37
|
-
* @param {function} [params.filter=() => { return true }] - Function to
|
|
38
|
-
* filter which UPnP messages should result in a
|
|
39
|
-
* {@link UpnpClient#event:deviceAlive deviceAlive} or a
|
|
40
|
-
* {@link UpnpClient#event:deviceFound deviceFound} event.
|
|
41
|
-
* @param {integer} [params.timemout=5] - Timeout (in seconds) for
|
|
42
|
-
* {@link UpnpClient@search search()} to listen for responses.
|
|
43
|
-
* @param {string} [params.class='upnp:rootdevice'] - Filter on class which UPnP
|
|
44
|
-
* messages should result in a
|
|
45
|
-
* {@link UpnpClient#event:deviceAlive deviceAlive} or a
|
|
46
|
-
* {@link UpnpClient#event:deviceFound deviceFound} event.
|
|
47
|
-
*/
|
|
48
|
-
constructor (params = {}) {
|
|
49
|
-
super()
|
|
50
|
-
this._options = {
|
|
51
|
-
filter: () => { return true },
|
|
52
|
-
hostname: '239.255.255.250',
|
|
53
|
-
port: 1900,
|
|
54
|
-
timeout: 5,
|
|
55
|
-
class: 'upnp:rootdevice'
|
|
56
|
-
}
|
|
57
|
-
const optionParser = new homebridgeLib.OptionParser(this._options)
|
|
58
|
-
optionParser.functionKey('filter')
|
|
59
|
-
// optionParser.hostKey() // TODO: need global listener per hostname.
|
|
60
|
-
optionParser.stringKey('class', true)
|
|
61
|
-
optionParser.intKey('timeout', 1, 60)
|
|
62
|
-
optionParser.parse(params)
|
|
63
|
-
this.requestId = 0
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
_onError (error) {
|
|
67
|
-
listener.socket = null
|
|
68
|
-
/** Emitted in case of error.
|
|
69
|
-
* @event UpnpClient#error
|
|
70
|
-
* @param {Error} error - The error.
|
|
71
|
-
*/
|
|
72
|
-
this.emit('error', error)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
_onListening () {
|
|
76
|
-
if (listener.init === this) {
|
|
77
|
-
listener.host = listener.socket.address().address +
|
|
78
|
-
':' + listener.socket.address().port
|
|
79
|
-
listener.socket.addMembership(this._options.hostname)
|
|
80
|
-
delete listener.init
|
|
81
|
-
}
|
|
82
|
-
/** Emitted when listening to UPnP alive broadcasts.
|
|
83
|
-
* @event UpnpClient#listening
|
|
84
|
-
* @param {string} host - IP address and port listening on.
|
|
85
|
-
*/
|
|
86
|
-
this.emit('listening', listener.host)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
_onMessage (buffer, rinfo) {
|
|
90
|
-
const rawMessage = buffer.toString().trim()
|
|
91
|
-
const message = convert(rawMessage)
|
|
92
|
-
if (
|
|
93
|
-
message.status !== 'NOTIFY * HTTP/1.1' ||
|
|
94
|
-
message.nts !== 'ssdp:alive'
|
|
95
|
-
) {
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
if (
|
|
99
|
-
this._options.class !== 'ssdp:all' &&
|
|
100
|
-
message.nt !== this._options.class
|
|
101
|
-
) {
|
|
102
|
-
return
|
|
103
|
-
}
|
|
104
|
-
if (this._options.filter(message)) {
|
|
105
|
-
/** Emitted for each alive message received, that passes the filers.
|
|
106
|
-
* @event UpnpClient#deviceAlive
|
|
107
|
-
* @param {string} address - IP address of the device.
|
|
108
|
-
* @param {object} message - The parsed message.
|
|
109
|
-
* @param {string} rawMessage - The raw message.
|
|
110
|
-
*/
|
|
111
|
-
this.emit('deviceAlive', rinfo.address, message, rawMessage)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** Listen for UPnP alive broadcast messages.
|
|
116
|
-
*/
|
|
117
|
-
async listen () {
|
|
118
|
-
if (listener.socket == null) {
|
|
119
|
-
listener.socket = dgram.createSocket({ type: 'udp4', reuseAddr: true })
|
|
120
|
-
listener.init = this
|
|
121
|
-
listener.socket.bind(this._options.port)
|
|
122
|
-
} else if (listener.init != null) {
|
|
123
|
-
const [host] = await events.once(listener.init, 'listening')
|
|
124
|
-
this.emit('listening', host)
|
|
125
|
-
} else {
|
|
126
|
-
this.emit('listening', listener.host)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
listener.socket
|
|
130
|
-
.on('error', this._onError.bind(this))
|
|
131
|
-
.on('listening', this._onListening.bind(this))
|
|
132
|
-
.on('message', this._onMessage.bind(this))
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/** Stop listening for UPnP alive broadcast messages.
|
|
136
|
-
*/
|
|
137
|
-
async stopListen () {
|
|
138
|
-
if (listener.socket != null) {
|
|
139
|
-
listener.socket
|
|
140
|
-
.removeListener('error', this._onError.bind(this))
|
|
141
|
-
.removeListener('listening', this._onListening.bind(this))
|
|
142
|
-
.removeListener('message', this._onMessage.bind(this))
|
|
143
|
-
this.emit('stopListening', listener.host)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/** Issue a UPnP search message and listen for responses.
|
|
148
|
-
*/
|
|
149
|
-
search () {
|
|
150
|
-
const socket = dgram.createSocket({ type: 'udp4' })
|
|
151
|
-
const request = Buffer.from([
|
|
152
|
-
'M-SEARCH * HTTP/1.1',
|
|
153
|
-
`HOST: ${this._options.hostname}:${this._options.port}`,
|
|
154
|
-
'MAN: "ssdp:discover"',
|
|
155
|
-
`MX: ${this._options.timeout}`,
|
|
156
|
-
`ST: ${this._options.class}`,
|
|
157
|
-
''
|
|
158
|
-
].join('\r\n'))
|
|
159
|
-
let host
|
|
160
|
-
|
|
161
|
-
socket
|
|
162
|
-
.on('error', (error) => {
|
|
163
|
-
this.emit('error', error)
|
|
164
|
-
})
|
|
165
|
-
.on('listening', () => {
|
|
166
|
-
host = socket.address().address + ':' + socket.address().port
|
|
167
|
-
/** Emitted when searching for UPnP devices.
|
|
168
|
-
* @event UpnpClient#searching
|
|
169
|
-
* @param {string} host - IP address and port listening on.
|
|
170
|
-
*/
|
|
171
|
-
this.emit('searching', host)
|
|
172
|
-
})
|
|
173
|
-
.on('message', (buffer, rinfo) => {
|
|
174
|
-
const rawMessage = buffer.toString().trim()
|
|
175
|
-
const message = convert(rawMessage)
|
|
176
|
-
if (message.status !== 'HTTP/1.1 200 OK') {
|
|
177
|
-
return
|
|
178
|
-
}
|
|
179
|
-
if (
|
|
180
|
-
this._options.class !== 'ssdp:all' &&
|
|
181
|
-
message.st !== this._options.class
|
|
182
|
-
) {
|
|
183
|
-
return
|
|
184
|
-
}
|
|
185
|
-
if (this._options.filter(message)) {
|
|
186
|
-
/** Emitted for each response received, that passes the filers.
|
|
187
|
-
* @event UpnpClient#deviceFound
|
|
188
|
-
* @param {string} address - IP address of the device.
|
|
189
|
-
* @param {object} message - The parsed message.
|
|
190
|
-
* @param {string} rawMessage - The raw message.
|
|
191
|
-
*/
|
|
192
|
-
this.emit('deviceFound', rinfo.address, message, rawMessage)
|
|
193
|
-
}
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
const requestInfo = {
|
|
197
|
-
host: this._options.hostname + ':' + this._options.port,
|
|
198
|
-
id: ++this.requestId,
|
|
199
|
-
method: 'M-SEARCH',
|
|
200
|
-
resource: '*'
|
|
201
|
-
}
|
|
202
|
-
this.emit('request', requestInfo)
|
|
203
|
-
socket.send(
|
|
204
|
-
request, 0, request.length, this._options.port, this._options.hostname
|
|
205
|
-
)
|
|
206
|
-
setTimeout(() => {
|
|
207
|
-
socket.close()
|
|
208
|
-
/** Emitted when searching has finished.
|
|
209
|
-
* @event UpnpClient#searchDone
|
|
210
|
-
* @param {string} host - IP address and port listening on.
|
|
211
|
-
*/
|
|
212
|
-
this.emit('searchDone', host)
|
|
213
|
-
}, this._options.timeout * 1000)
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
module.exports = UpnpClient
|
package/lib/UpnpTool.js
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// homebridge-lib/cli/upnp.js
|
|
4
|
-
//
|
|
5
|
-
// Logger for UPnP device announcements.
|
|
6
|
-
// Copyright © 2018-2023 Erik Baauw. All rights reserved.
|
|
7
|
-
|
|
8
|
-
'use strict'
|
|
9
|
-
|
|
10
|
-
const homebridgeLib = require('../index')
|
|
11
|
-
|
|
12
|
-
const { b, u } = homebridgeLib.CommandLineTool
|
|
13
|
-
|
|
14
|
-
const usage = `${b('upnp')} [${b('-hVDadnprsz')}] [${b('-c')} ${u('class')}] [${b('-t')} ${u('timeout')}]`
|
|
15
|
-
const help = `UPnP tool.
|
|
16
|
-
|
|
17
|
-
Search for UPnP devices and print found devices as JSON.
|
|
18
|
-
When running as daemon or service, log UPnP alive broadcasts as JSON.
|
|
19
|
-
|
|
20
|
-
Usage: ${usage}
|
|
21
|
-
|
|
22
|
-
Parameters:
|
|
23
|
-
${b('-h')}, ${b('--help')}
|
|
24
|
-
Print this help and exit.
|
|
25
|
-
|
|
26
|
-
${b('-V')}, ${b('--version')}
|
|
27
|
-
Print version and exit.
|
|
28
|
-
|
|
29
|
-
${b('-D')}, ${b('--debug')}
|
|
30
|
-
Print debug messages.
|
|
31
|
-
|
|
32
|
-
${b('-a')}, ${b('--all')}
|
|
33
|
-
Short for ${b('-c ssdp:all')}.
|
|
34
|
-
|
|
35
|
-
${b('-c')} ${u('class')}, ${b('--class=')}${u('class')}
|
|
36
|
-
Search for ${u('class')} instead of default ${b('ssdp:rootdevice')}.
|
|
37
|
-
|
|
38
|
-
${b('-d')}, ${b('--daemon')}
|
|
39
|
-
Run as daemon. Listen for UPnP alive broadcasts instead of searching.
|
|
40
|
-
|
|
41
|
-
${b('-n')}, ${b('--noWhiteSpace')}
|
|
42
|
-
Do not include spaces nor newlines in JSON output.
|
|
43
|
-
|
|
44
|
-
${b('-p')}, ${b('--hue')}
|
|
45
|
-
Search for Philips Hue bridges and/or deCONZ gateways.
|
|
46
|
-
|
|
47
|
-
${b('-r')}, ${b('--raw')}
|
|
48
|
-
Do not parse messages, output raw message data instead of JSON.
|
|
49
|
-
|
|
50
|
-
${b('-s')}, ${b('--service')}
|
|
51
|
-
Run as daemon. Listen for UPnP alive broadcasts instead of searching.
|
|
52
|
-
|
|
53
|
-
${b('-t')} ${u('timeout')}, ${b('--timeout=')}${u('timeout')}
|
|
54
|
-
Search for ${u('timeout')} seconds instead of default ${b('5')}.
|
|
55
|
-
|
|
56
|
-
${b('-z')}, ${b('--sonos')}
|
|
57
|
-
Search for Sonos Zone Players.`
|
|
58
|
-
|
|
59
|
-
class UpnpTool extends homebridgeLib.CommandLineTool {
|
|
60
|
-
constructor () {
|
|
61
|
-
super()
|
|
62
|
-
this.usage = usage
|
|
63
|
-
this.options = {
|
|
64
|
-
noWhiteSpace: false
|
|
65
|
-
}
|
|
66
|
-
this.upnp = {}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
parseArguments () {
|
|
70
|
-
const parser = new homebridgeLib.CommandLineParser()
|
|
71
|
-
parser
|
|
72
|
-
.help('h', 'help', help)
|
|
73
|
-
.version('V', 'version')
|
|
74
|
-
.flag('D', 'debug', () => { this.setOptions({ debug: true }) })
|
|
75
|
-
.flag('a', 'all', (key) => { this.upnp.class = 'ssdp:all' })
|
|
76
|
-
.option('c', 'class', (value, key) => { this.upnp.class = value })
|
|
77
|
-
.flag('d', 'daemon', (key) => { this.options.mode = 'daemon' })
|
|
78
|
-
.flag('n', 'noWhiteSpace', () => { this.options.noWhiteSpace = true })
|
|
79
|
-
.flag('r', 'raw', (key) => { this.options.raw = true })
|
|
80
|
-
.flag('s', 'service', (key) => { this.options.mode = 'service' })
|
|
81
|
-
.option('t', 'timeout', (value, key) => {
|
|
82
|
-
this.upnp.timeout = homebridgeLib.OptionParser.toInt(
|
|
83
|
-
'timeout', value, 1, 60, true
|
|
84
|
-
)
|
|
85
|
-
})
|
|
86
|
-
.flag('p', 'hue', (key) => {
|
|
87
|
-
this.upnp.filter = (message) => {
|
|
88
|
-
return /^[0-9A-F]{16}$/.test(message['hue-bridgeid'])
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
.flag('z', 'sonos', (key) => {
|
|
92
|
-
this.upnp.class = 'urn:schemas-upnp-org:device:ZonePlayer:1'
|
|
93
|
-
})
|
|
94
|
-
.parse()
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async destroy () {
|
|
98
|
-
if (this.upnpClient == null) {
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
|
-
return this.upnpClient.stopListen()
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async main () {
|
|
105
|
-
try {
|
|
106
|
-
this.parseArguments()
|
|
107
|
-
const jsonFormatter = new homebridgeLib.JsonFormatter({
|
|
108
|
-
noWhiteSpace: this.options.noWhiteSpace
|
|
109
|
-
})
|
|
110
|
-
this.upnpClient = new homebridgeLib.UpnpClient(this.upnp)
|
|
111
|
-
this.upnpClient
|
|
112
|
-
.on('error', (error) => { this.error(error) })
|
|
113
|
-
.on('listening', (host) => { this.debug('listening on %s', host) })
|
|
114
|
-
.on('stopListening', (host) => { this.debug('stopped listening on %s', host) })
|
|
115
|
-
.on('deviceAlive', (address, obj, message) => {
|
|
116
|
-
if (!this.options.raw) {
|
|
117
|
-
message = jsonFormatter.stringify(obj)
|
|
118
|
-
}
|
|
119
|
-
this.log('%s: %s', address, message)
|
|
120
|
-
})
|
|
121
|
-
.on('searching', (host) => { this.debug('listening on %s', host) })
|
|
122
|
-
.on('request', (request) => {
|
|
123
|
-
this.debug(
|
|
124
|
-
'%s: request %d: %s %s', request.host, request.id,
|
|
125
|
-
request.method, request.resource
|
|
126
|
-
)
|
|
127
|
-
})
|
|
128
|
-
.on('searchDone', (host) => { this.debug('search done') })
|
|
129
|
-
.on('deviceFound', (address, obj, message) => {
|
|
130
|
-
if (!this.options.raw) {
|
|
131
|
-
message = jsonFormatter.stringify(obj)
|
|
132
|
-
}
|
|
133
|
-
this.print('%s: %s', address, message)
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
if (this.options.mode) {
|
|
137
|
-
this.setOptions({ mode: this.options.mode })
|
|
138
|
-
this.upnpClient.listen()
|
|
139
|
-
} else {
|
|
140
|
-
this.upnpClient.search()
|
|
141
|
-
}
|
|
142
|
-
} catch (error) {
|
|
143
|
-
await this.fatal(error)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
module.exports = UpnpTool
|