import childProcess from 'child_process' | |
import { Readline } from '@serialport/parser-readline' | |
import { PortInfo } from '@serialport/binding-abstract' | |
// get only serial port names | |
function checkPathOfDevice(path: string) { | |
return /(tty(S|ACM|USB|AMA|MFD|O)|rfcomm)/.test(path) && path | |
} | |
type PropName = 'DEVNAME' | 'ID_VENDOR_ENC' | 'ID_SERIAL_SHORT' | 'ID_VENDOR_ID' | 'ID_MODEL_ID' | 'DEVLINKS' | |
type PortInfoProp = 'comName' | 'manufacturer' | 'serialNumber' | 'vendorId' | 'productId' | 'pnpId' | |
type PropertyMapping = { [key in PropName]: PortInfoProp } | |
const map: PropertyMapping = { | |
DEVNAME: 'comName', | |
ID_VENDOR_ENC: 'manufacturer', | |
ID_SERIAL_SHORT: 'serialNumber', | |
ID_VENDOR_ID: 'vendorId', | |
ID_MODEL_ID: 'productId', | |
DEVLINKS: 'pnpId', | |
} | |
function propName(name: PropName): PortInfoProp | null { | |
return map[name] || null | |
} | |
function decodeHexEscape(str: string) { | |
return str.replace(/\\x([a-fA-F0-9]{2})/g, (a, b) => { | |
return String.fromCharCode(parseInt(b, 16)) | |
}) | |
} | |
function propVal(name: string, val: string) { | |
if (name === 'pnpId') { | |
const match = val.match(/\/by-id\/([^\s]+)/) | |
return (match && match[1]) || undefined | |
} | |
if (name === 'manufacturer') { | |
return decodeHexEscape(val) | |
} | |
if (/^0x/.test(val)) { | |
return val.substr(2) | |
} | |
return val | |
} | |
export async function linuxList(): Promise<ReadonlyArray<PortInfo>> { | |
const ports: PortInfo[] = [] | |
const ude = childProcess.spawn('udevadm', ['info', '-e']) | |
const lines = ude.stdout.pipe(new Readline()) | |
let port: PortInfo | null = null | |
let skipPort = false | |
lines.on('data', (line: string) => { | |
const lineType = line.slice(0, 1) | |
const data = line.slice(3) | |
// new port entry | |
if (lineType === 'P') { | |
port = { | |
comName: '', | |
manufacturer: undefined, | |
serialNumber: undefined, | |
pnpId: undefined, | |
locationId: undefined, | |
vendorId: undefined, | |
productId: undefined, | |
} | |
skipPort = false | |
return | |
} | |
if (skipPort) { | |
return | |
} | |
// Check dev name and save port if it matches flag to skip the rest of the data if not | |
if (lineType === 'N') { | |
if (port && checkPathOfDevice(data)) { | |
ports.push(port) | |
} else { | |
skipPort = true | |
} | |
return | |
} | |
// parse data about each port | |
if (port && lineType === 'E') { | |
const keyValue = data.match(/^(.+)=(.*)/) | |
if (!keyValue) { | |
return | |
} | |
const key = propName(keyValue[1].toUpperCase() as PropName) | |
if (!key) { | |
return | |
} | |
port = { ...port, [key]: propVal(key, keyValue[2]) } | |
} | |
}) | |
return new Promise((resolve, reject) => { | |
ude.on('error', reject) | |
lines.on('error', reject) | |
lines.on('finish', () => resolve(ports)) | |
}) as Promise<PortInfo[]> | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment