hive_btle/platform/linux/
connection.rs

1//! BlueZ connection wrapper
2
3use bluer::Device;
4use std::sync::Arc;
5use std::time::{Duration, Instant};
6use tokio::sync::RwLock;
7
8use crate::config::BlePhy;
9use crate::error::{BleError, Result};
10use crate::transport::BleConnection;
11use crate::NodeId;
12
13/// Internal connection state
14struct ConnectionState {
15    /// Whether the connection is alive
16    alive: bool,
17    /// Negotiated MTU
18    mtu: u16,
19    /// Current PHY
20    phy: BlePhy,
21    /// Last RSSI reading
22    rssi: Option<i8>,
23}
24
25/// BlueZ connection wrapper
26///
27/// Wraps a `bluer::Device` with connection state tracking.
28#[derive(Clone)]
29pub struct BluerConnection {
30    /// Remote peer ID
31    peer_id: NodeId,
32    /// BlueZ device handle
33    device: Device,
34    /// Connection state
35    state: Arc<RwLock<ConnectionState>>,
36    /// When the connection was established
37    connected_at: Instant,
38}
39
40impl BluerConnection {
41    /// Create a new connection wrapper
42    pub(crate) async fn new(peer_id: NodeId, device: Device) -> Result<Self> {
43        // Get initial MTU
44        // BlueZ doesn't expose MTU directly, use default
45        let mtu = 23; // Will be updated after MTU exchange
46
47        let state = ConnectionState {
48            alive: true,
49            mtu,
50            phy: BlePhy::Le1M, // Default PHY
51            rssi: None,
52        };
53
54        let conn = Self {
55            peer_id,
56            device,
57            state: Arc::new(RwLock::new(state)),
58            connected_at: Instant::now(),
59        };
60
61        // Try to get initial RSSI
62        conn.update_rssi().await;
63
64        Ok(conn)
65    }
66
67    /// Get the underlying BlueZ device
68    pub fn device(&self) -> &Device {
69        &self.device
70    }
71
72    /// Update RSSI from device
73    pub async fn update_rssi(&self) {
74        if let Ok(Some(rssi)) = self.device.rssi().await {
75            let mut state = self.state.write().await;
76            state.rssi = Some(rssi as i8);
77        }
78    }
79
80    /// Update MTU
81    pub async fn set_mtu(&self, mtu: u16) {
82        let mut state = self.state.write().await;
83        state.mtu = mtu;
84    }
85
86    /// Update PHY
87    pub async fn set_phy(&self, phy: BlePhy) {
88        let mut state = self.state.write().await;
89        state.phy = phy;
90    }
91
92    /// Mark connection as dead
93    pub async fn mark_dead(&self) {
94        let mut state = self.state.write().await;
95        state.alive = false;
96    }
97
98    /// Disconnect from the device
99    pub async fn disconnect(&self) -> Result<()> {
100        self.device
101            .disconnect()
102            .await
103            .map_err(|e| BleError::ConnectionFailed(format!("Failed to disconnect: {}", e)))?;
104        self.mark_dead().await;
105        Ok(())
106    }
107
108    /// Discover GATT services
109    pub async fn discover_services(&self) -> Result<()> {
110        // Trigger service discovery
111        // In bluer, services are discovered automatically on connect
112        // but we can force a refresh
113        let _ = self.device.services().await;
114        Ok(())
115    }
116
117    /// Get GATT services
118    pub async fn services(&self) -> Result<Vec<bluer::gatt::remote::Service>> {
119        self.device
120            .services()
121            .await
122            .map_err(|e| BleError::GattError(format!("Failed to get services: {}", e)))
123    }
124
125    /// Find a service by UUID
126    pub async fn find_service(
127        &self,
128        uuid: uuid::Uuid,
129    ) -> Result<Option<bluer::gatt::remote::Service>> {
130        let services = self.services().await?;
131        for service in services {
132            if service.uuid().await.ok() == Some(uuid) {
133                return Ok(Some(service));
134            }
135        }
136        Ok(None)
137    }
138
139    /// Read a characteristic value
140    pub async fn read_characteristic(
141        &self,
142        service_uuid: uuid::Uuid,
143        char_uuid: uuid::Uuid,
144    ) -> Result<Vec<u8>> {
145        let service = self
146            .find_service(service_uuid)
147            .await?
148            .ok_or_else(|| BleError::ServiceNotFound(service_uuid.to_string()))?;
149
150        let characteristics = service
151            .characteristics()
152            .await
153            .map_err(|e| BleError::GattError(format!("Failed to get characteristics: {}", e)))?;
154
155        for char in characteristics {
156            if char.uuid().await.ok() == Some(char_uuid) {
157                return char.read().await.map_err(|e| {
158                    BleError::GattError(format!("Failed to read characteristic: {}", e))
159                });
160            }
161        }
162
163        Err(BleError::CharacteristicNotFound(char_uuid.to_string()))
164    }
165
166    /// Write a characteristic value
167    pub async fn write_characteristic(
168        &self,
169        service_uuid: uuid::Uuid,
170        char_uuid: uuid::Uuid,
171        value: &[u8],
172    ) -> Result<()> {
173        let service = self
174            .find_service(service_uuid)
175            .await?
176            .ok_or_else(|| BleError::ServiceNotFound(service_uuid.to_string()))?;
177
178        let characteristics = service
179            .characteristics()
180            .await
181            .map_err(|e| BleError::GattError(format!("Failed to get characteristics: {}", e)))?;
182
183        for char in characteristics {
184            if char.uuid().await.ok() == Some(char_uuid) {
185                return char.write(value).await.map_err(|e| {
186                    BleError::GattError(format!("Failed to write characteristic: {}", e))
187                });
188            }
189        }
190
191        Err(BleError::CharacteristicNotFound(char_uuid.to_string()))
192    }
193
194    /// Subscribe to characteristic notifications
195    pub async fn subscribe_characteristic(
196        &self,
197        service_uuid: uuid::Uuid,
198        char_uuid: uuid::Uuid,
199    ) -> Result<impl tokio_stream::Stream<Item = Vec<u8>>> {
200        let service = self
201            .find_service(service_uuid)
202            .await?
203            .ok_or_else(|| BleError::ServiceNotFound(service_uuid.to_string()))?;
204
205        let characteristics = service
206            .characteristics()
207            .await
208            .map_err(|e| BleError::GattError(format!("Failed to get characteristics: {}", e)))?;
209
210        for char in characteristics {
211            if char.uuid().await.ok() == Some(char_uuid) {
212                return char.notify().await.map_err(|e| {
213                    BleError::GattError(format!("Failed to subscribe to notifications: {}", e))
214                });
215            }
216        }
217
218        Err(BleError::CharacteristicNotFound(char_uuid.to_string()))
219    }
220}
221
222impl BleConnection for BluerConnection {
223    fn peer_id(&self) -> &NodeId {
224        &self.peer_id
225    }
226
227    fn is_alive(&self) -> bool {
228        // Try to read state without blocking
229        if let Ok(state) = self.state.try_read() {
230            state.alive
231        } else {
232            // If we can't get the lock, assume alive
233            true
234        }
235    }
236
237    fn mtu(&self) -> u16 {
238        if let Ok(state) = self.state.try_read() {
239            state.mtu
240        } else {
241            23 // Default BLE MTU
242        }
243    }
244
245    fn phy(&self) -> BlePhy {
246        if let Ok(state) = self.state.try_read() {
247            state.phy
248        } else {
249            BlePhy::Le1M
250        }
251    }
252
253    fn rssi(&self) -> Option<i8> {
254        if let Ok(state) = self.state.try_read() {
255            state.rssi
256        } else {
257            None
258        }
259    }
260
261    fn connected_duration(&self) -> Duration {
262        self.connected_at.elapsed()
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    // Integration tests require actual Bluetooth hardware
269    // and a connected device
270}