homebridge-lib 6.3.6 → 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/lib/SystemInfo.js DELETED
@@ -1,522 +0,0 @@
1
- // homebridge-lib/lib/SystemInfo.js
2
- //
3
- // Library for Homebridge plugins.
4
- // Copyright © 2019-2023 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
- 21: 'CM4S'
54
- },
55
- processors: {
56
- 0: 'BCM2835',
57
- 1: 'BCM2836',
58
- 2: 'BCM2837',
59
- 3: 'BCM2711'
60
- },
61
- oldRevisions: {
62
- 2: { model: 'B', revision: '1.0', memory: '256MB', manufacturer: 'Egoman' },
63
- 3: { model: 'B', revision: '1.0', memory: '256MB', manufacturer: 'Egoman' },
64
- 4: { model: 'B', revision: '2.0', memory: '256MB', manufacturer: 'Sony UK' },
65
- 5: { model: 'B', revision: '2.0', memory: '256MB', manufacturer: 'Qisda' },
66
- 6: { model: 'B', revision: '2.0', memory: '256MB', manufacturer: 'Egoman' },
67
- 7: { model: 'A', revision: '2.0', memory: '256MB', manufacturer: 'Egoman' },
68
- 8: { model: 'A', revision: '2.0', memory: '256MB', manufacturer: 'Sony UK' },
69
- 9: { model: 'A', revision: '2.0', memory: '256MB', manufacturer: 'Qisda' },
70
- 13: { model: 'B', revision: '2.0', memory: '512MB', manufacturer: 'Egoman' },
71
- 14: { model: 'B', revision: '2.0', memory: '512MB', manufacturer: 'Sony UK' },
72
- 15: { model: 'B', revision: '2.0', memory: '512MB', manufacturer: 'Egoman' },
73
- 16: { model: 'B+', revision: '1.2', memory: '512MB', manufacturer: 'Sony UK' },
74
- 17: { model: 'CM1', revision: '1.0', memory: '512MB', manufacturer: 'Sony UK' },
75
- 18: { model: 'A+', revision: '1.1', memory: '256MB', manufacturer: 'Sony UK' },
76
- 19: { model: 'B+', revision: '1.2', memory: '512MB', manufacturer: 'Embest' },
77
- 20: { model: 'CM1', revision: '1.0', memory: '512MB', manufacturer: 'Embest' },
78
- 21: { model: 'A+', revision: '1.1', memory: '256MB/512MB', manufacturer: 'Embest' }
79
- }
80
- }
81
-
82
- // See: https://en.wikipedia.org/wiki/MacOS_version_history
83
- const macOsInfo = {
84
- versionNames: {
85
- '10.0': 'Cheetah',
86
- 10.1: 'Puma',
87
- 10.2: 'Jaguar',
88
- 10.3: 'Panther',
89
- 10.4: 'Tiger',
90
- 10.5: 'Leopard',
91
- 10.6: 'Snow Leopard',
92
- 10.7: 'Lion',
93
- 10.8: 'Mountain Lion',
94
- 10.9: 'Mavericks',
95
- '10.10': 'Yosemite',
96
- 10.11: 'El Capitan',
97
- 10.12: 'Sierra',
98
- 10.13: 'High Sierra',
99
- 10.14: 'Mojave',
100
- 10.15: 'Catalina',
101
- 11: 'Big Sur',
102
- 12: 'Monterey',
103
- 13: 'Ventura'
104
- }
105
- }
106
-
107
- /** System information.
108
- * @extends EventEmitter
109
- */
110
- class SystemInfo extends events.EventEmitter {
111
- /** Extract Raspberry Pi serial number and hardware revision info from the
112
- * contents of `/proc/cpuinfo`.
113
- * @param {string} cpuInfo - The contents of `/proc/cpuinfo`.
114
- * @return {object} - The extracted info.
115
- */
116
- static parseRpiCpuInfo (cpuInfo) {
117
- let a = /Serial\s*: ([0-9a-f]{16})/.exec(cpuInfo)
118
- if (a == null || a.length < 2) {
119
- return null
120
- }
121
- const id = a[1].toUpperCase()
122
- a = /Revision\s*: ([0-9a-f]{4,})/.exec(cpuInfo)
123
- if (a == null || a.length < 2) {
124
- return null
125
- }
126
- const revision = parseInt(a[1], 16) & 0x00FFFFFF
127
- let gpioMask, manufacturer, memory, model, modelRevision, processor
128
- if ((revision & 0x00800000) !== 0) { // New revision scheme.
129
- manufacturer = rpiInfo.manufacturers[(revision & 0x000F0000) >> 16]
130
- memory = rpiInfo.memorySizes[(revision & 0x00700000) >> 20]
131
- model = rpiInfo.models[(revision & 0x00000FF0) >> 4]
132
- modelRevision = '1.' + ((revision & 0x0000000F) >> 0).toString()
133
- processor = rpiInfo.processors[(revision & 0x0000F000) >> 12]
134
- } else if (rpiInfo.oldRevisions[revision] != null) { // Old incremental revisions.
135
- manufacturer = rpiInfo.oldRevisions[revision].manufacturer
136
- memory = rpiInfo.oldRevisions[revision].memory
137
- model = rpiInfo.oldRevisions[revision].model
138
- modelRevision = rpiInfo.oldRevisions[revision].revision
139
- processor = 'BCM2835'
140
- }
141
- if (model != null && model.startsWith('CM')) {
142
- // Compute module
143
- gpioMask = 0xFFFFFFFF // 0-31
144
- } else if (revision >= 16) {
145
- // Type 3
146
- gpioMask = 0x0FFFFFFC // 2-27
147
- } else if (revision >= 4) {
148
- // Type 2
149
- gpioMask = 0xFBC6CF9C // 2-4, 7-11, 14-15, 17-18, 22-25, 27-31
150
- } else {
151
- // Type 1
152
- gpioMask = 0x03E6CF93 // 0-1, 4, 7-11, 14-15, 17-18, 21-25
153
- }
154
- return {
155
- gpioMask,
156
- gpioMaskSerial: (1 << 15) | (1 << 14),
157
- id,
158
- isRpi: true,
159
- manufacturer,
160
- memory,
161
- model,
162
- modelRevision,
163
- prettyName: [
164
- 'Raspberry Pi', model, modelRevision, '(' + memory + ')'
165
- ].join(' '),
166
- powerLed: !(['A', 'B', 'Zero', 'Zero W', 'Zero 2 W'].includes(model)),
167
- processor,
168
- revision: homebridgeLib.toHexString(revision, 6),
169
- usbPower: ['B+', '2B', '3B', '3B+'].includes(model)
170
- }
171
- }
172
-
173
- /** Initialise SystemInfo instance.
174
- */
175
- async init () {
176
- switch (process.platform) {
177
- case 'linux':
178
- if (await this.existsFile('/etc/synoinfo.conf')) {
179
- try {
180
- this.osInfo = await this.getDsmInfo()
181
- } catch (error) { this.emit('error', error) }
182
- try {
183
- this.hwInfo = await this.getSynoInfo()
184
- } catch (error) { this.emit('error', error) }
185
- } else {
186
- try {
187
- this.osInfo = await this.getPiOsInfo()
188
- } catch (error) { this.emit('error', error) }
189
- if (['arm', 'arm64'].includes(process.arch)) {
190
- try {
191
- this.hwInfo = await this.getRpiInfo()
192
- } catch (error) { this.emit('error', error) }
193
- }
194
- }
195
- break
196
- case 'darwin':
197
- try {
198
- this.osInfo = await this.getMacOsInfo()
199
- } catch (error) { this.emit('error', error) }
200
- try {
201
- this.hwInfo = await this.getMacInfo()
202
- } catch (error) { this.emit('error', error) }
203
- break
204
- default:
205
- break
206
- }
207
- if (this.osInfo == null) {
208
- this.osInfo = {
209
- name: process.platform,
210
- platform: process.platform,
211
- prettyName: process.platform
212
- }
213
- }
214
- if (this.hwInfo == null) {
215
- this.hwInfo = {
216
- nCores: os.cpus().length,
217
- prettyName: process.arch,
218
- processor: process.arch
219
- }
220
- }
221
- this.platform = this.osInfo.platform
222
- }
223
-
224
- /** Extract serial number and hardware revision info from `/proc/cpuinfo`.
225
- * @return {object} - The extracted info.
226
- */
227
- async getRpiInfo () {
228
- const cpuInfo = await this.readTextFile('/proc/cpuinfo')
229
- return SystemInfo.parseRpiCpuInfo(cpuInfo)
230
- }
231
-
232
- /** Extract OS info from /etc/os-release.
233
- * @return {object} - The extracted info.
234
- */
235
- async getPiOsInfo () {
236
- let name, platform, prettyName, version, versionName
237
- const text = await this.readTextFile('/etc/os-release')
238
- const lines = text.replace(/"/g, '').split('\n')
239
- for (const line of lines) {
240
- const fields = line.split('=')
241
- if (fields.length === 2) {
242
- switch (fields[0]) {
243
- case 'ID':
244
- platform = fields[1] // e.g. 'raspbian'
245
- break
246
- case 'NAME':
247
- name = fields[1] // e.g. 'Raspbian GNU/Linux'
248
- break
249
- case 'PRETTY_NAME':
250
- prettyName = fields[1] // e.g. 'Raspbian GNU/Linux 11 (bullseye)'
251
- break
252
- case 'VERSION_CODENAME':
253
- versionName = fields[1] // e.g. 'bullseye'
254
- break
255
- case 'VERSION_ID':
256
- version = fields[1] // e.g. '11'
257
- break
258
- default:
259
- break
260
- }
261
- }
262
- }
263
- return { name, platform, prettyName, version, versionName }
264
- }
265
-
266
- /** Extract Apple Mac hardware info from `system_profiler` command.
267
- * @return {object} - The extracted info.
268
- */
269
- async getMacInfo () {
270
- let id, memory, model, nCores, prettyName, processor, revision
271
- let text = await this.exec('system_profiler', 'SPHardwareDataType')
272
- const lines = text.split('\n')
273
- for (const line of lines) {
274
- const fields = line.split(': ')
275
- if (fields.length === 2) {
276
- switch (fields[0].trim()) {
277
- case 'Memory':
278
- memory = fields[1].replace(/ /g, '')
279
- break
280
- case 'Model Identifier':
281
- revision = fields[1]
282
- break
283
- case 'Model Name':
284
- model = fields[1]
285
- break
286
- case 'Chip':
287
- case 'Processor Name':
288
- processor = fields[1]
289
- break
290
- case 'Serial Number (system)':
291
- id = fields[1]
292
- break
293
- case 'Total Number of Cores':
294
- nCores = fields[1].split(' ')[0]
295
- break
296
- default:
297
- break
298
- }
299
- }
300
- }
301
- try {
302
- if (process.arch === 'x64') { // Intel
303
- text = await this.exec(
304
- 'plutil', '-p',
305
- process.env.HOME + '/Library/Preferences/com.apple.SystemProfiler.plist'
306
- )
307
- const regexp = RegExp(
308
- '"(' + id.slice(-4) + '|' + id.slice(-3) + ').*" => "(.*)"'
309
- )
310
- const a = regexp.exec(text)
311
- if (a != null) {
312
- prettyName = a[2]
313
- }
314
- } else { // Apple silicon
315
- text = await this.execShell('ioreg -l | grep product-description')
316
- const a = /"product-description" = <"([^"]*)">/.exec(text)
317
- if (a != null) {
318
- prettyName = a[1]
319
- }
320
- }
321
- } catch (error) {
322
- this.emit('error', error)
323
- }
324
- return {
325
- id,
326
- isMac: true,
327
- manufacturer: 'Apple Inc.',
328
- memory,
329
- model,
330
- nCores,
331
- prettyName: prettyName || model,
332
- processor,
333
- revision
334
- }
335
- }
336
-
337
- /** Extract macOS info from `sw_vers` command.
338
- * @return {object} - The extracted info.
339
- */
340
- async getMacOsInfo () {
341
- let name, version, build
342
- const text = await this.exec('sw_vers')
343
- const lines = text.split('\n')
344
- for (const line of lines) {
345
- const fields = line.split(':')
346
- if (fields.length === 2) {
347
- switch (fields[0]) {
348
- case 'ProductName': // e.g. 'macOS' or 'Mac OS X'
349
- name = fields[1].trim()
350
- break
351
- case 'ProductVersion': // e.g. '12.0.1' or '12.1'
352
- version = fields[1].trim()
353
- if (version.split('.').length === 2) {
354
- version += '.0'
355
- }
356
- break
357
- case 'BuildVersion': // e.g. '21A559'
358
- build = fields[1].trim()
359
- break
360
- default:
361
- break
362
- }
363
- }
364
- }
365
- let v = semver.major(version)
366
- if (v === 10) {
367
- v += '.' + semver.minor(version)
368
- }
369
- const versionName = macOsInfo.versionNames[v] // e.g. 'Monterey'
370
- return {
371
- build,
372
- catalina: semver.gte(version, '10.15.0'),
373
- name,
374
- platform: process.platform,
375
- prettyName: [name, versionName, version, '(' + build + ')'].join(' '),
376
- version,
377
- versionName
378
- }
379
- }
380
-
381
- /** Extract Synology info from `/etc/synoinfo.conf`
382
- * @return {object} - The extracted info.
383
- */
384
- async getSynoInfo () {
385
- let device = ''
386
- let id
387
- let model = ''
388
- const text = await this.readTextFile('/etc/synoinfo.conf')
389
- const lines = text.replace(/"/g, '').split('\n')
390
- for (const line of lines) {
391
- const fields = line.split('=')
392
- if (fields.length === 2) {
393
- switch (fields[0].trim()) {
394
- case 'pushservice_dsserial':
395
- id = fields[1] // e.g 1970PDN255608
396
- break
397
- case 'upnpdevicetype':
398
- device = fields[1] // e.g. DiskStation
399
- break
400
- case 'upnpmodelname':
401
- model = fields[1] // e.g. DS918+
402
- break
403
- default:
404
- break
405
- }
406
- }
407
- }
408
- return {
409
- id,
410
- manufacturer: 'Synology',
411
- model: [device, model].join(' '),
412
- prettyName: ['Synology', device, model].join(' ')
413
- }
414
- }
415
-
416
- /** Extract DSM info from `/etc/VERSION`.
417
- * @return {object} - The extracted info.
418
- */
419
- async getDsmInfo () {
420
- let build, prettyName, update, version
421
- const text = await this.readTextFile('/etc/VERSION')
422
- const lines = text.replace(/"/g, '').split('\n')
423
- for (const line of lines) {
424
- const fields = line.split('=')
425
- if (fields.length === 2) {
426
- switch (fields[0].trim()) {
427
- case 'buildnumber':
428
- build = fields[1] // e.g. 42661
429
- break
430
- case 'productversion':
431
- version = fields[1] // e.g. 7.1
432
- break
433
- case 'smallfixnumber':
434
- update = fields[1] // e.g. 3
435
- break
436
- default:
437
- break
438
- }
439
- }
440
- }
441
- prettyName = 'DSM'
442
- if (version != null) {
443
- prettyName += ' ' + version
444
- if (build != null) {
445
- prettyName += '-' + build
446
- }
447
- if (update != null) {
448
- prettyName += ' Update ' + update
449
- }
450
- }
451
- return {
452
- build,
453
- prettyName,
454
- update,
455
- version
456
- }
457
- }
458
-
459
- /** Execute a command on the local machine.
460
- * @param {string} command - The command.
461
- * @param {...string} ...args - The command parameters.
462
- * @return {string} - The output of the command.
463
- */
464
- async exec (command, ...args) {
465
- return new Promise((resolve, reject) => {
466
- /** Emitted when a command is executed.
467
- * @event SystemInfo#exec
468
- * @param {string} command - The command.
469
- */
470
- this.emit('exec', command + ' ' + args.join(' '))
471
- execFile(command, args, null, (error, stdout, stderr) => {
472
- if (error != null) {
473
- reject(error)
474
- }
475
- resolve(stdout)
476
- })
477
- })
478
- }
479
-
480
- /** Execute a shell command on the local machine.
481
- * @param {string} command - The command.
482
- * @return {string} - The output of the command.
483
- */
484
- async execShell (command) {
485
- return new Promise((resolve, reject) => {
486
- this.emit('exec', command)
487
- exec(command, (error, stdout, stderr) => {
488
- if (error != null) {
489
- reject(error)
490
- }
491
- resolve(stdout)
492
- })
493
- })
494
- }
495
-
496
- /** Check if file exists.
497
- * @param {string} fileName - The file name.
498
- * @return {bool} - True iff file exists,
499
- */
500
- async existsFile (fileName) {
501
- try {
502
- await fs.access(fileName)
503
- return true
504
- } catch (error) {}
505
- return false
506
- }
507
-
508
- /** Read a text file.
509
- * @param {string} fileName - The file name.
510
- * @return {string} - The contents of the file.
511
- */
512
- async readTextFile (fileName) {
513
- /** Emitted when a file is read.
514
- * @event SystemInfo#readFile
515
- * @param {string} fileName - The file name.
516
- */
517
- this.emit('readFile', fileName)
518
- return fs.readFile(fileName, 'utf8')
519
- }
520
- }
521
-
522
- module.exports = SystemInfo