Skip to main content

ble_windows_server/
lib.rs

1//! # Windows BLE GATT Server
2//!
3//! A simple, ergonomic BLE GATT server library for Windows using WinRT APIs.
4//!
5//! ## Features
6//! - Create BLE GATT services with multiple read/notify characteristics
7//! - Automatic adapter discovery and selection
8//! - Async/await support with Tokio
9//! - Simple data broadcast via notifications
10//!
11//! ## Example
12//! ```no_run
13//! use ble_windows_server::WindowsBLEGattServer;
14//! use uuid::Uuid;
15//!
16//! #[tokio::main]
17//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
18//!     let service_uuid = Uuid::parse_str("12345678-1234-5678-1234-567812345678")?;
19//!     let char_uuid = Uuid::from_u128(service_uuid.as_u128() + 1);
20//!     let mut server = WindowsBLEGattServer::new("MyDevice".into(), service_uuid);
21//!     server.add_characteristic("message", char_uuid, "A message");
22//!
23//!     server.start().await?;
24//!     server.notify("message", b"Hello BLE!").await?;
25//!
26//!     Ok(())
27//! }
28//! ```
29
30use std::collections::HashMap;
31use std::sync::Arc;
32use thiserror::Error;
33use tokio::sync::RwLock;
34use windows::core::GUID;
35use windows::{
36    Devices::Bluetooth::GenericAttributeProfile::*,
37    Devices::Bluetooth::*,
38    Devices::Enumeration::*,
39    Devices::Radios::*,
40    Foundation::TypedEventHandler,
41    Storage::Streams::DataWriter,
42};
43
44// ============================================================================
45// SECTION: Error Types
46// ============================================================================
47
48/// SRS-001: Unified error type for all BLE operations.
49///
50/// Covers Windows API errors, initialization failures, and runtime operations.
51#[derive(Error, Debug)]
52pub enum BleError {
53    #[error("Windows API error: {0}")]
54    Windows(#[from] windows::core::Error),
55
56    #[error("Initialization failed: {0}")]
57    Init(String),
58
59    #[error("Operation failed: {0}")]
60    Operation(String),
61
62    #[error("No Bluetooth adapters found")]
63    NoAdapters,
64
65    #[error("Server not started")]
66    NotStarted,
67
68    #[error("Unknown characteristic: {0}")]
69    UnknownCharacteristic(String),
70}
71
72/// SRS-002: Standard Result type alias for the library.
73pub type Result<T> = std::result::Result<T, BleError>;
74
75// ============================================================================
76// SECTION: Adapter Discovery
77// ============================================================================
78
79/// SRS-003: Bluetooth adapter information.
80///
81/// Contains metadata about a discovered Bluetooth adapter.
82#[derive(Debug, Clone)]
83pub struct AdapterInfo {
84    /// Index in the adapter list
85    pub index: usize,
86    /// Human-readable adapter name
87    pub name: String,
88    /// Windows device ID
89    pub id: String,
90    /// Whether this is the system default adapter
91    pub is_default: bool,
92    /// MAC address (if available)
93    pub mac_address: Option<String>,
94    /// Whether BLE is supported
95    pub ble_supported: bool,
96    /// Whether peripheral role is supported
97    pub peripheral_supported: bool,
98}
99
100impl std::fmt::Display for AdapterInfo {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        let default_marker = if self.is_default { " (Default)" } else { "" };
103        let mac = self.mac_address.as_deref().unwrap_or("Unknown");
104        write!(f, "[{}] {} - MAC: {}{}", self.index, self.name, mac, default_marker)
105    }
106}
107
108/// SRS-004: Enumerate all Bluetooth adapters on the system.
109///
110/// Returns a list of available adapters with their capabilities.
111pub async fn list_adapters() -> Result<Vec<AdapterInfo>> {
112    let selector = BluetoothAdapter::GetDeviceSelector()?;
113    let devices = DeviceInformation::FindAllAsyncAqsFilter(&selector)?
114        .get()
115        .map_err(BleError::Windows)?;
116
117    let count = devices.Size()?;
118    if count == 0 {
119        return Err(BleError::NoAdapters);
120    }
121
122    // Get default adapter for comparison
123    let default_id = BluetoothAdapter::GetDefaultAsync()?
124        .get()
125        .ok()
126        .and_then(|a| a.DeviceId().ok())
127        .map(|id| id.to_string());
128
129    let mut adapters = Vec::with_capacity(count as usize);
130
131    for i in 0..count {
132        let device = devices.GetAt(i)?;
133        let name = device.Name()?.to_string();
134        let id = device.Id()?.to_string();
135        let is_default = default_id.as_ref().map(|d| d == &id).unwrap_or(false);
136
137        // Get detailed adapter info
138        let (mac_address, ble_supported, peripheral_supported) =
139            if let Ok(adapter) = BluetoothAdapter::FromIdAsync(&device.Id()?)?.get() {
140                let mac = adapter.BluetoothAddress().ok().map(format_mac_address);
141                let ble = adapter.IsLowEnergySupported().unwrap_or(false);
142                let peripheral = adapter.IsPeripheralRoleSupported().unwrap_or(false);
143                (mac, ble, peripheral)
144            } else {
145                (None, false, false)
146            };
147
148        adapters.push(AdapterInfo {
149            index: i as usize,
150            name,
151            id,
152            is_default,
153            mac_address,
154            ble_supported,
155            peripheral_supported,
156        });
157    }
158
159    Ok(adapters)
160}
161
162/// SRS-005: List all Bluetooth radios (debug utility).
163///
164/// Useful for diagnosing adapter issues.
165pub async fn list_radios() -> Result<Vec<(String, bool)>> {
166    let radios = Radio::GetRadiosAsync()?.get().map_err(BleError::Windows)?;
167    let count = radios.Size()?;
168
169    let mut result = Vec::new();
170    for i in 0..count {
171        let radio = radios.GetAt(i)?;
172        if radio.Kind()? == RadioKind::Bluetooth {
173            let name = radio.Name()?.to_string();
174            let is_on = radio.State()? == RadioState::On;
175            result.push((name, is_on));
176        }
177    }
178
179    Ok(result)
180}
181
182// ============================================================================
183// SECTION: Internal Structs
184// ============================================================================
185
186/// Configuration for a characteristic, collected before start().
187struct CharacteristicConfig {
188    uuid: Uuid,
189    description: Option<String>,
190}
191
192/// Runtime state for a characteristic, populated by start().
193struct RuntimeCharacteristic {
194    data_buffer: Arc<RwLock<Vec<u8>>>,
195    handle: GattLocalCharacteristic,
196}
197
198// ============================================================================
199// SECTION: GATT Server
200// ============================================================================
201
202/// SRS-006: Windows BLE GATT Server.
203///
204/// A BLE peripheral that exposes a GATT service with multiple characteristics
205/// supporting Read and Notify operations.
206///
207/// # Lifecycle
208/// 1. Create with `new()`
209/// 2. Add characteristics with `add_characteristic()`
210/// 3. Optionally select adapter with `use_adapter()`
211/// 4. Start with `start()`
212/// 5. Send data with `notify(name, data)`
213/// 6. Stop with `stop()` (or drop)
214pub struct WindowsBLEGattServer {
215    // Configuration
216    device_name: String,
217    service_uuid: Uuid,
218    selected_adapter_id: Option<String>,
219
220    // Characteristic configuration (ordered, populated before start())
221    char_configs: Vec<(String, CharacteristicConfig)>,
222
223    // Runtime state (populated by start())
224    char_runtime: HashMap<String, RuntimeCharacteristic>,
225    service_provider: Option<GattServiceProvider>,
226}
227
228impl WindowsBLEGattServer {
229    /// SRS-007: Create a new BLE GATT server instance.
230    ///
231    /// After creation, add characteristics with `add_characteristic()` before calling `start()`.
232    ///
233    /// # Arguments
234    /// * `device_name` - Name for the BLE device (may not appear in scans on Windows)
235    /// * `service_uuid` - UUID for the GATT service
236    pub fn new(device_name: String, service_uuid: Uuid) -> Self {
237        Self {
238            device_name,
239            service_uuid,
240            selected_adapter_id: None,
241            char_configs: Vec::new(),
242            char_runtime: HashMap::new(),
243            service_provider: None,
244        }
245    }
246
247    /// Register a characteristic on this service.
248    ///
249    /// Must be called before `start()`. Each characteristic is identified by a unique name
250    /// that is used later to send notifications.
251    ///
252    /// # Arguments
253    /// * `name` - Unique name used to reference this characteristic (e.g. "heart_rate")
254    /// * `uuid` - UUID for the GATT characteristic
255    /// * `description` - Human-readable description (creates a 0x2901 descriptor)
256    pub fn add_characteristic(
257        &mut self,
258        name: impl Into<String>,
259        uuid: Uuid,
260        description: impl Into<String>,
261    ) -> &mut Self {
262        let desc_string = description.into();
263        self.char_configs.push((
264            name.into(),
265            CharacteristicConfig {
266                uuid,
267                description: if desc_string.is_empty() { None } else { Some(desc_string) },
268            },
269        ));
270        self
271    }
272
273    /// SRS-009: Select a specific Bluetooth adapter.
274    ///
275    /// Call before `start()` to use a non-default adapter.
276    pub fn use_adapter(&mut self, adapter: &AdapterInfo) -> &mut Self {
277        self.selected_adapter_id = Some(adapter.id.clone());
278        self
279    }
280
281    /// SRS-010: Use the system default adapter.
282    pub fn use_default_adapter(&mut self) -> &mut Self {
283        self.selected_adapter_id = None;
284        self
285    }
286
287    /// Get the device name.
288    pub fn device_name(&self) -> &str {
289        &self.device_name
290    }
291
292    /// Get the service UUID.
293    pub fn service_uuid(&self) -> Uuid {
294        self.service_uuid
295    }
296
297    /// Get the UUID of a characteristic by name.
298    pub fn characteristic_uuid(&self, name: &str) -> Option<Uuid> {
299        self.char_configs
300            .iter()
301            .find(|(n, _)| n == name)
302            .map(|(_, cfg)| cfg.uuid)
303    }
304
305    /// List all configured characteristic names in registration order.
306    pub fn characteristic_names(&self) -> Vec<&str> {
307        self.char_configs.iter().map(|(n, _)| n.as_str()).collect()
308    }
309
310    /// Check if the server is currently running.
311    pub fn is_running(&self) -> bool {
312        self.service_provider.is_some()
313    }
314
315    /// SRS-011: Start the BLE GATT server.
316    ///
317    /// Creates the GATT service, registers all characteristics, and begins advertising.
318    pub async fn start(&mut self) -> Result<()> {
319        if self.is_running() {
320            return Ok(());
321        }
322
323        if self.char_configs.is_empty() {
324            return Err(BleError::Init(
325                "No characteristics configured. Call add_characteristic() before start().".into(),
326            ));
327        }
328
329        let service_guid = GUID::from_u128(self.service_uuid.as_u128());
330
331        // Create service provider
332        let provider_result = GattServiceProvider::CreateAsync(service_guid)?
333            .get()
334            .map_err(BleError::Windows)?;
335
336        let service_provider = provider_result.ServiceProvider()?;
337        let service = service_provider.Service()?;
338
339        // Create each characteristic
340        for (name, config) in &self.char_configs {
341            let char_guid = GUID::from_u128(config.uuid.as_u128());
342
343            let char_params = GattLocalCharacteristicParameters::new()?;
344            char_params.SetCharacteristicProperties(
345                GattCharacteristicProperties::Read | GattCharacteristicProperties::Notify,
346            )?;
347            char_params.SetReadProtectionLevel(GattProtectionLevel::Plain)?;
348
349            // Set user description descriptor (0x2901) if configured
350            if let Some(ref description) = config.description {
351                char_params
352                    .SetUserDescription(&windows::core::HSTRING::from(description.as_str()))?;
353            }
354
355            // Set initial value
356            let initial_buffer = create_buffer(b"Ready")?;
357            char_params.SetStaticValue(&initial_buffer)?;
358
359            // Create characteristic on the service
360            let char_result = service
361                .CreateCharacteristicAsync(char_guid, &char_params)?
362                .get()
363                .map_err(BleError::Windows)?;
364
365            if char_result.Error()? != BluetoothError::Success {
366                return Err(BleError::Init(format!(
367                    "Failed to create characteristic '{}': {:?}",
368                    name,
369                    char_result.Error()?
370                )));
371            }
372
373            let characteristic = char_result.Characteristic()?;
374
375            // Set up read handler with its own buffer
376            let data_buffer = Arc::new(RwLock::new(Vec::<u8>::new()));
377            let buffer_clone = data_buffer.clone();
378            characteristic.ReadRequested(&TypedEventHandler::new(
379                move |_: &Option<GattLocalCharacteristic>,
380                      args: &Option<GattReadRequestedEventArgs>| {
381                    if let Some(args) = args {
382                        let request = args.GetRequestAsync()?.get()?;
383
384                        let response = if let Ok(guard) = buffer_clone.try_read() {
385                            if guard.is_empty() {
386                                create_buffer(b"No data")?
387                            } else {
388                                create_buffer(&guard)?
389                            }
390                        } else {
391                            create_buffer(b"Busy")?
392                        };
393
394                        request.RespondWithValue(&response)?;
395                    }
396                    Ok(())
397                },
398            ))?;
399
400            self.char_runtime.insert(
401                name.clone(),
402                RuntimeCharacteristic {
403                    data_buffer,
404                    handle: characteristic,
405                },
406            );
407        }
408
409        // Store provider and start advertising
410        self.service_provider = Some(service_provider.clone());
411
412        let adv_params = GattServiceProviderAdvertisingParameters::new()?;
413        adv_params.SetIsConnectable(true)?;
414        adv_params.SetIsDiscoverable(true)?;
415        service_provider.StartAdvertisingWithParameters(&adv_params)?;
416
417        Ok(())
418    }
419
420    // ========================================================================
421    // Notify Methods - SRS-013: Send notifications to subscribed clients
422    // ========================================================================
423
424    /// Send raw bytes as a notification on a named characteristic.
425    ///
426    /// This is the base method - all other notify methods use this internally.
427    pub async fn notify(&self, name: &str, data: &[u8]) -> Result<()> {
428        if self.service_provider.is_none() {
429            return Err(BleError::NotStarted);
430        }
431
432        let rt = self
433            .char_runtime
434            .get(name)
435            .ok_or_else(|| BleError::UnknownCharacteristic(name.to_string()))?;
436
437        // Update buffer
438        *rt.data_buffer.write().await = data.to_vec();
439
440        // Send notification
441        let buffer = create_buffer(data)?;
442        rt.handle.NotifyValueAsync(&buffer)?.get().map_err(BleError::Windows)?;
443
444        Ok(())
445    }
446
447    /// Send a string as a notification.
448    pub async fn notify_str(&self, name: &str, s: &str) -> Result<()> {
449        self.notify(name, s.as_bytes()).await
450    }
451
452    /// Send a formatted string as a notification.
453    pub async fn notify_fmt(&self, name: &str, args: std::fmt::Arguments<'_>) -> Result<()> {
454        self.notify_str(name, &args.to_string()).await
455    }
456
457    /// Send a u8 as a notification (1 byte).
458    pub async fn notify_u8(&self, name: &str, value: u8) -> Result<()> {
459        self.notify(name, &[value]).await
460    }
461
462    /// Send an i8 as a notification (1 byte).
463    pub async fn notify_i8(&self, name: &str, value: i8) -> Result<()> {
464        self.notify(name, &value.to_le_bytes()).await
465    }
466
467    /// Send a u16 as a notification (2 bytes, little-endian).
468    pub async fn notify_u16(&self, name: &str, value: u16) -> Result<()> {
469        self.notify(name, &value.to_le_bytes()).await
470    }
471
472    /// Send an i16 as a notification (2 bytes, little-endian).
473    pub async fn notify_i16(&self, name: &str, value: i16) -> Result<()> {
474        self.notify(name, &value.to_le_bytes()).await
475    }
476
477    /// Send a u32 as a notification (4 bytes, little-endian).
478    pub async fn notify_u32(&self, name: &str, value: u32) -> Result<()> {
479        self.notify(name, &value.to_le_bytes()).await
480    }
481
482    /// Send an i32 as a notification (4 bytes, little-endian).
483    pub async fn notify_i32(&self, name: &str, value: i32) -> Result<()> {
484        self.notify(name, &value.to_le_bytes()).await
485    }
486
487    /// Send a u64 as a notification (8 bytes, little-endian).
488    pub async fn notify_u64(&self, name: &str, value: u64) -> Result<()> {
489        self.notify(name, &value.to_le_bytes()).await
490    }
491
492    /// Send an i64 as a notification (8 bytes, little-endian).
493    pub async fn notify_i64(&self, name: &str, value: i64) -> Result<()> {
494        self.notify(name, &value.to_le_bytes()).await
495    }
496
497    /// Send an f32 as a notification (4 bytes, IEEE 754).
498    pub async fn notify_f32(&self, name: &str, value: f32) -> Result<()> {
499        self.notify(name, &value.to_le_bytes()).await
500    }
501
502    /// Send an f64 as a notification (8 bytes, IEEE 754).
503    pub async fn notify_f64(&self, name: &str, value: f64) -> Result<()> {
504        self.notify(name, &value.to_le_bytes()).await
505    }
506
507    /// Send a bool as a notification (1 byte: 0x00 or 0x01).
508    pub async fn notify_bool(&self, name: &str, value: bool) -> Result<()> {
509        self.notify(name, &[value as u8]).await
510    }
511
512    /// Send a JSON-serialized value as a notification.
513    ///
514    /// Requires the `json` feature to be enabled.
515    #[cfg(feature = "json")]
516    pub async fn notify_json<T: serde::Serialize>(&self, name: &str, value: &T) -> Result<()> {
517        let json = serde_json::to_vec(value)
518            .map_err(|e| BleError::Operation(format!("JSON serialization failed: {}", e)))?;
519        self.notify(name, &json).await
520    }
521
522    /// SRS-014: Stop the BLE server.
523    ///
524    /// Stops advertising and releases resources.
525    pub fn stop(&mut self) -> Result<()> {
526        if let Some(provider) = self.service_provider.take() {
527            provider.StopAdvertising()?;
528        }
529        self.char_runtime.clear();
530        Ok(())
531    }
532}
533
534impl Drop for WindowsBLEGattServer {
535    fn drop(&mut self) {
536        let _ = self.stop();
537    }
538}
539
540// ============================================================================
541// SECTION: Helper Functions
542// ============================================================================
543
544/// Convert bytes to a Windows IBuffer.
545fn create_buffer(data: &[u8]) -> windows::core::Result<windows::Storage::Streams::IBuffer> {
546    let writer = DataWriter::new()?;
547    writer.WriteBytes(data)?;
548    writer.DetachBuffer()
549}
550
551/// Format a Bluetooth MAC address from u64.
552fn format_mac_address(address: u64) -> String {
553    format!(
554        "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
555        (address >> 40) & 0xFF,
556        (address >> 32) & 0xFF,
557        (address >> 24) & 0xFF,
558        (address >> 16) & 0xFF,
559        (address >> 8) & 0xFF,
560        address & 0xFF
561    )
562}
563
564// ============================================================================
565// SECTION: Re-exports
566// ============================================================================
567
568pub use uuid::Uuid;