Skip to content

Commit

Permalink
Add list_devices_fs function.
Browse files Browse the repository at this point in the history
  • Loading branch information
newAM committed Oct 16, 2020
1 parent 96531b5 commit 93fed45
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 7 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.22.0] - 2020-10-15
### Added
- Added `list_devices_fs` to work around vendor driver bug.
- Added `DeviceType::with_pid`.

### Changed
- `Speed`, `DeviceType`, and `DeviceInfo` derive `Ord` and `PartialOrd`.
- The return vector from `list_devices` is now sorted.

## [0.21.1] - 2020-10-08
### Fixed
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "libftd2xx"
version = "0.21.1" # remember to update html_root_url
version = "0.22.0" # remember to update html_root_url
authors = ["Alex M. <[email protected]>"]
edition = "2018"
description = "Rust safe wrapper around the libftd2xx-ffi crate."
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ permission from FTDI.

```toml
[dependencies]
libftd2xx = "~0.21.1"
libftd2xx = "~0.22.0"
```

This is a basic example to get your started.
Expand Down
12 changes: 12 additions & 0 deletions examples/list_fs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#![deny(unsafe_code, warnings)]
use libftd2xx::{list_devices_fs};

fn main() -> std::io::Result<()> {
let mut devices = list_devices_fs()?;

while let Some(device) = devices.pop() {
println!("device: {:?}", device);
}

Ok(())
}
132 changes: 130 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//!
//! ```toml
//! [dependencies]
//! libftd2xx = "~0.21.1"
//! libftd2xx = "~0.22.0"
//! ```
//!
//! This is a basic example to get your started.
Expand Down Expand Up @@ -74,7 +74,7 @@
//! [libftd2xx-ffi]: https://github.com/newAM/libftd2xx-ffi-rs
//! [setup executable]: https://www.ftdichip.com/Drivers/CDM/CDM21228_Setup.zip
//! [udev]: https://en.wikipedia.org/wiki/Udev
#![doc(html_root_url = "https://docs.rs/libftd2xx/0.21.1")]
#![doc(html_root_url = "https://docs.rs/libftd2xx/0.22.0")]
#![deny(missing_docs)]

mod errors;
Expand Down Expand Up @@ -120,10 +120,16 @@ use libftd2xx_ffi::{FT_GetVIDPID, FT_SetVIDPID};
use log::trace;
use std::convert::TryFrom;
use std::ffi::c_void;
use std::fs;
use std::io;
use std::mem;
use std::path::Path;
use std::time::Duration;
use std::vec::Vec;

/// FTDI USB vendor id.
pub const FTDI_VID: u16 = 0x0403;

fn ft_result<T>(value: T, status: FT_STATUS) -> Result<T, FtStatus> {
if status != 0 {
Err(status.into())
Expand Down Expand Up @@ -304,6 +310,128 @@ pub fn list_devices() -> Result<Vec<DeviceInfo>, FtStatus> {
description: slice_into_string(&info_node.Description),
});
}
devices.sort_unstable();
Ok(devices)
}
}

