Skip to content

Commit

Permalink
[all] Unify the serialport.list output (#1266)
Browse files Browse the repository at this point in the history
- [osx] remove the 0x from productID and locationId and vendorId
- [linux] remove the starting 0x from any values, small refactor
- [linux] Fixup output using the hex encoded fields
- [windows] Grab the serial number from the pnp id
- Isolate list functions to their own platform files (c++)
- Better ensure our serialports fields are present

closes #1220
  • Loading branch information
reconbot committed Jul 29, 2017
1 parent 8b07a48 commit 4fc1404
Show file tree
Hide file tree
Showing 21 changed files with 794 additions and 551 deletions.
34 changes: 17 additions & 17 deletions .docs/README.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -405,15 +405,15 @@ $ serialport-list -h


$ serialport-list
/dev/cu.Bluetooth-Incoming-Port
/dev/cu.usbmodem1421 Arduino (www.arduino.cc)
/dev/tty.Bluetooth-Incoming-Port
/dev/tty.usbmodem1421 Arduino (www.arduino.cc)

$ serialport-list -f json
[{"comName":"/dev/cu.Bluetooth-Incoming-Port"},{"comName":"/dev/cu.usbmodem1421","manufacturer":"Arduino (www.arduino.cc)","serialNumber":"752303138333518011C1","locationId":"0x14200000","vendorId":"0x2341","productId":"0x0043"}]
[{"comName":"/dev/tty.Bluetooth-Incoming-Port"},{"comName":"/dev/tty.usbmodem1421","manufacturer":"Arduino (www.arduino.cc)","serialNumber":"752303138333518011C1","locationId":"14200000","vendorId":"2341","productId":"0043"}]

$ serialport-list -f jsonline
{"comName":"/dev/cu.Bluetooth-Incoming-Port"}
{"comName":"/dev/cu.usbmodem1421","manufacturer":"Arduino (www.arduino.cc)","serialNumber":"752303138333518011C1","locationId":"0x14200000","vendorId":"0x2341","productId":"0x0043"}
{"comName":"/dev/tty.Bluetooth-Incoming-Port"}
{"comName":"/dev/tty.usbmodem1421","manufacturer":"Arduino (www.arduino.cc)","serialNumber":"752303138333518011C1","locationId":"14200000","vendorId":"2341","productId":"0043"}
```

### Serial Port Terminal
Expand All @@ -439,40 +439,40 @@ $ serialport-term -h
--echo --localecho Print characters as you type them

$ serialport-term -l
/dev/cu.Bluetooth-Incoming-Port
/dev/cu.usbmodem1421 Arduino (www.arduino.cc)
/dev/tty.Bluetooth-Incoming-Port
/dev/tty.usbmodem1421 Arduino (www.arduino.cc)
```

### Serial Port Repl
`serialport-repl` provides a nodejs repl for working with serialport. This is valuable when debugging.

You can make use of the `serialport-repl` command with;
```bash
serialport-repl # to auto detect an arduino
serialport-repl /path/name # to connect to a specific port
$ serialport-repl # to auto detect an arduino
$ serialport-repl /dev/tty.usbmodem1421 # to connect to a specific port
```

It will load a serialport object with debugging turned on.
```
serialport:binding:auto-detect loading DarwinBinding +0ms
port = SerialPort("/path/name", { autoOpen: false })
port = SerialPort("/dev/tty.usbmodem1421", { autoOpen: false })
globals { SerialPort, portName, port }
> SerialPort.list()
serialport:main .list +6s
[ { comName: '/path/name',
manufacturer: undefined,
serialNumber: undefined,
[ { comName: '/dev/tty.usbmodem1421',
manufacturer: 'Arduino (www.arduino.cc)',
serialNumber: '752303138333518011C1',
pnpId: undefined,
locationId: undefined,
vendorId: undefined,
productId: undefined } ]
locationId: '14200000',
vendorId: '2341',
productId: '0043' } ]
> port.write('Calling all Autobots!')
true
> port.read()
serialport:main _read queueing _read for after open +1m
null
> port.open()
serialport:main opening path: serialport-repl +30s
serialport:main opening path: /dev/tty.usbmodem1421 +30s
serialport:bindings open +1ms
```

Expand Down
70 changes: 47 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,8 @@ parser.on('data', console.log);
#### `SerialPort.list([callback])`<code>Promise</code>
Retrieves a list of available serial ports with metadata. Only the `comName` is guaranteed. If unavailable the other fields will be undefined. The `comName` is either the path or an identifier (eg `COM1`) used to open the SerialPort.

We make an effort to identify the hardware attached and have consistent results between systems. Linux and OS X are mostly consistent. Windows relies on 3rd party device drivers for the information and is unable to guarantee the information. On windows If you have a USB connected device can we provide a serial number otherwise it will be `undefined`. The `pnpId` and `locationId` are not the same or present on all systems. The examples below were run with the same Arduino Uno.

**Kind**: static method of [<code>SerialPort</code>](#exp_module_serialport--SerialPort)
**Returns**: <code>Promise</code> - Resolves with the list of available serial ports.

Expand All @@ -797,17 +799,38 @@ Retrieves a list of available serial ports with metadata. Only the `comName` is

**Example**
```js
// example port information
// OSX example port
{
comName: '/dev/cu.usbmodem1421',
comName: '/dev/tty.usbmodem1421',
manufacturer: 'Arduino (www.arduino.cc)',
serialNumber: '757533138333964011C1',
serialNumber: '752303138333518011C1',
pnpId: undefined,
locationId: '0x14200000',
vendorId: '0x2341',
productId: '0x0043'
locationId: '14500000',
productId: '0043',
vendorId: '2341'
}
// Linux example port
{
comName: '/dev/ttyACM0',
manufacturer: 'Arduino (www.arduino.cc)',
serialNumber: '752303138333518011C1',
pnpId: 'usb-Arduino__www.arduino.cc__0043_752303138333518011C1-if00',
locationId: undefined,
productId: '0043',
vendorId: '2341'
}
// Windows example port
{
comName: 'COM3',
manufacturer: 'Arduino LLC (www.arduino.cc)',
serialNumber: '752303138333518011C1',
pnpId: 'USB\\VID_2341&PID_0043\\752303138333518011C1',
locationId: 'Port_#0003.Hub_#0001',
productId: '0043',
vendorId: '2341'
}
```

```js
Expand All @@ -820,6 +843,7 @@ SerialPort.list(function (err, ports) {
console.log(port.manufacturer);
});
});
// promise approach
SerialPort.list()
.then(ports) {...});
Expand Down Expand Up @@ -1145,15 +1169,15 @@ $ serialport-list -h


$ serialport-list
/dev/cu.Bluetooth-Incoming-Port
/dev/cu.usbmodem1421 Arduino (www.arduino.cc)
/dev/tty.Bluetooth-Incoming-Port
/dev/tty.usbmodem1421 Arduino (www.arduino.cc)

$ serialport-list -f json
[{"comName":"/dev/cu.Bluetooth-Incoming-Port"},{"comName":"/dev/cu.usbmodem1421","manufacturer":"Arduino (www.arduino.cc)","serialNumber":"752303138333518011C1","locationId":"0x14200000","vendorId":"0x2341","productId":"0x0043"}]
[{"comName":"/dev/tty.Bluetooth-Incoming-Port"},{"comName":"/dev/tty.usbmodem1421","manufacturer":"Arduino (www.arduino.cc)","serialNumber":"752303138333518011C1","locationId":"14200000","vendorId":"2341","productId":"0043"}]

$ serialport-list -f jsonline
{"comName":"/dev/cu.Bluetooth-Incoming-Port"}
{"comName":"/dev/cu.usbmodem1421","manufacturer":"Arduino (www.arduino.cc)","serialNumber":"752303138333518011C1","locationId":"0x14200000","vendorId":"0x2341","productId":"0x0043"}
{"comName":"/dev/tty.Bluetooth-Incoming-Port"}
{"comName":"/dev/tty.usbmodem1421","manufacturer":"Arduino (www.arduino.cc)","serialNumber":"752303138333518011C1","locationId":"14200000","vendorId":"2341","productId":"0043"}
```
### Serial Port Terminal
Expand All @@ -1179,40 +1203,40 @@ $ serialport-term -h
--echo --localecho Print characters as you type them

$ serialport-term -l
/dev/cu.Bluetooth-Incoming-Port
/dev/cu.usbmodem1421 Arduino (www.arduino.cc)
/dev/tty.Bluetooth-Incoming-Port
/dev/tty.usbmodem1421 Arduino (www.arduino.cc)
```
### Serial Port Repl
`serialport-repl` provides a nodejs repl for working with serialport. This is valuable when debugging.
You can make use of the `serialport-repl` command with;
```bash
serialport-repl # to auto detect an arduino
serialport-repl /path/name # to connect to a specific port
$ serialport-repl # to auto detect an arduino
$ serialport-repl /dev/tty.usbmodem1421 # to connect to a specific port
```
It will load a serialport object with debugging turned on.
```
serialport:binding:auto-detect loading DarwinBinding +0ms
port = SerialPort("/path/name", { autoOpen: false })
port = SerialPort("/dev/tty.usbmodem1421", { autoOpen: false })
globals { SerialPort, portName, port }
> SerialPort.list()
serialport:main .list +6s
[ { comName: '/path/name',
manufacturer: undefined,
serialNumber: undefined,
[ { comName: '/dev/tty.usbmodem1421',
manufacturer: 'Arduino (www.arduino.cc)',
serialNumber: '752303138333518011C1',
pnpId: undefined,
locationId: undefined,
vendorId: undefined,
productId: undefined } ]
locationId: '14200000',
vendorId: '2341',
productId: '0043' } ]
> port.write('Calling all Autobots!')
true
> port.read()
serialport:main _read queueing _read for after open +1m
null
> port.open()
serialport:main opening path: serialport-repl +30s
serialport:main opening path: /dev/tty.usbmodem1421 +30s
serialport:bindings open +1ms
```
Expand Down
4 changes: 3 additions & 1 deletion UPGRADE_GUIDE.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
Upgrading from 4.x to 5.x
-------------
5.x is a major rewrite to make node serialport a NodeJS stream. While the api surface is similar there have been a number of changes to ensure more consistent error handling and operation of a serial port.
5.x is a major rewrite to make node serialport a NodeJS stream. While the api surface is similar there have been a number of changes to ensure more consistent error handling and operation of a serial port.

- Removed the `disconnect` event. The `close` event now fires with a disconnect error object in the event of a disconnection.
- `drain` now waits for the current javascript write to complete before calling the system level drain.
- `port.isOpen` is now a property not a function
- `SerialPort.list` has slightly different output with more information, decoded strings and `0x` prefixes removed from some properties.
- `SerialPort.list` now returns a promise if no call back is provided

The exact changes will go here see #1046

Expand Down
19 changes: 8 additions & 11 deletions bin/find-arduino.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@
// outputs the path to an arduino or nothing

const serialport = require('../');
serialport.list((err, ports) => {
if (err) {
console.error(err);
serialport.list()
.then(ports => ports.find(port => /arduino/i.test(port.manufacturer)))
.then(port => {
if (!port) { throw new Error('Arduino Not found') }
console.log(port.comName);
})
.catch((err) => {
console.error(err.message);
process.exit(1);
}
ports.forEach((port) => {
if (/arduino/i.test(port.manufacturer)) {
console.log(port.comName);
process.exit(0);
}
});
process.exit(1);
});
3 changes: 2 additions & 1 deletion binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
{
'sources': [
'src/serialport_unix.cpp',
'src/poller.cpp'
'src/poller.cpp',
'src/darwin_list.cpp'
],
'xcode_settings': {
'OTHER_LDFLAGS': [
Expand Down
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Version 5.0.0-beta9
-------------
- [all] `Serialport.list` now has more consistent output across all platforms.
- [linux] `Serialport.list` is now faster and less resource intensive thanks to @akaJes for contributing this!

Version 5.0.0-beta8
-------------
If we're lucky this will be the last of the betas. The remaining potentially blocking issues have to do with improving `SerialPort.list` which would change their output. The two issues are #1220 and #1084. I need help on those two issues, if I'm not able to close them soon, I'll release anyway, and they'll be fixed for 6x. This release is large enough. -@reconbot
Expand Down
97 changes: 67 additions & 30 deletions lib/bindings/linux-list.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,97 @@
'use strict';

const childProcess = require('child_process');
const LineStream = require('../parsers/readline');
const Readline = require('../parsers/readline');

function checkPathAndDevice(path) {
// get only serial port names
// get only serial port names
function checkPathOfDevice(path) {
return (/(tty(S|ACM|USB|AMA|MFD)|rfcomm)/).test(path) && path;
}

function propName(name) {
return {
'DEVNAME': 'comName',
'ID_VENDOR': 'manufacturer',
'ID_SERIAL': 'serialNumber',
'ID_VENDOR_ENC': 'manufacturer',
'ID_SERIAL_SHORT': 'serialNumber',
'ID_VENDOR_ID': 'vendorId',
'ID_MODEL_ID': 'productId',
'ID_MODEL_ENC': 'productId',

This comment has been minimized.

Copy link
@erikkallen

erikkallen Aug 3, 2017

Contributor

Changing this breaks the product_id on linux, it then lists the product name not the id, which is used in a lot of applications to check for a specific device

'DEVLINKS': 'pnpId'
}[name.toUpperCase()];
}

function decodeHexEscape(str) {
return str.replace(/\\x([a-fA-F0-9]{2})/g, (a, b) => {
return String.fromCharCode(parseInt(b, 16));
});
}

function propVal(name, val) {
if (/productId|vendorId/.test(name) && !/^0x/.test(val)) {
return `0x${val}`;
}
if (name === 'pnpId') {
return val.split(' ')
.map((path) => path.match(/\/by-id\/(.*)/))
.filter(a => a).map(a => a[1])[0];
const match = val.match(/\/by-id\/([^\s]+)/);
return (match && match[1]) || undefined;
}
if (name === 'manufacturer') {
return decodeHexEscape(val);
}
if (name === 'productId') {
return decodeHexEscape(val);
}
if (/^0x/.test(val)) {
return val.substr(2);
}
return val;
}

function listLinux() {
return new Promise((resolve, reject) => {
const ports = [];
const ude = childProcess.spawn('udevadm', ['info', '-e']);
const lines = ude.stdout.pipe(new Readline());
ude.on('error', reject);
const lines = new LineStream();
const ports = [];
let obj, prop, name;
lines.on('finish', () => resolve(ports));
lines.on('error', reject);
lines.on('data', (data) => {
const line = data.toString();
if ((name = line.match(/^N: (.*)/))) {
if (checkPathAndDevice(name[1])) {
ports.push(obj = {});
}
} else
if ((name = line.match(/^E: (.*)=(.*)/))) {
if (obj && (prop = propName(name[1]))) {
obj[prop] = propVal(prop, name[2]);

let port = {};
let skipPort = false;
lines.on('data', (line) => {
const lineType = line.slice(0, 1);
const data = line.slice(3);
// new port entry
if (lineType === 'P') {
port = {
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 (checkPathOfDevice(data)) {
ports.push(port);
} else {
skipPort = true;
}
} else
if (/^P: /.test(line)) {
obj = undefined;
return;
}

// parse data about each port
if (lineType === 'E') {
const keyValue = data.match(/^(.+)=(.*)/);
if (!keyValue) { return }
const key = propName(keyValue[1]);
if (!key) { return }
port[key] = propVal(key, keyValue[2]);
}
});
ude.stdout.pipe(lines);

lines.on('finish', () => resolve(ports));
});
}

Expand Down
Loading

0 comments on commit 4fc1404

Please sign in to comment.