From 4fc1404c8cc381f38118e3842a0ddbb2b11ba717 Mon Sep 17 00:00:00 2001 From: Francis Gulotta Date: Sat, 29 Jul 2017 19:40:39 -0400 Subject: [PATCH] [all] Unify the serialport.list output (#1266) - [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 --- .docs/README.hbs | 34 ++-- README.md | 70 ++++--- UPGRADE_GUIDE.md | 4 +- bin/find-arduino.js | 19 +- binding.gyp | 3 +- changelog.md | 5 + lib/bindings/linux-list.js | 97 +++++++--- lib/bindings/win32.js | 12 +- lib/serialport.js | 36 +++- src/darwin_list.cpp | 356 ++++++++++++++++++++++++++++++++++++ src/darwin_list.h | 44 +++++ src/serialport.cpp | 73 +------- src/serialport.h | 21 --- src/serialport_unix.cpp | 318 +------------------------------- src/serialport_unix.h | 7 + src/serialport_win.cpp | 63 +++++++ src/serialport_win.h | 27 ++- test/bindings-linux-list.js | 117 +++++++----- test/bindings.js | 26 ++- test/initializers/assert.js | 5 +- test/mocks/linux-list.js | 8 +- 21 files changed, 794 insertions(+), 551 deletions(-) create mode 100644 src/darwin_list.cpp create mode 100644 src/darwin_list.h create mode 100644 src/serialport_unix.h diff --git a/.docs/README.hbs b/.docs/README.hbs index 8b3ccd230..08e883cfa 100644 --- a/.docs/README.hbs +++ b/.docs/README.hbs @@ -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 @@ -439,8 +439,8 @@ $ 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 @@ -448,31 +448,31 @@ $ serialport-term -l 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 ``` diff --git a/README.md b/README.md index 5f591c057..012e6796b 100644 --- a/README.md +++ b/README.md @@ -788,6 +788,8 @@ parser.on('data', console.log); #### `SerialPort.list([callback])` ⇒ Promise 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 [SerialPort](#exp_module_serialport--SerialPort) **Returns**: Promise - Resolves with the list of available serial ports. @@ -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 @@ -820,6 +843,7 @@ SerialPort.list(function (err, ports) { console.log(port.manufacturer); }); }); + // promise approach SerialPort.list() .then(ports) {...}); @@ -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 @@ -1179,8 +1203,8 @@ $ 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 @@ -1188,31 +1212,31 @@ $ serialport-term -l 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 ``` diff --git a/UPGRADE_GUIDE.md b/UPGRADE_GUIDE.md index 976d769ee..a47dc6813 100644 --- a/UPGRADE_GUIDE.md +++ b/UPGRADE_GUIDE.md @@ -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 diff --git a/bin/find-arduino.js b/bin/find-arduino.js index f6f4543dd..6b66c1907 100755 --- a/bin/find-arduino.js +++ b/bin/find-arduino.js @@ -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); -}); diff --git a/binding.gyp b/binding.gyp index c3b30ef5f..56e6e49fa 100644 --- a/binding.gyp +++ b/binding.gyp @@ -25,7 +25,8 @@ { 'sources': [ 'src/serialport_unix.cpp', - 'src/poller.cpp' + 'src/poller.cpp', + 'src/darwin_list.cpp' ], 'xcode_settings': { 'OTHER_LDFLAGS': [ diff --git a/changelog.md b/changelog.md index 39d4c174e..6a5915169 100644 --- a/changelog.md +++ b/changelog.md @@ -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 diff --git a/lib/bindings/linux-list.js b/lib/bindings/linux-list.js index dd9a2cc15..4f291ddf6 100644 --- a/lib/bindings/linux-list.js +++ b/lib/bindings/linux-list.js @@ -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', '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)); }); } diff --git a/lib/bindings/win32.js b/lib/bindings/win32.js index fa5cfb9ed..333167f8b 100644 --- a/lib/bindings/win32.js +++ b/lib/bindings/win32.js @@ -5,7 +5,17 @@ const promisify = require('../util').promisify; class WindowsBinding extends BaseBinding { static list() { - return promisify(binding.list)(); + return promisify(binding.list)().then(ports => { + // Grab the serial number from the pnp id + ports.forEach(port => { + if (port.pnpId) { + const parts = port.pnpId.match(/USB\\(.+)\\(.+)/); + if (!parts) { return } + port.serialNumber = parts.pop(); + } + }); + return ports; + }); } constructor(opt) { diff --git a/lib/serialport.js b/lib/serialport.js index 4ad8a768f..db40e2245 100644 --- a/lib/serialport.js +++ b/lib/serialport.js @@ -553,22 +553,45 @@ SerialPort.prototype.drain = function(callback) { /** * 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. * @type {function} * @param {listCallback=} callback * @returns {Promise} Resolves with the list of available serial ports. * @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 @@ -581,6 +604,7 @@ SerialPort.list(function (err, ports) { console.log(port.manufacturer); }); }); + // promise approach SerialPort.list() .then(ports) {...}); diff --git a/src/darwin_list.cpp b/src/darwin_list.cpp new file mode 100644 index 000000000..f793760bc --- /dev/null +++ b/src/darwin_list.cpp @@ -0,0 +1,356 @@ +#include "./darwin_list.h" + +#include +#include +#include +#include + +#if defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) +#include +#include +#endif + +uv_mutex_t list_mutex; +Boolean lockInitialised = FALSE; + +NAN_METHOD(List) { + // callback + if (!info[0]->IsFunction()) { + Nan::ThrowTypeError("First argument must be a function"); + return; + } + + ListBaton* baton = new ListBaton(); + strcpy(baton->errorString, ""); + baton->callback.Reset(info[0].As()); + + uv_work_t* req = new uv_work_t(); + req->data = baton; + uv_queue_work(uv_default_loop(), req, EIO_List, (uv_after_work_cb)EIO_AfterList); +} + +void setIfNotEmpty(v8::Local item, std::string key, const char *value) { + v8::Local v8key = Nan::New(key).ToLocalChecked(); + if (strlen(value) > 0) { + Nan::Set(item, v8key, Nan::New(value).ToLocalChecked()); + } else { + Nan::Set(item, v8key, Nan::Undefined()); + } +} + + +// Function prototypes +static kern_return_t FindModems(io_iterator_t *matchingServices); +static io_service_t GetUsbDevice(io_service_t service); +static stDeviceListItem* GetSerialDevices(); + + +static kern_return_t FindModems(io_iterator_t *matchingServices) { + kern_return_t kernResult; + CFMutableDictionaryRef classesToMatch; + classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue); + if (classesToMatch != NULL) { + CFDictionarySetValue(classesToMatch, + CFSTR(kIOSerialBSDTypeKey), + CFSTR(kIOSerialBSDAllTypes)); + } + + kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatch, matchingServices); + + return kernResult; +} + +static io_service_t GetUsbDevice(io_service_t service) { + IOReturn status; + io_iterator_t iterator = 0; + io_service_t device = 0; + + if (!service) { + return device; + } + + status = IORegistryEntryCreateIterator(service, + kIOServicePlane, + (kIORegistryIterateParents | kIORegistryIterateRecursively), + &iterator); + + if (status == kIOReturnSuccess) { + io_service_t currentService; + while ((currentService = IOIteratorNext(iterator)) && device == 0) { + io_name_t serviceName; + status = IORegistryEntryGetNameInPlane(currentService, kIOServicePlane, serviceName); + if (status == kIOReturnSuccess && IOObjectConformsTo(currentService, kIOUSBDeviceClassName)) { + device = currentService; + } else { + // Release the service object which is no longer needed + (void) IOObjectRelease(currentService); + } + } + + // Release the iterator + (void) IOObjectRelease(iterator); + } + + return device; +} + +static void ExtractUsbInformation(stSerialDevice *serialDevice, IOUSBDeviceInterface **deviceInterface) { + kern_return_t kernResult; + UInt32 locationID; + kernResult = (*deviceInterface)->GetLocationID(deviceInterface, &locationID); + if (KERN_SUCCESS == kernResult) { + snprintf(serialDevice->locationId, 11, "%08x", locationID); + } + + UInt16 vendorID; + kernResult = (*deviceInterface)->GetDeviceVendor(deviceInterface, &vendorID); + if (KERN_SUCCESS == kernResult) { + snprintf(serialDevice->vendorId, 7, "%04x", vendorID); + } + + UInt16 productID; + kernResult = (*deviceInterface)->GetDeviceProduct(deviceInterface, &productID); + if (KERN_SUCCESS == kernResult) { + snprintf(serialDevice->productId, 7, "%04x", productID); + } +} + +static stDeviceListItem* GetSerialDevices() { + char bsdPath[MAXPATHLEN]; + + io_iterator_t serialPortIterator; + FindModems(&serialPortIterator); + + kern_return_t kernResult = KERN_FAILURE; + Boolean modemFound = false; + + // Initialize the returned path + *bsdPath = '\0'; + + stDeviceListItem* devices = NULL; + stDeviceListItem* lastDevice = NULL; + int length = 0; + + io_service_t modemService; + while ((modemService = IOIteratorNext(serialPortIterator))) { + CFTypeRef bsdPathAsCFString; + bsdPathAsCFString = IORegistryEntrySearchCFProperty( + modemService, + kIOServicePlane, + CFSTR(kIODialinDeviceKey), + kCFAllocatorDefault, + kIORegistryIterateRecursively); + + if (bsdPathAsCFString) { + Boolean result; + + // Convert the path from a CFString to a C (NUL-terminated) + result = CFStringGetCString((CFStringRef) bsdPathAsCFString, + bsdPath, + sizeof(bsdPath), + kCFStringEncodingUTF8); + CFRelease(bsdPathAsCFString); + + if (result) { + stDeviceListItem *deviceListItem = (stDeviceListItem*) malloc(sizeof(stDeviceListItem)); + stSerialDevice *serialDevice = &(deviceListItem->value); + strcpy(serialDevice->port, bsdPath); + memset(serialDevice->locationId, 0, sizeof(serialDevice->locationId)); + memset(serialDevice->vendorId, 0, sizeof(serialDevice->vendorId)); + memset(serialDevice->productId, 0, sizeof(serialDevice->productId)); + serialDevice->manufacturer[0] = '\0'; + serialDevice->serialNumber[0] = '\0'; + deviceListItem->next = NULL; + deviceListItem->length = &length; + + if (devices == NULL) { + devices = deviceListItem; + } else { + lastDevice->next = deviceListItem; + } + + lastDevice = deviceListItem; + length++; + + modemFound = true; + kernResult = KERN_SUCCESS; + + uv_mutex_lock(&list_mutex); + + io_service_t device = GetUsbDevice(modemService); + + if (device) { + CFStringRef manufacturerAsCFString = (CFStringRef) IORegistryEntryCreateCFProperty(device, + CFSTR(kUSBVendorString), + kCFAllocatorDefault, + 0); + + if (manufacturerAsCFString) { + Boolean result; + char manufacturer[MAXPATHLEN]; + + // Convert from a CFString to a C (NUL-terminated) + result = CFStringGetCString(manufacturerAsCFString, + manufacturer, + sizeof(manufacturer), + kCFStringEncodingUTF8); + + if (result) { + strcpy(serialDevice->manufacturer, manufacturer); + } + + CFRelease(manufacturerAsCFString); + } + + CFStringRef serialNumberAsCFString = (CFStringRef) IORegistryEntrySearchCFProperty(device, + kIOServicePlane, + CFSTR(kUSBSerialNumberString), + kCFAllocatorDefault, + kIORegistryIterateRecursively); + + if (serialNumberAsCFString) { + Boolean result; + char serialNumber[MAXPATHLEN]; + + // Convert from a CFString to a C (NUL-terminated) + result = CFStringGetCString(serialNumberAsCFString, + serialNumber, + sizeof(serialNumber), + kCFStringEncodingUTF8); + + if (result) { + strcpy(serialDevice->serialNumber, serialNumber); + } + + CFRelease(serialNumberAsCFString); + } + + IOCFPlugInInterface **plugInInterface = NULL; + SInt32 score; + HRESULT res; + + IOUSBDeviceInterface **deviceInterface = NULL; + + kernResult = IOCreatePlugInInterfaceForService(device, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, + &plugInInterface, &score); + + if ((kIOReturnSuccess != kernResult) || !plugInInterface) { + continue; + } + + // Use the plugin interface to retrieve the device interface. + res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), + (LPVOID*) &deviceInterface); + + // Now done with the plugin interface. + (*plugInInterface)->Release(plugInInterface); + + if (res || deviceInterface == NULL) { + continue; + } + + // Extract the desired Information + ExtractUsbInformation(serialDevice, deviceInterface); + + // Release the Interface + (*deviceInterface)->Release(deviceInterface); + + // Release the device + (void) IOObjectRelease(device); + } + + uv_mutex_unlock(&list_mutex); + } + } + + // Release the io_service_t now that we are done with it. + (void) IOObjectRelease(modemService); + } + + IOObjectRelease(serialPortIterator); // Release the iterator. + + return devices; +} + +void EIO_List(uv_work_t* req) { + ListBaton* data = static_cast(req->data); + + if (!lockInitialised) { + uv_mutex_init(&list_mutex); + lockInitialised = TRUE; + } + + stDeviceListItem* devices = GetSerialDevices(); + if (*(devices->length) > 0) { + stDeviceListItem* next = devices; + + for (int i = 0, len = *(devices->length); i < len; i++) { + stSerialDevice device = (* next).value; + + ListResultItem* resultItem = new ListResultItem(); + resultItem->comName = device.port; + + if (*device.locationId) { + resultItem->locationId = device.locationId; + } + if (*device.vendorId) { + resultItem->vendorId = device.vendorId; + } + if (*device.productId) { + resultItem->productId = device.productId; + } + if (*device.manufacturer) { + resultItem->manufacturer = device.manufacturer; + } + if (*device.serialNumber) { + resultItem->serialNumber = device.serialNumber; + } + data->results.push_back(resultItem); + + stDeviceListItem* current = next; + + if (next->next != NULL) { + next = next->next; + } + + free(current); + } + } +} + +void EIO_AfterList(uv_work_t* req) { + Nan::HandleScope scope; + + ListBaton* data = static_cast(req->data); + + v8::Local argv[2]; + if (data->errorString[0]) { + argv[0] = v8::Exception::Error(Nan::New(data->errorString).ToLocalChecked()); + argv[1] = Nan::Undefined(); + } else { + v8::Local results = Nan::New(); + int i = 0; + for (std::list::iterator it = data->results.begin(); it != data->results.end(); ++it, i++) { + v8::Local item = Nan::New(); + + setIfNotEmpty(item, "comName", (*it)->comName.c_str()); + setIfNotEmpty(item, "manufacturer", (*it)->manufacturer.c_str()); + setIfNotEmpty(item, "serialNumber", (*it)->serialNumber.c_str()); + setIfNotEmpty(item, "pnpId", (*it)->pnpId.c_str()); + setIfNotEmpty(item, "locationId", (*it)->locationId.c_str()); + setIfNotEmpty(item, "vendorId", (*it)->vendorId.c_str()); + setIfNotEmpty(item, "productId", (*it)->productId.c_str()); + + Nan::Set(results, i, item); + } + argv[0] = Nan::Null(); + argv[1] = results; + } + data->callback.Call(2, argv); + + for (std::list::iterator it = data->results.begin(); it != data->results.end(); ++it) { + delete *it; + } + delete data; + delete req; +} diff --git a/src/darwin_list.h b/src/darwin_list.h new file mode 100644 index 000000000..4fd281118 --- /dev/null +++ b/src/darwin_list.h @@ -0,0 +1,44 @@ +#ifndef SRC_SERIALPORT_DARWIN_LIST_H_ +#define SRC_SERIALPORT_DARWIN_LIST_H_ +#include +#include +#include // For MAXPATHLEN + +#define ERROR_STRING_SIZE 1024 + +NAN_METHOD(List); +void EIO_List(uv_work_t* req); +void EIO_AfterList(uv_work_t* req); + +struct ListResultItem { + std::string comName; + std::string manufacturer; + std::string serialNumber; + std::string pnpId; + std::string locationId; + std::string vendorId; + std::string productId; +}; + +struct ListBaton { + Nan::Callback callback; + std::list results; + char errorString[ERROR_STRING_SIZE]; +}; + +typedef struct SerialDevice { + char port[MAXPATHLEN]; + char locationId[MAXPATHLEN]; + char vendorId[MAXPATHLEN]; + char productId[MAXPATHLEN]; + char manufacturer[MAXPATHLEN]; + char serialNumber[MAXPATHLEN]; +} stSerialDevice; + +typedef struct DeviceListItem { + struct SerialDevice value; + struct DeviceListItem *next; + int* length; +} stDeviceListItem; + +#endif // SRC_SERIALPORT_DARWIN_LIST_H_ diff --git a/src/serialport.cpp b/src/serialport.cpp index e0554f7b4..8f3c8adc3 100644 --- a/src/serialport.cpp +++ b/src/serialport.cpp @@ -1,5 +1,9 @@ #include "./serialport.h" +#ifdef __APPLE__ + #include "./darwin_list.h" +#endif + #ifdef WIN32 #define strncasecmp strnicmp #include "./serialport_win.h" @@ -190,68 +194,6 @@ void EIO_AfterClose(uv_work_t* req) { delete req; } -NAN_METHOD(List) { - // callback - if (!info[0]->IsFunction()) { - Nan::ThrowTypeError("First argument must be a function"); - return; - } - - ListBaton* baton = new ListBaton(); - strcpy(baton->errorString, ""); - baton->callback.Reset(info[0].As()); - - uv_work_t* req = new uv_work_t(); - req->data = baton; - uv_queue_work(uv_default_loop(), req, EIO_List, (uv_after_work_cb)EIO_AfterList); -} - -void setIfNotEmpty(v8::Local item, std::string key, const char *value) { - v8::Local v8key = Nan::New(key).ToLocalChecked(); - if (strlen(value) > 0) { - Nan::Set(item, v8key, Nan::New(value).ToLocalChecked()); - } else { - Nan::Set(item, v8key, Nan::Undefined()); - } -} - -void EIO_AfterList(uv_work_t* req) { - Nan::HandleScope scope; - - ListBaton* data = static_cast(req->data); - - v8::Local argv[2]; - if (data->errorString[0]) { - argv[0] = v8::Exception::Error(Nan::New(data->errorString).ToLocalChecked()); - argv[1] = Nan::Undefined(); - } else { - v8::Local results = Nan::New(); - int i = 0; - for (std::list::iterator it = data->results.begin(); it != data->results.end(); ++it, i++) { - v8::Local item = Nan::New(); - - setIfNotEmpty(item, "comName", (*it)->comName.c_str()); - setIfNotEmpty(item, "manufacturer", (*it)->manufacturer.c_str()); - setIfNotEmpty(item, "serialNumber", (*it)->serialNumber.c_str()); - setIfNotEmpty(item, "pnpId", (*it)->pnpId.c_str()); - setIfNotEmpty(item, "locationId", (*it)->locationId.c_str()); - setIfNotEmpty(item, "vendorId", (*it)->vendorId.c_str()); - setIfNotEmpty(item, "productId", (*it)->productId.c_str()); - - Nan::Set(results, i, item); - } - argv[0] = Nan::Null(); - argv[1] = results; - } - data->callback.Call(2, argv); - - for (std::list::iterator it = data->results.begin(); it != data->results.end(); ++it) { - delete *it; - } - delete data; - delete req; -} - NAN_METHOD(Flush) { // file descriptor if (!info[0]->IsInt32()) { @@ -482,12 +424,17 @@ extern "C" { Nan::SetMethod(target, "open", Open); Nan::SetMethod(target, "update", Update); Nan::SetMethod(target, "close", Close); - Nan::SetMethod(target, "list", List); Nan::SetMethod(target, "flush", Flush); Nan::SetMethod(target, "drain", Drain); + + #ifdef __APPLE__ + Nan::SetMethod(target, "list", List); + #endif + #ifdef WIN32 Nan::SetMethod(target, "write", Write); Nan::SetMethod(target, "read", Read); + Nan::SetMethod(target, "list", List); #else Poller::Init(target); #endif diff --git a/src/serialport.h b/src/serialport.h index 34645e295..63e1e5cbf 100644 --- a/src/serialport.h +++ b/src/serialport.h @@ -4,15 +4,10 @@ #include #include #include -#include #include #define ERROR_STRING_SIZE 1024 -NAN_METHOD(List); -void EIO_List(uv_work_t* req); -void EIO_AfterList(uv_work_t* req); - NAN_METHOD(Open); void EIO_Open(uv_work_t* req); void EIO_AfterOpen(uv_work_t* req); @@ -88,22 +83,6 @@ struct ConnectionOptionsBaton { int baudRate; }; -struct ListResultItem { - std::string comName; - std::string manufacturer; - std::string serialNumber; - std::string pnpId; - std::string locationId; - std::string vendorId; - std::string productId; -}; - -struct ListBaton { - Nan::Callback callback; - std::list results; - char errorString[ERROR_STRING_SIZE]; -}; - struct SetBaton { int fd; Nan::Callback callback; diff --git a/src/serialport_unix.cpp b/src/serialport_unix.cpp index 243b55b76..065f3b933 100644 --- a/src/serialport_unix.cpp +++ b/src/serialport_unix.cpp @@ -1,4 +1,6 @@ +#include "./serialport_unix.h" #include "./serialport.h" + #include #include #include @@ -8,13 +10,6 @@ #ifdef __APPLE__ #include #include -#include -#include -#include -#include - -uv_mutex_t list_mutex; -Boolean lockInitialised = FALSE; #endif #if defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) @@ -31,8 +26,6 @@ Boolean lockInitialised = FALSE; #include #endif -int ToBaudConstant(int baudRate); -int ToDataBitsConstant(int dataBits); int ToStopBitsConstant(SerialPortStopBits stopBits); int ToBaudConstant(int baudRate) { @@ -74,23 +67,6 @@ int ToBaudConstant(int baudRate) { return -1; } -#ifdef __APPLE__ -typedef struct SerialDevice { - char port[MAXPATHLEN]; - char locationId[MAXPATHLEN]; - char vendorId[MAXPATHLEN]; - char productId[MAXPATHLEN]; - char manufacturer[MAXPATHLEN]; - char serialNumber[MAXPATHLEN]; -} stSerialDevice; - -typedef struct DeviceListItem { - struct SerialDevice value; - struct DeviceListItem *next; - int* length; -} stDeviceListItem; -#endif - int ToDataBitsConstant(int dataBits) { switch (dataBits) { case 8: default: return CS8; @@ -334,296 +310,6 @@ void EIO_Close(uv_work_t* req) { } } -#ifdef __APPLE__ - -// Function prototypes -static kern_return_t FindModems(io_iterator_t *matchingServices); -static io_service_t GetUsbDevice(io_service_t service); -static stDeviceListItem* GetSerialDevices(); - - -static kern_return_t FindModems(io_iterator_t *matchingServices) { - kern_return_t kernResult; - CFMutableDictionaryRef classesToMatch; - classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue); - if (classesToMatch != NULL) { - CFDictionarySetValue(classesToMatch, - CFSTR(kIOSerialBSDTypeKey), - CFSTR(kIOSerialBSDAllTypes)); - } - - kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatch, matchingServices); - - return kernResult; -} - -static io_service_t GetUsbDevice(io_service_t service) { - IOReturn status; - io_iterator_t iterator = 0; - io_service_t device = 0; - - if (!service) { - return device; - } - - status = IORegistryEntryCreateIterator(service, - kIOServicePlane, - (kIORegistryIterateParents | kIORegistryIterateRecursively), - &iterator); - - if (status == kIOReturnSuccess) { - io_service_t currentService; - while ((currentService = IOIteratorNext(iterator)) && device == 0) { - io_name_t serviceName; - status = IORegistryEntryGetNameInPlane(currentService, kIOServicePlane, serviceName); - if (status == kIOReturnSuccess && IOObjectConformsTo(currentService, kIOUSBDeviceClassName)) { - device = currentService; - } else { - // Release the service object which is no longer needed - (void) IOObjectRelease(currentService); - } - } - - // Release the iterator - (void) IOObjectRelease(iterator); - } - - return device; -} - -static void ExtractUsbInformation(stSerialDevice *serialDevice, IOUSBDeviceInterface **deviceInterface) { - kern_return_t kernResult; - UInt32 locationID; - kernResult = (*deviceInterface)->GetLocationID(deviceInterface, &locationID); - if (KERN_SUCCESS == kernResult) { - snprintf(serialDevice->locationId, 11, "0x%08x", locationID); - } - - UInt16 vendorID; - kernResult = (*deviceInterface)->GetDeviceVendor(deviceInterface, &vendorID); - if (KERN_SUCCESS == kernResult) { - snprintf(serialDevice->vendorId, 7, "0x%04x", vendorID); - } - - UInt16 productID; - kernResult = (*deviceInterface)->GetDeviceProduct(deviceInterface, &productID); - if (KERN_SUCCESS == kernResult) { - snprintf(serialDevice->productId, 7, "0x%04x", productID); - } -} - -static stDeviceListItem* GetSerialDevices() { - kern_return_t kernResult; - io_iterator_t serialPortIterator; - char bsdPath[MAXPATHLEN]; - - FindModems(&serialPortIterator); - - io_service_t modemService; - kernResult = KERN_FAILURE; - Boolean modemFound = false; - - // Initialize the returned path - *bsdPath = '\0'; - - stDeviceListItem* devices = NULL; - stDeviceListItem* lastDevice = NULL; - int length = 0; - - while ((modemService = IOIteratorNext(serialPortIterator))) { - CFTypeRef bsdPathAsCFString; - bsdPathAsCFString = IORegistryEntrySearchCFProperty( - modemService, - kIOServicePlane, - CFSTR(kIODialinDeviceKey), - kCFAllocatorDefault, - kIORegistryIterateRecursively); - - if (bsdPathAsCFString) { - Boolean result; - - // Convert the path from a CFString to a C (NUL-terminated) - result = CFStringGetCString((CFStringRef) bsdPathAsCFString, - bsdPath, - sizeof(bsdPath), - kCFStringEncodingUTF8); - CFRelease(bsdPathAsCFString); - - if (result) { - stDeviceListItem *deviceListItem = (stDeviceListItem*) malloc(sizeof(stDeviceListItem)); - stSerialDevice *serialDevice = &(deviceListItem->value); - strcpy(serialDevice->port, bsdPath); - memset(serialDevice->locationId, 0, sizeof(serialDevice->locationId)); - memset(serialDevice->vendorId, 0, sizeof(serialDevice->vendorId)); - memset(serialDevice->productId, 0, sizeof(serialDevice->productId)); - serialDevice->manufacturer[0] = '\0'; - serialDevice->serialNumber[0] = '\0'; - deviceListItem->next = NULL; - deviceListItem->length = &length; - - if (devices == NULL) { - devices = deviceListItem; - } else { - lastDevice->next = deviceListItem; - } - - lastDevice = deviceListItem; - length++; - - modemFound = true; - kernResult = KERN_SUCCESS; - - uv_mutex_lock(&list_mutex); - - io_service_t device = GetUsbDevice(modemService); - - if (device) { - CFStringRef manufacturerAsCFString = (CFStringRef) IORegistryEntryCreateCFProperty(device, - CFSTR(kUSBVendorString), - kCFAllocatorDefault, - 0); - - if (manufacturerAsCFString) { - Boolean result; - char manufacturer[MAXPATHLEN]; - - // Convert from a CFString to a C (NUL-terminated) - result = CFStringGetCString(manufacturerAsCFString, - manufacturer, - sizeof(manufacturer), - kCFStringEncodingUTF8); - - if (result) { - strcpy(serialDevice->manufacturer, manufacturer); - } - - CFRelease(manufacturerAsCFString); - } - - CFStringRef serialNumberAsCFString = (CFStringRef) IORegistryEntrySearchCFProperty(device, - kIOServicePlane, - CFSTR(kUSBSerialNumberString), - kCFAllocatorDefault, - kIORegistryIterateRecursively); - - if (serialNumberAsCFString) { - Boolean result; - char serialNumber[MAXPATHLEN]; - - // Convert from a CFString to a C (NUL-terminated) - result = CFStringGetCString(serialNumberAsCFString, - serialNumber, - sizeof(serialNumber), - kCFStringEncodingUTF8); - - if (result) { - strcpy(serialDevice->serialNumber, serialNumber); - } - - CFRelease(serialNumberAsCFString); - } - - IOCFPlugInInterface **plugInInterface = NULL; - SInt32 score; - HRESULT res; - - IOUSBDeviceInterface **deviceInterface = NULL; - - kernResult = IOCreatePlugInInterfaceForService(device, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, - &plugInInterface, &score); - - if ((kIOReturnSuccess != kernResult) || !plugInInterface) { - continue; - } - - // Use the plugin interface to retrieve the device interface. - res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), - (LPVOID*) &deviceInterface); - - // Now done with the plugin interface. - (*plugInInterface)->Release(plugInInterface); - - if (res || deviceInterface == NULL) { - continue; - } - - // Extract the desired Information - ExtractUsbInformation(serialDevice, deviceInterface); - - // Release the Interface - (*deviceInterface)->Release(deviceInterface); - - // Release the device - (void) IOObjectRelease(device); - } - - uv_mutex_unlock(&list_mutex); - } - } - - // Release the io_service_t now that we are done with it. - (void) IOObjectRelease(modemService); - } - - IOObjectRelease(serialPortIterator); // Release the iterator. - - return devices; -} - -#endif - -void EIO_List(uv_work_t* req) { - ListBaton* data = static_cast(req->data); - -#ifndef __APPLE__ - // This code exists in javascript for other unix platforms - snprintf(data->errorString, sizeof(data->errorString), "List is not Implemented"); - return; -# else - if (!lockInitialised) { - uv_mutex_init(&list_mutex); - lockInitialised = TRUE; - } - - stDeviceListItem* devices = GetSerialDevices(); - if (*(devices->length) > 0) { - stDeviceListItem* next = devices; - - for (int i = 0, len = *(devices->length); i < len; i++) { - stSerialDevice device = (* next).value; - - ListResultItem* resultItem = new ListResultItem(); - resultItem->comName = device.port; - - if (*device.locationId) { - resultItem->locationId = device.locationId; - } - if (*device.vendorId) { - resultItem->vendorId = device.vendorId; - } - if (*device.productId) { - resultItem->productId = device.productId; - } - if (*device.manufacturer) { - resultItem->manufacturer = device.manufacturer; - } - if (*device.serialNumber) { - resultItem->serialNumber = device.serialNumber; - } - data->results.push_back(resultItem); - - stDeviceListItem* current = next; - - if (next->next != NULL) { - next = next->next; - } - - free(current); - } - } -#endif -} - void EIO_Set(uv_work_t* req) { SetBaton* data = static_cast(req->data); diff --git a/src/serialport_unix.h b/src/serialport_unix.h new file mode 100644 index 000000000..aac479643 --- /dev/null +++ b/src/serialport_unix.h @@ -0,0 +1,7 @@ +#ifndef SRC_SERIALPORT_UNIX_H_ +#define SRC_SERIALPORT_UNIX_H_ + +int ToBaudConstant(int baudRate); +int ToDataBitsConstant(int dataBits); + +#endif // SRC_SERIALPORT_UNIX_H_ diff --git a/src/serialport_win.cpp b/src/serialport_win.cpp index 02e651209..f6be1d86b 100644 --- a/src/serialport_win.cpp +++ b/src/serialport_win.cpp @@ -505,6 +505,22 @@ char *copySubstring(char *someString, int n) return new_; } +NAN_METHOD(List) { + // callback + if (!info[0]->IsFunction()) { + Nan::ThrowTypeError("First argument must be a function"); + return; + } + + ListBaton* baton = new ListBaton(); + strcpy(baton->errorString, ""); + baton->callback.Reset(info[0].As()); + + uv_work_t* req = new uv_work_t(); + req->data = baton; + uv_queue_work(uv_default_loop(), req, EIO_List, (uv_after_work_cb)EIO_AfterList); +} + void EIO_List(uv_work_t* req) { ListBaton* data = static_cast(req->data); @@ -602,6 +618,53 @@ void EIO_List(uv_work_t* req) { } } +void setIfNotEmpty(v8::Local item, std::string key, const char *value) { + v8::Local v8key = Nan::New(key).ToLocalChecked(); + if (strlen(value) > 0) { + Nan::Set(item, v8key, Nan::New(value).ToLocalChecked()); + } else { + Nan::Set(item, v8key, Nan::Undefined()); + } +} + +void EIO_AfterList(uv_work_t* req) { + Nan::HandleScope scope; + + ListBaton* data = static_cast(req->data); + + v8::Local argv[2]; + if (data->errorString[0]) { + argv[0] = v8::Exception::Error(Nan::New(data->errorString).ToLocalChecked()); + argv[1] = Nan::Undefined(); + } else { + v8::Local results = Nan::New(); + int i = 0; + for (std::list::iterator it = data->results.begin(); it != data->results.end(); ++it, i++) { + v8::Local item = Nan::New(); + + setIfNotEmpty(item, "comName", (*it)->comName.c_str()); + setIfNotEmpty(item, "manufacturer", (*it)->manufacturer.c_str()); + setIfNotEmpty(item, "serialNumber", (*it)->serialNumber.c_str()); + setIfNotEmpty(item, "pnpId", (*it)->pnpId.c_str()); + setIfNotEmpty(item, "locationId", (*it)->locationId.c_str()); + setIfNotEmpty(item, "vendorId", (*it)->vendorId.c_str()); + setIfNotEmpty(item, "productId", (*it)->productId.c_str()); + + Nan::Set(results, i, item); + } + argv[0] = Nan::Null(); + argv[1] = results; + } + data->callback.Call(2, argv); + + for (std::list::iterator it = data->results.begin(); it != data->results.end(); ++it) { + delete *it; + } + delete data; + delete req; +} + + void EIO_Flush(uv_work_t* req) { VoidBaton* data = static_cast(req->data); diff --git a/src/serialport_win.h b/src/serialport_win.h index 71e7f7de7..2eb194efc 100644 --- a/src/serialport_win.h +++ b/src/serialport_win.h @@ -1,8 +1,8 @@ #ifndef SRC_SERIALPORT_WIN_H_ #define SRC_SERIALPORT_WIN_H_ -#include -#include -#include +// #include +// #include +// #include #include #include #include @@ -39,4 +39,25 @@ struct ReadBaton { NAN_METHOD(Read); void EIO_Read(uv_work_t* req); void EIO_AfterRead(uv_work_t* req); + +NAN_METHOD(List); +void EIO_List(uv_work_t* req); +void EIO_AfterList(uv_work_t* req); + +struct ListResultItem { + std::string comName; + std::string manufacturer; + std::string serialNumber; + std::string pnpId; + std::string locationId; + std::string vendorId; + std::string productId; +}; + +struct ListBaton { + Nan::Callback callback; + std::list results; + char errorString[ERROR_STRING_SIZE]; +}; + #endif // SRC_SERIALPORT_WIN_H_ diff --git a/test/bindings-linux-list.js b/test/bindings-linux-list.js index 4fd0f5916..6d636d63a 100644 --- a/test/bindings-linux-list.js +++ b/test/bindings-linux-list.js @@ -2,50 +2,77 @@ const listLinux = require('./mocks/linux-list'); -const ports = - 'P: /devices/platform/serial8250/tty/ttyS0\n' + - 'N: ttyS0\n' + - 'E: DEVNAME=/dev/ttyS0\n' + - 'E: DEVPATH=/devices/platform/serial8250/tty/ttyS0\n' + - 'E: MAJOR=4\n' + - 'E: MINOR=64\n' + - 'E: SUBSYSTEM=tty\n' + +const ports = String.raw` +P: /devices/platform/serial8250/tty/ttyS0 +N: ttyS0 +E: DEVNAME=/dev/ttyS0 +E: DEVPATH=/devices/platform/serial8250/tty/ttyS0 +E: MAJOR=4 +E: MINOR=64 +E: SUBSYSTEM=tty - 'P: /devices/platform/serial8250/tty/ttyS1\n' + - 'N: ttyS1\n' + - 'E: DEVNAME=/dev/ttyS1\n' + - 'E: DEVPATH=/devices/platform/serial8250/tty/ttyS1\n' + - 'E: MAJOR=4\n' + - 'E: MINOR=65\n' + - 'E: SUBSYSTEM=tty\n' + +P: /devices/platform/serial8250/tty/ttyS1 +N: ttyS1 +E: DEVNAME=/dev/ttyS1 +E: DEVPATH=/devices/platform/serial8250/tty/ttyS1 +E: MAJOR=4 +E: MINOR=65 +E: SUBSYSTEM=tty - 'P: /dev/ttyUSB-Arduino\n' + - 'N: ttyUSB-Arduino\n' + - 'E: DEVNAME=/dev/ttyUSB-Arduino\n' + - 'E: ID_VENDOR=Arduino (www.arduino.cc)\n' + - 'E: ID_SERIAL=752303138333518011C1\n' + - 'E: ID_VENDOR_ID=2341\n' + - 'E: ID_MODEL_ID=0043\n' + - 'E: DEVLINKS=/dev/serial/by-path/pci-0000:00:14.0-usb-0:2:1.0-port0 /dev/serial/by-id/pci-NATA_Siolynx2_C8T6VI1F-if00-port0\n' + +P: /devices/pci0000:00/0000:00:06.0/usb1/1-2/1-2:1.0/tty/ttyACM0 +N: ttyACM0 +S: serial/by-id/usb-Arduino__www.arduino.cc__0043_752303138333518011C1-if00 +S: serial/by-path/pci-0000:00:06.0-usb-0:2:1.0 +E: DEVLINKS=/dev/serial/by-path/pci-0000:00:06.0-usb-0:2:1.0 /dev/serial/by-id/usb-Arduino__www.arduino.cc__0043_752303138333518011C1-if00 +E: DEVNAME=/dev/ttyACM0 +E: DEVPATH=/devices/pci0000:00/0000:00:06.0/usb1/1-2/1-2:1.0/tty/ttyACM0 +E: ID_BUS=usb +E: ID_MM_CANDIDATE=1 +E: ID_MODEL=0043 +E: ID_MODEL_ENC=0043 +E: ID_MODEL_FROM_DATABASE=Uno R3 (CDC ACM) +E: ID_MODEL_ID=0043 +E: ID_PATH=pci-0000:00:06.0-usb-0:2:1.0 +E: ID_PATH_TAG=pci-0000_00_06_0-usb-0_2_1_0 +E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller +E: ID_PCI_INTERFACE_FROM_DATABASE=OHCI +E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller +E: ID_REVISION=0001 +E: ID_SERIAL=Arduino__www.arduino.cc__0043_752303138333518011C1 +E: ID_SERIAL_SHORT=752303138333518011C1 +E: ID_TYPE=generic +E: ID_USB_CLASS_FROM_DATABASE=Communications +E: ID_USB_DRIVER=cdc_acm +E: ID_USB_INTERFACES=:020201:0a0000: +E: ID_USB_INTERFACE_NUM=00 +E: ID_VENDOR=Arduino__www.arduino.cc_ +E: ID_VENDOR_ENC=Arduino\x20\x28www.arduino.cc\x29 +E: ID_VENDOR_FROM_DATABASE=Arduino SA +E: ID_VENDOR_ID=2341 +E: MAJOR=166 +E: MINOR=0 +E: SUBSYSTEM=tty +E: TAGS=:systemd: +E: USEC_INITIALIZED=2219936602 - 'P: /devices/unknown\n' + - 'N: ttyAMA_im_a_programmer\n' + - 'E: DEVNAME=/dev/ttyAMA_im_a_programmer\n' + - 'E: DEVLINKS=/dev/serial/by-id/pci-NATA_Siolynx2_C8T6VI1F-if00-port0 /dev/serial/by-path/pci-0000:00:14.0-usb-0:2:1.0-port0\n' + +P: /devices/unknown +N: ttyAMA_im_a_programmer +E: DEVNAME=/dev/ttyAMA_im_a_programmer +E: DEVLINKS=/dev/serial/by-id/pci-NATA_Siolynx2_C8T6VI1F-if00-port0 /dev/serial/by-path/pci-0000:00:14.0-usb-0:2:1.0-port0 - 'P: /devices/unknown\n' + - 'N: ttyMFD0\n' + - 'E: DEVNAME=/dev/ttyMFD0\n' + - 'E: ID_VENDOR_ID=0x2341\n' + - 'E: ID_MODEL_ID=0x0043\n' + +P: /devices/unknown +N: ttyMFD0 +E: DEVNAME=/dev/ttyMFD0 +E: ID_VENDOR_ID=0x2343 +E: ID_MODEL_ENC=0043 - 'P: /devices/unknown\n' + - 'N: rfcomm4\n' + - 'E: DEVNAME=/dev/rfcomm4\n' + +P: /devices/unknown +N: rfcomm4 +E: DEVNAME=/dev/rfcomm4 - 'P: /devices/unknown\n' + - 'N: ttyNOTASERIALPORT\n' -; +P: /devices/unknown +N: ttyNOTASERIALPORT +`; const portOutput = [ { @@ -55,12 +82,12 @@ const portOutput = [ comName: '/dev/ttyS1' }, { - comName: '/dev/ttyUSB-Arduino', + comName: '/dev/ttyACM0', manufacturer: 'Arduino (www.arduino.cc)', serialNumber: '752303138333518011C1', - pnpId: 'pci-NATA_Siolynx2_C8T6VI1F-if00-port0', - vendorId: '0x2341', - productId: '0x0043' + productId: '0043', + vendorId: '2341', + pnpId: 'usb-Arduino__www.arduino.cc__0043_752303138333518011C1-if00' }, { comName: '/dev/ttyAMA_im_a_programmer', @@ -68,8 +95,8 @@ const portOutput = [ }, { comName: '/dev/ttyMFD0', - vendorId: '0x2341', - productId: '0x0043' + vendorId: '2343', + productId: '0043' }, { comName: '/dev/rfcomm4' @@ -84,7 +111,7 @@ describe('listLinux', () => { it('lists available serialports', () => { listLinux.setPorts(ports); return listLinux().then((ports) => { - assert.deepEqual(ports, portOutput); + assert.containSubset(ports, portOutput); }); }); }); diff --git a/test/bindings.js b/test/bindings.js index 5295ef8b5..ed6cf3cc3 100644 --- a/test/bindings.js +++ b/test/bindings.js @@ -34,6 +34,16 @@ const defaultSetFlags = Object.freeze({ rts: true }); +const listFields = [ + 'comName', + 'manufacturer', + 'serialNumber', + 'pnpId', + 'locationId', + 'vendorId', + 'productId' +]; + const bindingsToTest = [ 'mock', platform @@ -81,16 +91,18 @@ function testBinding(bindingName, Binding, testPort) { }); it('has objects with undefined when there is no data', () => { - return Binding.list().then((data) => { - assert.isArray(data); - if (data.length === 0) { + return Binding.list().then((ports) => { + assert.isArray(ports); + if (ports.length === 0) { console.log('no ports to test'); return; } - const obj = data[0]; - Object.keys(obj).forEach((key) => { - assert.notEqual(obj[key], '', 'empty values should be undefined'); - assert.isNotNull(obj[key], 'empty values should be undefined'); + ports.forEach(port => { + assert.containSubset(Object.keys(port), listFields); + Object.keys(port).forEach((key) => { + assert.notEqual(port[key], '', 'empty values should be undefined'); + assert.isNotNull(port[key], 'empty values should be undefined'); + }); }); }); }); diff --git a/test/initializers/assert.js b/test/initializers/assert.js index a722977ec..dfb32c767 100644 --- a/test/initializers/assert.js +++ b/test/initializers/assert.js @@ -1 +1,4 @@ -global.assert = require('chai').assert; +const chai = require('chai'); +const chaiSubset = require('chai-subset'); +global.assert = chai.assert; +chai.use(chaiSubset); diff --git a/test/mocks/linux-list.js b/test/mocks/linux-list.js index baaec8697..559bbea03 100644 --- a/test/mocks/linux-list.js +++ b/test/mocks/linux-list.js @@ -2,26 +2,24 @@ 'use strict'; +const EventEmitter = require('events'); const proxyquire = require('proxyquire'); - +const Readable = require('stream').Readable; let mockPorts; proxyquire.noPreserveCache(); const listLinux = proxyquire('../../lib/bindings/linux-list', { child_process: { spawn() { - const EventEmitter = require('events'); const event = new EventEmitter(); - const Readable = require('stream').Readable; const stream = new Readable(); + event.stdout = stream; stream.push(mockPorts); stream.push(null); - event.stdout = stream; return event; } } }); - proxyquire.preserveCache(); listLinux.setPorts = (ports) => {