use super::{super::utils::to_descriptor_value, descriptor::BLEDescriptor};
use crate::{
Error, Result,
api::{Characteristic, WriteType},
winrtble::utils,
};
use log::{debug, trace};
use std::{collections::HashMap, future::IntoFuture};
use uuid::Uuid;
use windows::core::Ref;
use windows::{
Devices::Bluetooth::{
BluetoothCacheMode,
GenericAttributeProfile::{
GattCharacteristic, GattClientCharacteristicConfigurationDescriptorValue,
GattCommunicationStatus, GattValueChangedEventArgs, GattWriteOption,
},
},
Foundation::TypedEventHandler,
Storage::Streams::{DataReader, DataWriter},
};
pub type NotifiyEventHandler = Box<dyn Fn(Vec<u8>) + Send>;
impl From<WriteType> for GattWriteOption {
fn from(val: WriteType) -> Self {
match val {
WriteType::WithoutResponse => GattWriteOption::WriteWithoutResponse,
WriteType::WithResponse => GattWriteOption::WriteWithResponse,
}
}
}
#[derive(Debug)]
pub struct BLECharacteristic {
characteristic: GattCharacteristic,
pub descriptors: HashMap<Uuid, BLEDescriptor>,
notify_token: Option<i64>,
}
impl BLECharacteristic {
pub fn new(
characteristic: GattCharacteristic,
descriptors: HashMap<Uuid, BLEDescriptor>,
) -> Self {
BLECharacteristic {
characteristic,
descriptors,
notify_token: None,
}
}
pub async fn write_value(&self, data: &[u8], write_type: WriteType) -> Result<()> {
let writer = DataWriter::new()?;
writer.WriteBytes(data)?;
let operation = self
.characteristic
.WriteValueWithOptionAsync(&writer.DetachBuffer()?, write_type.into())?;
let result = operation.into_future().await?;
if result == GattCommunicationStatus::Success {
Ok(())
} else {
Err(Error::Other(
format!("Windows UWP threw error on write: {:?}", result).into(),
))
}
}
pub async fn read_value(&self) -> Result<Vec<u8>> {
let result = self
.characteristic
.ReadValueWithCacheModeAsync(BluetoothCacheMode::Uncached)?
.into_future()
.await?;
if result.Status()? == GattCommunicationStatus::Success {
let value = result.Value()?;
let reader = DataReader::FromBuffer(&value)?;
let len = reader.UnconsumedBufferLength()? as usize;
let mut input = vec![0u8; len];
reader.ReadBytes(&mut input[0..len])?;
Ok(input)
} else {
Err(Error::Other(
format!("Windows UWP threw error on read: {:?}", result).into(),
))
}
}
pub async fn subscribe(&mut self, on_value_changed: NotifiyEventHandler) -> Result<()> {
{
let value_handler = TypedEventHandler::new(
move |_: Ref<GattCharacteristic>, args: Ref<GattValueChangedEventArgs>| {
if let Ok(args) = args.ok() {
let value = args.CharacteristicValue()?;
let reader = DataReader::FromBuffer(&value)?;
let len = reader.UnconsumedBufferLength()? as usize;
let mut input: Vec<u8> = vec![0u8; len];
reader.ReadBytes(&mut input[0..len])?;
trace!("changed {:?}", input);
on_value_changed(input);
}
Ok(())
},
);
let token = self.characteristic.ValueChanged(&value_handler)?;
self.notify_token = Some(token);
}
let config = to_descriptor_value(self.characteristic.CharacteristicProperties()?);
if config == GattClientCharacteristicConfigurationDescriptorValue::None {
return Err(Error::NotSupported("Can not subscribe to attribute".into()));
}
let status = self
.characteristic
.WriteClientCharacteristicConfigurationDescriptorAsync(config)?
.into_future()
.await?;
trace!("subscribe {:?}", status);
if status == GattCommunicationStatus::Success {
Ok(())
} else {
Err(Error::Other(
format!("Windows UWP threw error on subscribe: {:?}", status).into(),
))
}
}
pub async fn unsubscribe(&mut self) -> Result<()> {
if let Some(token) = &self.notify_token {
self.characteristic.RemoveValueChanged(*token)?;
}
self.notify_token = None;
let config = GattClientCharacteristicConfigurationDescriptorValue::None;
let status = self
.characteristic
.WriteClientCharacteristicConfigurationDescriptorAsync(config)?
.into_future()
.await?;
trace!("unsubscribe {:?}", status);
if status == GattCommunicationStatus::Success {
Ok(())
} else {
Err(Error::Other(
format!("Windows UWP threw error on unsubscribe: {:?}", status).into(),
))
}
}
pub fn uuid(&self) -> Uuid {
utils::to_uuid(&self.characteristic.Uuid().unwrap())
}
pub fn to_characteristic(&self, service_uuid: Uuid) -> Characteristic {
let uuid = self.uuid();
let properties =
utils::to_char_props(&self.characteristic.CharacteristicProperties().unwrap());
let descriptors = self
.descriptors
.values()
.map(|descriptor| descriptor.to_descriptor(service_uuid, uuid))
.collect();
Characteristic {
uuid,
service_uuid,
descriptors,
properties,
}
}
}
impl Drop for BLECharacteristic {
fn drop(&mut self) {
if let Some(token) = &self.notify_token {
let result = self.characteristic.RemoveValueChanged(*token);
if let Err(err) = result {
debug!("Drop:remove_connection_status_changed {:?}", err);
}
}
}
}