Skip to content

Instantly share code, notes, and snippets.

@will-hart
Created August 25, 2023 14:24
Show Gist options
  • Save will-hart/b49eaed1dd87c80fa6291bf9d71f195a to your computer and use it in GitHub Desktop.
Save will-hart/b49eaed1dd87c80fa6291bf9d71f195a to your computer and use it in GitHub Desktop.
PC to USB Device comms example
[package]
name = "pedalrs_gui"
version = "0.1.0"
authors = ["Will Hart <[email protected]>"]
edition = "2021"
rust-version = "1.56"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
rusb = "0.9.0"
[profile.release]
opt-level = 2 # fast and small wasm
use crate::usb_device;
static VID: u16 = 0x16c0;
static PID: u16 = 0x27dd;
pub fn main() {
usb_device::send_message(VID, PID, 0x08).ok();
}
use std::time::Duration;
use rusb::{
Context, Device, DeviceDescriptor, DeviceHandle, Direction, Result, TransferType, UsbContext,
};
#[derive(Debug)]
struct Endpoint {
config: u8,
iface: u8,
setting: u8,
address: u8,
}
pub fn send_message(vid: u16, pid: u16, command: u8) -> Result<bool> {
match Context::new() {
Ok(mut context) => match open_device(&mut context, vid, pid) {
Some((mut device, device_desc, mut handle)) => {
write_device(&mut device, &device_desc, &mut handle, command)
}
None => {
println!("could not find device {:04x}:{:04x}", vid, pid);
Ok(false)
}
},
Err(e) => panic!("could not initialize libusb: {}", e),
}
}
fn open_device<T: UsbContext>(
context: &mut T,
vid: u16,
pid: u16,
) -> Option<(Device<T>, DeviceDescriptor, DeviceHandle<T>)> {
let devices = match context.devices() {
Ok(d) => d,
Err(_) => return None,
};
for device in devices.iter() {
let device_desc = match device.device_descriptor() {
Ok(d) => d,
Err(_) => continue,
};
if device_desc.vendor_id() == vid && device_desc.product_id() == pid {
match device.open() {
Ok(handle) => return Some((device, device_desc, handle)),
Err(_) => continue,
}
}
}
None
}
fn write_device<T: UsbContext>(
device: &mut Device<T>,
device_desc: &DeviceDescriptor,
handle: &mut DeviceHandle<T>,
command: u8,
) -> Result<bool> {
handle.reset()?;
let timeout = Duration::from_secs(1);
let languages = handle.read_languages(timeout)?;
println!("Active configuration: {}", handle.active_configuration()?);
println!("Languages: {:?}", languages);
if languages.len() > 0 {
let language = languages[0];
println!(
"Manufacturer: {:?}",
handle
.read_manufacturer_string(language, device_desc, timeout)
.ok()
);
println!(
"Product: {:?}",
handle
.read_product_string(language, device_desc, timeout)
.ok()
);
println!(
"Serial Number: {:?}",
handle
.read_serial_number_string(language, device_desc, timeout)
.ok()
);
}
match find_writable_endpoint(device, device_desc, TransferType::Interrupt) {
Some(endpoint) => {
println!(" - Found interrupt mode on endpoint");
write_endpoint(handle, endpoint, TransferType::Interrupt, command);
return Ok(true);
}
None => println!(" - No interrupt mode on endpoint"),
}
match find_writable_endpoint(device, device_desc, TransferType::Bulk) {
Some(endpoint) => {
println!(" - Found bulk mode on endpoint");
write_endpoint(handle, endpoint, TransferType::Bulk, command);
return Ok(true);
}
None => println!(" - No bulk mode on endpoint"),
}
Ok(false)
}
fn find_writable_endpoint<T: UsbContext>(
device: &mut Device<T>,
device_desc: &DeviceDescriptor,
transfer_type: TransferType,
) -> Option<Endpoint> {
for n in 0..device_desc.num_configurations() {
let config_desc = match device.config_descriptor(n) {
Ok(c) => c,
Err(_) => continue,
};
for (interface_number, interface) in config_desc.interfaces().enumerate() {
for interface_desc in interface.descriptors() {
for (endpoint_number, endpoint_desc) in
interface_desc.endpoint_descriptors().enumerate()
{
if endpoint_desc.direction() == Direction::Out
&& endpoint_desc.transfer_type() == transfer_type
{
println!(
"Found writable endpoint {}:{} at address {} for device {}",
interface_number,
endpoint_number,
endpoint_desc.address(),
device.address()
);
return Some(Endpoint {
config: config_desc.number(),
iface: interface_desc.interface_number(),
setting: interface_desc.setting_number(),
address: endpoint_desc.address(),
});
}
}
}
}
}
None
}
fn write_endpoint<T: UsbContext>(
handle: &mut DeviceHandle<T>,
endpoint: Endpoint,
transfer_type: TransferType,
command: u8,
) {
println!("Writing to endpoint: {:?}", endpoint);
let has_kernel_driver = match handle.kernel_driver_active(endpoint.iface) {
Ok(true) => {
handle.detach_kernel_driver(endpoint.iface).ok();
true
}
_ => false,
};
println!(" - kernel driver? {}", has_kernel_driver);
match configure_endpoint(handle, &endpoint) {
Ok(_) => {
let timeout = Duration::from_secs(1);
println!("Handle state {:?}", handle);
match transfer_type {
TransferType::Interrupt => {
match handle.write_interrupt(endpoint.address, &mut [0x00; 4], timeout) {
Ok(len) => {
println!(" - wrote: {} bytes", len);
}
Err(err) => {
println!("could not write to endpoint: {}", err);
}
}
}
TransferType::Bulk => {
match handle.write_bulk(endpoint.address, &mut [0x01, command], timeout) {
Ok(len) => {
println!(" - wrote {:?} bytes", len);
}
Err(err) => println!("could not write to endpoint: {}", err),
}
}
_ => (),
}
}
Err(err) => println!("could not configure endpoint: {}", err),
}
if has_kernel_driver {
handle.attach_kernel_driver(endpoint.iface).ok();
}
}
fn configure_endpoint<T: UsbContext>(
handle: &mut DeviceHandle<T>,
endpoint: &Endpoint,
) -> Result<()> {
println!(
"Configuring for sending, and claiming the interface. {:?}",
endpoint
);
handle.set_active_configuration(endpoint.config)?;
handle.claim_interface(endpoint.iface)?;
handle.set_alternate_setting(endpoint.iface, endpoint.setting)?;
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment