Compare commits

...

12 commits

Author SHA1 Message Date
Martin Michaelis
44222843d8 Add list devices option 2024-12-31 23:28:06 +00:00
dependabot[bot]
98b6f0b081 Bump the minor-updates group across 1 directory with 9 updates
Bumps the minor-updates group with 8 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.86` | `1.0.95` |
| [clap](https://github.com/clap-rs/clap) | `4.5.13` | `4.5.23` |
| [hidapi](https://github.com/ruabmbua/hidapi-rs) | `2.6.1` | `2.6.3` |
| [btleplug](https://github.com/deviceplug/btleplug) | `0.11.5` | `0.11.6` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.10.0` | `1.11.0` |
| [serde](https://github.com/serde-rs/serde) | `1.0.204` | `1.0.217` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.121` | `1.0.134` |
| [time](https://github.com/time-rs/time) | `0.3.36` | `0.3.37` |



Updates `anyhow` from 1.0.86 to 1.0.95
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.86...1.0.95)

Updates `clap` from 4.5.13 to 4.5.23
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.13...clap_complete-v4.5.23)

Updates `hidapi` from 2.6.1 to 2.6.3
- [Release notes](https://github.com/ruabmbua/hidapi-rs/releases)
- [Commits](https://github.com/ruabmbua/hidapi-rs/compare/v2.6.1...v2.6.3)

Updates `btleplug` from 0.11.5 to 0.11.6
- [Changelog](https://github.com/deviceplug/btleplug/blob/master/CHANGELOG.md)
- [Commits](https://github.com/deviceplug/btleplug/compare/0.11.5...0.11.6)

Updates `uuid` from 1.10.0 to 1.11.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.10.0...1.11.0)

Updates `tokio` from 1.39.2 to 1.42.0
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.39.2...tokio-1.42.0)

Updates `serde` from 1.0.204 to 1.0.217
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.204...v1.0.217)

Updates `serde_json` from 1.0.121 to 1.0.134
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.121...v1.0.134)

Updates `time` from 0.3.36 to 0.3.37
- [Release notes](https://github.com/time-rs/time/releases)
- [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md)
- [Commits](https://github.com/time-rs/time/compare/v0.3.36...v0.3.37)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-updates
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-updates
- dependency-name: hidapi
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-updates
- dependency-name: btleplug
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-updates
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-updates
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-updates
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-updates
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-updates
- dependency-name: time
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-31 23:51:16 +01:00
Martin Michaelis
116a506fe4 Update to zerocopy 0.8 2024-12-31 21:58:20 +00:00
dependabot[bot]
e7989e763f Bump the minor-updates group across 1 directory with 6 updates
Bumps the minor-updates group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [clap](https://github.com/clap-rs/clap) | `4.5.8` | `4.5.13` |
| [tokio](https://github.com/tokio-rs/tokio) | `1.38.1` | `1.39.2` |
| [serde](https://github.com/serde-rs/serde) | `1.0.203` | `1.0.204` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.118` | `1.0.121` |
| [toml](https://github.com/toml-rs/toml) | `0.8.14` | `0.8.19` |
| [zerocopy](https://github.com/google/zerocopy) | `0.7.34` | `0.7.35` |



Updates `clap` from 4.5.8 to 4.5.13
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.8...v4.5.13)

Updates `tokio` from 1.38.1 to 1.39.2
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.38.1...tokio-1.39.2)

Updates `serde` from 1.0.203 to 1.0.204
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.203...v1.0.204)

Updates `serde_json` from 1.0.118 to 1.0.121
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.118...v1.0.121)

Updates `toml` from 0.8.14 to 0.8.19
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.14...toml-v0.8.19)

Updates `zerocopy` from 0.7.34 to 0.7.35
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/commits)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-updates
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-updates
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-updates
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-updates
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-updates
- dependency-name: zerocopy
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-01 23:09:45 +02:00
Lilian
71f9ab3f19 Remove redundant BLE service UUID check as it is already done using a ScanFilter 2024-08-01 23:04:10 +02:00
Lilian
19db575be2 Address additional PR review comments for code simplification 2024-08-01 23:04:10 +02:00
Lilian
ba41bf3a65 Simplify error handling of BLE writing 2024-08-01 23:04:10 +02:00
Lilian
925fb45617 Address PR review comments for BLE feature 2024-08-01 23:04:10 +02:00
Lilian
e8a1ed3907 Improve error handling in BLE device enumeration 2024-08-01 23:04:10 +02:00
Lilian
c273d4cbfc Add support for transferring badge config via Bluetooth Low Energy
Adds a new crate feature `ble` that allows for transfering badge
configurations over Bluetooth Low Energy instead of USB.
Expands the CLI to allow for specification of the transport protocol,
either USB or BLE.

The BLE library uses async rust requiring to add tokio as a async runtime
to the CLI.
2024-08-01 23:04:10 +02:00
dependabot[bot]
9a155a85fb Bump the minor-updates group across 1 directory with 2 updates
Bumps the minor-updates group with 2 updates in the / directory: [clap](https://github.com/clap-rs/clap) and [serde_json](https://github.com/serde-rs/json).


Updates `clap` from 4.5.7 to 4.5.8
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.7...v4.5.8)

Updates `serde_json` from 1.0.117 to 1.0.118
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.117...v1.0.118)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-updates
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-29 15:28:40 +02:00
dependabot[bot]
5e92559843 Bump clap from 4.5.6 to 4.5.7 in the minor-updates group
Bumps the minor-updates group with 1 update: [clap](https://github.com/clap-rs/clap).


Updates `clap` from 4.5.6 to 4.5.7
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.6...v4.5.7)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-10 21:41:26 +02:00
9 changed files with 1091 additions and 75 deletions