/// Lists FTDI devices using the Linux file system.
///
/// There is a bug in the vendor driver where the `serial_number` and
/// `description` fields may be blank on the FT4232H and FT2232H when only
/// some of the ports are unbound from the `ftdi_sio` linux kernel module.
///
/// This will not work if you have a custom VID/PID programmed onto your FTDI
/// device.
///
/// # Limitations
///
/// * `port_open` will always be `false`.
/// * `speed` will currently be `None`.
/// * This will return an empty vector if `/sys/bus/usb/devices` does not exist.
///
/// # Example
///
/// ```no_run
/// use libftd2xx::list_devices_fs;
///
/// let mut devices = list_devices_fs()?;
///
/// while let Some(device) = devices.pop() {
/// println!("device: {:?}", device);
/// }
/// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
/// ```
#[allow(clippy::redundant_field_names)]
pub fn list_devices_fs() -> io::Result<Vec<DeviceInfo>> {
let sys_bus_usb_devices = Path::new("/sys/bus/usb/devices");
let mut devices: Vec<DeviceInfo> = Vec::new();
if sys_bus_usb_devices.is_dir() {
for entry in fs::read_dir(sys_bus_usb_devices)? {
let entry = entry?;
let path = entry.path();
let mut vendor_path = path.clone();
vendor_path.push("idVendor");
if vendor_path.is_file() {
let vid: String = fs::read_to_string(vendor_path)?;
let vid: u16 = u16::from_str_radix(vid.trim(), 16)
.expect("idVendor file contains non-hex digits");
if vid != FTDI_VID {
continue;
}
} else {
continue;
}

let mut product_path = path.clone();
product_path.push("idProduct");
let pid: String = fs::read_to_string(product_path)?;
let pid: u16 = u16::from_str_radix(pid.trim(), 16)
.expect("idProduct file contains non-hex digits");

let device_type: DeviceType = match DeviceType::with_pid(pid) {
Some(device_type) => device_type,
None => continue,
};

let serial: String = {
let mut serial_path = path.clone();
serial_path.push("serial");
let mut data: String = fs::read_to_string(serial_path)?;
let ch = data.pop(); // remove newline
debug_assert_eq!(ch, Some('\n'));
data
};

let description: String = {
let mut product_path = path.clone();
product_path.push("product");
let mut data: String = fs::read_to_string(product_path)?;
let ch = data.pop(); // remove newline
debug_assert_eq!(ch, Some('\n'));
data
};

let port_letters: Option<&'static [char]> = match device_type {
DeviceType::FT2232H => Some(&['A', 'B']),
DeviceType::FT4232H => Some(&['A', 'B', 'C', 'D']),
_ => None,
};

if let Some(port_letters) = port_letters {
for letter in port_letters {
let mut port_serial = serial.clone();
port_serial.push(*letter);
let mut port_description = description.clone();
port_description.push(' ');
port_description.push(*letter);
devices.push(DeviceInfo {
port_open: false,
speed: None,
device_type: device_type,
product_id: pid,
vendor_id: FTDI_VID,
serial_number: port_serial,
description: port_description,
})
}
} else {
devices.push(DeviceInfo {
port_open: false,
speed: None,
device_type: device_type,
product_id: pid,
vendor_id: FTDI_VID,
serial_number: serial,
description: description,
})
}
}

devices.sort_unstable();
Ok(devices)
} else {
// windows
Ok(devices)
}
}
Expand Down
36 changes: 33 additions & 3 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ const DEVICE_4222_PROG: u32 = FT_DEVICE_4222_PROG as u32;
///
/// [`DeviceInfo`]: ./struct.DeviceInfo.html
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[repr(u32)]
pub enum DeviceType {
/// FTDI BM device.
Expand Down Expand Up @@ -210,6 +210,36 @@ pub enum DeviceType {
FT4222_PROG = DEVICE_4222_PROG,
}

impl DeviceType {
/// Get a device type with a USB product ID.
///
/// This is not entirely accurate since soem devices share the same PID.
///
/// # Example
///
/// ```
/// use libftd2xx::DeviceType;
///
/// let my_device: Option<DeviceType> = DeviceType::with_pid(0x6014);
/// assert_eq!(my_device, Some(DeviceType::FT232H));
/// ```
pub const fn with_pid(pid: u16) -> Option<DeviceType> {
if pid == 0x6001 {
Some(DeviceType::FTAM)
} else if pid == 0x6010 {
Some(DeviceType::FT2232H)
} else if pid == 0x6011 {
Some(DeviceType::FT4232H)
} else if pid == 0x6014 {
Some(DeviceType::FT232H)
} else if pid == 0x6015 {
Some(DeviceType::FT_X_SERIES)
} else {
None
}
}
}

impl Default for DeviceType {
fn default() -> Self {
DeviceType::Unknown
Expand Down Expand Up @@ -436,7 +466,7 @@ fn bit_mode_sanity() {
/// This is used in the [`DeviceInfo`] struct.
///
/// [`DeviceInfo`]: ./struct.DeviceInfo.html
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[repr(u8)]
pub enum Speed {
/// High speed USB.
Expand Down Expand Up @@ -541,7 +571,7 @@ impl ModemStatus {
///
/// [`list_devices`]: ./fn.list_devices.html
/// [`device_info`]: ./struct.Ftdi.html#method.device_info
#[derive(Clone, Eq, PartialEq, Default)]
#[derive(Clone, Eq, PartialEq, Default, Ord, PartialOrd)]
pub struct DeviceInfo {
/// `true` if the port is open.
pub port_open: bool,
Expand Down

0 comments on commit 93fed45

Please sign in to comment.