1use std::time::Duration;
7
8use btleplug::api::{Central, Manager as _, Peripheral as _, ScanFilter};
9use btleplug::platform::{Adapter, Manager, Peripheral, PeripheralId};
10use tokio::time::sleep;
11use tracing::{debug, info, warn};
12
13use crate::error::{Error, Result};
14use crate::util::{create_identifier, format_peripheral_id};
15use crate::uuid::{MANUFACTURER_ID, SAF_TEHNIKA_SERVICE_NEW, SAF_TEHNIKA_SERVICE_OLD};
16use aranet_types::DeviceType;
17
18#[derive(Debug, Clone)]
20pub struct DiscoveredDevice {
21 pub name: Option<String>,
23 pub id: PeripheralId,
25 pub address: String,
27 pub identifier: String,
29 pub rssi: Option<i16>,
31 pub device_type: Option<DeviceType>,
33 pub is_aranet: bool,
35 pub manufacturer_data: Option<Vec<u8>>,
37}
38
39#[derive(Debug, Clone)]
41pub struct ScanOptions {
42 pub duration: Duration,
44 pub filter_aranet_only: bool,
46}
47
48impl Default for ScanOptions {
49 fn default() -> Self {
50 Self {
51 duration: Duration::from_secs(5),
52 filter_aranet_only: true,
53 }
54 }
55}
56
57impl ScanOptions {
58 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn duration(mut self, duration: Duration) -> Self {
65 self.duration = duration;
66 self
67 }
68
69 pub fn duration_secs(mut self, secs: u64) -> Self {
71 self.duration = Duration::from_secs(secs);
72 self
73 }
74
75 pub fn filter_aranet_only(mut self, filter: bool) -> Self {
77 self.filter_aranet_only = filter;
78 self
79 }
80
81 pub fn all_devices(self) -> Self {
83 self.filter_aranet_only(false)
84 }
85}
86
87pub async fn get_adapter() -> Result<Adapter> {
89 use crate::error::DeviceNotFoundReason;
90
91 let manager = Manager::new().await?;
92 let adapters = manager.adapters().await?;
93
94 adapters
95 .into_iter()
96 .next()
97 .ok_or(Error::DeviceNotFound(DeviceNotFoundReason::NoAdapter))
98}
99
100pub async fn scan_for_devices() -> Result<Vec<DiscoveredDevice>> {
112 scan_with_options(ScanOptions::default()).await
113}
114
115pub async fn scan_with_options(options: ScanOptions) -> Result<Vec<DiscoveredDevice>> {
117 let adapter = get_adapter().await?;
118 scan_with_adapter(&adapter, options).await
119}
120
121pub async fn scan_with_retry(
144 options: ScanOptions,
145 max_retries: u32,
146 retry_on_empty: bool,
147) -> Result<Vec<DiscoveredDevice>> {
148 let mut attempt = 0;
149 let mut delay = Duration::from_millis(500);
150
151 loop {
152 match scan_with_options(options.clone()).await {
153 Ok(devices) if devices.is_empty() && retry_on_empty && attempt < max_retries => {
154 attempt += 1;
155 warn!(
156 "No devices found, retrying ({}/{})...",
157 attempt, max_retries
158 );
159 sleep(delay).await;
160 delay = delay.saturating_mul(2).min(Duration::from_secs(5));
161 }
162 Ok(devices) => return Ok(devices),
163 Err(e) if attempt < max_retries => {
164 attempt += 1;
165 warn!(
166 "Scan failed ({}), retrying ({}/{})...",
167 e, attempt, max_retries
168 );
169 sleep(delay).await;
170 delay = delay.saturating_mul(2).min(Duration::from_secs(5));
171 }
172 Err(e) => return Err(e),
173 }
174 }
175}
176
177pub async fn scan_with_adapter(
179 adapter: &Adapter,
180 options: ScanOptions,
181) -> Result<Vec<DiscoveredDevice>> {
182 info!(
183 "Starting BLE scan for {} seconds...",
184 options.duration.as_secs()
185 );
186
187 adapter.start_scan(ScanFilter::default()).await?;
189
190 sleep(options.duration).await;
192
193 adapter.stop_scan().await?;
195
196 let peripherals = adapter.peripherals().await?;
198 let mut discovered = Vec::new();
199
200 for peripheral in peripherals {
201 match process_peripheral(&peripheral, options.filter_aranet_only).await {
202 Ok(Some(device)) => {
203 info!("Found Aranet device: {:?}", device.name);
204 discovered.push(device);
205 }
206 Ok(None) => {
207 }
209 Err(e) => {
210 debug!("Error processing peripheral: {}", e);
211 }
212 }
213 }
214
215 info!("Scan complete. Found {} device(s)", discovered.len());
216 Ok(discovered)
217}
218
219async fn process_peripheral(
221 peripheral: &Peripheral,
222 filter_aranet_only: bool,
223) -> Result<Option<DiscoveredDevice>> {
224 let properties = peripheral.properties().await?;
225 let properties = match properties {
226 Some(p) => p,
227 None => return Ok(None),
228 };
229
230 let id = peripheral.id();
231 let address = properties.address.to_string();
232 let name = properties.local_name.clone();
233 let rssi = properties.rssi;
234
235 let is_aranet = is_aranet_device(&properties);
237
238 if filter_aranet_only && !is_aranet {
239 return Ok(None);
240 }
241
242 let device_type = name.as_ref().and_then(|n| DeviceType::from_name(n));
244
245 let manufacturer_data = properties.manufacturer_data.get(&MANUFACTURER_ID).cloned();
247
248 let identifier = create_identifier(&address, &id);
251
252 Ok(Some(DiscoveredDevice {
253 name,
254 id,
255 address,
256 identifier,
257 rssi,
258 device_type,
259 is_aranet,
260 manufacturer_data,
261 }))
262}
263
264fn is_aranet_device(properties: &btleplug::api::PeripheralProperties) -> bool {
266 if properties.manufacturer_data.contains_key(&MANUFACTURER_ID) {
268 return true;
269 }
270
271 for service_uuid in properties.service_data.keys() {
273 if *service_uuid == SAF_TEHNIKA_SERVICE_NEW || *service_uuid == SAF_TEHNIKA_SERVICE_OLD {
274 return true;
275 }
276 }
277
278 for service_uuid in &properties.services {
280 if *service_uuid == SAF_TEHNIKA_SERVICE_NEW || *service_uuid == SAF_TEHNIKA_SERVICE_OLD {
281 return true;
282 }
283 }
284
285 if let Some(name) = &properties.local_name {
287 let name_lower = name.to_lowercase();
288 if name_lower.contains("aranet") {
289 return true;
290 }
291 }
292
293 false
294}
295
296pub async fn find_device(identifier: &str) -> Result<(Adapter, Peripheral)> {
298 find_device_with_options(identifier, ScanOptions::default()).await
299}
300
301pub async fn find_device_with_options(
303 identifier: &str,
304 options: ScanOptions,
305) -> Result<(Adapter, Peripheral)> {
306 let adapter = get_adapter().await?;
307 let identifier_lower = identifier.to_lowercase();
308
309 info!("Scanning for device: {}", identifier);
310
311 adapter.start_scan(ScanFilter::default()).await?;
313 sleep(options.duration).await;
314 adapter.stop_scan().await?;
315
316 let peripherals = adapter.peripherals().await?;
318
319 for peripheral in peripherals {
320 if let Ok(Some(props)) = peripheral.properties().await {
321 let address = props.address.to_string().to_lowercase();
322 let peripheral_id = format_peripheral_id(&peripheral.id()).to_lowercase();
323
324 if peripheral_id.contains(&identifier_lower) {
326 info!("Found device by peripheral ID");
327 return Ok((adapter, peripheral));
328 }
329
330 if address != "00:00:00:00:00:00"
332 && (address == identifier_lower
333 || address.replace(':', "") == identifier_lower.replace(':', ""))
334 {
335 info!("Found device by address: {}", address);
336 return Ok((adapter, peripheral));
337 }
338
339 if let Some(name) = &props.local_name
341 && name.to_lowercase().contains(&identifier_lower)
342 {
343 info!("Found device by name: {}", name);
344 return Ok((adapter, peripheral));
345 }
346 }
347 }
348
349 warn!("Device not found: {}", identifier);
350 Err(Error::device_not_found(identifier))
351}