View file

@ -51,7 +51,7 @@ jobs:
run: |
rustup toolchain install ${{ matrix.rust }} --profile minimal --no-self-update
- name: Install build dependencies
run: sudo apt-get install -y libudev-dev
run: sudo apt-get install -y libudev-dev libdbus-1-dev
- name: ${{ matrix.cmd.name }}
run: ${{ matrix.cmd.run }} ${{ matrix.features }} -- ${{ matrix.cmd.run2 }}
@ -66,7 +66,7 @@ jobs:
runs-on: ubuntu-latest
target: x86_64-unknown-linux-gnu
pre-build: |
sudo apt-get install -y libudev-dev
sudo apt-get install -y libudev-dev libdbus-1-dev
- name: Windows (x86_64)
runs-on: windows-latest
target: x86_64-pc-windows-msvc

817
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -24,6 +24,7 @@ cli = [
"embedded-graphics",
"serde",
"usb-hid",
"ble",
"dep:base64",
"dep:clap",
"dep:serde_json",
@ -33,16 +34,19 @@ cli = [
embedded-graphics = ["dep:embedded-graphics"]
serde = ["dep:serde"]
usb-hid = ["dep:hidapi"]
ble = ["dep:btleplug", "dep:uuid", "dep:tokio"]
[dependencies]
anyhow = "1.0.86"
anyhow = "1.0.95"
base64 = { version = "0.22.1", optional = true }
clap = { version = "4.5.6", features = ["derive"], optional = true }
clap = { version = "4.5.23", features = ["derive"], optional = true }
embedded-graphics = { version = "0.8.1", optional = true }
hidapi = { version = "2.6.1", optional = true }
serde = { version = "1.0.203", features = ["derive"], optional = true }
serde_json = { version = "1.0.117", optional = true }
time = "0.3.36"
toml = { version = "0.8.13", optional = true }
zerocopy = { version = "0.7.34", features = ["derive"] }
hidapi = { version = "2.6.3", optional = true }
btleplug = { version = "0.11.6", optional = true }
uuid = { version = "1.11.0", optional = true }
tokio = { version = "1.39.2", features = ["rt"], optional = true }
serde = { version = "1.0.217", features = ["derive"], optional = true }
serde_json = { version = "1.0.134", optional = true }
time = "0.3.37"
toml = { version = "0.8.19", optional = true }
zerocopy = { version = "0.8.14", features = ["derive"] }

View file

@ -46,7 +46,8 @@ cargo run --features cli -- --help
## Usage
Execute the `badgemagic` tool and pass the file name of your configuration file. Depending on how you installed the tool:
Execute the `badgemagic` tool and pass the file name of your configuration file alongside the mode of transport (USB or Bluetooth Low Energy).
Depending on how you installed the tool:
```sh
# Downloaded from release page
@ -60,6 +61,8 @@ cargo run --features cli -- config.toml
```
The above command will read your configuration from a file named `config.toml` in the current directory.
The transport mode can be either `--transport usb` or `--transport ble` for transferring the message via Bluetooth Low Energy.
Usage of BLE on macOS requires special permissions, which is explained in more detail [here](https://github.com/deviceplug/btleplug#macos).
## Configuration

206
src/ble.rs Normal file
View file

@ -0,0 +1,206 @@
//! Connect to an LED badge via Bluetooth Low Energy (BLE)
use std::time::Duration;
use anyhow::{Context, Result};
use btleplug::{
api::{bleuuid, Central as _, Manager as _, Peripheral as _, ScanFilter, WriteType},
platform::{Manager, Peripheral},
};
use tokio::time;
use uuid::Uuid;
use crate::protocol::PayloadBuffer;
/// `0000fee0-0000-1000-8000-00805f9b34fb`
const BADGE_SERVICE_UUID: Uuid = bleuuid::uuid_from_u16(0xfee0);
/// `0000fee1-0000-1000-8000-00805f9b34fb`
const BADGE_CHAR_UUID: Uuid = bleuuid::uuid_from_u16(0xfee1);
const BADGE_BLE_DEVICE_NAME: &str = "LSLED";
const BLE_CHAR_CHUNK_SIZE: usize = 16;
/// A discovered BLE device
pub struct Device {
peripheral: Peripheral,
}
impl Device {
/// Return a list of all BLE devies as a string representation.
pub async fn list_all() -> Result<Vec<String>> {
// 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.
pub async fn enumerate() -> Result<Vec<Self>> {
Self::enumerate_duration(Duration::from_secs(2)).await
}
/// Return all supported devices that are found in the given duration.
///
/// Returns all badges that are in BLE range and are in Bluetooth transfer mode.
/// # Panics
/// This function panics if it is unable to access the Bluetooth adapter.
pub async fn enumerate_duration(scan_duration: Duration) -> Result<Vec<Self>> {
// 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 {
services: vec![BADGE_SERVICE_UUID],
})
.await
.context("bluetooth scan start")?;
time::sleep(scan_duration).await;
// Filter for badge devices
let mut led_badges = vec![];
for p in adapter
.peripherals()
.await
.context("enumerating bluetooth devices")?
{
if let Some(badge) = Self::from_peripheral(p).await {
led_badges.push(badge);
}
}
Ok(led_badges)
}
async fn from_peripheral(peripheral: Peripheral) -> Option<Self> {
// The existance of the service with the correct UUID
// exists is already checked by the scan filter.
// But we also need to check the device name to make sure
// we're talking to a badge as some devices that are not led badges
// also use the same service UUID.
let props = peripheral.properties().await.ok()??;
let local_name = props.local_name.as_ref()?;
if local_name == BADGE_BLE_DEVICE_NAME {
Some(Self { peripheral })
} else {
None
}
}
/// Return the single supported device
///
/// This function returns an error if no device could be found
/// or if multiple devices would match.
pub async fn single() -> Result<Self> {
let mut devices = Self::enumerate()
.await
.context("enumerating badges")?
.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.
///
/// This function connects to the device, writes the payload and disconnects.
/// When the device went out of range between discovering it
/// and writing the payload, an error is returned.
/// # Panics
/// This functions panics if the BLE device does not have the expected badge characteristic.
pub async fn write(&self, payload: PayloadBuffer) -> Result<()> {
self.peripheral
.connect()
.await
.context("bluetooth device connect")?;
let result = self.write_connected(payload).await;
let disconnect_result = self.peripheral.disconnect().await;
if result.is_ok() {
// Write succesful, return disconnect result
Ok(disconnect_result?)
} else {
// Write failed, return write result and ignore disconnect result
result
}
}
async fn write_connected(&self, payload: PayloadBuffer) -> Result<()> {
// Get characteristic
self.peripheral
.discover_services()
.await
.context("discovering services")?;
let characteristics = self.peripheral.characteristics();
let badge_char = characteristics
.iter()
.find(|c| c.uuid == BADGE_CHAR_UUID)
.context("badge characteristic not found")?;
// Write payload
let bytes = payload.into_padded_bytes();
let data = bytes.as_ref();
anyhow::ensure!(
data.len() % BLE_CHAR_CHUNK_SIZE == 0,
"Payload size must be a multiple of {} bytes",
BLE_CHAR_CHUNK_SIZE
);
// 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)");
for chunk in data.chunks(BLE_CHAR_CHUNK_SIZE) {
self.peripheral
.write(badge_char, chunk, WriteType::WithoutResponse)
.await
.context("writing payload chunk")?;
}
Ok(())
}
}

View file

@ -6,6 +6,9 @@ pub mod protocol;
#[cfg(feature = "usb-hid")]
pub mod usb_hid;
#[cfg(feature = "ble")]
pub mod ble;
#[cfg(feature = "embedded-graphics")]
pub mod util;

View file

@ -4,11 +4,12 @@ use std::{fs, path::PathBuf};
use anyhow::{Context, Result};
use badgemagic::{
ble::Device as BleDevice,
protocol::{Mode, PayloadBuffer, Speed, Style},
usb_hid::Device,
usb_hid::Device as UsbDevice,
};
use base64::Engine;
use clap::Parser;
use clap::{Parser, ValueEnum};
use embedded_graphics::{
geometry::Point,
image::{Image, ImageRawLE},
@ -38,8 +39,24 @@ struct Args {
#[clap(long)]
format: Option<String>,
/// Transport protocol to use
#[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<PathBuf>,
}
#[derive(Clone, Deserialize, ValueEnum)]
#[serde(rename_all = "kebab-case")]
enum TransportProtocol {
Usb,
Ble,
}
#[derive(Deserialize)]
@ -79,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<PayloadBuffer> {
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")?,
@ -177,7 +226,18 @@ fn main() -> Result<()> {
}
}
Device::single()?.write(payload)?;
Ok(())
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 }),
}
}

