Skip to content

Commit

Permalink
Add location field to UsbPortInfo
Browse files Browse the repository at this point in the history
It returns stable full path to the serial port. Returned value is formatted as `a-b.c` where `a` is a bus_id and `b` and `c` are port numbers.
  • Loading branch information
kindermax committed Nov 24, 2024
1 parent c3743e2 commit 67ce13e
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,8 @@ pub struct UsbPortInfo {
pub manufacturer: Option<String>,
/// Product name (arbitrary string)
pub product: Option<String>,
/// Location (bus_id-a.b string)
pub location: String,
/// The interface index of the USB serial port. This can be either the interface number of
/// the communication interface (as is the case on Windows and Linux) or the data
/// interface (as is the case on macOS), so you should recognize both interface numbers.
Expand Down
20 changes: 20 additions & 0 deletions src/posix/enumerate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,24 @@ fn get_string_property(device_type: io_registry_entry_t, property: &str) -> Resu
.ok_or(Error::new(ErrorKind::Unknown, "Failed to get string value"))
}

/// Parse location_id by extracting bits that represent specific parts of
/// the USB device’s location within the USB topology, such as the bus and
/// port numbers, and then formats them in a hierarchical form like “0-1.2.4”.
fn location_to_string(location_id: u32) -> String {
let mut location_id = location_id;
let bus_id = format!("{:02x}", (location_id >> 24) as u8);
let mut path = format!("{bus_id}-");
while location_id & 0xf00000 != 0 {
let item = (location_id >> 20) & 0xf;
if !path.ends_with("-") {
path.push_str(".");
}
path.push_str(item.to_string().as_str());
location_id <<= 4;
}
return path;
}

#[cfg(any(target_os = "ios", target_os = "macos"))]
/// Determine the serial port type based on the service object (like that returned by
/// `IOIteratorNext`). Specific properties are extracted for USB devices.
Expand All @@ -355,12 +373,14 @@ fn port_type(service: io_object_t) -> SerialPortType {
let maybe_usb_device = get_parent_device_by_type(service, usb_device_class_name)
.or_else(|| get_parent_device_by_type(service, legacy_usb_device_class_name));
if let Some(usb_device) = maybe_usb_device {
let location_id = get_int_property(usb_device, "locationID").unwrap_or_default() as u32;
SerialPortType::UsbPort(UsbPortInfo {
vid: get_int_property(usb_device, "idVendor").unwrap_or_default() as u16,
pid: get_int_property(usb_device, "idProduct").unwrap_or_default() as u16,
serial_number: get_string_property(usb_device, "USB Serial Number").ok(),
manufacturer: get_string_property(usb_device, "USB Vendor Name").ok(),
product: get_string_property(usb_device, "USB Product Name").ok(),
location: location_to_string(location_id),
// Apple developer documentation indicates `bInterfaceNumber` is the supported key for
// looking up the composite usb interface id. `idVendor` and `idProduct` are included in the same tables, so
// we will lookup the interface number using the same method. See:
Expand Down

0 comments on commit 67ce13e

Please sign in to comment.