homebridge-lib 5.1.16 → 5.1.18
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/CommandLineTool.js +8 -3
- package/lib/MyHomeKitTypes.js +15 -0
- package/lib/Platform.js +14 -0
- package/lib/SystemInfo.js +441 -0
- package/package.json +3 -2
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/CommandLineTool.js
CHANGED
|
@@ -53,10 +53,15 @@ class CommandLineTool {
|
|
|
53
53
|
.on('SIGINT', this._onSignal.bind(this))
|
|
54
54
|
.on('SIGTERM', this._onSignal.bind(this))
|
|
55
55
|
.on('SIGABRT', this._onSignal.bind(this))
|
|
56
|
-
.on('uncaughtException', (error) => {
|
|
57
|
-
|
|
56
|
+
.on('uncaughtException', async (error) => {
|
|
57
|
+
await this.fatal('uncaught exception: %s', error.stack)
|
|
58
|
+
})
|
|
59
|
+
.removeAllListeners('unhandledRejection')
|
|
60
|
+
.on('unhandledRejection', async (error) => {
|
|
61
|
+
await this.fatal('unhandled rejection: %s', error.stack)
|
|
62
|
+
})
|
|
58
63
|
} catch (error) {
|
|
59
|
-
this.fatal
|
|
64
|
+
this._log({ label: 'fatal', chalk: chalk.bold.red }, error)
|
|
60
65
|
}
|
|
61
66
|
}
|
|
62
67
|
|
package/lib/MyHomeKitTypes.js
CHANGED
|
@@ -120,6 +120,8 @@ class MyHomeKitTypes extends homebridgeLib.CustomHomeKitTypes {
|
|
|
120
120
|
* <br>Used by: homebridge-otgw.
|
|
121
121
|
* @property {Class} PumpStarts - Number of pump starts.
|
|
122
122
|
* <br>Used by: homebridge-otgw.
|
|
123
|
+
* @property {Class} Recall - Recall a scene.
|
|
124
|
+
* <br>Used by: homebridge-hue.
|
|
123
125
|
* @property {Class} Repeat - Repeat tracks (off, 1, all).
|
|
124
126
|
* <br>Used by: homebridge-zp.
|
|
125
127
|
* @property {Class} Resource - REST API resource corresponding for
|
|
@@ -133,6 +135,8 @@ class MyHomeKitTypes extends homebridgeLib.CustomHomeKitTypes {
|
|
|
133
135
|
* <br>Used by: homebridge-otgw.
|
|
134
136
|
* @property {Class} RingToOpen - Ring to Open active.
|
|
135
137
|
* <br> Used by: homebridge-nb.
|
|
138
|
+
* @property {Class} Search - Search for new devices.
|
|
139
|
+
* <br>Used by: homebridge-hue.
|
|
136
140
|
* @property {Class} Sensitivity - Motion sensor sensitivity.
|
|
137
141
|
* <br>See also: {@link EveHomeKitTypes#Characteristics eve.Sensitivity}.
|
|
138
142
|
* <br>Previously used by: homebridge-hue in MotionSensor service.
|
|
@@ -664,6 +668,17 @@ class MyHomeKitTypes extends homebridgeLib.CustomHomeKitTypes {
|
|
|
664
668
|
perms: [this.Perms.READ, this.Perms.NOTIFY, this.Perms.WRITE]
|
|
665
669
|
}, 'Motor Speed')
|
|
666
670
|
|
|
671
|
+
this.createCharacteristicClass('Search', uuid('073'), {
|
|
672
|
+
format: this.Formats.BOOL,
|
|
673
|
+
perms: [this.Perms.READ, this.Perms.NOTIFY, this.Perms.WRITE],
|
|
674
|
+
adminOnlyAccess: [this.Access.WRITE]
|
|
675
|
+
})
|
|
676
|
+
|
|
677
|
+
this.createCharacteristicClass('Recall', uuid('074'), {
|
|
678
|
+
format: this.Formats.BOOL,
|
|
679
|
+
perms: [this.Perms.READ, this.Perms.NOTIFY, this.Perms.WRITE]
|
|
680
|
+
})
|
|
681
|
+
|
|
667
682
|
// Characteristic for Unique ID, used by homebridge-hue.
|
|
668
683
|
// Source: as exposed by the Philips Hue bridge. This characteristic is
|
|
669
684
|
// used by the Hue app to select the accessories when syncing Hue bridge
|
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,441 @@
|
|
|
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
|
+
usbPower: ['B+', '2B', '3B', '3B+'].includes(model)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Initialise SystemInfo instance.
|
|
173
|
+
*/
|
|
174
|
+
async init () {
|
|
175
|
+
switch (process.platform) {
|
|
176
|
+
case 'linux':
|
|
177
|
+
try {
|
|
178
|
+
this.osInfo = await this.getPiOsInfo()
|
|
179
|
+
} catch (error) { this.emit('error', error) }
|
|
180
|
+
if (['arm', 'arm64'].includes(process.arch)) {
|
|
181
|
+
try {
|
|
182
|
+
this.hwInfo = await this.getRpiInfo()
|
|
183
|
+
} catch (error) { this.emit('error', error) }
|
|
184
|
+
}
|
|
185
|
+
break
|
|
186
|
+
case 'darwin':
|
|
187
|
+
try {
|
|
188
|
+
this.osInfo = await this.getMacOsInfo()
|
|
189
|
+
} catch (error) { this.emit('error', error) }
|
|
190
|
+
try {
|
|
191
|
+
this.hwInfo = await this.getMacInfo()
|
|
192
|
+
} catch (error) { this.emit('error', error) }
|
|
193
|
+
break
|
|
194
|
+
default:
|
|
195
|
+
break
|
|
196
|
+
}
|
|
197
|
+
if (this.osInfo == null) {
|
|
198
|
+
this.osInfo = {
|
|
199
|
+
name: process.platform,
|
|
200
|
+
platform: process.platform,
|
|
201
|
+
prettyName: process.platform
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (this.hwInfo == null) {
|
|
205
|
+
this.hwInfo = {
|
|
206
|
+
nCores: os.cpus().length,
|
|
207
|
+
prettyName: process.arch,
|
|
208
|
+
processor: process.arch
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
this.platform = this.osInfo.platform
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Extract serial number and hardware revision info from `/proc/cpuinfo`.
|
|
215
|
+
* @return {object} - The extracted info.
|
|
216
|
+
*/
|
|
217
|
+
async getRpiInfo () {
|
|
218
|
+
const cpuInfo = await this.readTextFile('/proc/cpuinfo')
|
|
219
|
+
const rpi = SystemInfo.parseRpiCpuInfo(cpuInfo)
|
|
220
|
+
return {
|
|
221
|
+
gpioMask: rpi.gpioMask,
|
|
222
|
+
gpioMaskSerial: rpi.gpioMaskSerial,
|
|
223
|
+
id: rpi.id,
|
|
224
|
+
isRpi: true,
|
|
225
|
+
manufacturer: rpi.manufacturer,
|
|
226
|
+
memory: rpi.memory,
|
|
227
|
+
model: rpi.model,
|
|
228
|
+
modelRevision: rpi.modelRevision,
|
|
229
|
+
nCores: os.cpus().length,
|
|
230
|
+
powerLed: rpi.powerLed,
|
|
231
|
+
prettyName: rpi.prettyName,
|
|
232
|
+
processor: rpi.processor,
|
|
233
|
+
revision: rpi.revision
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Extract OS info from /etc/os-release.
|
|
238
|
+
* @return {object} - The extracted info.
|
|
239
|
+
*/
|
|
240
|
+
async getPiOsInfo () {
|
|
241
|
+
let name, platform, prettyName, version, versionName
|
|
242
|
+
const text = await this.readTextFile('/etc/os-release')
|
|
243
|
+
const lines = text.replace(/"/g, '').split('\n')
|
|
244
|
+
for (const line of lines) {
|
|
245
|
+
const fields = line.split('=')
|
|
246
|
+
if (fields.length === 2) {
|
|
247
|
+
switch (fields[0]) {
|
|
248
|
+
case 'ID':
|
|
249
|
+
platform = fields[1] // e.g. 'raspbian'
|
|
250
|
+
break
|
|
251
|
+
case 'NAME':
|
|
252
|
+
name = fields[1] // e.g. 'Raspbian GNU/Linux'
|
|
253
|
+
break
|
|
254
|
+
case 'PRETTY_NAME':
|
|
255
|
+
prettyName = fields[1] // e.g. 'Raspbian GNU/Linux 11 (bullseye)'
|
|
256
|
+
break
|
|
257
|
+
case 'VERSION_CODENAME':
|
|
258
|
+
versionName = fields[1] // e.g. 'bullseye'
|
|
259
|
+
break
|
|
260
|
+
case 'VERSION_ID':
|
|
261
|
+
version = fields[1] // e.g. '11'
|
|
262
|
+
break
|
|
263
|
+
default:
|
|
264
|
+
break
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
name: name,
|
|
270
|
+
platform: platform,
|
|
271
|
+
prettyName: prettyName,
|
|
272
|
+
version: version,
|
|
273
|
+
versionName: versionName
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Extract Apple Mac hardware info from `system_profiler` command.
|
|
278
|
+
* @return {object} - The extracted info.
|
|
279
|
+
*/
|
|
280
|
+
async getMacInfo () {
|
|
281
|
+
let id, memory, model, nCores, prettyName, processor, revision
|
|
282
|
+
let text = await this.exec('system_profiler', 'SPHardwareDataType')
|
|
283
|
+
const lines = text.split('\n')
|
|
284
|
+
for (const line of lines) {
|
|
285
|
+
const fields = line.split(': ')
|
|
286
|
+
if (fields.length === 2) {
|
|
287
|
+
switch (fields[0].trim()) {
|
|
288
|
+
case 'Memory':
|
|
289
|
+
memory = fields[1].replace(/ /g, '')
|
|
290
|
+
break
|
|
291
|
+
case 'Model Identifier':
|
|
292
|
+
revision = fields[1]
|
|
293
|
+
break
|
|
294
|
+
case 'Model Name':
|
|
295
|
+
model = fields[1]
|
|
296
|
+
break
|
|
297
|
+
case 'Chip':
|
|
298
|
+
case 'Processor Name':
|
|
299
|
+
processor = fields[1]
|
|
300
|
+
break
|
|
301
|
+
case 'Serial Number (system)':
|
|
302
|
+
id = fields[1]
|
|
303
|
+
break
|
|
304
|
+
case 'Total Number of Cores':
|
|
305
|
+
nCores = fields[1].split(' ')[0]
|
|
306
|
+
break
|
|
307
|
+
default:
|
|
308
|
+
break
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
text = await this.exec(
|
|
313
|
+
'plutil', '-convert', 'json', '-o', '-',
|
|
314
|
+
process.env.HOME + '/Library/Preferences/com.apple.SystemProfiler.plist'
|
|
315
|
+
)
|
|
316
|
+
const s = JSON.parse(text)['CPU Names']
|
|
317
|
+
if (s != null) {
|
|
318
|
+
// This is empty on my MacBook Pro (14-inch, 2021).
|
|
319
|
+
for (const key of Object.keys(s)) {
|
|
320
|
+
if (key.startsWith(id.slice(-4))) {
|
|
321
|
+
prettyName = s[key]
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
325
|
+
// text = await this.exec(
|
|
326
|
+
// 'ioreg', '-c', 'IOPlatformDevice', '-d', '4', '-n', 'product'
|
|
327
|
+
// )
|
|
328
|
+
text = await this.execShell('ioreg -l | grep product-description')
|
|
329
|
+
// This is empty on my Intel Macs - I think the key is registered for
|
|
330
|
+
// running iPad apps on Apple chips.
|
|
331
|
+
const a = /"product-description" = <"([^"]*)">/.exec(text)
|
|
332
|
+
if (a != null) {
|
|
333
|
+
prettyName = a[1]
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
id: id,
|
|
338
|
+
isMac: true,
|
|
339
|
+
manufacturer: 'Apple Inc.',
|
|
340
|
+
memory: memory,
|
|
341
|
+
model: model,
|
|
342
|
+
nCores: nCores,
|
|
343
|
+
prettyName: prettyName || model,
|
|
344
|
+
processor: processor,
|
|
345
|
+
revision: revision
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Extract macOS info from `sw_vers` command.
|
|
350
|
+
* @return {object} - The extracted info.
|
|
351
|
+
*/
|
|
352
|
+
async getMacOsInfo () {
|
|
353
|
+
let name, version, build
|
|
354
|
+
const text = await this.exec('sw_vers')
|
|
355
|
+
const lines = text.split('\n')
|
|
356
|
+
for (const line of lines) {
|
|
357
|
+
const fields = line.split(':\t')
|
|
358
|
+
if (fields.length === 2) {
|
|
359
|
+
switch (fields[0]) {
|
|
360
|
+
case 'ProductName': // e.g. 'macOS' or 'Mac OS X'
|
|
361
|
+
name = fields[1]
|
|
362
|
+
break
|
|
363
|
+
case 'ProductVersion': // e.g. '12.0.1'
|
|
364
|
+
version = fields[1]
|
|
365
|
+
break
|
|
366
|
+
case 'BuildVersion': // e.g. '21A559'
|
|
367
|
+
build = fields[1]
|
|
368
|
+
break
|
|
369
|
+
default:
|
|
370
|
+
break
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
let v = semver.major(version)
|
|
375
|
+
if (v === 10) {
|
|
376
|
+
v += '.' + semver.minor(version)
|
|
377
|
+
}
|
|
378
|
+
const versionName = macOsInfo.versionNames[v] // e.g. 'Monterey'
|
|
379
|
+
return {
|
|
380
|
+
build: build,
|
|
381
|
+
catalina: semver.gte(version, '10.15.0'),
|
|
382
|
+
name: name,
|
|
383
|
+
platform: process.platform,
|
|
384
|
+
prettyName: [name, versionName, version, '(' + build + ')'].join(' '),
|
|
385
|
+
version: version,
|
|
386
|
+
versionName: versionName
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Execute a command on the local machine.
|
|
391
|
+
* @param {string} command - The command.
|
|
392
|
+
* @param {...string} ...args - The command parameters.
|
|
393
|
+
* @return {string} - The output of the command.
|
|
394
|
+
*/
|
|
395
|
+
async exec (command, ...args) {
|
|
396
|
+
return new Promise((resolve, reject) => {
|
|
397
|
+
/** Emitted when a command is executed.
|
|
398
|
+
* @event SystemInfo#exec
|
|
399
|
+
* @param {string} command - The command.
|
|
400
|
+
*/
|
|
401
|
+
this.emit('exec', command + ' ' + args.join(' '))
|
|
402
|
+
execFile(command, args, null, (error, stdout, stderr) => {
|
|
403
|
+
if (error != null) {
|
|
404
|
+
reject(error)
|
|
405
|
+
}
|
|
406
|
+
resolve(stdout)
|
|
407
|
+
})
|
|
408
|
+
})
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Execute a shell command on the local machine.
|
|
412
|
+
* @param {string} command - The command.
|
|
413
|
+
* @return {string} - The output of the command.
|
|
414
|
+
*/
|
|
415
|
+
async execShell (command) {
|
|
416
|
+
return new Promise((resolve, reject) => {
|
|
417
|
+
this.emit('exec', command)
|
|
418
|
+
exec(command, (error, stdout, stderr) => {
|
|
419
|
+
if (error != null) {
|
|
420
|
+
reject(error)
|
|
421
|
+
}
|
|
422
|
+
resolve(stdout)
|
|
423
|
+
})
|
|
424
|
+
})
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** Read a text file.
|
|
428
|
+
* @param {string} fileName - The file name.
|
|
429
|
+
* @return {string} - The contents of the file.
|
|
430
|
+
*/
|
|
431
|
+
async readTextFile (fileName) {
|
|
432
|
+
/** Emitted when a file is read.
|
|
433
|
+
* @event SystemInfo#readFile
|
|
434
|
+
* @param {string} fileName - The file name.
|
|
435
|
+
*/
|
|
436
|
+
this.emit('readFile', fileName)
|
|
437
|
+
return fs.readFile(fileName, 'utf8')
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
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",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"homekit",
|
|
9
9
|
"homebridge"
|
|
@@ -17,10 +17,11 @@
|
|
|
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
|
+
"homebridge": "^1.3.8",
|
|
24
25
|
"node": "^16.13.0"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|