mirror of
https://github.com/fossasia/badgemagic-rs
synced 2025-06-24 07:43:58 +00:00
Compare commits
14 commits
commit-03f
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
44222843d8 | ||
|
98b6f0b081 | ||
|
116a506fe4 | ||
|
e7989e763f | ||
|
71f9ab3f19 | ||
|
19db575be2 | ||
|
ba41bf3a65 | ||
|
925fb45617 | ||
|
e8a1ed3907 | ||
|
c273d4cbfc | ||
|
9a155a85fb | ||
|
5e92559843 | ||
|
043edac683 | ||
|
d0149fc11e |
10 changed files with 1109 additions and 75 deletions
18
.github/dependabot.yml
vendored
Normal file
18
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
|
||||||
|
- package-ecosystem: cargo
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
groups:
|
||||||
|
minor-updates:
|
||||||
|
applies-to: version-updates
|
||||||
|
update-types:
|
||||||
|
- minor
|
||||||
|
- patch
|
||||||
|
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
|
@ -51,7 +51,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
rustup toolchain install ${{ matrix.rust }} --profile minimal --no-self-update
|
rustup toolchain install ${{ matrix.rust }} --profile minimal --no-self-update
|
||||||
- name: Install build dependencies
|
- 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 }}
|
- name: ${{ matrix.cmd.name }}
|
||||||
run: ${{ matrix.cmd.run }} ${{ matrix.features }} -- ${{ matrix.cmd.run2 }}
|
run: ${{ matrix.cmd.run }} ${{ matrix.features }} -- ${{ matrix.cmd.run2 }}
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
target: x86_64-unknown-linux-gnu
|
target: x86_64-unknown-linux-gnu
|
||||||
pre-build: |
|
pre-build: |
|
||||||
sudo apt-get install -y libudev-dev
|
sudo apt-get install -y libudev-dev libdbus-1-dev
|
||||||
- name: Windows (x86_64)
|
- name: Windows (x86_64)
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
target: x86_64-pc-windows-msvc
|
target: x86_64-pc-windows-msvc
|
||||||
|
|
817
Cargo.lock
generated
817
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
22
Cargo.toml
22
Cargo.toml
|
@ -24,6 +24,7 @@ cli = [
|
||||||
"embedded-graphics",
|
"embedded-graphics",
|
||||||
"serde",
|
"serde",
|
||||||
"usb-hid",
|
"usb-hid",
|
||||||
|
"ble",
|
||||||
"dep:base64",
|
"dep:base64",
|
||||||
"dep:clap",
|
"dep:clap",
|
||||||
"dep:serde_json",
|
"dep:serde_json",
|
||||||
|
@ -33,16 +34,19 @@ cli = [
|
||||||
embedded-graphics = ["dep:embedded-graphics"]
|
embedded-graphics = ["dep:embedded-graphics"]
|
||||||
serde = ["dep:serde"]
|
serde = ["dep:serde"]
|
||||||
usb-hid = ["dep:hidapi"]
|
usb-hid = ["dep:hidapi"]
|
||||||
|
ble = ["dep:btleplug", "dep:uuid", "dep:tokio"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.95"
|
||||||
base64 = { version = "0.22.1", optional = true }
|
base64 = { version = "0.22.1", optional = true }
|
||||||
clap = { version = "4.5.4", features = ["derive"], optional = true }
|
clap = { version = "4.5.23", features = ["derive"], optional = true }
|
||||||
embedded-graphics = { version = "0.8.1", optional = true }
|
embedded-graphics = { version = "0.8.1", optional = true }
|
||||||
hidapi = { version = "2.6.1", optional = true }
|
hidapi = { version = "2.6.3", optional = true }
|
||||||
serde = { version = "1.0.203", features = ["derive"], optional = true }
|
btleplug = { version = "0.11.6", optional = true }
|
||||||
serde_json = { version = "1.0.117", optional = true }
|
uuid = { version = "1.11.0", optional = true }
|
||||||
time = "0.3.36"
|
tokio = { version = "1.39.2", features = ["rt"], optional = true }
|
||||||
toml = { version = "0.8.13", optional = true }
|
serde = { version = "1.0.217", features = ["derive"], optional = true }
|
||||||
zerocopy = { version = "0.7.34", features = ["derive"] }
|
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"] }
|
||||||
|
|
|
@ -46,7 +46,8 @@ cargo run --features cli -- --help
|
||||||
|
|
||||||
## Usage
|
## 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
|
```sh
|
||||||
# Downloaded from release page
|
# 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 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
|
## Configuration
|
||||||
|
|
||||||
|
|
206
src/ble.rs
Normal file
206
src/ble.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,9 @@ pub mod protocol;
|
||||||
#[cfg(feature = "usb-hid")]
|
#[cfg(feature = "usb-hid")]
|
||||||
pub mod usb_hid;
|
pub mod usb_hid;
|
||||||
|
|
||||||
|
#[cfg(feature = "ble")]
|
||||||
|
pub mod ble;
|
||||||
|
|
||||||
#[cfg(feature = "embedded-graphics")]
|
#[cfg(feature = "embedded-graphics")]
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
|
|
80
src/main.rs
80
src/main.rs
|
@ -4,11 +4,12 @@ use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use badgemagic::{
|
use badgemagic::{
|
||||||
|
ble::Device as BleDevice,
|
||||||
protocol::{Mode, PayloadBuffer, Speed, Style},
|
protocol::{Mode, PayloadBuffer, Speed, Style},
|
||||||
usb_hid::Device,
|
usb_hid::Device as UsbDevice,
|
||||||
};
|
};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use clap::Parser;
|
use clap::{Parser, ValueEnum};
|
||||||
use embedded_graphics::{
|
use embedded_graphics::{
|
||||||
geometry::Point,
|
geometry::Point,
|
||||||
image::{Image, ImageRawLE},
|
image::{Image, ImageRawLE},
|
||||||
|
@ -38,8 +39,24 @@ struct Args {
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
format: Option<String>,
|
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
|
/// 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)]
|
#[derive(Deserialize)]
|
||||||
|
@ -79,16 +96,48 @@ enum Content {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let args = Args::parse();
|
let mut args = Args::parse();
|
||||||
let config = fs::read_to_string(&args.config)
|
|
||||||
.with_context(|| format!("load config: {:?}", args.config))?;
|
|
||||||
|
|
||||||
|
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 config: Config = {
|
||||||
let extension = args
|
let extension = args
|
||||||
.format
|
.format
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(AsRef::as_ref)
|
.map(AsRef::as_ref)
|
||||||
.or(args.config.extension())
|
.or(config_path.extension())
|
||||||
.context("missing file extension for config file")?;
|
.context("missing file extension for config file")?;
|
||||||
match extension.to_str().unwrap_or_default() {
|
match extension.to_str().unwrap_or_default() {
|
||||||
"json" => serde_json::from_str(&config).context("parse config")?,
|
"json" => serde_json::from_str(&config).context("parse config")?,
|
||||||
|
@ -177,7 +226,18 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Device::single()?.write(payload)?;
|
Ok(payload)
|
||||||
|
}
|
||||||
Ok(())
|
|
||||||
|
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 }),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ use embedded_graphics::{
|
||||||
Drawable,
|
Drawable,
|
||||||
};
|
};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U16};
|
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U16};
|
||||||
|
|
||||||
/// Message style configuration
|
/// Message style configuration
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -197,7 +197,7 @@ const MSG_PADDING_ALIGN: usize = 64;
|
||||||
|
|
||||||
const MAGIC: [u8; 6] = *b"wang\0\0";
|
const MAGIC: [u8; 6] = *b"wang\0\0";
|
||||||
|
|
||||||
#[derive(FromZeroes, FromBytes, AsBytes)]
|
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
struct Header {
|
struct Header {
|
||||||
magic: [u8; 6],
|
magic: [u8; 6],
|
||||||
|
@ -210,7 +210,7 @@ struct Header {
|
||||||
_padding_2: [u8; 20],
|
_padding_2: [u8; 20],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromZeroes, FromBytes, AsBytes)]
|
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
struct Timestamp {
|
struct Timestamp {
|
||||||
year: u8,
|
year: u8,
|
||||||
|
@ -297,7 +297,7 @@ impl PayloadBuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header_mut(&mut self) -> &mut Header {
|
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
|
/// Return the current number of messages
|
||||||
|
@ -359,7 +359,7 @@ impl PayloadBuffer {
|
||||||
|
|
||||||
let start = self.data.len();
|
let start = self.data.len();
|
||||||
self.data.resize(start + count * 11, 0);
|
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)
|
/// Get the current payload as bytes (without padding)
|
||||||
|
|
|
@ -29,6 +29,25 @@ pub struct Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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
|
/// Return all supported devices
|
||||||
pub fn enumerate() -> Result<Vec<Self>> {
|
pub fn enumerate() -> Result<Vec<Self>> {
|
||||||
let api = HidApi::new().context("create hid api")?;
|
let api = HidApi::new().context("create hid api")?;
|
||||||
|
|
Loading…
Reference in a new issue