badgemagic-rs/src/usb_hid.rs
Martin Michaelis 97d5561e81 Initial commit
2024-06-07 13:33:27 +00:00

91 lines
2.5 KiB
Rust

//! Connect to an LED badge via USB HID
use std::sync::Arc;
use anyhow::{Context, Result};
use hidapi::{DeviceInfo, HidApi, HidDevice};
use crate::protocol::PayloadBuffer;
enum DeviceType {
// rename if we add another device type
TheOnlyOneWeSupportForNow,
}
impl DeviceType {
fn new(info: &DeviceInfo) -> Option<Self> {
Some(match (info.vendor_id(), info.product_id()) {
(0x0416, 0x5020) => Self::TheOnlyOneWeSupportForNow,
_ => return None,
})
}
}
/// A discovered USB device
pub struct Device {
api: Arc<HidApi>,
info: DeviceInfo,
type_: DeviceType,
}
impl Device {
/// Return all supported devices
pub fn enumerate() -> Result<Vec<Self>> {
let api = HidApi::new().context("create hid api")?;
let api = Arc::new(api);
let devices = api.device_list();
let devices = devices
.filter_map(|info| {
DeviceType::new(info).map(|type_| Device {
api: api.clone(),
info: info.clone(),
type_,
})
})
.collect();
Ok(devices)
}
/// Return the single supported device
///
/// This function returns an error if no device could be found
/// or if multiple devices would match.
pub fn single() -> Result<Self> {
let mut devices = Self::enumerate()?.into_iter();
let device = devices.next().context("no device found")?;
anyhow::ensure!(devices.next().is_none(), "multiple devices found");
Ok(device)
}
/// Write a payload to the device
pub fn write(&self, payload: PayloadBuffer) -> Result<()> {
let device = self.info.open_device(&self.api).context("open device")?;
match self.type_ {
DeviceType::TheOnlyOneWeSupportForNow => {
write_raw(&device, payload.into_padded_bytes().as_ref())
}
}
}
}
fn write_raw(device: &HidDevice, data: &[u8]) -> Result<()> {
anyhow::ensure!(data.len() % 64 == 0, "payload not padded to 64 bytes");
// the device will brick itself if the payload is too long (more then 8192 bytes)
anyhow::ensure!(data.len() <= 8192, "payload too long (max 8192 bytes)");
// just to be sure
assert!(data.len() <= 8192);
let n = device.write(data).context("write payload")?;
anyhow::ensure!(
n == data.len(),
"incomplete write: {n} of {} bytes",
data.len()
);
Ok(())
}