diff --git a/src/ble.rs b/src/ble.rs index 3af8408..51c8eb5 100644 --- a/src/ble.rs +++ b/src/ble.rs @@ -26,6 +26,50 @@ pub struct Device { } impl Device { + /// Return a list of all BLE devies as a string representation. + pub async fn list_all() -> Result> { + // Run device scan + let manager = Manager::new().await.context("create BLE manager")?; + let adapters = manager + .adapters() + .await + .context("enumerate bluetooth adapters")?; + let adapter = adapters.first().context("no bluetooth adapter found")?; + + adapter + .start_scan(ScanFilter { + // don't filter by service + services: Vec::new(), + }) + .await + .context("bluetooth scan start")?; + time::sleep(Duration::from_secs(2)).await; + + let mut devices = Vec::new(); + for peripheral in adapter + .peripherals() + .await + .context("enumerating bluetooth devices")? + { + let device = async { + let props = peripheral + .properties() + .await? + .context("missing device info")?; + + Ok(format!( + "{}: name={:?} services={:?}", + props.address, props.local_name, props.services + )) + }; + devices.push(device.await.unwrap_or_else(|err: anyhow::Error| { + format!("{} failed to collect info: {err:?}", peripheral.address()) + })); + } + + Ok(devices) + } + /// Return all supported devices that are found in two seconds. /// /// Returns all badges that are in BLE range and are in Bluetooth transfer mode. diff --git a/src/main.rs b/src/main.rs index bd24fea..fcbe5ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use badgemagic::{ usb_hid::Device as UsbDevice, }; use base64::Engine; -use clap::Parser; +use clap::{Parser, ValueEnum}; use embedded_graphics::{ geometry::Point, image::{Image, ImageRawLE}, @@ -43,11 +43,16 @@ struct Args { #[clap(long)] transport: TransportProtocol, + /// List all devices visible to a transport and exit + #[clap(long)] + list_devices: bool, + /// Path to TOML configuration file - config: PathBuf, + #[clap(required_unless_present = "list_devices")] + config: Option, } -#[derive(Clone, Deserialize, clap::ValueEnum)] +#[derive(Clone, Deserialize, ValueEnum)] #[serde(rename_all = "kebab-case")] enum TransportProtocol { Usb, @@ -91,16 +96,48 @@ enum Content { } fn main() -> Result<()> { - let args = Args::parse(); - let config = fs::read_to_string(&args.config) - .with_context(|| format!("load config: {:?}", args.config))?; + let mut args = Args::parse(); + if args.list_devices { + return list_devices(&args.transport); + } + + let payload = gnerate_payload(&mut args)?; + + write_payload(&args.transport, payload) +} + +fn list_devices(transport: &TransportProtocol) -> Result<()> { + let devices = match transport { + TransportProtocol::Usb => UsbDevice::list_all(), + TransportProtocol::Ble => tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()? + .block_on(async { BleDevice::list_all().await }), + }?; + + eprintln!( + "found {} {} devices", + devices.len(), + transport.to_possible_value().unwrap().get_name(), + ); + for device in devices { + println!("- {device}"); + } + + Ok(()) +} + +fn gnerate_payload(args: &mut Args) -> Result { + let config_path = args.config.take().unwrap_or_default(); + let config = fs::read_to_string(&config_path) + .with_context(|| format!("load config: {config_path:?}"))?; let config: Config = { let extension = args .format .as_deref() .map(AsRef::as_ref) - .or(args.config.extension()) + .or(config_path.extension()) .context("missing file extension for config file")?; match extension.to_str().unwrap_or_default() { "json" => serde_json::from_str(&config).context("parse config")?, @@ -189,13 +226,18 @@ fn main() -> Result<()> { } } - match args.transport { + Ok(payload) +} + +fn write_payload( + transport: &TransportProtocol, + payload: PayloadBuffer, +) -> Result<(), anyhow::Error> { + match transport { TransportProtocol::Usb => UsbDevice::single()?.write(payload), TransportProtocol::Ble => tokio::runtime::Builder::new_current_thread() .enable_all() .build()? .block_on(async { BleDevice::single().await?.write(payload).await }), - }?; - - Ok(()) + } } diff --git a/src/usb_hid.rs b/src/usb_hid.rs index 55a822e..d9324c1 100644 --- a/src/usb_hid.rs +++ b/src/usb_hid.rs @@ -29,6 +29,25 @@ pub struct Device { } impl Device { + /// Return a list of all usb devies as a string representation + pub fn list_all() -> Result> { + let api = HidApi::new().context("create hid api")?; + let devices = api.device_list(); + + Ok(devices + .map(|info| { + format!( + "{:?}: vendor_id={:#06x} product_id={:#06x} manufacturer={:?} product={:?}", + info.path(), + info.vendor_id(), + info.product_id(), + info.manufacturer_string(), + info.product_string(), + ) + }) + .collect()) + } + /// Return all supported devices pub fn enumerate() -> Result> { let api = HidApi::new().context("create hid api")?;