1use 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#[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
72pub type Result<T> = std::result::Result<T, BleError>;
74
75#[derive(Debug, Clone)]
83pub struct AdapterInfo {
84 pub index: usize,
86 pub name: String,
88 pub id: String,
90 pub is_default: bool,
92 pub mac_address: Option<String>,
94 pub ble_supported: bool,
96 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
108pub 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 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 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
162pub 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
182struct CharacteristicConfig {
188 uuid: Uuid,
189 description: Option<String>,
190}
191
192struct RuntimeCharacteristic {
194 data_buffer: Arc<RwLock<Vec<u8>>>,
195 handle: GattLocalCharacteristic,
196}
197
198pub struct WindowsBLEGattServer {
215 device_name: String,
217 service_uuid: Uuid,
218 selected_adapter_id: Option<String>,
219
220 char_configs: Vec<(String, CharacteristicConfig)>,
222
223 char_runtime: HashMap<String, RuntimeCharacteristic>,
225 service_provider: Option<GattServiceProvider>,
226}
227
228impl WindowsBLEGattServer {
229 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 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 pub fn use_adapter(&mut self, adapter: &AdapterInfo) -> &mut Self {
277 self.selected_adapter_id = Some(adapter.id.clone());
278 self
279 }
280
281 pub fn use_default_adapter(&mut self) -> &mut Self {
283 self.selected_adapter_id = None;
284 self
285 }
286
287 pub fn device_name(&self) -> &str {
289 &self.device_name
290 }
291
292 pub fn service_uuid(&self) -> Uuid {
294 self.service_uuid
295 }
296
297 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 pub fn characteristic_names(&self) -> Vec<&str> {
307 self.char_configs.iter().map(|(n, _)| n.as_str()).collect()
308 }
309
310 pub fn is_running(&self) -> bool {
312 self.service_provider.is_some()
313 }
314
315 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 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 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 if let Some(ref description) = config.description {
351 char_params
352 .SetUserDescription(&windows::core::HSTRING::from(description.as_str()))?;
353 }
354
355 let initial_buffer = create_buffer(b"Ready")?;
357 char_params.SetStaticValue(&initial_buffer)?;
358
359 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 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 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 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 *rt.data_buffer.write().await = data.to_vec();
439
440 let buffer = create_buffer(data)?;
442 rt.handle.NotifyValueAsync(&buffer)?.get().map_err(BleError::Windows)?;
443
444 Ok(())
445 }
446
447 pub async fn notify_str(&self, name: &str, s: &str) -> Result<()> {
449 self.notify(name, s.as_bytes()).await
450 }
451
452 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 pub async fn notify_u8(&self, name: &str, value: u8) -> Result<()> {
459 self.notify(name, &[value]).await
460 }
461
462 pub async fn notify_i8(&self, name: &str, value: i8) -> Result<()> {
464 self.notify(name, &value.to_le_bytes()).await
465 }
466
467 pub async fn notify_u16(&self, name: &str, value: u16) -> Result<()> {
469 self.notify(name, &value.to_le_bytes()).await
470 }
471
472 pub async fn notify_i16(&self, name: &str, value: i16) -> Result<()> {
474 self.notify(name, &value.to_le_bytes()).await
475 }
476
477 pub async fn notify_u32(&self, name: &str, value: u32) -> Result<()> {
479 self.notify(name, &value.to_le_bytes()).await
480 }
481
482 pub async fn notify_i32(&self, name: &str, value: i32) -> Result<()> {
484 self.notify(name, &value.to_le_bytes()).await
485 }
486
487 pub async fn notify_u64(&self, name: &str, value: u64) -> Result<()> {
489 self.notify(name, &value.to_le_bytes()).await
490 }
491
492 pub async fn notify_i64(&self, name: &str, value: i64) -> Result<()> {
494 self.notify(name, &value.to_le_bytes()).await
495 }
496
497 pub async fn notify_f32(&self, name: &str, value: f32) -> Result<()> {
499 self.notify(name, &value.to_le_bytes()).await
500 }
501
502 pub async fn notify_f64(&self, name: &str, value: f64) -> Result<()> {
504 self.notify(name, &value.to_le_bytes()).await
505 }
506
507 pub async fn notify_bool(&self, name: &str, value: bool) -> Result<()> {
509 self.notify(name, &[value as u8]).await
510 }
511
512 #[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 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
540fn 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
551fn 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
564pub use uuid::Uuid;