Search code examples
rusthidtauri

Reading HID device from Rust with a Tauri App


I'm trying to read out a HID device (barcode scanner) using the hidapi crate in a Tauri App.

I have this scenario in mind:

  • Scan for the device on app startup -> if not there emit event to UI : device-disconnected
  • If device is there, open device and emit event : device-data with the payload when a scan happens
  • If device is disconnected trigger a Tauri command from the UI to rescan for the device

Now I have this code

// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

extern crate hidapi;

use hidapi::HidApi;
use tauri::Manager;

#[derive(Clone, serde::Serialize)]
struct Payload {
    message: String,
}

#[tauri::command]
fn detect_barcode_scanner(app: &mut tauri::App) -> () {

    let vid = 0x1234;
    let pid = 0x5678;

    match HidApi::new() {
        Ok(api) => {
            let mut device_found = false;
            
            for device_info in api.device_list() {
                // Check if the scanner is in the list
                if device_info.vendor_id() == vid && device_info.product_id() == pid {
                    device_found = true;
                    if let Ok(device) = device_info.open_device(&api) {
                        println!("Barcode scanner found & opened successfully.");

                        // Allocate mem buffer for data
                        let mut mem_buf = Vec::<u8>::with_capacity(256);

                        // Start reading data
                        loop {
                            match device.read(&mut mem_buf) {
                                Ok(count) => {
                                    let data = &mem_buf[..count];
                                    println!("Scanned data: {:?}", data);
                                    app.emit_all("scanner-data", Payload { message: "Tauri is awesome!".into() }).unwrap();
                                }
                                Err(e) => {
                                    eprintln!("Error reading from HID device: {:?}", e);
                                    break; 
                                } 
                            }
                        }
                    }
                }
            }
            
            if !device_found {
                println!("Barcode scanner not found.");
            }
        }
        Err(e) => {
            eprintln!("Error initializing HID API: {}", e);
        }
    }
}

fn main() {
  tauri::Builder::default()
    .setup(|app|{
        detect_barcode_scanner(app);
        Ok(())
    })
    .invoke_handler(tauri::generate_handler![detect_barcode_scanner])
    .run(tauri::generate_context!())
    .expect("failed to run app");
}

I now have the problem that the emit_all function is only available on the App Handle, so I call the function detect_barcode_scanner from the setup function.

But this gives that the events send by emit_all function get lost because the UI is not ready.

This also prevents me from keeping the connection open to the device to continue to receive messages after the setup.

Any guidelines / tips on how to solve this ?


Solution

  • You'd want to manage the state differently, and in async way.

    Rust's paradigm of managing the state without sharing memory differs from other languages.

    I'd recommend to do a slightly simpler version:

    1. Run async setup function to create the instance of your scanner
    2. Pass duplex channels into your setup function
    3. Issue message from your setup function, that you'll receive later in a setup function to be passed further to your client side via emit all (or stored in a state).

    Here's a quick code (not tested):

    fn main() {
        let (in_tx, in_rx) = mpsc::channel(1);
        let (out_tx, mut out_rx) = mpsc::channel(1);
    
        tauri::Builder::default()
            .manage(AsyncProcInputTx {
                inner: Mutex::new(async_proc_input_tx),
            })
            .invoke_handler(tauri::generate_handler![your_function_here])
            .setup(|app| {
                tauri::async_runtime::spawn(async move {
                    async_process_model(
                        input_rx,
                        out_tx,
                    ).await
                });
    
                let app_handle = app.handle();
                tauri::async_runtime::spawn(async move {
                    loop {
                        if let Some(hid_device) = out_rx.recv().await {
                            // your_function_here(hid_device, &app_handle);
                        }
                    }
                });
    
                Ok(())
            })
            .run(tauri::generate_context!())
            .expect("error while running tauri application");
    }