1use std::collections::HashMap;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::time::Duration;
9
10use btleplug::api::{Characteristic, Peripheral as _, WriteType};
11use btleplug::platform::{Adapter, Peripheral};
12use tokio::sync::RwLock;
13use tokio::time::timeout;
14use tracing::{debug, info, warn};
15use uuid::Uuid;
16
17use crate::error::{Error, Result};
18use crate::scan::{ScanOptions, find_device};
19use crate::traits::AranetDevice;
20use crate::util::{create_identifier, format_peripheral_id};
21use crate::uuid::{
22 BATTERY_LEVEL, BATTERY_SERVICE, CURRENT_READINGS_DETAIL, CURRENT_READINGS_DETAIL_ALT,
23 DEVICE_INFO_SERVICE, DEVICE_NAME, FIRMWARE_REVISION, GAP_SERVICE, HARDWARE_REVISION,
24 MANUFACTURER_NAME, MODEL_NUMBER, SAF_TEHNIKA_SERVICE_NEW, SAF_TEHNIKA_SERVICE_OLD,
25 SERIAL_NUMBER, SOFTWARE_REVISION,
26};
27use aranet_types::{CurrentReading, DeviceInfo, DeviceType};
28
29pub struct Device {
45 #[allow(dead_code)]
51 adapter: Adapter,
52 peripheral: Peripheral,
54 name: Option<String>,
56 address: String,
58 device_type: Option<DeviceType>,
60 services_discovered: bool,
62 characteristics_cache: RwLock<HashMap<Uuid, Characteristic>>,
65 notification_handles: tokio::sync::Mutex<Vec<tokio::task::JoinHandle<()>>>,
67 disconnected: AtomicBool,
69 config: ConnectionConfig,
71}
72
73impl std::fmt::Debug for Device {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 f.debug_struct("Device")
79 .field("name", &self.name)
80 .field("address", &self.address)
81 .field("device_type", &self.device_type)
82 .field("services_discovered", &self.services_discovered)
83 .finish_non_exhaustive()
84 }
85}
86
87const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(10);
89
90const DEFAULT_WRITE_TIMEOUT: Duration = Duration::from_secs(10);
92
93const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(15);
95
96const DEFAULT_DISCOVERY_TIMEOUT: Duration = Duration::from_secs(10);
98
99const DEFAULT_VALIDATION_TIMEOUT: Duration = Duration::from_secs(3);
101
102#[derive(Debug, Clone)]
120pub struct ConnectionConfig {
121 pub connection_timeout: Duration,
123 pub read_timeout: Duration,
125 pub write_timeout: Duration,
127 pub discovery_timeout: Duration,
129 pub validation_timeout: Duration,
131}
132
133impl Default for ConnectionConfig {
134 fn default() -> Self {
135 Self {
136 connection_timeout: DEFAULT_CONNECT_TIMEOUT,
137 read_timeout: DEFAULT_READ_TIMEOUT,
138 write_timeout: DEFAULT_WRITE_TIMEOUT,
139 discovery_timeout: DEFAULT_DISCOVERY_TIMEOUT,
140 validation_timeout: DEFAULT_VALIDATION_TIMEOUT,
141 }
142 }
143}
144
145impl ConnectionConfig {
146 pub fn new() -> Self {
148 Self::default()
149 }
150
151 pub fn for_current_platform() -> Self {
153 let platform = crate::platform::PlatformConfig::for_current_platform();
154 Self {
155 connection_timeout: platform.recommended_connection_timeout,
156 read_timeout: platform.recommended_operation_timeout,
157 write_timeout: platform.recommended_operation_timeout,
158 discovery_timeout: platform.recommended_operation_timeout,
159 validation_timeout: DEFAULT_VALIDATION_TIMEOUT,
160 }
161 }
162
163 pub fn challenging_environment() -> Self {
168 Self {
169 connection_timeout: Duration::from_secs(90),
170 read_timeout: Duration::from_secs(30),
171 write_timeout: Duration::from_secs(15),
172 discovery_timeout: Duration::from_secs(30),
173 validation_timeout: Duration::from_secs(5),
174 }
175 }
176
177 pub fn fast() -> Self {
182 Self {
183 connection_timeout: Duration::from_secs(8),
184 read_timeout: Duration::from_secs(5),
185 write_timeout: Duration::from_secs(5),
186 discovery_timeout: Duration::from_secs(5),
187 validation_timeout: Duration::from_secs(2),
188 }
189 }
190
191 #[must_use]
193 pub fn connection_timeout(mut self, timeout: Duration) -> Self {
194 self.connection_timeout = timeout;
195 self
196 }
197
198 #[must_use]
200 pub fn read_timeout(mut self, timeout: Duration) -> Self {
201 self.read_timeout = timeout;
202 self
203 }
204
205 #[must_use]
207 pub fn write_timeout(mut self, timeout: Duration) -> Self {
208 self.write_timeout = timeout;
209 self
210 }
211
212 #[must_use]
214 pub fn discovery_timeout(mut self, timeout: Duration) -> Self {
215 self.discovery_timeout = timeout;
216 self
217 }
218
219 #[must_use]
221 pub fn validation_timeout(mut self, timeout: Duration) -> Self {
222 self.validation_timeout = timeout;
223 self
224 }
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
229pub enum SignalQuality {
230 Poor,
232 Fair,
234 Good,
236 Excellent,
238}
239
240impl SignalQuality {
241 pub fn from_rssi(rssi: i16) -> Self {
251 match rssi {
252 r if r > -60 => SignalQuality::Excellent,
253 r if r > -75 => SignalQuality::Good,
254 r if r > -85 => SignalQuality::Fair,
255 _ => SignalQuality::Poor,
256 }
257 }
258
259 pub fn description(&self) -> &'static str {
261 match self {
262 SignalQuality::Excellent => "Excellent signal",
263 SignalQuality::Good => "Good signal",
264 SignalQuality::Fair => "Fair signal - connection may be unstable",
265 SignalQuality::Poor => "Poor signal - consider moving closer",
266 }
267 }
268
269 pub fn recommended_read_delay(&self) -> Duration {
271 match self {
272 SignalQuality::Excellent => Duration::from_millis(30),
273 SignalQuality::Good => Duration::from_millis(50),
274 SignalQuality::Fair => Duration::from_millis(100),
275 SignalQuality::Poor => Duration::from_millis(200),
276 }
277 }
278
279 pub fn is_usable(&self) -> bool {
281 matches!(
282 self,
283 SignalQuality::Excellent | SignalQuality::Good | SignalQuality::Fair
284 )
285 }
286}
287
288impl Device {
289 #[tracing::instrument(level = "info", skip_all, fields(identifier = %identifier))]
304 pub async fn connect(identifier: &str) -> Result<Self> {
305 Self::connect_with_config(identifier, ConnectionConfig::default()).await
306 }
307
308 #[tracing::instrument(level = "info", skip_all, fields(identifier = %identifier, timeout_secs = scan_timeout.as_secs()))]
310 pub async fn connect_with_timeout(identifier: &str, scan_timeout: Duration) -> Result<Self> {
311 let config = ConnectionConfig::default().connection_timeout(scan_timeout);
312 Self::connect_with_config(identifier, config).await
313 }
314
315 #[tracing::instrument(level = "info", skip_all, fields(identifier = %identifier))]
335 pub async fn connect_with_config(identifier: &str, config: ConnectionConfig) -> Result<Self> {
336 let options = ScanOptions {
337 duration: config.connection_timeout,
338 filter_aranet_only: false, use_service_filter: false,
340 };
341
342 let (adapter, peripheral) = match find_device(identifier).await {
344 Ok(result) => result,
345 Err(_) => crate::scan::find_device_with_options(identifier, options).await?,
346 };
347
348 Self::from_peripheral_with_config(adapter, peripheral, config).await
349 }
350
351 #[tracing::instrument(level = "info", skip_all, fields(identifier = %identifier))]
357 pub async fn connect_with_adapter(
358 adapter: Adapter,
359 identifier: &str,
360 config: ConnectionConfig,
361 ) -> Result<Self> {
362 let options = ScanOptions {
363 duration: config.connection_timeout,
364 filter_aranet_only: false,
365 use_service_filter: false,
366 };
367
368 let peripheral = match crate::scan::find_device_with_adapter(
369 &adapter,
370 identifier,
371 ScanOptions::default(),
372 )
373 .await
374 {
375 Ok(p) => p,
376 Err(e) => {
377 debug!("Fast scan failed ({e}), retrying with extended options");
378 crate::scan::find_device_with_adapter(&adapter, identifier, options).await?
379 }
380 };
381
382 Self::from_peripheral_with_config(adapter, peripheral, config).await
383 }
384
385 #[tracing::instrument(level = "info", skip_all)]
387 pub async fn from_peripheral(adapter: Adapter, peripheral: Peripheral) -> Result<Self> {
388 Self::from_peripheral_with_config(adapter, peripheral, ConnectionConfig::default()).await
389 }
390
391 #[tracing::instrument(level = "info", skip_all, fields(timeout_secs = connect_timeout.as_secs()))]
393 pub async fn from_peripheral_with_timeout(
394 adapter: Adapter,
395 peripheral: Peripheral,
396 connect_timeout: Duration,
397 ) -> Result<Self> {
398 let config = ConnectionConfig::default().connection_timeout(connect_timeout);
399 Self::from_peripheral_with_config(adapter, peripheral, config).await
400 }
401
402 #[tracing::instrument(level = "info", skip_all, fields(connect_timeout = ?config.connection_timeout))]
404 pub async fn from_peripheral_with_config(
405 adapter: Adapter,
406 peripheral: Peripheral,
407 config: ConnectionConfig,
408 ) -> Result<Self> {
409 #[cfg(target_os = "linux")]
411 crate::bluez_agent::allow_pairing(&peripheral.address().to_string());
412
413 info!("Connecting to device...");
415 timeout(config.connection_timeout, peripheral.connect())
416 .await
417 .map_err(|_| Error::Timeout {
418 operation: "connect to device".to_string(),
419 duration: config.connection_timeout,
420 })??;
421 info!("Connected!");
422
423 info!("Discovering services...");
425 timeout(config.discovery_timeout, peripheral.discover_services())
426 .await
427 .map_err(|_| Error::Timeout {
428 operation: "discover services".to_string(),
429 duration: config.discovery_timeout,
430 })??;
431
432 let mut services = peripheral.services();
433
434 if services.is_empty() {
438 warn!("Service discovery returned 0 services — retrying with fresh connection");
439 let _ = peripheral.disconnect().await;
440 tokio::time::sleep(Duration::from_secs(2)).await;
441
442 timeout(config.connection_timeout, peripheral.connect())
443 .await
444 .map_err(|_| Error::Timeout {
445 operation: "reconnect to device".to_string(),
446 duration: config.connection_timeout,
447 })??;
448
449 timeout(config.discovery_timeout, peripheral.discover_services())
450 .await
451 .map_err(|_| Error::Timeout {
452 operation: "rediscover services".to_string(),
453 duration: config.discovery_timeout,
454 })??;
455
456 services = peripheral.services();
457 }
458
459 debug!("Found {} services", services.len());
460
461 let mut characteristics_cache = HashMap::new();
463 for service in &services {
464 debug!(" Service: {}", service.uuid);
465 for char in &service.characteristics {
466 debug!(" Characteristic: {}", char.uuid);
467 characteristics_cache.insert(char.uuid, char.clone());
468 }
469 }
470 debug!(
471 "Cached {} characteristics for fast lookup",
472 characteristics_cache.len()
473 );
474
475 let properties = peripheral.properties().await?;
477 let name = properties.as_ref().and_then(|p| p.local_name.clone());
478
479 let address = properties
481 .as_ref()
482 .map(|p| create_identifier(&p.address.to_string(), &peripheral.id()))
483 .unwrap_or_else(|| format_peripheral_id(&peripheral.id()));
484
485 let device_type = name.as_ref().and_then(|n| DeviceType::from_name(n));
487
488 Ok(Self {
489 adapter,
490 peripheral,
491 name,
492 address,
493 device_type,
494 services_discovered: true,
495 characteristics_cache: RwLock::new(characteristics_cache),
496 notification_handles: tokio::sync::Mutex::new(Vec::new()),
497 disconnected: AtomicBool::new(false),
498 config,
499 })
500 }
501
502 pub async fn is_connected(&self) -> bool {
507 match self.peripheral.is_connected().await {
508 Ok(connected) => connected,
509 Err(e) => {
510 warn!("Failed to query connection state: {e}");
511 false
512 }
513 }
514 }
515
516 pub async fn validate_connection(&self) -> bool {
529 timeout(self.config.validation_timeout, self.read_battery())
530 .await
531 .map(|r| r.is_ok())
532 .unwrap_or(false)
533 }
534
535 pub async fn is_connection_alive(&self) -> bool {
549 self.validate_connection().await
550 }
551
552 pub fn config(&self) -> &ConnectionConfig {
554 &self.config
555 }
556
557 pub async fn signal_quality(&self) -> Option<SignalQuality> {
561 self.read_rssi().await.ok().map(SignalQuality::from_rssi)
562 }
563
564 #[tracing::instrument(level = "info", skip(self), fields(device_name = ?self.name))]
573 pub async fn disconnect(&self) -> Result<()> {
574 info!("Disconnecting from device...");
575 self.disconnected.store(true, Ordering::SeqCst);
576
577 {
579 let mut handles = self.notification_handles.lock().await;
580 for handle in handles.drain(..) {
581 handle.abort();
582 }
583 }
584
585 self.peripheral.disconnect().await?;
586 Ok(())
587 }
588
589 pub fn name(&self) -> Option<&str> {
591 self.name.as_deref()
592 }
593
594 pub fn address(&self) -> &str {
599 &self.address
600 }
601
602 pub fn device_type(&self) -> Option<DeviceType> {
604 self.device_type
605 }
606
607 pub async fn read_rssi(&self) -> Result<i16> {
612 let properties = self.peripheral.properties().await?;
613 properties
614 .and_then(|p| p.rssi)
615 .ok_or_else(|| Error::InvalidData("RSSI not available".to_string()))
616 }
617
618 async fn find_characteristic(&self, uuid: Uuid) -> Result<Characteristic> {
624 {
626 let cache = self.characteristics_cache.read().await;
627 if let Some(char) = cache.get(&uuid) {
628 return Ok(char.clone());
629 }
630
631 if !cache.is_empty() {
633 return Err(Error::characteristic_not_found(
634 uuid.to_string(),
635 self.peripheral.services().len(),
636 ));
637 }
638 }
639
640 warn!(
642 "Characteristics cache empty, falling back to service search for {}",
643 uuid
644 );
645 let services = self.peripheral.services();
646 let service_count = services.len();
647
648 for service in &services {
650 if service.uuid == SAF_TEHNIKA_SERVICE_NEW || service.uuid == SAF_TEHNIKA_SERVICE_OLD {
651 for char in &service.characteristics {
652 if char.uuid == uuid {
653 return Ok(char.clone());
654 }
655 }
656 }
657 }
658
659 for service in &services {
661 if service.uuid == GAP_SERVICE
662 || service.uuid == DEVICE_INFO_SERVICE
663 || service.uuid == BATTERY_SERVICE
664 {
665 for char in &service.characteristics {
666 if char.uuid == uuid {
667 return Ok(char.clone());
668 }
669 }
670 }
671 }
672
673 for service in &services {
675 for char in &service.characteristics {
676 if char.uuid == uuid {
677 return Ok(char.clone());
678 }
679 }
680 }
681
682 Err(Error::characteristic_not_found(
683 uuid.to_string(),
684 service_count,
685 ))
686 }
687
688 pub async fn read_characteristic(&self, uuid: Uuid) -> Result<Vec<u8>> {
693 let characteristic = self.find_characteristic(uuid).await?;
694 let data = timeout(
695 self.config.read_timeout,
696 self.peripheral.read(&characteristic),
697 )
698 .await
699 .map_err(|_| Error::Timeout {
700 operation: format!("read characteristic {}", uuid),
701 duration: self.config.read_timeout,
702 })??;
703 Ok(data)
704 }
705
706 pub async fn read_characteristic_with_timeout(
711 &self,
712 uuid: Uuid,
713 read_timeout: Duration,
714 ) -> Result<Vec<u8>> {
715 let characteristic = self.find_characteristic(uuid).await?;
716 let data = timeout(read_timeout, self.peripheral.read(&characteristic))
717 .await
718 .map_err(|_| Error::Timeout {
719 operation: format!("read characteristic {}", uuid),
720 duration: read_timeout,
721 })??;
722 Ok(data)
723 }
724
725 pub async fn write_characteristic(&self, uuid: Uuid, data: &[u8]) -> Result<()> {
730 let characteristic = self.find_characteristic(uuid).await?;
731 timeout(
732 self.config.write_timeout,
733 self.peripheral
734 .write(&characteristic, data, WriteType::WithResponse),
735 )
736 .await
737 .map_err(|_| Error::Timeout {
738 operation: format!("write characteristic {}", uuid),
739 duration: self.config.write_timeout,
740 })??;
741 Ok(())
742 }
743
744 pub async fn write_characteristic_with_timeout(
746 &self,
747 uuid: Uuid,
748 data: &[u8],
749 write_timeout: Duration,
750 ) -> Result<()> {
751 let characteristic = self.find_characteristic(uuid).await?;
752 timeout(
753 write_timeout,
754 self.peripheral
755 .write(&characteristic, data, WriteType::WithResponse),
756 )
757 .await
758 .map_err(|_| Error::Timeout {
759 operation: format!("write characteristic {}", uuid),
760 duration: write_timeout,
761 })??;
762 Ok(())
763 }
764
765 #[tracing::instrument(level = "debug", skip(self), fields(device_name = ?self.name, device_type = ?self.device_type))]
771 pub async fn read_current(&self) -> Result<CurrentReading> {
772 let data = match self.device_type {
775 Some(DeviceType::Aranet4) => self.read_characteristic(CURRENT_READINGS_DETAIL).await?,
776 Some(DeviceType::Aranet2 | DeviceType::AranetRadon | DeviceType::AranetRadiation) => {
777 self.read_characteristic(CURRENT_READINGS_DETAIL_ALT)
778 .await?
779 }
780 None | Some(_) => {
781 match self.read_characteristic(CURRENT_READINGS_DETAIL).await {
783 Ok(data) => data,
784 Err(Error::CharacteristicNotFound { .. }) => {
785 debug!("Primary reading characteristic not found, trying alternative");
786 self.read_characteristic(CURRENT_READINGS_DETAIL_ALT)
787 .await?
788 }
789 Err(e) => return Err(e),
790 }
791 }
792 };
793
794 let device_type = match self.device_type {
796 Some(dt) => dt,
797 None => {
798 warn!(
799 "Device type unknown for {}; defaulting to Aranet4 — \
800 readings may be incorrect if this is a different model",
801 self.name().unwrap_or("unknown")
802 );
803 DeviceType::Aranet4
804 }
805 };
806 crate::readings::parse_reading_for_device(&data, device_type)
807 }
808
809 #[tracing::instrument(level = "debug", skip(self))]
811 pub async fn read_battery(&self) -> Result<u8> {
812 let data = self.read_characteristic(BATTERY_LEVEL).await?;
813 if data.is_empty() {
814 return Err(Error::InvalidData("Empty battery data".to_string()));
815 }
816 Ok(data[0])
817 }
818
819 #[tracing::instrument(level = "debug", skip(self))]
823 pub async fn read_device_info(&self) -> Result<DeviceInfo> {
824 fn read_string(data: Vec<u8>) -> String {
825 String::from_utf8(data)
826 .unwrap_or_default()
827 .trim_end_matches('\0')
828 .to_string()
829 }
830
831 let (
833 name_result,
834 model_result,
835 serial_result,
836 firmware_result,
837 hardware_result,
838 software_result,
839 manufacturer_result,
840 ) = tokio::join!(
841 self.read_characteristic(DEVICE_NAME),
842 self.read_characteristic(MODEL_NUMBER),
843 self.read_characteristic(SERIAL_NUMBER),
844 self.read_characteristic(FIRMWARE_REVISION),
845 self.read_characteristic(HARDWARE_REVISION),
846 self.read_characteristic(SOFTWARE_REVISION),
847 self.read_characteristic(MANUFACTURER_NAME),
848 );
849
850 let name = name_result
851 .map(read_string)
852 .unwrap_or_else(|_| self.name.clone().unwrap_or_default());
853
854 let model = model_result.map(read_string).unwrap_or_default();
855 let serial = serial_result.map(read_string).unwrap_or_default();
856 let firmware = firmware_result.map(read_string).unwrap_or_default();
857 let hardware = hardware_result.map(read_string).unwrap_or_default();
858 let software = software_result.map(read_string).unwrap_or_default();
859 let manufacturer = manufacturer_result.map(read_string).unwrap_or_default();
860
861 Ok(DeviceInfo {
862 name,
863 model,
864 serial,
865 firmware,
866 hardware,
867 software,
868 manufacturer,
869 })
870 }
871
872 #[tracing::instrument(level = "debug", skip(self))]
878 pub async fn read_device_info_essential(&self) -> Result<DeviceInfo> {
879 fn read_string(data: Vec<u8>) -> String {
880 String::from_utf8(data)
881 .unwrap_or_default()
882 .trim_end_matches('\0')
883 .to_string()
884 }
885
886 let (name_result, serial_result, firmware_result) = tokio::join!(
888 self.read_characteristic(DEVICE_NAME),
889 self.read_characteristic(SERIAL_NUMBER),
890 self.read_characteristic(FIRMWARE_REVISION),
891 );
892
893 let name = name_result
894 .map(read_string)
895 .unwrap_or_else(|_| self.name.clone().unwrap_or_default());
896 let serial = serial_result.map(read_string).unwrap_or_default();
897 let firmware = firmware_result.map(read_string).unwrap_or_default();
898
899 Ok(DeviceInfo {
900 name,
901 model: String::new(),
902 serial,
903 firmware,
904 hardware: String::new(),
905 software: String::new(),
906 manufacturer: String::new(),
907 })
908 }
909
910 pub async fn subscribe_to_notifications<F>(&self, uuid: Uuid, callback: F) -> Result<()>
916 where
917 F: Fn(&[u8]) + Send + Sync + 'static,
918 {
919 let characteristic = self.find_characteristic(uuid).await?;
920
921 self.peripheral.subscribe(&characteristic).await?;
922
923 let mut stream = self.peripheral.notifications().await?;
925 let char_uuid = characteristic.uuid;
926
927 let handle = tokio::spawn(async move {
928 use futures::StreamExt;
929 while let Some(notification) = stream.next().await {
930 if notification.uuid == char_uuid {
931 callback(¬ification.value);
932 }
933 }
934 });
935
936 self.notification_handles.lock().await.push(handle);
938
939 Ok(())
940 }
941
942 pub async fn unsubscribe_from_notifications(&self, uuid: Uuid) -> Result<()> {
944 let characteristic = self.find_characteristic(uuid).await?;
945 self.peripheral.unsubscribe(&characteristic).await?;
946 Ok(())
947 }
948
949 pub async fn cached_characteristic_count(&self) -> usize {
953 self.characteristics_cache.read().await.len()
954 }
955}
956
957impl Drop for Device {
970 fn drop(&mut self) {
971 if !self.disconnected.load(Ordering::SeqCst) {
972 self.disconnected.store(true, Ordering::SeqCst);
974
975 warn!(
977 device_name = ?self.name,
978 device_address = %self.address,
979 "Device dropped without calling disconnect() - performing best-effort cleanup. \
980 For reliable cleanup, call device.disconnect().await before dropping."
981 );
982
983 if let Ok(mut handles) = self.notification_handles.try_lock() {
986 for handle in handles.drain(..) {
987 handle.abort();
988 }
989 }
990
991 let peripheral = self.peripheral.clone();
994 let address = self.address.clone();
995
996 if let Ok(handle) = tokio::runtime::Handle::try_current() {
998 handle.spawn(async move {
999 if let Err(e) = peripheral.disconnect().await {
1000 debug!(
1001 device_address = %address,
1002 error = %e,
1003 "Best-effort disconnect failed (device may already be disconnected)"
1004 );
1005 } else {
1006 debug!(
1007 device_address = %address,
1008 "Best-effort disconnect completed"
1009 );
1010 }
1011 });
1012 }
1013 }
1014 }
1015}
1016
1017impl AranetDevice for Device {
1018 async fn is_connected(&self) -> bool {
1021 Device::is_connected(self).await
1022 }
1023
1024 async fn disconnect(&self) -> Result<()> {
1025 Device::disconnect(self).await
1026 }
1027
1028 fn name(&self) -> Option<&str> {
1031 Device::name(self)
1032 }
1033
1034 fn address(&self) -> &str {
1035 Device::address(self)
1036 }
1037
1038 fn device_type(&self) -> Option<DeviceType> {
1039 Device::device_type(self)
1040 }
1041
1042 async fn read_current(&self) -> Result<CurrentReading> {
1045 Device::read_current(self).await
1046 }
1047
1048 async fn read_device_info(&self) -> Result<DeviceInfo> {
1049 Device::read_device_info(self).await
1050 }
1051
1052 async fn read_rssi(&self) -> Result<i16> {
1053 Device::read_rssi(self).await
1054 }
1055
1056 async fn read_battery(&self) -> Result<u8> {
1059 Device::read_battery(self).await
1060 }
1061
1062 async fn get_history_info(&self) -> Result<crate::history::HistoryInfo> {
1065 Device::get_history_info(self).await
1066 }
1067
1068 async fn download_history(&self) -> Result<Vec<aranet_types::HistoryRecord>> {
1069 Device::download_history(self).await
1070 }
1071
1072 async fn download_history_with_options(
1073 &self,
1074 options: crate::history::HistoryOptions,
1075 ) -> Result<Vec<aranet_types::HistoryRecord>> {
1076 Device::download_history_with_options(self, options).await
1077 }
1078
1079 async fn get_interval(&self) -> Result<crate::settings::MeasurementInterval> {
1082 Device::get_interval(self).await
1083 }
1084
1085 async fn set_interval(&self, interval: crate::settings::MeasurementInterval) -> Result<()> {
1086 Device::set_interval(self, interval).await
1087 }
1088
1089 async fn get_calibration(&self) -> Result<crate::settings::CalibrationData> {
1090 Device::get_calibration(self).await
1091 }
1092}