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 info!("Connecting to device...");
411 timeout(config.connection_timeout, peripheral.connect())
412 .await
413 .map_err(|_| Error::Timeout {
414 operation: "connect to device".to_string(),
415 duration: config.connection_timeout,
416 })??;
417 info!("Connected!");
418
419 info!("Discovering services...");
421 timeout(config.discovery_timeout, peripheral.discover_services())
422 .await
423 .map_err(|_| Error::Timeout {
424 operation: "discover services".to_string(),
425 duration: config.discovery_timeout,
426 })??;
427
428 let mut services = peripheral.services();
429
430 if services.is_empty() {
434 warn!("Service discovery returned 0 services — retrying with fresh connection");
435 let _ = peripheral.disconnect().await;
436 tokio::time::sleep(Duration::from_secs(2)).await;
437
438 timeout(config.connection_timeout, peripheral.connect())
439 .await
440 .map_err(|_| Error::Timeout {
441 operation: "reconnect to device".to_string(),
442 duration: config.connection_timeout,
443 })??;
444
445 timeout(config.discovery_timeout, peripheral.discover_services())
446 .await
447 .map_err(|_| Error::Timeout {
448 operation: "rediscover services".to_string(),
449 duration: config.discovery_timeout,
450 })??;
451
452 services = peripheral.services();
453 }
454
455 debug!("Found {} services", services.len());
456
457 let mut characteristics_cache = HashMap::new();
459 for service in &services {
460 debug!(" Service: {}", service.uuid);
461 for char in &service.characteristics {
462 debug!(" Characteristic: {}", char.uuid);
463 characteristics_cache.insert(char.uuid, char.clone());
464 }
465 }
466 debug!(
467 "Cached {} characteristics for fast lookup",
468 characteristics_cache.len()
469 );
470
471 let properties = peripheral.properties().await?;
473 let name = properties.as_ref().and_then(|p| p.local_name.clone());
474
475 let address = properties
477 .as_ref()
478 .map(|p| create_identifier(&p.address.to_string(), &peripheral.id()))
479 .unwrap_or_else(|| format_peripheral_id(&peripheral.id()));
480
481 let device_type = name.as_ref().and_then(|n| DeviceType::from_name(n));
483
484 Ok(Self {
485 adapter,
486 peripheral,
487 name,
488 address,
489 device_type,
490 services_discovered: true,
491 characteristics_cache: RwLock::new(characteristics_cache),
492 notification_handles: tokio::sync::Mutex::new(Vec::new()),
493 disconnected: AtomicBool::new(false),
494 config,
495 })
496 }
497
498 pub async fn is_connected(&self) -> bool {
503 match self.peripheral.is_connected().await {
504 Ok(connected) => connected,
505 Err(e) => {
506 warn!("Failed to query connection state: {e}");
507 false
508 }
509 }
510 }
511
512 pub async fn validate_connection(&self) -> bool {
525 timeout(self.config.validation_timeout, self.read_battery())
526 .await
527 .map(|r| r.is_ok())
528 .unwrap_or(false)
529 }
530
531 pub async fn is_connection_alive(&self) -> bool {
545 self.validate_connection().await
546 }
547
548 pub fn config(&self) -> &ConnectionConfig {
550 &self.config
551 }
552
553 pub async fn signal_quality(&self) -> Option<SignalQuality> {
557 self.read_rssi().await.ok().map(SignalQuality::from_rssi)
558 }
559
560 #[tracing::instrument(level = "info", skip(self), fields(device_name = ?self.name))]
569 pub async fn disconnect(&self) -> Result<()> {
570 info!("Disconnecting from device...");
571 self.disconnected.store(true, Ordering::SeqCst);
572
573 {
575 let mut handles = self.notification_handles.lock().await;
576 for handle in handles.drain(..) {
577 handle.abort();
578 }
579 }
580
581 self.peripheral.disconnect().await?;
582 Ok(())
583 }
584
585 pub fn name(&self) -> Option<&str> {
587 self.name.as_deref()
588 }
589
590 pub fn address(&self) -> &str {
595 &self.address
596 }
597
598 pub fn device_type(&self) -> Option<DeviceType> {
600 self.device_type
601 }
602
603 pub async fn read_rssi(&self) -> Result<i16> {
608 let properties = self.peripheral.properties().await?;
609 properties
610 .and_then(|p| p.rssi)
611 .ok_or_else(|| Error::InvalidData("RSSI not available".to_string()))
612 }
613
614 async fn find_characteristic(&self, uuid: Uuid) -> Result<Characteristic> {
620 {
622 let cache = self.characteristics_cache.read().await;
623 if let Some(char) = cache.get(&uuid) {
624 return Ok(char.clone());
625 }
626
627 if !cache.is_empty() {
629 return Err(Error::characteristic_not_found(
630 uuid.to_string(),
631 self.peripheral.services().len(),
632 ));
633 }
634 }
635
636 warn!(
638 "Characteristics cache empty, falling back to service search for {}",
639 uuid
640 );
641 let services = self.peripheral.services();
642 let service_count = services.len();
643
644 for service in &services {
646 if service.uuid == SAF_TEHNIKA_SERVICE_NEW || service.uuid == SAF_TEHNIKA_SERVICE_OLD {
647 for char in &service.characteristics {
648 if char.uuid == uuid {
649 return Ok(char.clone());
650 }
651 }
652 }
653 }
654
655 for service in &services {
657 if service.uuid == GAP_SERVICE
658 || service.uuid == DEVICE_INFO_SERVICE
659 || service.uuid == BATTERY_SERVICE
660 {
661 for char in &service.characteristics {
662 if char.uuid == uuid {
663 return Ok(char.clone());
664 }
665 }
666 }
667 }
668
669 for service in &services {
671 for char in &service.characteristics {
672 if char.uuid == uuid {
673 return Ok(char.clone());
674 }
675 }
676 }
677
678 Err(Error::characteristic_not_found(
679 uuid.to_string(),
680 service_count,
681 ))
682 }
683
684 pub async fn read_characteristic(&self, uuid: Uuid) -> Result<Vec<u8>> {
689 let characteristic = self.find_characteristic(uuid).await?;
690 let data = timeout(
691 self.config.read_timeout,
692 self.peripheral.read(&characteristic),
693 )
694 .await
695 .map_err(|_| Error::Timeout {
696 operation: format!("read characteristic {}", uuid),
697 duration: self.config.read_timeout,
698 })??;
699 Ok(data)
700 }
701
702 pub async fn read_characteristic_with_timeout(
707 &self,
708 uuid: Uuid,
709 read_timeout: Duration,
710 ) -> Result<Vec<u8>> {
711 let characteristic = self.find_characteristic(uuid).await?;
712 let data = timeout(read_timeout, self.peripheral.read(&characteristic))
713 .await
714 .map_err(|_| Error::Timeout {
715 operation: format!("read characteristic {}", uuid),
716 duration: read_timeout,
717 })??;
718 Ok(data)
719 }
720
721 pub async fn write_characteristic(&self, uuid: Uuid, data: &[u8]) -> Result<()> {
726 let characteristic = self.find_characteristic(uuid).await?;
727 timeout(
728 self.config.write_timeout,
729 self.peripheral
730 .write(&characteristic, data, WriteType::WithResponse),
731 )
732 .await
733 .map_err(|_| Error::Timeout {
734 operation: format!("write characteristic {}", uuid),
735 duration: self.config.write_timeout,
736 })??;
737 Ok(())
738 }
739
740 pub async fn write_characteristic_with_timeout(
742 &self,
743 uuid: Uuid,
744 data: &[u8],
745 write_timeout: Duration,
746 ) -> Result<()> {
747 let characteristic = self.find_characteristic(uuid).await?;
748 timeout(
749 write_timeout,
750 self.peripheral
751 .write(&characteristic, data, WriteType::WithResponse),
752 )
753 .await
754 .map_err(|_| Error::Timeout {
755 operation: format!("write characteristic {}", uuid),
756 duration: write_timeout,
757 })??;
758 Ok(())
759 }
760
761 #[tracing::instrument(level = "debug", skip(self), fields(device_name = ?self.name, device_type = ?self.device_type))]
767 pub async fn read_current(&self) -> Result<CurrentReading> {
768 let data = match self.device_type {
771 Some(DeviceType::Aranet4) => self.read_characteristic(CURRENT_READINGS_DETAIL).await?,
772 Some(DeviceType::Aranet2 | DeviceType::AranetRadon | DeviceType::AranetRadiation) => {
773 self.read_characteristic(CURRENT_READINGS_DETAIL_ALT)
774 .await?
775 }
776 None | Some(_) => {
777 match self.read_characteristic(CURRENT_READINGS_DETAIL).await {
779 Ok(data) => data,
780 Err(Error::CharacteristicNotFound { .. }) => {
781 debug!("Primary reading characteristic not found, trying alternative");
782 self.read_characteristic(CURRENT_READINGS_DETAIL_ALT)
783 .await?
784 }
785 Err(e) => return Err(e),
786 }
787 }
788 };
789
790 let device_type = match self.device_type {
792 Some(dt) => dt,
793 None => {
794 warn!(
795 "Device type unknown for {}; defaulting to Aranet4 — \
796 readings may be incorrect if this is a different model",
797 self.name().unwrap_or("unknown")
798 );
799 DeviceType::Aranet4
800 }
801 };
802 crate::readings::parse_reading_for_device(&data, device_type)
803 }
804
805 #[tracing::instrument(level = "debug", skip(self))]
807 pub async fn read_battery(&self) -> Result<u8> {
808 let data = self.read_characteristic(BATTERY_LEVEL).await?;
809 if data.is_empty() {
810 return Err(Error::InvalidData("Empty battery data".to_string()));
811 }
812 Ok(data[0])
813 }
814
815 #[tracing::instrument(level = "debug", skip(self))]
819 pub async fn read_device_info(&self) -> Result<DeviceInfo> {
820 fn read_string(data: Vec<u8>) -> String {
821 String::from_utf8(data)
822 .unwrap_or_default()
823 .trim_end_matches('\0')
824 .to_string()
825 }
826
827 let (
829 name_result,
830 model_result,
831 serial_result,
832 firmware_result,
833 hardware_result,
834 software_result,
835 manufacturer_result,
836 ) = tokio::join!(
837 self.read_characteristic(DEVICE_NAME),
838 self.read_characteristic(MODEL_NUMBER),
839 self.read_characteristic(SERIAL_NUMBER),
840 self.read_characteristic(FIRMWARE_REVISION),
841 self.read_characteristic(HARDWARE_REVISION),
842 self.read_characteristic(SOFTWARE_REVISION),
843 self.read_characteristic(MANUFACTURER_NAME),
844 );
845
846 let name = name_result
847 .map(read_string)
848 .unwrap_or_else(|_| self.name.clone().unwrap_or_default());
849
850 let model = model_result.map(read_string).unwrap_or_default();
851 let serial = serial_result.map(read_string).unwrap_or_default();
852 let firmware = firmware_result.map(read_string).unwrap_or_default();
853 let hardware = hardware_result.map(read_string).unwrap_or_default();
854 let software = software_result.map(read_string).unwrap_or_default();
855 let manufacturer = manufacturer_result.map(read_string).unwrap_or_default();
856
857 Ok(DeviceInfo {
858 name,
859 model,
860 serial,
861 firmware,
862 hardware,
863 software,
864 manufacturer,
865 })
866 }
867
868 #[tracing::instrument(level = "debug", skip(self))]
874 pub async fn read_device_info_essential(&self) -> Result<DeviceInfo> {
875 fn read_string(data: Vec<u8>) -> String {
876 String::from_utf8(data)
877 .unwrap_or_default()
878 .trim_end_matches('\0')
879 .to_string()
880 }
881
882 let (name_result, serial_result, firmware_result) = tokio::join!(
884 self.read_characteristic(DEVICE_NAME),
885 self.read_characteristic(SERIAL_NUMBER),
886 self.read_characteristic(FIRMWARE_REVISION),
887 );
888
889 let name = name_result
890 .map(read_string)
891 .unwrap_or_else(|_| self.name.clone().unwrap_or_default());
892 let serial = serial_result.map(read_string).unwrap_or_default();
893 let firmware = firmware_result.map(read_string).unwrap_or_default();
894
895 Ok(DeviceInfo {
896 name,
897 model: String::new(),
898 serial,
899 firmware,
900 hardware: String::new(),
901 software: String::new(),
902 manufacturer: String::new(),
903 })
904 }
905
906 pub async fn subscribe_to_notifications<F>(&self, uuid: Uuid, callback: F) -> Result<()>
912 where
913 F: Fn(&[u8]) + Send + Sync + 'static,
914 {
915 let characteristic = self.find_characteristic(uuid).await?;
916
917 self.peripheral.subscribe(&characteristic).await?;
918
919 let mut stream = self.peripheral.notifications().await?;
921 let char_uuid = characteristic.uuid;
922
923 let handle = tokio::spawn(async move {
924 use futures::StreamExt;
925 while let Some(notification) = stream.next().await {
926 if notification.uuid == char_uuid {
927 callback(¬ification.value);
928 }
929 }
930 });
931
932 self.notification_handles.lock().await.push(handle);
934
935 Ok(())
936 }
937
938 pub async fn unsubscribe_from_notifications(&self, uuid: Uuid) -> Result<()> {
940 let characteristic = self.find_characteristic(uuid).await?;
941 self.peripheral.unsubscribe(&characteristic).await?;
942 Ok(())
943 }
944
945 pub async fn cached_characteristic_count(&self) -> usize {
949 self.characteristics_cache.read().await.len()
950 }
951}
952
953impl Drop for Device {
966 fn drop(&mut self) {
967 if !self.disconnected.load(Ordering::SeqCst) {
968 self.disconnected.store(true, Ordering::SeqCst);
970
971 warn!(
973 device_name = ?self.name,
974 device_address = %self.address,
975 "Device dropped without calling disconnect() - performing best-effort cleanup. \
976 For reliable cleanup, call device.disconnect().await before dropping."
977 );
978
979 if let Ok(mut handles) = self.notification_handles.try_lock() {
982 for handle in handles.drain(..) {
983 handle.abort();
984 }
985 }
986
987 let peripheral = self.peripheral.clone();
990 let address = self.address.clone();
991
992 if let Ok(handle) = tokio::runtime::Handle::try_current() {
994 handle.spawn(async move {
995 if let Err(e) = peripheral.disconnect().await {
996 debug!(
997 device_address = %address,
998 error = %e,
999 "Best-effort disconnect failed (device may already be disconnected)"
1000 );
1001 } else {
1002 debug!(
1003 device_address = %address,
1004 "Best-effort disconnect completed"
1005 );
1006 }
1007 });
1008 }
1009 }
1010 }
1011}
1012
1013impl AranetDevice for Device {
1014 async fn is_connected(&self) -> bool {
1017 Device::is_connected(self).await
1018 }
1019
1020 async fn disconnect(&self) -> Result<()> {
1021 Device::disconnect(self).await
1022 }
1023
1024 fn name(&self) -> Option<&str> {
1027 Device::name(self)
1028 }
1029
1030 fn address(&self) -> &str {
1031 Device::address(self)
1032 }
1033
1034 fn device_type(&self) -> Option<DeviceType> {
1035 Device::device_type(self)
1036 }
1037
1038 async fn read_current(&self) -> Result<CurrentReading> {
1041 Device::read_current(self).await
1042 }
1043
1044 async fn read_device_info(&self) -> Result<DeviceInfo> {
1045 Device::read_device_info(self).await
1046 }
1047
1048 async fn read_rssi(&self) -> Result<i16> {
1049 Device::read_rssi(self).await
1050 }
1051
1052 async fn read_battery(&self) -> Result<u8> {
1055 Device::read_battery(self).await
1056 }
1057
1058 async fn get_history_info(&self) -> Result<crate::history::HistoryInfo> {
1061 Device::get_history_info(self).await
1062 }
1063
1064 async fn download_history(&self) -> Result<Vec<aranet_types::HistoryRecord>> {
1065 Device::download_history(self).await
1066 }
1067
1068 async fn download_history_with_options(
1069 &self,
1070 options: crate::history::HistoryOptions,
1071 ) -> Result<Vec<aranet_types::HistoryRecord>> {
1072 Device::download_history_with_options(self, options).await
1073 }
1074
1075 async fn get_interval(&self) -> Result<crate::settings::MeasurementInterval> {
1078 Device::get_interval(self).await
1079 }
1080
1081 async fn set_interval(&self, interval: crate::settings::MeasurementInterval) -> Result<()> {
1082 Device::set_interval(self, interval).await
1083 }
1084
1085 async fn get_calibration(&self) -> Result<crate::settings::CalibrationData> {
1086 Device::get_calibration(self).await
1087 }
1088}