homebridge-lib 5.1.14 → 5.1.18-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/README.md +6 -5
- package/cli/sysinfo.js +81 -0
- package/index.js +19 -0
- package/lib/HttpClient.js +6 -1
- package/lib/Platform.js +14 -0
- package/lib/SystemInfo.js +440 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -28,11 +28,12 @@ See [Homebridge WS](https://github.com/ebaauw/homebridge-ws) for an example plug
|
|
|
28
28
|
### Command-Line Tools
|
|
29
29
|
The Homebridge Lib library comes with a number of command-line tools for troubleshooting Homebridge installations.
|
|
30
30
|
|
|
31
|
-
Tool
|
|
32
|
-
|
|
33
|
-
`hap`
|
|
34
|
-
`json`
|
|
35
|
-
`
|
|
31
|
+
Tool | Description
|
|
32
|
+
--------- | -----------
|
|
33
|
+
`hap` | Logger for HomeKit accessory announcements.
|
|
34
|
+
`json` | JSON formatter.
|
|
35
|
+
`sysinfo` | Print hardware and operating system information.
|
|
36
|
+
`upnp` | UPnP tool.
|
|
36
37
|
|
|
37
38
|
Each command-line tool takes a `-h` or `--help` argument to provide a brief overview of its functionality and command-line arguments.
|
|
38
39
|
|
package/cli/sysinfo.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// homebridge-lib/cli/sysinfo.js
|
|
4
|
+
//
|
|
5
|
+
// Library for Homebridge plugins.
|
|
6
|
+
// Copyright © 2021 Erik Baauw. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
// Show system info.
|
|
9
|
+
|
|
10
|
+
'use strict'
|
|
11
|
+
|
|
12
|
+
const homebridgeLib = require('../index')
|
|
13
|
+
|
|
14
|
+
const { b } = homebridgeLib.CommandLineTool
|
|
15
|
+
|
|
16
|
+
const usage = `${b('sysinfo')} [${b('-hVDj')}]`
|
|
17
|
+
const help = `System information tool.
|
|
18
|
+
|
|
19
|
+
Print Hardware and Operating System information.
|
|
20
|
+
|
|
21
|
+
Usage: ${usage}
|
|
22
|
+
|
|
23
|
+
Parameters:
|
|
24
|
+
${b('-h')}, ${b('--help')}
|
|
25
|
+
Print this help and exit.
|
|
26
|
+
|
|
27
|
+
${b('-V')}, ${b('--version')}
|
|
28
|
+
Print version and exit.
|
|
29
|
+
|
|
30
|
+
${b('-D')}, ${b('--debug')}
|
|
31
|
+
Print debug messages.
|
|
32
|
+
|
|
33
|
+
${b('-j')}, ${b('--json')}
|
|
34
|
+
Print full info in json.`
|
|
35
|
+
|
|
36
|
+
class Main extends homebridgeLib.CommandLineTool {
|
|
37
|
+
constructor () {
|
|
38
|
+
super()
|
|
39
|
+
this.usage = usage
|
|
40
|
+
this.options = {
|
|
41
|
+
noWhiteSpace: false
|
|
42
|
+
}
|
|
43
|
+
this.upnp = {}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
parseArguments () {
|
|
47
|
+
const parser = new homebridgeLib.CommandLineParser()
|
|
48
|
+
parser
|
|
49
|
+
.help('h', 'help', help)
|
|
50
|
+
.version('V', 'version')
|
|
51
|
+
.flag('D', 'debug', () => { this.setOptions({ debug: true }) })
|
|
52
|
+
.flag('j', 'json', () => { this.json = true })
|
|
53
|
+
.parse()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async main () {
|
|
57
|
+
try {
|
|
58
|
+
this.parseArguments()
|
|
59
|
+
this.systemInfo = new homebridgeLib.SystemInfo()
|
|
60
|
+
this.systemInfo
|
|
61
|
+
.on('error', (error) => { this.error(error) })
|
|
62
|
+
.on('exec', (command) => { this.debug('exec: %s', command) })
|
|
63
|
+
.on('readFile', (fileName) => { this.debug('read file: %s', fileName) })
|
|
64
|
+
await this.systemInfo.init()
|
|
65
|
+
if (this.json) {
|
|
66
|
+
const jsonFormatter = new homebridgeLib.JsonFormatter(this.options)
|
|
67
|
+
this.print(jsonFormatter.stringify({
|
|
68
|
+
hardware: this.systemInfo.hwInfo,
|
|
69
|
+
os: this.systemInfo.osInfo
|
|
70
|
+
}))
|
|
71
|
+
} else {
|
|
72
|
+
this.print(this.systemInfo.hwInfo.prettyName)
|
|
73
|
+
this.print(this.systemInfo.osInfo.prettyName)
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
await this.fatal(error)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
new Main().main()
|
package/index.js
CHANGED
|
@@ -30,6 +30,8 @@ function isNodejsError (e) {
|
|
|
30
30
|
return typeof e.code === 'string' && e.code.startsWith('ERR_')
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
const zeroes = '00000000000000000000000000000000'
|
|
34
|
+
|
|
33
35
|
/** Library for Homebridge plugins.
|
|
34
36
|
* see the {@tutorial homebridgeLib} tutorial.
|
|
35
37
|
*
|
|
@@ -180,6 +182,13 @@ class homebridgeLib {
|
|
|
180
182
|
*/
|
|
181
183
|
static get ServiceDelegate () { return require('./lib/ServiceDelegate') }
|
|
182
184
|
|
|
185
|
+
/** System information.
|
|
186
|
+
* <br>See {@link SystemInfo}.
|
|
187
|
+
* @type {Class}
|
|
188
|
+
* @memberof module:homebridgeLib
|
|
189
|
+
*/
|
|
190
|
+
static get SystemInfo () { return require('./lib/SystemInfo') }
|
|
191
|
+
|
|
183
192
|
/** Universal Plug and Play client.
|
|
184
193
|
* <br>See {@link UpnpClient}.
|
|
185
194
|
* @type {Class}
|
|
@@ -258,6 +267,16 @@ class homebridgeLib {
|
|
|
258
267
|
}
|
|
259
268
|
return e.message
|
|
260
269
|
}
|
|
270
|
+
|
|
271
|
+
/** Convert integer to hex string.
|
|
272
|
+
* @param {integer} i - The integer.
|
|
273
|
+
* @param {integer} [length=4] - The number of digits in the hex string.
|
|
274
|
+
* @returns {string} - The hex string.
|
|
275
|
+
* @memberof module:homebridgeLib
|
|
276
|
+
*/
|
|
277
|
+
static toHexString (i, length = 4) {
|
|
278
|
+
return (zeroes + i.toString(16)).slice(-length).toUpperCase()
|
|
279
|
+
}
|
|
261
280
|
}
|
|
262
281
|
|
|
263
282
|
module.exports = homebridgeLib
|
package/lib/HttpClient.js
CHANGED
|
@@ -39,7 +39,11 @@ class HttpError extends Error {
|
|
|
39
39
|
* @memberof HttpClient
|
|
40
40
|
*/
|
|
41
41
|
class HttpRequest {
|
|
42
|
-
constuctor (id, method, resource, headers, body, url) {
|
|
42
|
+
constuctor (name, id, method, resource, headers, body, url) {
|
|
43
|
+
/** @member {string} - The server name.
|
|
44
|
+
*/
|
|
45
|
+
this.name = name
|
|
46
|
+
|
|
43
47
|
/** @member {integer} - The request ID.
|
|
44
48
|
*/
|
|
45
49
|
this.id = id
|
|
@@ -349,6 +353,7 @@ class HttpClient extends events.EventEmitter {
|
|
|
349
353
|
this.__params.suffix + suffix
|
|
350
354
|
const options = Object.assign({ method: method }, this.__options)
|
|
351
355
|
const requestInfo = Object.assign({
|
|
356
|
+
name: this.name,
|
|
352
357
|
id: requestId,
|
|
353
358
|
method: method,
|
|
354
359
|
resource: resource,
|
package/lib/Platform.js
CHANGED
|
@@ -210,6 +210,18 @@ class Platform extends homebridgeLib.Delegate {
|
|
|
210
210
|
// this.error('unhandled rejection: %s', error.stack)
|
|
211
211
|
// })
|
|
212
212
|
global[globalKey].Platform.heartbeat = this
|
|
213
|
+
/** System information.
|
|
214
|
+
* @type {SystemInfo}
|
|
215
|
+
* @readonly
|
|
216
|
+
*/
|
|
217
|
+
this.systemInfo = new homebridgeLib.SystemInfo()
|
|
218
|
+
this.systemInfo
|
|
219
|
+
.on('error', (error) => { this.warn(error) })
|
|
220
|
+
.on('exec', (command) => { this.debug('exec: %s', command) })
|
|
221
|
+
.on('readFile', (filename) => { this.debug('read file: %s', filename) })
|
|
222
|
+
await this.systemInfo.init()
|
|
223
|
+
this.log('hardware: %s', this.systemInfo.hwInfo.prettyName)
|
|
224
|
+
this.log('os: %s', this.systemInfo.osInfo.prettyName)
|
|
213
225
|
this.heartbeatClients = Object.keys(global[globalKey]).filter((key) => {
|
|
214
226
|
return global[globalKey][key].platform != null
|
|
215
227
|
})
|
|
@@ -221,6 +233,8 @@ class Platform extends homebridgeLib.Delegate {
|
|
|
221
233
|
this._homebridge.updatePlatformAccessories()
|
|
222
234
|
this.log('goodbye')
|
|
223
235
|
})
|
|
236
|
+
} else {
|
|
237
|
+
this.systemInfo = global[globalKey].Platform.heartbeat.systemInfo
|
|
224
238
|
}
|
|
225
239
|
if (this._isHomebridgeLib) {
|
|
226
240
|
const jobs = []
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
// homebridge-lib/lib/SystemInfo.js
|
|
2
|
+
//
|
|
3
|
+
// Library for Homebridge plugins.
|
|
4
|
+
// Copyright © 2019-2021 Erik Baauw. All rights reserved.
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const events = require('events')
|
|
9
|
+
const { exec, execFile } = require('child_process')
|
|
10
|
+
const fs = require('fs').promises
|
|
11
|
+
const os = require('os')
|
|
12
|
+
const semver = require('semver')
|
|
13
|
+
|
|
14
|
+
const homebridgeLib = require('../index')
|
|
15
|
+
|
|
16
|
+
const rpiInfo = {
|
|
17
|
+
manufacturers: {
|
|
18
|
+
0: 'Sony UK',
|
|
19
|
+
1: 'Egoman',
|
|
20
|
+
2: 'Embest',
|
|
21
|
+
3: 'Sony Japan',
|
|
22
|
+
4: 'Embest',
|
|
23
|
+
5: 'Stadium'
|
|
24
|
+
},
|
|
25
|
+
memorySizes: {
|
|
26
|
+
0: '256MB',
|
|
27
|
+
1: '512MB',
|
|
28
|
+
2: '1GB',
|
|
29
|
+
3: '2GB',
|
|
30
|
+
4: '4GB',
|
|
31
|
+
5: '8GB'
|
|
32
|
+
},
|
|
33
|
+
models: {
|
|
34
|
+
0: 'A',
|
|
35
|
+
1: 'B',
|
|
36
|
+
2: 'A+',
|
|
37
|
+
3: 'B+',
|
|
38
|
+
4: '2B',
|
|
39
|
+
5: 'Alpha', // early prototype
|
|
40
|
+
6: 'CM1',
|
|
41
|
+
8: '3B',
|
|
42
|
+
9: 'Zero',
|
|
43
|
+
10: 'CM3',
|
|
44
|
+
12: 'Zero W',
|
|
45
|
+
13: '3B+',
|
|
46
|
+
14: '3A+',
|
|
47
|
+
// 15: '', // Internal use only
|
|
48
|
+
16: 'CM3+',
|
|
49
|
+
17: '4B',
|
|
50
|
+
18: 'Zero 2 W',
|
|
51
|
+
19: '400',
|
|
52
|
+
20: 'CM4'
|
|
53
|
+
},
|
|
54
|
+
processors: {
|
|
55
|
+
0: 'BCM2835',
|
|
56
|
+
1: 'BCM2836',
|
|
57
|
+
2: 'BCM2837',
|
|
58
|
+
3: 'BCM2711'
|
|
59
|
+
},
|
|
60
|
+
oldRevisions: {
|
|
61
|
+
2: { model: 'B', revision: '1.0', memory: '256MB', manufacturer: 'Egoman' },
|
|
62
|
+
3: { model: 'B', revision: '1.0', memory: '256MB', manufacturer: 'Egoman' },
|
|
63
|
+
4: { model: 'B', revision: '2.0', memory: '256MB', manufacturer: 'Sony UK' },
|
|
64
|
+
5: { model: 'B', revision: '2.0', memory: '256MB', manufacturer: 'Qisda' },
|
|
65
|
+
6: { model: 'B', revision: '2.0', memory: '256MB', manufacturer: 'Egoman' },
|
|
66
|
+
7: { model: 'A', revision: '2.0', memory: '256MB', manufacturer: 'Egoman' },
|
|
67
|
+
8: { model: 'A', revision: '2.0', memory: '256MB', manufacturer: 'Sony UK' },
|
|
68
|
+
9: { model: 'A', revision: '2.0', memory: '256MB', manufacturer: 'Qisda' },
|
|
69
|
+
13: { model: 'B', revision: '2.0', memory: '512MB', manufacturer: 'Egoman' },
|
|
70
|
+
14: { model: 'B', revision: '2.0', memory: '512MB', manufacturer: 'Sony UK' },
|
|
71
|
+
15: { model: 'B', revision: '2.0', memory: '512MB', manufacturer: 'Egoman' },
|
|
72
|
+
16: { model: 'B+', revision: '1.2', memory: '512MB', manufacturer: 'Sony UK' },
|
|
73
|
+
17: { model: 'CM1', revision: '1.0', memory: '512MB', manufacturer: 'Sony UK' },
|
|
74
|
+
18: { model: 'A+', revision: '1.1', memory: '256MB', manufacturer: 'Sony UK' },
|
|
75
|
+
19: { model: 'B+', revision: '1.2', memory: '512MB', manufacturer: 'Embest' },
|
|
76
|
+
20: { model: 'CM1', revision: '1.0', memory: '512MB', manufacturer: 'Embest' },
|
|
77
|
+
21: { model: 'A+', revision: '1.1', memory: '256MB/512MB', manufacturer: 'Embest' }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// See: https://en.wikipedia.org/wiki/MacOS_version_history
|
|
82
|
+
const macOsInfo = {
|
|
83
|
+
versionNames: {
|
|
84
|
+
'10.0': 'Cheetah',
|
|
85
|
+
10.1: 'Puma',
|
|
86
|
+
10.2: 'Jaguar',
|
|
87
|
+
10.3: 'Panther',
|
|
88
|
+
10.4: 'Tiger',
|
|
89
|
+
10.5: 'Leopard',
|
|
90
|
+
10.6: 'Snow Leopard',
|
|
91
|
+
10.7: 'Lion',
|
|
92
|
+
10.8: 'Mountain Lion',
|
|
93
|
+
10.9: 'Mavericks',
|
|
94
|
+
'10.10': 'Yosemite',
|
|
95
|
+
10.11: 'El Capitan',
|
|
96
|
+
10.12: 'Sierra',
|
|
97
|
+
10.13: 'High Sierra',
|
|
98
|
+
10.14: 'Mojave',
|
|
99
|
+
10.15: 'Catalina',
|
|
100
|
+
11: 'Big Sur',
|
|
101
|
+
12: 'Monterey'
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** System information.
|
|
106
|
+
* @extends EventEmitter
|
|
107
|
+
*/
|
|
108
|
+
class SystemInfo extends events.EventEmitter {
|
|
109
|
+
/** Extract Raspberry Pi serial number and hardware revision info from the
|
|
110
|
+
* contents of `/proc/cpuinfo`.
|
|
111
|
+
* @param {string} cpuInfo - The contents of `/proc/cpuinfo`.
|
|
112
|
+
* @return {object} - The extracted info.
|
|
113
|
+
*/
|
|
114
|
+
static parseRpiCpuInfo (cpuInfo) {
|
|
115
|
+
const id = /Serial\s*: ([0-9a-f]{16})/.exec(cpuInfo)[1].toUpperCase()
|
|
116
|
+
const revision = parseInt(/Revision\s*: ([0-9a-f]{4,})/.exec(cpuInfo)[1], 16)
|
|
117
|
+
const rpi = SystemInfo.parseRpiRevision(revision & 0x00FFFFFF)
|
|
118
|
+
return Object.assign({ id: id }, rpi)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Parse the revision of a Raspberry Pi.
|
|
122
|
+
*
|
|
123
|
+
* @see https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-revision-codes
|
|
124
|
+
* @param {int} revision - The Raspberry Pi revision.
|
|
125
|
+
* @return {object} - The parsed revision.
|
|
126
|
+
*/
|
|
127
|
+
static parseRpiRevision (revision) {
|
|
128
|
+
let gpioMask, manufacturer, memory, model, modelRevision, processor
|
|
129
|
+
if ((revision & 0x00800000) !== 0) { // New revision scheme.
|
|
130
|
+
manufacturer = rpiInfo.manufacturers[(revision & 0x000f0000) >> 16]
|
|
131
|
+
memory = rpiInfo.memorySizes[(revision & 0x00700000) >> 20]
|
|
132
|
+
model = rpiInfo.models[(revision & 0x00000ff0) >> 4]
|
|
133
|
+
modelRevision = '1.' + ((revision & 0x0000000f) >> 0).toString()
|
|
134
|
+
processor = rpiInfo.processors[(revision & 0x0000f000) >> 12]
|
|
135
|
+
} else if (rpiInfo.oldRevisions[revision] != null) { // Old incremental revisions.
|
|
136
|
+
manufacturer = rpiInfo.oldRevisions[revision].manufacturer
|
|
137
|
+
memory = rpiInfo.oldRevisions[revision].memory
|
|
138
|
+
model = rpiInfo.oldRevisions[revision].model
|
|
139
|
+
modelRevision = rpiInfo.oldRevisions[revision].revision
|
|
140
|
+
processor = 'BCM2835'
|
|
141
|
+
}
|
|
142
|
+
if (model != null && model.startsWith('CM')) {
|
|
143
|
+
// Compute module
|
|
144
|
+
gpioMask = 0xFFFFFFFF // 0-31
|
|
145
|
+
} else if (revision >= 16) {
|
|
146
|
+
// Type 3
|
|
147
|
+
gpioMask = 0x0FFFFFFC // 2-27
|
|
148
|
+
} else if (revision >= 4) {
|
|
149
|
+
// Type 2
|
|
150
|
+
gpioMask = 0xFBC6CF9C // 2-4, 7-11, 14-15, 17-18, 22-25, 27-31
|
|
151
|
+
} else {
|
|
152
|
+
// Type 1
|
|
153
|
+
gpioMask = 0x03E6CF93 // 0-1, 4, 7-11, 14-15, 17-18, 21-25
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
gpioMask: gpioMask,
|
|
157
|
+
gpioMaskSerial: (1 << 15) | (1 << 14),
|
|
158
|
+
manufacturer: manufacturer,
|
|
159
|
+
memory: memory,
|
|
160
|
+
model: model,
|
|
161
|
+
modelRevision: modelRevision,
|
|
162
|
+
prettyName: [
|
|
163
|
+
'Raspberry Pi', model, modelRevision, '(' + memory + ')'
|
|
164
|
+
].join(' '),
|
|
165
|
+
powerLed: !(['A', 'B', 'Zero', 'Zero W', 'Zero 2 W'].includes(model)),
|
|
166
|
+
processor: processor,
|
|
167
|
+
revision: homebridgeLib.toHexString(revision, 6)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Initialise SystemInfo instance.
|
|
172
|
+
*/
|
|
173
|
+
async init () {
|
|
174
|
+
switch (process.platform) {
|
|
175
|
+
case 'linux':
|
|
176
|
+
try {
|
|
177
|
+
this.osInfo = await this.getPiOsInfo()
|
|
178
|
+
} catch (error) { this.emit('error', error) }
|
|
179
|
+
if (['arm', 'arm64'].includes(process.arch)) {
|
|
180
|
+
try {
|
|
181
|
+
this.hwInfo = await this.getRpiInfo()
|
|
182
|
+
} catch (error) { this.emit('error', error) }
|
|
183
|
+
}
|
|
184
|
+
break
|
|
185
|
+
case 'darwin':
|
|
186
|
+
try {
|
|
187
|
+
this.osInfo = await this.getMacOsInfo()
|
|
188
|
+
} catch (error) { this.emit('error', error) }
|
|
189
|
+
try {
|
|
190
|
+
this.hwInfo = await this.getMacInfo()
|
|
191
|
+
} catch (error) { this.emit('error', error) }
|
|
192
|
+
break
|
|
193
|
+
default:
|
|
194
|
+
break
|
|
195
|
+
}
|
|
196
|
+
if (this.osInfo == null) {
|
|
197
|
+
this.osInfo = {
|
|
198
|
+
name: process.platform,
|
|
199
|
+
platform: process.platform,
|
|
200
|
+
prettyName: process.platform
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (this.hwInfo == null) {
|
|
204
|
+
this.hwInfo = {
|
|
205
|
+
nCores: os.cpus().length,
|
|
206
|
+
prettyName: process.arch,
|
|
207
|
+
processor: process.arch
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
this.platform = this.osInfo.platform
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Extract serial number and hardware revision info from `/proc/cpuinfo`.
|
|
214
|
+
* @return {object} - The extracted info.
|
|
215
|
+
*/
|
|
216
|
+
async getRpiInfo () {
|
|
217
|
+
const cpuInfo = await this.readTextFile('/proc/cpuinfo')
|
|
218
|
+
const rpi = SystemInfo.parseRpiCpuInfo(cpuInfo)
|
|
219
|
+
return {
|
|
220
|
+
gpioMask: rpi.gpioMask,
|
|
221
|
+
gpioMaskSerial: rpi.gpioMaskSerial,
|
|
222
|
+
id: rpi.id,
|
|
223
|
+
isRpi: true,
|
|
224
|
+
manufacturer: rpi.manufacturer,
|
|
225
|
+
memory: rpi.memory,
|
|
226
|
+
model: rpi.model,
|
|
227
|
+
modelRevision: rpi.modelRevision,
|
|
228
|
+
nCores: os.cpus().length,
|
|
229
|
+
powerLed: rpi.powerLed,
|
|
230
|
+
prettyName: rpi.prettyName,
|
|
231
|
+
processor: rpi.processor,
|
|
232
|
+
revision: rpi.revision
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Extract OS info from /etc/os-release.
|
|
237
|
+
* @return {object} - The extracted info.
|
|
238
|
+
*/
|
|
239
|
+
async getPiOsInfo () {
|
|
240
|
+
let name, platform, prettyName, version, versionName
|
|
241
|
+
const text = await this.readTextFile('/etc/os-release')
|
|
242
|
+
const lines = text.replace(/"/g, '').split('\n')
|
|
243
|
+
for (const line of lines) {
|
|
244
|
+
const fields = line.split('=')
|
|
245
|
+
if (fields.length === 2) {
|
|
246
|
+
switch (fields[0]) {
|
|
247
|
+
case 'ID':
|
|
248
|
+
platform = fields[1] // e.g. 'raspbian'
|
|
249
|
+
break
|
|
250
|
+
case 'NAME':
|
|
251
|
+
name = fields[1] // e.g. 'Raspbian GNU/Linux'
|
|
252
|
+
break
|
|
253
|
+
case 'PRETTY_NAME':
|
|
254
|
+
prettyName = fields[1] // e.g. 'Raspbian GNU/Linux 11 (bullseye)'
|
|
255
|
+
break
|
|
256
|
+
case 'VERSION_CODENAME':
|
|
257
|
+
versionName = fields[1] // e.g. 'bullseye'
|
|
258
|
+
break
|
|
259
|
+
case 'VERSION_ID':
|
|
260
|
+
version = fields[1] // e.g. '11'
|
|
261
|
+
break
|
|
262
|
+
default:
|
|
263
|
+
break
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
name: name,
|
|
269
|
+
platform: platform,
|
|
270
|
+
prettyName: prettyName,
|
|
271
|
+
version: version,
|
|
272
|
+
versionName: versionName
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Extract Apple Mac hardware info from `system_profiler` command.
|
|
277
|
+
* @return {object} - The extracted info.
|
|
278
|
+
*/
|
|
279
|
+
async getMacInfo () {
|
|
280
|
+
let id, memory, model, nCores, prettyName, processor, revision
|
|
281
|
+
let text = await this.exec('system_profiler', 'SPHardwareDataType')
|
|
282
|
+
const lines = text.split('\n')
|
|
283
|
+
for (const line of lines) {
|
|
284
|
+
const fields = line.split(': ')
|
|
285
|
+
if (fields.length === 2) {
|
|
286
|
+
switch (fields[0].trim()) {
|
|
287
|
+
case 'Memory':
|
|
288
|
+
memory = fields[1].replace(/ /g, '')
|
|
289
|
+
break
|
|
290
|
+
case 'Model Identifier':
|
|
291
|
+
revision = fields[1]
|
|
292
|
+
break
|
|
293
|
+
case 'Model Name':
|
|
294
|
+
model = fields[1]
|
|
295
|
+
break
|
|
296
|
+
case 'Chip':
|
|
297
|
+
case 'Processor Name':
|
|
298
|
+
processor = fields[1]
|
|
299
|
+
break
|
|
300
|
+
case 'Serial Number (system)':
|
|
301
|
+
id = fields[1]
|
|
302
|
+
break
|
|
303
|
+
case 'Total Number of Cores':
|
|
304
|
+
nCores = fields[1].split(' ')[0]
|
|
305
|
+
break
|
|
306
|
+
default:
|
|
307
|
+
break
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
text = await this.exec(
|
|
312
|
+
'plutil', '-convert', 'json', '-o', '-',
|
|
313
|
+
process.env.HOME + '/Library/Preferences/com.apple.SystemProfiler.plist'
|
|
314
|
+
)
|
|
315
|
+
const s = JSON.parse(text)['CPU Names']
|
|
316
|
+
if (s != null) {
|
|
317
|
+
// This is empty on my MacBook Pro (14-inch, 2021).
|
|
318
|
+
for (const key of Object.keys(s)) {
|
|
319
|
+
if (key.startsWith(id.slice(-4))) {
|
|
320
|
+
prettyName = s[key]
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
// text = await this.exec(
|
|
325
|
+
// 'ioreg', '-c', 'IOPlatformDevice', '-d', '4', '-n', 'product'
|
|
326
|
+
// )
|
|
327
|
+
text = await this.execShell('ioreg -l | grep product-description')
|
|
328
|
+
// This is empty on my Intel Macs - I think the key is registered for
|
|
329
|
+
// running iPad apps on Apple chips.
|
|
330
|
+
const a = /"product-description" = <"([^"]*)">/.exec(text)
|
|
331
|
+
if (a != null) {
|
|
332
|
+
prettyName = a[1]
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
id: id,
|
|
337
|
+
isMac: true,
|
|
338
|
+
manufacturer: 'Apple Inc.',
|
|
339
|
+
memory: memory,
|
|
340
|
+
model: model,
|
|
341
|
+
nCores: nCores,
|
|
342
|
+
prettyName: prettyName || model,
|
|
343
|
+
processor: processor,
|
|
344
|
+
revision: revision
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Extract macOS info from `sw_vers` command.
|
|
349
|
+
* @return {object} - The extracted info.
|
|
350
|
+
*/
|
|
351
|
+
async getMacOsInfo () {
|
|
352
|
+
let name, version, build
|
|
353
|
+
const text = await this.exec('sw_vers')
|
|
354
|
+
const lines = text.split('\n')
|
|
355
|
+
for (const line of lines) {
|
|
356
|
+
const fields = line.split(':\t')
|
|
357
|
+
if (fields.length === 2) {
|
|
358
|
+
switch (fields[0]) {
|
|
359
|
+
case 'ProductName': // e.g. 'macOS' or 'Mac OS X'
|
|
360
|
+
name = fields[1]
|
|
361
|
+
break
|
|
362
|
+
case 'ProductVersion': // e.g. '12.0.1'
|
|
363
|
+
version = fields[1]
|
|
364
|
+
break
|
|
365
|
+
case 'BuildVersion': // e.g. '21A559'
|
|
366
|
+
build = fields[1]
|
|
367
|
+
break
|
|
368
|
+
default:
|
|
369
|
+
break
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
let v = semver.major(version)
|
|
374
|
+
if (v === 10) {
|
|
375
|
+
v += '.' + semver.minor(version)
|
|
376
|
+
}
|
|
377
|
+
const versionName = macOsInfo.versionNames[v] // e.g. 'Monterey'
|
|
378
|
+
return {
|
|
379
|
+
build: build,
|
|
380
|
+
catalina: semver.gte(version, '10.15.0'),
|
|
381
|
+
name: name,
|
|
382
|
+
platform: process.platform,
|
|
383
|
+
prettyName: [name, versionName, version, '(' + build + ')'].join(' '),
|
|
384
|
+
version: version,
|
|
385
|
+
versionName: versionName
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** Execute a command on the local machine.
|
|
390
|
+
* @param {string} command - The command.
|
|
391
|
+
* @param {...string} ...args - The command parameters.
|
|
392
|
+
* @return {string} - The output of the command.
|
|
393
|
+
*/
|
|
394
|
+
async exec (command, ...args) {
|
|
395
|
+
return new Promise((resolve, reject) => {
|
|
396
|
+
/** Emitted when a command is executed.
|
|
397
|
+
* @event SystemInfo#exec
|
|
398
|
+
* @param {string} command - The command.
|
|
399
|
+
*/
|
|
400
|
+
this.emit('exec', command + ' ' + args.join(' '))
|
|
401
|
+
execFile(command, args, null, (error, stdout, stderr) => {
|
|
402
|
+
if (error != null) {
|
|
403
|
+
reject(error)
|
|
404
|
+
}
|
|
405
|
+
resolve(stdout)
|
|
406
|
+
})
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** Execute a shell command on the local machine.
|
|
411
|
+
* @param {string} command - The command.
|
|
412
|
+
* @return {string} - The output of the command.
|
|
413
|
+
*/
|
|
414
|
+
async execShell (command) {
|
|
415
|
+
return new Promise((resolve, reject) => {
|
|
416
|
+
this.emit('exec', command)
|
|
417
|
+
exec(command, (error, stdout, stderr) => {
|
|
418
|
+
if (error != null) {
|
|
419
|
+
reject(error)
|
|
420
|
+
}
|
|
421
|
+
resolve(stdout)
|
|
422
|
+
})
|
|
423
|
+
})
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/** Read a text file.
|
|
427
|
+
* @param {string} fileName - The file name.
|
|
428
|
+
* @return {string} - The contents of the file.
|
|
429
|
+
*/
|
|
430
|
+
async readTextFile (fileName) {
|
|
431
|
+
/** Emitted when a file is read.
|
|
432
|
+
* @event SystemInfo#readFile
|
|
433
|
+
* @param {string} fileName - The file name.
|
|
434
|
+
*/
|
|
435
|
+
this.emit('readFile', fileName)
|
|
436
|
+
return fs.readFile(fileName, 'utf8')
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
module.exports = SystemInfo
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Library for homebridge plugins",
|
|
4
4
|
"author": "Erik Baauw",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
-
"version": "5.1.
|
|
6
|
+
"version": "5.1.18-0",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"homekit",
|
|
9
9
|
"homebridge"
|
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
"bin": {
|
|
18
18
|
"hap": "cli/hap.js",
|
|
19
19
|
"json": "cli/json.js",
|
|
20
|
+
"sysinfo": "cli/sysinfo.js",
|
|
20
21
|
"upnp": "cli/upnp.js"
|
|
21
22
|
},
|
|
22
23
|
"engines": {
|
|
23
|
-
"homebridge": "^1.3.
|
|
24
|
-
"node": "^
|
|
24
|
+
"homebridge": "^1.3.6",
|
|
25
|
+
"node": "^16.13.0"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
27
28
|
"bonjour-hap": "^3.6.3",
|