hive_btle/platform/
mod.rs

1// Copyright (c) 2025-2026 (r)evolve - Revolve Team LLC
2// SPDX-License-Identifier: Apache-2.0
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Platform abstraction layer for BLE
17//!
18//! This module defines the traits that platform-specific implementations
19//! must implement to provide BLE functionality.
20//!
21//! ## Supported Platforms
22//!
23//! - **Linux**: BlueZ via D-Bus (`bluer` crate)
24//! - **Android**: JNI to Android Bluetooth APIs
25//! - **macOS/iOS**: CoreBluetooth
26//! - **Windows**: WinRT Bluetooth APIs
27//! - **Embedded**: ESP-IDF NimBLE
28//!
29//! ## Architecture
30//!
31//! Each platform provides an implementation of `BleAdapter` that handles:
32//! - Adapter initialization and power management
33//! - Discovery (scanning and advertising)
34//! - GATT server and client operations
35//! - Connection management
36
37#[cfg(not(feature = "std"))]
38use alloc::{boxed::Box, format, string::String, string::ToString, vec::Vec};
39
40use async_trait::async_trait;
41
42use crate::config::{BleConfig, BlePhy, DiscoveryConfig};
43use crate::error::Result;
44use crate::transport::BleConnection;
45use crate::NodeId;
46
47// Platform-specific modules (conditionally compiled)
48#[cfg(all(feature = "linux", target_os = "linux"))]
49pub mod linux;
50
51#[cfg(feature = "android")]
52pub mod android;
53
54#[cfg(any(feature = "macos", feature = "ios"))]
55pub mod apple;
56
57#[cfg(feature = "windows")]
58pub mod windows;
59
60#[cfg(feature = "embedded")]
61pub mod embedded;
62
63#[cfg(feature = "esp32")]
64pub mod esp32;
65
66// Mock adapter for testing (always available in std builds)
67#[cfg(feature = "std")]
68pub mod mock;
69
70/// Discovered BLE device
71#[derive(Debug, Clone)]
72pub struct DiscoveredDevice {
73    /// Device address (MAC or platform-specific)
74    pub address: String,
75    /// Device name (if available)
76    pub name: Option<String>,
77    /// RSSI in dBm
78    pub rssi: i8,
79    /// Is this a HIVE node?
80    pub is_hive_node: bool,
81    /// Parsed HIVE node ID (if HIVE node)
82    pub node_id: Option<NodeId>,
83    /// Raw advertising data
84    pub adv_data: Vec<u8>,
85}
86
87/// Callback for discovered devices
88pub type DiscoveryCallback = std::sync::Arc<dyn Fn(DiscoveredDevice) + Send + Sync>;
89
90/// Callback for connection events
91pub type ConnectionCallback = std::sync::Arc<dyn Fn(NodeId, ConnectionEvent) + Send + Sync>;
92
93/// Connection event types
94#[derive(Debug, Clone)]
95pub enum ConnectionEvent {
96    /// Connection established
97    Connected {
98        /// Negotiated MTU
99        mtu: u16,
100        /// Connection PHY
101        phy: BlePhy,
102    },
103    /// Connection lost
104    Disconnected {
105        /// Reason for disconnection
106        reason: DisconnectReason,
107    },
108    /// GATT services discovered
109    ServicesDiscovered {
110        /// Whether the HIVE service was found
111        has_hive_service: bool,
112    },
113    /// Data received from peer (characteristic read or notification)
114    DataReceived {
115        /// The received data
116        data: Vec<u8>,
117    },
118    /// MTU changed
119    MtuChanged {
120        /// New MTU value
121        mtu: u16,
122    },
123    /// PHY changed
124    PhyChanged {
125        /// New PHY
126        phy: BlePhy,
127    },
128    /// RSSI updated
129    RssiUpdated {
130        /// New RSSI value in dBm
131        rssi: i8,
132    },
133}
134
135/// Reason for disconnection
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub enum DisconnectReason {
138    /// Disconnected by local request
139    LocalRequest,
140    /// Disconnected by remote device
141    RemoteRequest,
142    /// Connection timeout
143    Timeout,
144    /// Link loss (device out of range)
145    LinkLoss,
146    /// Connection failed
147    ConnectionFailed,
148    /// Unknown reason
149    Unknown,
150}
151
152/// Platform-specific BLE adapter
153///
154/// This is the main abstraction trait that each platform must implement.
155/// It provides all BLE functionality needed by the transport layer.
156#[async_trait]
157pub trait BleAdapter: Send + Sync {
158    /// Initialize the adapter with the given configuration
159    async fn init(&mut self, config: &BleConfig) -> Result<()>;
160
161    /// Start the adapter (begin advertising and/or scanning)
162    async fn start(&self) -> Result<()>;
163
164    /// Stop the adapter
165    async fn stop(&self) -> Result<()>;
166
167    /// Check if the adapter is powered on
168    fn is_powered(&self) -> bool;
169
170    /// Get the adapter's Bluetooth address
171    fn address(&self) -> Option<String>;
172
173    // === Discovery ===
174
175    /// Start scanning for devices
176    async fn start_scan(&self, config: &DiscoveryConfig) -> Result<()>;
177
178    /// Stop scanning
179    async fn stop_scan(&self) -> Result<()>;
180
181    /// Start advertising
182    async fn start_advertising(&self, config: &DiscoveryConfig) -> Result<()>;
183
184    /// Stop advertising
185    async fn stop_advertising(&self) -> Result<()>;
186
187    /// Set callback for discovered devices
188    fn set_discovery_callback(&mut self, callback: Option<DiscoveryCallback>);
189
190    // === Connections ===
191
192    /// Connect to a peer by node ID
193    async fn connect(&self, peer_id: &NodeId) -> Result<Box<dyn BleConnection>>;
194
195    /// Disconnect from a peer
196    async fn disconnect(&self, peer_id: &NodeId) -> Result<()>;
197
198    /// Get an existing connection
199    fn get_connection(&self, peer_id: &NodeId) -> Option<Box<dyn BleConnection>>;
200
201    /// Get the number of connected peers
202    fn peer_count(&self) -> usize;
203
204    /// Get list of connected peer IDs
205    fn connected_peers(&self) -> Vec<NodeId>;
206
207    /// Set callback for connection events
208    fn set_connection_callback(&mut self, callback: Option<ConnectionCallback>);
209
210    // === GATT ===
211
212    /// Register the HIVE GATT service
213    async fn register_gatt_service(&self) -> Result<()>;
214
215    /// Unregister the HIVE GATT service
216    async fn unregister_gatt_service(&self) -> Result<()>;
217
218    // === Capabilities ===
219
220    /// Check if Coded PHY is supported
221    fn supports_coded_phy(&self) -> bool;
222
223    /// Check if extended advertising is supported
224    fn supports_extended_advertising(&self) -> bool;
225
226    /// Get maximum supported MTU
227    fn max_mtu(&self) -> u16;
228
229    /// Get maximum number of connections
230    fn max_connections(&self) -> u8;
231}
232
233/// Stub adapter for testing and platforms without BLE
234#[derive(Debug, Default)]
235pub struct StubAdapter {
236    powered: bool,
237}
238
239#[async_trait]
240impl BleAdapter for StubAdapter {
241    async fn init(&mut self, _config: &BleConfig) -> Result<()> {
242        self.powered = true;
243        Ok(())
244    }
245
246    async fn start(&self) -> Result<()> {
247        Ok(())
248    }
249
250    async fn stop(&self) -> Result<()> {
251        Ok(())
252    }
253
254    fn is_powered(&self) -> bool {
255        self.powered
256    }
257
258    fn address(&self) -> Option<String> {
259        Some("00:00:00:00:00:00".to_string())
260    }
261
262    async fn start_scan(&self, _config: &DiscoveryConfig) -> Result<()> {
263        Ok(())
264    }
265
266    async fn stop_scan(&self) -> Result<()> {
267        Ok(())
268    }
269
270    async fn start_advertising(&self, _config: &DiscoveryConfig) -> Result<()> {
271        Ok(())
272    }
273
274    async fn stop_advertising(&self) -> Result<()> {
275        Ok(())
276    }
277
278    fn set_discovery_callback(&mut self, _callback: Option<DiscoveryCallback>) {}
279
280    async fn connect(&self, peer_id: &NodeId) -> Result<Box<dyn BleConnection>> {
281        Err(crate::error::BleError::NotSupported(format!(
282            "Stub adapter cannot connect to {}",
283            peer_id
284        )))
285    }
286
287    async fn disconnect(&self, _peer_id: &NodeId) -> Result<()> {
288        Ok(())
289    }
290
291    fn get_connection(&self, _peer_id: &NodeId) -> Option<Box<dyn BleConnection>> {
292        None
293    }
294
295    fn peer_count(&self) -> usize {
296        0
297    }
298
299    fn connected_peers(&self) -> Vec<NodeId> {
300        Vec::new()
301    }
302
303    fn set_connection_callback(&mut self, _callback: Option<ConnectionCallback>) {}
304
305    async fn register_gatt_service(&self) -> Result<()> {
306        Ok(())
307    }
308
309    async fn unregister_gatt_service(&self) -> Result<()> {
310        Ok(())
311    }
312
313    fn supports_coded_phy(&self) -> bool {
314        false
315    }
316
317    fn supports_extended_advertising(&self) -> bool {
318        false
319    }
320
321    fn max_mtu(&self) -> u16 {
322        23
323    }
324
325    fn max_connections(&self) -> u8 {
326        0
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use super::*;
333
334    #[tokio::test]
335    async fn test_stub_adapter() {
336        let mut adapter = StubAdapter::default();
337        assert!(!adapter.is_powered());
338
339        adapter.init(&BleConfig::default()).await.unwrap();
340        assert!(adapter.is_powered());
341        assert_eq!(adapter.peer_count(), 0);
342        assert!(!adapter.supports_coded_phy());
343    }
344}