Skip to content

Commit

Permalink
Extracts more information from serial devices on Windows
Browse files Browse the repository at this point in the history
At rockot@'s suggestion, I tested this with a small Chrome app that I
wrote (https://github.com/zeptonaut/serial-device-enumerator-app) that
opens a window showing all information that Chrome knows about serial
devices. We chose this route over unit tests because we didn't think
it was wise to write unit tests that assumed specific devices were
plugged into our test servers.

As far as devices, I used an FTDI Chipset High Speed USB 2.0 to Serial
RS-232 DB-9 Converter. This was useful because it's one of not-so-many
devices that has both serial properties (COM port) and USB properties
(vendor ID, device ID, and display name).

BUG=522217

Review URL: https://codereview.chromium.org/1357993002

Cr-Commit-Position: refs/heads/master@{#351163}
  • Loading branch information
charliea authored and Commit bot committed Sep 28, 2015
1 parent c496054 commit e940c46
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 10 deletions.
1 change: 1 addition & 0 deletions device/serial/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ static_library("serial") {
]
deps = [
"//third_party/mojo/src/mojo/public/cpp/system",
"//third_party/re2",
]

if (use_udev) {
Expand Down
1 change: 1 addition & 0 deletions device/serial/DEPS
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
include_rules = [
"+dbus",
"+net/base",
"+third_party/re2",
]
1 change: 1 addition & 0 deletions device/serial/serial.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
'device_serial_mojo',
'../../net/net.gyp:net',
'../../third_party/mojo/mojo_public.gyp:mojo_cpp_bindings',
'../../third_party/re2/re2.gyp:re2',
],
'export_dependent_settings': [
'device_serial_mojo',
Expand Down
116 changes: 106 additions & 10 deletions device/serial/serial_device_enumerator_win.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,75 @@

#include <windows.h>

#include <ntddser.h>
#include <setupapi.h>

#include "base/memory/scoped_ptr.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "third_party/re2/re2/re2.h"

namespace device {

namespace {

// Searches the specified device info for a property with the specified key,
// assigns the result to value, and returns whether the operation was
// successful.
bool GetProperty(HDEVINFO dev_info,
SP_DEVINFO_DATA dev_info_data,
const int key,
std::string* value) {
// We don't know how much space the property's value will take up, so we call
// the property retrieval function once to fetch the size of the required
// value buffer, then again once we've allocated a sufficiently large buffer.
DWORD buffer_size = 0;
SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data, key, nullptr,
nullptr, buffer_size, &buffer_size);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
return false;

scoped_ptr<wchar_t[]> buffer(new wchar_t[buffer_size]);
if (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data, key, nullptr,
reinterpret_cast<PBYTE>(buffer.get()),
buffer_size, nullptr))
return false;

*value = base::WideToUTF8(buffer.get());
return true;
}

// Searches for the COM port in the device's friendly name, assigns its value to
// com_port, and returns whether the operation was successful.
bool GetCOMPort(const std::string friendly_name, std::string* com_port) {
return RE2::PartialMatch(friendly_name, ".* \\((COM[0-9]+)\\)", com_port);
}

// Searches for the display name in the device's friendly name, assigns its
// value to display_name, and returns whether the operation was successful.
bool GetDisplayName(const std::string friendly_name,
std::string* display_name) {
return RE2::PartialMatch(friendly_name, "(.*) \\(COM[0-9]+\\)", display_name);
}

// Searches for the vendor ID in the device's hardware ID, assigns its value to
// vendor_id, and returns whether the operation was successful.
bool GetVendorID(const std::string hardware_id, uint32_t* vendor_id) {
std::string vendor_id_str;
return RE2::PartialMatch(hardware_id, "VID_([0-9]+)", &vendor_id_str) &&
base::HexStringToUInt(vendor_id_str, vendor_id);
}

// Searches for the product ID in the device's product ID, assigns its value to
// product_id, and returns whether the operation was successful.
bool GetProductID(const std::string hardware_id, uint32_t* product_id) {
std::string product_id_str;
return RE2::PartialMatch(hardware_id, "PID_([0-9]+)", &product_id_str) &&
base::HexStringToUInt(product_id_str, product_id);
}

} // namespace

// static
scoped_ptr<SerialDeviceEnumerator> SerialDeviceEnumerator::Create() {
return scoped_ptr<SerialDeviceEnumerator>(new SerialDeviceEnumeratorWin());
Expand All @@ -23,18 +84,53 @@ SerialDeviceEnumeratorWin::SerialDeviceEnumeratorWin() {}

SerialDeviceEnumeratorWin::~SerialDeviceEnumeratorWin() {}

// TODO(rockot): Query the system for more information than just device paths.
// This may or may not require using a different strategy than scanning the
// registry location below.
mojo::Array<serial::DeviceInfoPtr> SerialDeviceEnumeratorWin::GetDevices() {
base::win::RegistryValueIterator iter_key(
HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM\\");
mojo::Array<serial::DeviceInfoPtr> devices(0);
for (; iter_key.Valid(); ++iter_key) {

// Make a device interface query to find all serial devices.
HDEVINFO dev_info =
SetupDiGetClassDevs(&GUID_DEVINTERFACE_SERENUM_BUS_ENUMERATOR, 0, 0,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (dev_info == INVALID_HANDLE_VALUE)
return devices.Pass();

SP_DEVINFO_DATA dev_info_data;
dev_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
for (DWORD i = 0; SetupDiEnumDeviceInfo(dev_info, i, &dev_info_data); i++) {
std::string friendly_name, com_port;
// SPDRP_FRIENDLYNAME looks like "USB_SERIAL_PORT (COM3)".
if (!GetProperty(dev_info, dev_info_data, SPDRP_FRIENDLYNAME,
&friendly_name) ||
!GetCOMPort(friendly_name, &com_port))
// In Windows, the COM port is the path used to uniquely identify the
// serial device. If the COM can't be found, ignore the device.
continue;

serial::DeviceInfoPtr info(serial::DeviceInfo::New());
info->path = base::UTF16ToASCII(iter_key.Value());
info->path = com_port;

std::string display_name;
if (GetDisplayName(friendly_name, &display_name))
info->display_name = display_name;

std::string hardware_id;
// SPDRP_HARDWAREID looks like "FTDIBUS\COMPORT&VID_0403&PID_6001".
if (GetProperty(dev_info, dev_info_data, SPDRP_HARDWAREID, &hardware_id)) {
uint32_t vendor_id, product_id;
if (GetVendorID(hardware_id, &vendor_id)) {
info->has_vendor_id = true;
info->vendor_id = vendor_id;
}
if (GetProductID(hardware_id, &product_id)) {
info->has_product_id = true;
info->product_id = product_id;
}
}

devices.push_back(info.Pass());
}

SetupDiDestroyDeviceInfoList(dev_info);
return devices.Pass();
}

Expand Down

0 comments on commit e940c46

Please sign in to comment.