kenwood_thd75/transport/
bluetooth.rs1#[cfg(any(target_os = "macos", doc))]
10#[allow(unsafe_code)]
11mod inner {
12 use std::io;
13
14 use crate::error::TransportError;
15 use crate::transport::Transport;
16
17 unsafe extern "C" {
18 fn bt_rfcomm_open(device_name: *const i8, channel: u8) -> *mut std::ffi::c_void;
19 fn bt_rfcomm_write(handle: *mut std::ffi::c_void, data: *const u8, len: usize) -> i32;
20 fn bt_rfcomm_read_fd(handle: *mut std::ffi::c_void) -> i32;
21 fn bt_rfcomm_close(handle: *mut std::ffi::c_void);
22 fn bt_pump_runloop();
23 }
24
25 const SPP_CHANNEL: u8 = 2;
27
28 const DEFAULT_DEVICE_NAME: &str = "TH-D75";
30
31 pub struct BluetoothTransport {
33 handle: *mut std::ffi::c_void,
34 read_fd: i32,
35 }
36
37 unsafe impl Send for BluetoothTransport {}
38 unsafe impl Sync for BluetoothTransport {}
39
40 impl std::fmt::Debug for BluetoothTransport {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 f.debug_struct("BluetoothTransport")
43 .field("handle", &self.handle)
44 .field("read_fd", &self.read_fd)
45 .finish()
46 }
47 }
48
49 impl BluetoothTransport {
50 pub fn open(device_name: Option<&str>) -> Result<Self, TransportError> {
56 let name = device_name.unwrap_or(DEFAULT_DEVICE_NAME);
57 tracing::info!(device = %name, channel = SPP_CHANNEL, "opening Bluetooth RFCOMM");
58
59 let c_name = std::ffi::CString::new(name).map_err(|_| TransportError::NotFound)?;
60 let handle = unsafe { bt_rfcomm_open(c_name.as_ptr(), SPP_CHANNEL) };
61 if handle.is_null() {
62 return Err(TransportError::NotFound);
63 }
64
65 let read_fd = unsafe { bt_rfcomm_read_fd(handle) };
66 if read_fd < 0 {
67 unsafe { bt_rfcomm_close(handle) };
68 return Err(TransportError::NotFound);
69 }
70
71 tracing::info!(device = %name, "Bluetooth RFCOMM connected");
72 Ok(Self { handle, read_fd })
73 }
74 }
75
76 impl Transport for BluetoothTransport {
77 async fn write(&mut self, data: &[u8]) -> Result<(), TransportError> {
78 tracing::debug!(bytes = data.len(), "BT write");
79 let ret = unsafe { bt_rfcomm_write(self.handle, data.as_ptr(), data.len()) };
80 if ret != 0 {
81 return Err(TransportError::Write(io::Error::other(
82 "RFCOMM write failed",
83 )));
84 }
85 unsafe { bt_pump_runloop() };
86 Ok(())
87 }
88
89 async fn read(&mut self, buf: &mut [u8]) -> Result<usize, TransportError> {
90 loop {
91 unsafe { bt_pump_runloop() };
92
93 let r = unsafe { libc_read(self.read_fd, buf.as_mut_ptr(), buf.len()) };
94 if r > 0 {
95 tracing::debug!(bytes = r, "BT read");
96 #[allow(clippy::cast_sign_loss)]
97 return Ok(r as usize);
98 }
99 if r == 0 {
100 return Err(TransportError::Read(io::Error::new(
101 io::ErrorKind::UnexpectedEof,
102 "BT pipe closed",
103 )));
104 }
105 let err = io::Error::last_os_error();
106 if err.kind() == io::ErrorKind::WouldBlock {
107 tokio::time::sleep(std::time::Duration::from_millis(5)).await;
108 continue;
109 }
110 return Err(TransportError::Read(err));
111 }
112 }
113
114 async fn close(&mut self) -> Result<(), TransportError> {
115 tracing::info!("closing Bluetooth RFCOMM");
116 if !self.handle.is_null() {
117 unsafe { bt_rfcomm_close(self.handle) };
118 self.handle = std::ptr::null_mut();
119 self.read_fd = -1;
120 }
121 Ok(())
122 }
123 }
124
125 impl Drop for BluetoothTransport {
126 fn drop(&mut self) {
127 if !self.handle.is_null() {
128 unsafe { bt_rfcomm_close(self.handle) };
129 }
130 }
131 }
132
133 unsafe fn libc_read(fd: i32, buf: *mut u8, len: usize) -> isize {
135 unsafe extern "C" {
136 fn read(fd: i32, buf: *mut u8, count: usize) -> isize;
137 }
138 unsafe { read(fd, buf, len) }
139 }
140}
141
142#[cfg(any(target_os = "macos", doc))]
143pub use inner::BluetoothTransport;