use async_hid::{
AsyncHidRead, AsyncHidWrite, Device as HidDevice, DeviceId, DeviceInfo as HidDeviceInfo,
DeviceReader, DeviceWriter, HidBackend,
};
use async_io::Timer;
use futures_lite::{FutureExt, Stream, StreamExt};
use image::DynamicImage;
use std::{
collections::{HashMap, HashSet},
convert::identity,
str::{from_utf8, Utf8Error},
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Duration,
};
use tokio::sync::Mutex;
use crate::{
error::MirajazzError,
images::convert_image_with_format,
state::{DeviceState, DeviceStateReader},
types::{DeviceInfo, DeviceInput, DeviceLifecycleEvent, ImageFormat},
};
pub fn new_hid_backend() -> HidBackend {
HidBackend::default()
}
fn hid_device_info_to_lib_device_info(
vids: &[u16],
device_info: HidDeviceInfo,
) -> Option<DeviceInfo> {
if !vids.contains(&device_info.vendor_id) {
return None;
}
if let Some(serial) = device_info.serial_number {
Some(DeviceInfo::new(
device_info.vendor_id,
device_info.product_id,
serial.to_string(),
))
} else {
None
}
}
pub async fn list_devices(vids: &[u16]) -> Result<HashSet<DeviceInfo>, MirajazzError> {
let devices = HidBackend::default()
.enumerate()
.await?
.map(HidDevice::to_device_info)
.filter_map(|d| hid_device_info_to_lib_device_info(vids, d))
.collect::<HashSet<_>>()
.await;
Ok(devices)
}
pub struct DeviceWatcher {
initialized: bool,
id_map: Arc<Mutex<HashMap<DeviceId, DeviceInfo>>>,
connected: Arc<Mutex<HashSet<DeviceInfo>>>,
}
impl DeviceWatcher {
pub fn new() -> Self {
Self {
initialized: false,
id_map: Arc::new(Mutex::new(HashMap::new())),
connected: Arc::new(Mutex::new(HashSet::new())),
}
}
pub async fn watch<'a>(
&'a mut self,
vids: &'a [u16],
) -> Result<impl Stream<Item = DeviceLifecycleEvent> + Send + Unpin + use<'a>, MirajazzError>
{
let backend = HidBackend::default();
if self.initialized {
return Err(MirajazzError::WatcherAlreadyInitialized);
}
self.initialized = true;
let already_connected = HidBackend::default()
.enumerate()
.await?
.map(HidDevice::to_device_info)
.filter_map(|d| Some((d.id.clone(), hid_device_info_to_lib_device_info(vids, d)?)))
.collect::<HashSet<_>>()
.await;
let mut map = self.id_map.lock().await;
let mut connected = self.connected.lock().await;
for (id, device) in already_connected.into_iter() {
map.insert(id, device.clone());
connected.insert(device);
}
drop(map);
drop(connected);
let watcher = backend
.watch()?
.then(|e| async {
match e {
async_hid::DeviceEvent::Connected(device_id) => {
let device = HidBackend::default()
.query_devices(&device_id)
.await
.unwrap()
.last()?;
let info =
hid_device_info_to_lib_device_info(vids, device.to_device_info())?;
self.id_map.lock().await.insert(device_id, info.clone());
let new = self.connected.lock().await.insert(info.clone());
if new {
Some(DeviceLifecycleEvent::Connected(info))
} else {
None
}
}
async_hid::DeviceEvent::Disconnected(device_id) => {
let info = self.id_map.lock().await.remove(&device_id)?;
let existed = self.connected.lock().await.remove(&info);
if existed {
Some(DeviceLifecycleEvent::Disconnected(info))
} else {
None
}
}
}
})
.filter_map(identity);
Ok(Box::pin(watcher))
}
}
pub fn extract_str(bytes: &[u8]) -> Result<String, Utf8Error> {
Ok(from_utf8(bytes)?.replace('\0', "").to_string())
}
struct ImageCache {
key: u8,
image_data: Vec<u8>,
}
pub struct Device {
pub vid: u16,
pub pid: u16,
pub serial_number: String,
is_v2: bool,
supports_both_states: bool,
key_count: usize,
encoder_count: usize,
packet_size: usize,
reader: Arc<Mutex<DeviceReader>>,
writer: Arc<Mutex<DeviceWriter>>,
image_cache: Mutex<Vec<ImageCache>>,
initialized: AtomicBool,
}
impl Device {
pub async fn connect(
dev: DeviceInfo,
is_v2: bool,
supports_both_states: bool,
key_count: usize,
encoder_count: usize,
) -> Result<Device, MirajazzError> {
let hid_device = HidBackend::default()
.enumerate()
.await?
.find(|d| {
if let Some(serial_number) = &d.serial_number {
d.vendor_id == dev.vid
&& d.product_id == dev.pid
&& serial_number == &dev.serial_number
} else {
false
}
})
.await;
if let Some(hid_device) = hid_device {
let (reader, writer) = hid_device.open().await?;
Ok(Device {
vid: dev.vid,
pid: dev.pid,
serial_number: dev.serial_number,
is_v2,
supports_both_states,
key_count,
encoder_count,
reader: Arc::new(Mutex::new(reader)),
writer: Arc::new(Mutex::new(writer)),
packet_size: if is_v2 { 1024 } else { 512 },
image_cache: Mutex::new(vec![]),
initialized: false.into(),
})
} else {
Err(MirajazzError::DeviceNotFoundError)
}
}
}
impl Device {
pub fn key_count(&self) -> usize {
self.key_count
}
pub fn encoder_count(&self) -> usize {
self.encoder_count
}
pub fn serial_number(&self) -> &String {
&self.serial_number
}
async fn initialize(&self) -> Result<(), MirajazzError> {
if self.initialized.load(Ordering::Acquire) {
return Ok(());
}
self.initialized.store(true, Ordering::Release);
let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x44, 0x49, 0x53];
self.write_extended_data(&mut buf).await?;
let mut buf = vec![
0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x4c, 0x49, 0x47, 0x00, 0x00, 0x00, 0x00,
];
self.write_extended_data(&mut buf).await?;
Ok(())
}
pub fn supports_both_states(&self) -> bool {
self.supports_both_states
}
pub async fn read_input(
&self,
timeout: Option<Duration>,
process_input: fn(u8, u8) -> Result<DeviceInput, MirajazzError>,
) -> Result<DeviceInput, MirajazzError> {
self.initialize().await?;
let data = if timeout.is_some() {
self.read_data_with_timeout(512, timeout.unwrap()).await?
} else {
Some(self.read_data(512).await?)
};
if data.is_none() {
return Ok(DeviceInput::NoData);
}
let data = data.unwrap();
if data[0] == 0 {
return Ok(DeviceInput::NoData);
}
let state = if self.supports_both_states() {
data[10]
} else {
0x1u8
};
Ok(process_input(data[9], state)?)
}
pub async fn reset(&self) -> Result<(), MirajazzError> {
self.initialize().await?;
self.set_brightness(100).await?;
self.clear_all_button_images().await?;
Ok(())
}
pub async fn set_brightness(&self, percent: u8) -> Result<(), MirajazzError> {
self.initialize().await?;
let percent = percent.clamp(0, 100);
let mut buf = vec![
0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x4c, 0x49, 0x47, 0x00, 0x00, percent,
];
self.write_extended_data(&mut buf).await?;
Ok(())
}
async fn send_image(&self, key: u8, image_data: &[u8]) -> Result<(), MirajazzError> {
let mut buf = vec![
0x00,
0x43,
0x52,
0x54,
0x00,
0x00,
0x42,
0x41,
0x54,
0x00,
0x00,
(image_data.len() >> 8) as u8,
image_data.len() as u8,
key + 1,
];
self.write_extended_data(&mut buf).await?;
self.write_image_data_reports(image_data).await?;
Ok(())
}
pub async fn write_image(&self, key: u8, image_data: &[u8]) -> Result<(), MirajazzError> {
let cache_entry = ImageCache {
key,
image_data: image_data.to_vec(), };
self.image_cache.lock().await.push(cache_entry);
Ok(())
}
pub async fn clear_button_image(&self, key: u8) -> Result<(), MirajazzError> {
self.initialize().await?;
let mut buf = vec![
0x00,
0x43,
0x52,
0x54,
0x00,
0x00,
0x43,
0x4c,
0x45,
0x00,
0x00,
0x00,
if key == 0xff { 0xff } else { key + 1 },
];
self.write_extended_data(&mut buf).await?;
Ok(())
}
pub async fn clear_all_button_images(&self) -> Result<(), MirajazzError> {
self.initialize().await?;
self.clear_button_image(0xFF).await?;
if self.is_v2 {
let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x53, 0x54, 0x50];
self.write_extended_data(&mut buf).await?;
}
Ok(())
}
pub async fn set_button_image(
&self,
key: u8,
image_format: ImageFormat,
image: DynamicImage,
) -> Result<(), MirajazzError> {
self.initialize().await?;
let image_data = convert_image_with_format(image_format, image).await?;
self.write_image(key, &image_data).await?;
Ok(())
}
pub async fn sleep(&self) -> Result<(), MirajazzError> {
self.initialize().await?;
let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x48, 0x41, 0x4e];
self.write_extended_data(&mut buf).await?;
Ok(())
}
pub async fn keep_alive(&self) -> Result<(), MirajazzError> {
self.initialize().await?;
let mut buf = vec![
0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x43, 0x4F, 0x4E, 0x4E, 0x45, 0x43, 0x54,
];
self.write_extended_data(&mut buf).await?;
Ok(())
}
pub async fn shutdown(&self) -> Result<(), MirajazzError> {
self.initialize().await?;
let mut buf = vec![
0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x43, 0x4c, 0x45, 0x00, 0x00, 0x44, 0x43,
];
self.write_extended_data(&mut buf).await?;
let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x48, 0x41, 0x4E];
self.write_extended_data(&mut buf).await?;
Ok(())
}
pub async fn flush(&self) -> Result<(), MirajazzError> {
let mut cache = self.image_cache.lock().await;
self.initialize().await?;
if cache.is_empty() {
return Ok(());
}
for image in cache.iter() {
self.send_image(image.key, &image.image_data).await?;
}
let mut buf = vec![0x00, 0x43, 0x52, 0x54, 0x00, 0x00, 0x53, 0x54, 0x50];
self.write_extended_data(&mut buf).await?;
cache.clear();
Ok(())
}
pub fn get_reader(
self: &Arc<Self>,
process_input: fn(u8, u8) -> Result<DeviceInput, MirajazzError>,
) -> Arc<DeviceStateReader> {
#[allow(clippy::arc_with_non_send_sync)]
Arc::new(DeviceStateReader {
device: self.clone(),
states: Mutex::new(DeviceState {
buttons: vec![false; self.key_count],
encoders: vec![false; self.encoder_count],
}),
process_input,
})
}
async fn write_image_data_reports(&self, image_data: &[u8]) -> Result<(), MirajazzError> {
let image_report_length = self.packet_size + 1;
let image_report_header_length = 1;
let image_report_payload_length = image_report_length - image_report_header_length;
let mut page_number = 0;
let mut bytes_remaining = image_data.len();
while bytes_remaining > 0 {
let this_length = bytes_remaining.min(image_report_payload_length);
let bytes_sent = page_number * image_report_payload_length;
let mut buf: Vec<u8> = [0x00].to_vec();
buf.extend(&image_data[bytes_sent..bytes_sent + this_length]);
buf.extend(vec![0u8; image_report_length - buf.len()]);
self.write_data(&buf).await?;
bytes_remaining -= this_length;
page_number += 1;
}
Ok(())
}
pub async fn read_data(&self, length: usize) -> Result<Vec<u8>, MirajazzError> {
let mut buf = vec![0u8; length];
let _size = self.reader.lock().await.read_input_report(&mut buf).await?;
Ok(buf)
}
pub async fn read_data_with_timeout(
&self,
length: usize,
timeout: Duration,
) -> Result<Option<Vec<u8>>, MirajazzError> {
let mut buf = vec![0u8; length];
let size = self
.reader
.lock()
.await
.read_input_report(&mut buf)
.or(async {
Timer::after(timeout).await;
Ok(0)
})
.await?;
if size == 0 {
return Ok(None);
}
Ok(Some(buf))
}
pub async fn write_data(&self, payload: &[u8]) -> Result<(), MirajazzError> {
Ok(self
.writer
.lock()
.await
.write_output_report(&payload)
.await?)
}
pub async fn write_extended_data(&self, payload: &mut Vec<u8>) -> Result<(), MirajazzError> {
payload.extend(vec![0u8; 1 + self.packet_size - payload.len()]);
self.write_data(payload).await
}
}