Files
home/Projects/sexy.pivoine.art/packages/buttplug/src/webbluetooth/webbluetooth_hardware.rs
2025-10-08 10:35:48 +02:00

433 lines
13 KiB
Rust

use async_trait::async_trait;
use buttplug::{
core::{
errors::ButtplugDeviceError,
message::Endpoint,
},
server::device::{
configuration::{BluetoothLESpecifier, ProtocolCommunicationSpecifier},
hardware::{
Hardware,
HardwareConnector,
HardwareEvent,
HardwareInternal,
HardwareReadCmd,
HardwareReading,
HardwareSpecializer,
HardwareSubscribeCmd,
HardwareUnsubscribeCmd,
HardwareWriteCmd,
},
},
util::future::{ButtplugFuture, ButtplugFutureStateShared},
};
use futures::future::{self, BoxFuture};
use js_sys::{DataView, Uint8Array};
use std::{
collections::HashMap,
convert::TryFrom,
fmt::{self, Debug},
};
use tokio::sync::{broadcast, mpsc};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::{spawn_local, JsFuture};
use web_sys::{
BluetoothDevice,
BluetoothRemoteGattCharacteristic,
BluetoothRemoteGattServer,
BluetoothRemoteGattService,
Event,
MessageEvent,
};
type WebBluetoothResultFuture = ButtplugFuture<Result<(), ButtplugDeviceError>>;
type WebBluetoothReadResultFuture = ButtplugFuture<Result<HardwareReading, ButtplugDeviceError>>;
struct BluetoothDeviceWrapper {
pub device: BluetoothDevice
}
unsafe impl Send for BluetoothDeviceWrapper {
}
unsafe impl Sync for BluetoothDeviceWrapper {
}
pub struct WebBluetoothHardwareConnector {
device: Option<BluetoothDeviceWrapper>,
}
impl WebBluetoothHardwareConnector {
pub fn new(
device: BluetoothDevice,
) -> Self {
Self {
device: Some(BluetoothDeviceWrapper {
device,
})
}
}
}
impl Debug for WebBluetoothHardwareConnector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let device = &self.device.as_ref();
match device {
Some(device) => {
f.debug_struct("WebBluetoothHardwareCreator")
.field("name", &device.device.name().unwrap())
.finish()
}
&None => {
f.debug_struct("WebBluetoothHardwareCreator")
.field("name", &"Unknown device")
.finish()
}
}
}
}
#[async_trait]
impl HardwareConnector for WebBluetoothHardwareConnector {
fn specifier(&self) -> ProtocolCommunicationSpecifier {
let device = &self.device.as_ref();
match device {
Some(device) => {
ProtocolCommunicationSpecifier::BluetoothLE(BluetoothLESpecifier::new_from_device(
&device.device.name().unwrap(),
&HashMap::new(),
&[]
))
}
&None => {
ProtocolCommunicationSpecifier::BluetoothLE(BluetoothLESpecifier::new_from_device(
"Unknown device",
&HashMap::new(),
&[]
))
}
}
}
async fn connect(&mut self) -> Result<Box<dyn HardwareSpecializer>, ButtplugDeviceError> {
Ok(Box::new(WebBluetoothHardwareSpecializer::new(self.device.take().unwrap())))
}
}
pub struct WebBluetoothHardwareSpecializer {
device: Option<BluetoothDeviceWrapper>,
}
impl WebBluetoothHardwareSpecializer {
fn new(device: BluetoothDeviceWrapper) -> Self {
Self {
device: Some(device),
}
}
}
#[async_trait]
impl HardwareSpecializer for WebBluetoothHardwareSpecializer {
async fn specialize(
&mut self,
specifiers: &[ProtocolCommunicationSpecifier],
) -> Result<Hardware, ButtplugDeviceError> {
let (sender, mut receiver) = mpsc::channel(256);
let (command_sender, command_receiver) = mpsc::channel(256);
let name;
let address;
let event_sender;
// This block limits the lifetime of device. Since the compiler doesn't
// realize we move device in the spawn_local block, it'll complain that
// device's lifetime lives across the channel await, which gets all
// angry because it's a *mut u8. So this limits the visible lifetime to
// before we start waiting for the reply from the event loop.
let protocol = if let ProtocolCommunicationSpecifier::BluetoothLE(btle) = &specifiers[0] {
btle
} else {
panic!("No bluetooth, we quit");
};
{
let device = self.device.take().unwrap().device;
name = device.name().unwrap();
address = device.id();
let (es, _) = broadcast::channel(256);
event_sender = es;
let event_loop_fut = run_webbluetooth_loop(
device,
protocol.clone(),
sender,
event_sender.clone(),
command_receiver,
);
spawn_local(async move {
event_loop_fut.await;
});
}
match receiver.recv().await.unwrap() {
WebBluetoothEvent::Connected(_) => {
info!("Web Bluetooth device connected, returning device");
let device_impl: Box<dyn HardwareInternal> = Box::new(WebBluetoothHardware::new(
event_sender,
receiver,
command_sender,
));
Ok(Hardware::new(&name, &address, &[], device_impl))
}
WebBluetoothEvent::Disconnected => Err(
ButtplugDeviceError::DeviceCommunicationError(
"Could not connect to WebBluetooth device".to_string(),
)
.into(),
),
}
}
}
#[derive(Debug, Clone)]
pub enum WebBluetoothEvent {
// This is the only way we have to get our endpoints back to device creation
// right now. My god this is a mess.
Connected(Vec<Endpoint>),
Disconnected,
}
pub enum WebBluetoothDeviceCommand {
Write(
HardwareWriteCmd,
ButtplugFutureStateShared<Result<(), ButtplugDeviceError>>,
),
Read(
HardwareReadCmd,
ButtplugFutureStateShared<Result<HardwareReading, ButtplugDeviceError>>,
),
Subscribe(
HardwareSubscribeCmd,
ButtplugFutureStateShared<Result<(), ButtplugDeviceError>>,
),
Unsubscribe(
HardwareUnsubscribeCmd,
ButtplugFutureStateShared<Result<(), ButtplugDeviceError>>,
),
}
async fn run_webbluetooth_loop(
device: BluetoothDevice,
btle_protocol: BluetoothLESpecifier,
device_local_event_sender: mpsc::Sender<WebBluetoothEvent>,
device_external_event_sender: broadcast::Sender<HardwareEvent>,
mut device_command_receiver: mpsc::Receiver<WebBluetoothDeviceCommand>,
) {
//let device = self.device.take().unwrap();
let mut char_map = HashMap::new();
let connect_future = device.gatt().unwrap().connect();
let server: BluetoothRemoteGattServer = match JsFuture::from(connect_future).await {
Ok(val) => val.into(),
Err(_) => {
device_local_event_sender
.send(WebBluetoothEvent::Disconnected)
.await
.unwrap();
return;
}
};
for (service_uuid, service_endpoints) in btle_protocol.services() {
let service = if let Ok(serv) =
JsFuture::from(server.get_primary_service_with_str(&service_uuid.to_string())).await
{
info!(
"Service {} found on device {}",
service_uuid,
device.name().unwrap()
);
BluetoothRemoteGattService::from(serv)
} else {
info!(
"Service {} not found on device {}",
service_uuid,
device.name().unwrap()
);
continue;
};
for (chr_name, chr_uuid) in service_endpoints.iter() {
info!("Connecting chr {} {}", chr_name, chr_uuid.to_string());
let char: BluetoothRemoteGattCharacteristic =
JsFuture::from(service.get_characteristic_with_str(&chr_uuid.to_string()))
.await
.unwrap()
.into();
char_map.insert(chr_name.clone(), char);
}
}
{
let event_sender = device_external_event_sender.clone();
let id = device.id().clone();
let ondisconnected_callback = Closure::wrap(Box::new(move |_: Event| {
info!("device disconnected!");
event_sender
.send(HardwareEvent::Disconnected(id.clone()))
.unwrap();
}) as Box<dyn FnMut(Event)>);
// set disconnection event handler on BluetoothDevice
device.set_ongattserverdisconnected(Some(ondisconnected_callback.as_ref().unchecked_ref()));
ondisconnected_callback.forget();
}
//let web_btle_device = WebBluetoothDeviceImpl::new(device, char_map);
info!("device created!");
let endpoints = char_map.keys().into_iter().cloned().collect();
device_local_event_sender
.send(WebBluetoothEvent::Connected(endpoints))
.await;
while let Some(msg) = device_command_receiver.recv().await {
match msg {
WebBluetoothDeviceCommand::Write(write_cmd, waker) => {
debug!("Writing to endpoint {:?}", write_cmd.endpoint());
let chr = char_map.get(&write_cmd.endpoint()).unwrap().clone();
spawn_local(async move {
let uint8buf = unsafe { Uint8Array::new(&Uint8Array::view(&write_cmd.data().clone())) };
JsFuture::from(chr.write_value_with_u8_array(&uint8buf).unwrap())
.await
.unwrap();
waker.set_reply(Ok(()));
});
}
WebBluetoothDeviceCommand::Read(read_cmd, waker) => {
debug!("Writing to endpoint {:?}", read_cmd.endpoint());
let chr = char_map.get(&read_cmd.endpoint()).unwrap().clone();
spawn_local(async move {
let read_value = JsFuture::from(chr.read_value()).await.unwrap();
let data_view = DataView::try_from(read_value).unwrap();
let mut body = vec![0; data_view.byte_length() as usize];
Uint8Array::new(&data_view).copy_to(&mut body[..]);
let reading = HardwareReading::new(read_cmd.endpoint(), &body);
waker.set_reply(Ok(reading));
});
}
WebBluetoothDeviceCommand::Subscribe(subscribe_cmd, waker) => {
debug!("Subscribing to endpoint {:?}", subscribe_cmd.endpoint());
let chr = char_map.get(&subscribe_cmd.endpoint()).unwrap().clone();
let ep = subscribe_cmd.endpoint();
let event_sender = device_external_event_sender.clone();
let id = device.id().clone();
let onchange_callback = Closure::wrap(Box::new(move |e: MessageEvent| {
let event_chr: BluetoothRemoteGattCharacteristic =
BluetoothRemoteGattCharacteristic::from(JsValue::from(e.target().unwrap()));
let value = Uint8Array::new_with_byte_offset(
&JsValue::from(event_chr.value().unwrap().buffer()),
0,
);
let value_vec = value.to_vec();
debug!("Subscription notification from {}: {:?}", ep, value_vec);
event_sender
.send(HardwareEvent::Notification(id.clone(), ep, value_vec))
.unwrap();
}) as Box<dyn FnMut(MessageEvent)>);
// set message event handler on WebSocket
chr.set_oncharacteristicvaluechanged(Some(onchange_callback.as_ref().unchecked_ref()));
onchange_callback.forget();
spawn_local(async move {
JsFuture::from(chr.start_notifications()).await.unwrap();
debug!("Endpoint subscribed");
waker.set_reply(Ok(()));
});
}
WebBluetoothDeviceCommand::Unsubscribe(_unsubscribe_cmd, _waker) => {}
}
}
debug!("run_webbluetooth_loop exited!");
}
#[derive(Debug)]
pub struct WebBluetoothHardware {
device_command_sender: mpsc::Sender<WebBluetoothDeviceCommand>,
device_event_receiver: mpsc::Receiver<WebBluetoothEvent>,
event_sender: broadcast::Sender<HardwareEvent>,
}
/*
unsafe impl Send for WebBluetoothHardware {
}
unsafe impl Sync for WebBluetoothHardware {
}
*/
impl WebBluetoothHardware {
pub fn new(
event_sender: broadcast::Sender<HardwareEvent>,
device_event_receiver: mpsc::Receiver<WebBluetoothEvent>,
device_command_sender: mpsc::Sender<WebBluetoothDeviceCommand>,
) -> Self {
Self {
event_sender,
device_event_receiver,
device_command_sender,
}
}
}
impl HardwareInternal for WebBluetoothHardware {
fn event_stream(&self) -> broadcast::Receiver<HardwareEvent> {
self.event_sender.subscribe()
}
fn disconnect(&self) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> {
Box::pin(future::ready(Ok(())))
}
fn read_value(
&self,
msg: &HardwareReadCmd,
) -> BoxFuture<'static, Result<HardwareReading, ButtplugDeviceError>> {
let sender = self.device_command_sender.clone();
let msg = msg.clone();
Box::pin(async move {
let fut = WebBluetoothReadResultFuture::default();
let waker = fut.get_state_clone();
sender
.send(WebBluetoothDeviceCommand::Read(msg, waker))
.await;
fut.await
})
}
fn write_value(&self, msg: &HardwareWriteCmd) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> {
let sender = self.device_command_sender.clone();
let msg = msg.clone();
Box::pin(async move {
let fut = WebBluetoothResultFuture::default();
let waker = fut.get_state_clone();
sender
.send(WebBluetoothDeviceCommand::Write(msg.clone(), waker))
.await;
fut.await
})
}
fn subscribe(&self, msg: &HardwareSubscribeCmd) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> {
let sender = self.device_command_sender.clone();
let msg = msg.clone();
Box::pin(async move {
let fut = WebBluetoothResultFuture::default();
let waker = fut.get_state_clone();
sender
.send(WebBluetoothDeviceCommand::Subscribe(msg.clone(), waker))
.await;
fut.await
})
}
fn unsubscribe(&self, _msg: &HardwareUnsubscribeCmd) -> BoxFuture<'static, Result<(), ButtplugDeviceError>> {
Box::pin(async move {
error!("IMPLEMENT UNSUBSCRIBE FOR WEBBLUETOOTH WASM");
Ok(())
})
}
}