I am creating a Tauri app that uses the btleplug
crate to make bluetooth calls. I also need to use a library for the sensors called MetaWear. I have been able to generate bindings for the MetaWear library and am now setting up callbacks from a struct defined by the C library to make async bluetooth calls in btleplug
.
The issue is that I haven’t come up with exactly the right syntax with either lifetimes or for blocking sync calls within tauri’s tokio async_runtime
.
The board
and the btleplug
Peripheral
are initialized from within a #[tauri::command(async)]
block. The board
struct from the MetaWear library allows me to pass in user data via the context as *mut ::std::os::raw::c_void
. I’m passing in a Boxed Peripheral
that has been connected to bluetooth. As the MetaWear board is being initialized, it calls the functions passed into the struct like read_gatt_char
.
The issue begins with being unable to make async calls from within theunsafe extern "C" fn read_gatt_char
. I moved past this by trampolining out of the C FFI callback to unsafe fn on_read_gatt_char
. Initially I had this function as async
but learned fairly quickly that futures are lazy in Rust and so even though I had an await in the rusty async on_read_gatt_char
, the promise was never being called. Then I tried a few different versions of adding blocking in this call. Errors ranged from lifetimes not being defined long enough to being unable to spawn another tokio runtime (this was a panic during runtime).
Since I’m new to Rust, I’d really appreciate a sanity check on this approach.
How do I get past this lifetime error?
error[E0597]: `**cb` does not live long enough
--> src\main.rs:364:18
|
348 | let cb: Box<Box<Peripheral>> = Box::from_raw(context as *mut Box<Peripheral>);
| -- binding `cb` declared here
...
364 | let output = cb.on_read_gatt_char(ACharacteristic { characteristic: &c });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| borrowed value does not live long enough
| argument requires that `**cb` is borrowed for `'static`
...
395 | }
| - `**cb` dropped here while still borrowed
error[E0597]: `c` does not live long enough
--> src\main.rs:364:73
|
359 | let c = Characteristic {
| - binding `c` declared here
...
364 | let output = cb.on_read_gatt_char(ACharacteristic { characteristic: &c });
| -------------------------------------------------------^^---
| | |
| | borrowed value does not live long enough
| argument requires that `c` is borrowed for `'static`
...
395 | }
| - `c` dropped here while still borrowed
#[tauri::command(async)]
async fn bluetooth(state: tauri::State<'_, ConnectedDevices>) -> Result<Vec<String>, ()> {
let manager = BManager::new().await.unwrap();
...
let peripheral: Box<Box<dyn PeripheralExt>> = Box::new(Box::new(p.clone()));
let btle_conn = MblMwBtleConnection {
write_gatt_char: Some(write_gatt_char),
read_gatt_char: Some(read_gatt_char),
enable_notifications: Some(enable_notifications),
on_disconnect: Some(on_disconnect),
context: Box::into_raw(peripheral) as *mut c_void,
};
let board;
unsafe {
board = mbl_mw_metawearboard_create(&btle_conn);
// Initialization must be done every time you connect to a board.
if mbl_mw_metawearboard_is_initialized(board) == 0 {
mbl_mw_metawearboard_initialize(
board,
ptr::null_mut(),
Some(status), // board is failing to initialize when status callback is called.
);
}
let model =
CStr::from_ptr(mbl_mw_metawearboard_get_model_name(board));
trace!("model: {:?}", model);
}
MblMwBtleConnection struct from generated bindings:
#[doc = " Wrapper class containing functions for communicating with the MetaWear through a Bluetooth Low Energy connection."]
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct MblMwBtleConnection {
#[doc = " Provides the calling function the ability to pass any context specific data required"]
pub context: *mut ::std::os::raw::c_void,
#[doc = " Reads the value of the characteristic from the device.\n @param context Pointer to the <code>context</code> field\n @param caller Object using this function pointer\n @param characteristic Gatt characteristic to read\n @param handler Callback function to handle the received value"]
pub read_gatt_char: ::std::option::Option<
unsafe extern "C" fn(
context: *mut ::std::os::raw::c_void,
caller: *const ::std::os::raw::c_void,
characteristic: *const MblMwGattChar,
handler: MblMwFnIntVoidPtrArray,
),
>,
...
#[async_trait]
pub trait PeripheralExt {
unsafe fn on_read_gatt_char(&'static self, characteristic: ACharacteristic<'static>) -> Result<Vec<u8>, btleplug::Error>;
}
#[async_trait]
impl<'a> PeripheralExt for Peripheral {
unsafe fn on_read_gatt_char(&'static self, characteristic: ACharacteristic<'static>) -> Result<Vec<u8>, btleplug::Error> {
let ac = characteristic.characteristic;
// let future = self.read(&ac);
// let task = tauri::async_runtime::spawn(future);
// let output = tauri::async_runtime::block_on(async { task.await});
let output = tauri::async_runtime::block_on(async {
let ac = characteristic.characteristic;
let future = self.read(&ac);
let task = tauri::async_runtime::spawn(future);
task.await
});
let result = output.unwrap();
trace!("after on_read_gatt_char {:?}", result);
return result
}
}
unsafe extern "C" fn read_gatt_char(
context: *mut c_void,
caller: *const c_void,
characteristic: *const MblMwGattChar,
handler: MblMwFnIntVoidPtrArray,
) {
trace!("Reading characteristic: {{service_uuid_high: {:x}, service_uuid_low: {:x}, uuid_high: {:x}, uuid_low: {:x}}}\n", (*characteristic).service_uuid_high, (*characteristic).service_uuid_low,
(*characteristic).uuid_high, (*characteristic).uuid_low);
if handler.is_none() {
return;
}
let cb: Box<Box<Peripheral>> = Box::from_raw(context as *mut Box<Peripheral>);
let c = Characteristic {
uuid: uuid::Uuid::from_str(uuid_str.as_str()).unwrap(),
service_uuid: uuid::Uuid::from_str(service_uuid.as_str()).unwrap(),
properties: CharPropFlags::READ,
};
let output = cb.on_read_gatt_char(ACharacteristic { characteristic: &c });
let val = output.unwrap();
trace!("after on_read_gatt_char {:?}", val);
handler.unwrap()(caller, val.as_ptr(), (val.len() as i32).try_into().unwrap());
This version panicked during runtime about launching another runtime:
let cb: Box<Box<Peripheral>> = Box::from_raw(context as *mut Box<Peripheral>);
let future = cb.on_read_gatt_char(c);
let task = tauri::async_runtime::spawn(future);
let output = tauri::async_runtime::block_on(async { task.await});
let val = output.unwrap();
trace!("after on_read_gatt_char {:?}", val);
handler.unwrap()(caller, val.as_ptr(), val.len() as i32);