View file

@ -12,7 +12,7 @@ use embedded_graphics::{
Drawable,
};
use time::OffsetDateTime;
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U16};
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U16};
/// Message style configuration
/// ```
@ -197,7 +197,7 @@ const MSG_PADDING_ALIGN: usize = 64;
const MAGIC: [u8; 6] = *b"wang\0\0";
#[derive(FromZeroes, FromBytes, AsBytes)]
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C)]
struct Header {
magic: [u8; 6],
@ -210,7 +210,7 @@ struct Header {
_padding_2: [u8; 20],
}
#[derive(FromZeroes, FromBytes, AsBytes)]
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C)]
struct Timestamp {
year: u8,
@ -297,7 +297,7 @@ impl PayloadBuffer {
}
fn header_mut(&mut self) -> &mut Header {
Header::mut_from_prefix(&mut self.data).unwrap()
Header::mut_from_prefix(&mut self.data).unwrap().0
}
/// Return the current number of messages
@ -359,7 +359,7 @@ impl PayloadBuffer {
let start = self.data.len();
self.data.resize(start + count * 11, 0);
MessageBuffer(FromBytes::mut_slice_from(&mut self.data[start..]).unwrap())
MessageBuffer(FromBytes::mut_from_bytes(&mut self.data[start..]).unwrap())
}
/// Get the current payload as bytes (without padding)

View file

@ -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<Vec<String>> {
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<Vec<Self>> {
let api = HidApi::new().context("create hid api")?;