diff --git a/Cargo.toml b/Cargo.toml index ed77ba06..964d10e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ objc2-foundation = { version = "0.2.2", default-features = false, features = [ "NSString", "NSUUID", "NSValue", + "NSProcessInfo", ] } objc2-core-bluetooth = { version = "0.2.2", default-features = false, features = [ "std", diff --git a/src/corebluetooth/internal.rs b/src/corebluetooth/internal.rs index 93597bc7..ac2550bc 100644 --- a/src/corebluetooth/internal.rs +++ b/src/corebluetooth/internal.rs @@ -31,13 +31,14 @@ use objc2_core_bluetooth::{ CBCharacteristicProperties, CBCharacteristicWriteType, CBDescriptor, CBManager, CBManagerAuthorization, CBManagerState, CBPeripheral, CBPeripheralState, CBService, CBUUID, }; -use objc2_foundation::{NSArray, NSData, NSMutableDictionary, NSNumber}; +use objc2_foundation::{NSArray, NSData, NSMutableDictionary, NSNumber, NSProcessInfo}; use std::{ collections::{BTreeSet, HashMap, VecDeque}, ffi::CString, fmt::{self, Debug, Formatter}, ops::Deref, thread, + time::Duration, }; use tokio::runtime; use uuid::Uuid; @@ -361,6 +362,12 @@ impl PeripheralInternal { } } +/// Optional CoreBluetooth API capabilities that are available depending on the macOS version. +struct CoreBluetoothFeatures { + /// `peripheral.canSendWriteWithoutResponse` property is available (since macOS 11.2.0) + can_send_write_without_response: bool, +} + // All of CoreBluetooth is basically async. It's all just waiting on delegate // events/callbacks. Therefore, we should be able to round up all of our wacky // ass mut *Object values, keep them in a single struct, in a single thread, and @@ -375,6 +382,7 @@ struct CoreBluetoothInternal { // task::block this when sending even though it'll never actually block. event_sender: Sender, message_receiver: Fuse>, + features: CoreBluetoothFeatures, } impl Debug for CoreBluetoothInternal { @@ -473,6 +481,16 @@ pub enum CoreBluetoothEvent { }, } +fn get_features() -> CoreBluetoothFeatures { + let process_info = NSProcessInfo::processInfo(); + let version = process_info.operatingSystemVersion(); + let current = (version.majorVersion, version.minorVersion); + + CoreBluetoothFeatures { + can_send_write_without_response: current >= (11, 2), + } +} + impl CoreBluetoothInternal { pub fn new( message_receiver: Receiver, @@ -498,6 +516,7 @@ impl CoreBluetoothInternal { event_sender, message_receiver: message_receiver.fuse(), delegate, + features: get_features(), } } @@ -887,6 +906,21 @@ impl CoreBluetoothInternal { { trace!("Writing value! With kind {:?}", kind); unsafe { + if kind == WriteType::WithoutResponse + && self.features.can_send_write_without_response + { + // probably better idea would be to wait for the result of peripheral.peripheralIsReadyToSendWriteWithoutResponse + let mut attempts = 0; + while !peripheral.peripheral.canSendWriteWithoutResponse() + && attempts < 100 + { + attempts += 1; + // min. connection interval time is 15ms. see the document: + // https://developer.apple.com/library/archive/qa/qa1931/_index.html + thread::sleep(Duration::from_millis(15)); + } + } + peripheral.peripheral.writeValue_forCharacteristic_type( &NSData::from_vec(data), &characteristic.characteristic,