Struct Device

Source
pub struct Device { /* private fields */ }
Expand description

Represents a connected Aranet device.

§Note on Clone

This struct intentionally does not implement Clone. A Device represents an active BLE connection with associated state (services discovered, notification handlers, etc.). Cloning would create ambiguity about connection ownership and could lead to resource conflicts. If you need to share a device across multiple tasks, wrap it in Arc<Device>.

§Cleanup

You MUST call Device::disconnect before dropping the device to properly release BLE resources. If a Device is dropped without calling disconnect, a warning will be logged.

§Timeouts

With default settings, Device::connect gives up after about 30 s of scanning on a device that the adapter doesn’t already know and that isn’t advertising. A device that the adapter still lists (on Linux a paired device, or one seen in the last 30 s or so; on macOS one that this process found in an earlier scan and hasn’t disconnected from since) is connected to without scanning. If it has gone, the connect fails within about 20 s (15 s connecting, up to 5 s to confirm the disconnect). A device that is found by scanning but refuses the connection fails after about 50 s (30 s scanning, 15 s connecting, up to 5 s to confirm the disconnect). One that connects slowly and then stops answering fails after at most about 70 s (adding up to 10 s each for service discovery and for reading its properties), and the rare retry after an empty service discovery can take about 102 s. On Linux, service discovery can take up to 20 s instead of 10 s while BlueZ is still discovering a device it has just connected (a first connection to an Aranet4, for example), so those two cases can take about 80 s and 122 s there. A search can also wait for another scan in the same process to finish. Use Device::connect_with_scan_options to change the scan time and ConnectionConfig to change the connect timeouts.

On Linux, a connection to a sensor that BlueZ doesn’t list as paired pairs it first, which can add up to connection_timeout plus the wait for BlueZ’s service discovery (discovery_timeout, but at least 20 s), plus 5 s: 40 s by default. A sensor that refused to pair is connected without pairing for 10 minutes.

Implementations§

Source§

impl Device

Source

pub async fn connect(identifier: &str) -> Result<Self>

Connect to an Aranet device by its address, identifier or full name.

identifier must match exactly, as find_device describes (case is ignored): a name must be the device’s whole name, not part of it. On macOS, where Bluetooth doesn’t expose MAC addresses, use the device’s identifier (a CoreBluetooth UUID) or its name.

The device is searched for with scans of 5, 10 and 15 s (up to about 30 s) and connected with the default ConnectionConfig. See the “Timeouts” section of Device for how long that can take.

§Errors
§Example
use aranet_core::device::Device;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let device = Device::connect("Aranet4 12345").await?;
    println!("Connected to {:?}", device);
    Ok(())
}
Source

pub async fn connect_with_timeout( identifier: &str, timeout: Duration, ) -> Result<Self>

Connect with a custom connection timeout.

identifier must match exactly, and errors are returned, as Device::connect describes.

The device search uses scans of 5, 10 and 15 s (up to about 30 s). timeout replaces only the connect timeout (connection_timeout); every other timeout keeps its default. Use Device::connect_with_scan_options to change the scan time as well.

Source

pub async fn connect_with_config( identifier: &str, config: ConnectionConfig, ) -> Result<Self>

Connect to an Aranet device with full configuration.

identifier must match exactly, and errors are returned, as Device::connect describes.

config sets every timeout of the connection itself. The device is searched for with scans of 5, 10 and 15 s (up to about 30 s); use Device::connect_with_scan_options to change the scan time too.

§Example
use std::time::Duration;
use aranet_core::device::{Device, ConnectionConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Longer connect timeouts for a challenging RF environment. They don't
    // lengthen the search for the device.
    let config = ConnectionConfig::challenging_environment();
    let device = Device::connect_with_config("Aranet4 12345", config).await?;
    Ok(())
}
Source

pub async fn connect_with_scan_options( identifier: &str, scan: ScanOptions, config: ConnectionConfig, ) -> Result<Self>

Find identifier with scan, then connect with config.

identifier must match exactly, and errors are returned, as Device::connect describes.

The search makes up to three scans of scan.duration / 2, scan.duration and 1.5 × scan.duration (at least 2, 4 and 6 s), so it takes up to about 3 × scan.duration, plus any wait for another scan in the same process. Only scan.duration is used: as in find_device_with_options, the search ignores scan’s filter flags. On macOS every scan asks the Bluetooth stack only for devices that advertise an Aranet service, as every current Aranet sensor does. A device that the adapter already knows from an earlier scan is used without scanning. config sets the timeouts of the connection itself; see the “Timeouts” section of Device.

§Example
use std::time::Duration;
use aranet_core::device::{ConnectionConfig, Device};
use aranet_core::scan::ScanOptions;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Scan for 10, 20 and 30 s (up to about 60 s, twice the default
    // search), then allow 30 s to connect.
    let scan = ScanOptions::default().duration(Duration::from_secs(20));
    let config = ConnectionConfig::default().connection_timeout(Duration::from_secs(30));
    let device = Device::connect_with_scan_options("Aranet4 12345", scan, config).await?;
    device.disconnect().await?;
    Ok(())
}
Source

pub async fn connect_with_adapter( adapter: Adapter, identifier: &str, config: ConnectionConfig, ) -> Result<Self>

Connect to a device using an existing BLE adapter.

This avoids creating a new btleplug Manager (and D-Bus connection) on every call. Prefer this over connect_with_config in long-running services that poll devices repeatedly.

Like connect_with_config, it searches once with scans of 5, 10 and 15 s (up to about 30 s), and config sets the timeouts of the connection itself.

identifier must match exactly, and errors are returned, as Device::connect describes.

Source

pub async fn from_peripheral( adapter: Adapter, peripheral: Peripheral, ) -> Result<Self>

Create a Device from an already-discovered peripheral.

Source

pub async fn from_peripheral_with_timeout( adapter: Adapter, peripheral: Peripheral, connect_timeout: Duration, ) -> Result<Self>

Create a Device from an already-discovered peripheral with custom timeout.

Source

pub async fn from_peripheral_with_config( adapter: Adapter, peripheral: Peripheral, config: ConnectionConfig, ) -> Result<Self>

Create a Device from an already-discovered peripheral with full configuration.

If the connect fails or times out, the peripheral is disconnected before the error is returned. If this future is dropped before it finishes (a caller’s timeout, a cancelled task), the peripheral is disconnected in the background; that is best effort if the process is exiting. Either way the disconnect waits at most 5 s for the Bluetooth stack to confirm it.

On Linux, if BlueZ doesn’t list the sensor as paired, this pairs it first (BlueZ’s Device1.Pair, through an agent that exists only for that pairing and approves only this sensor), which takes at most connection_timeout plus the wait for BlueZ’s service discovery (discovery_timeout, but at least 20 s), plus 5 s. If pairing fails, a warning with the bluetoothctl commands that fix it is logged and the connection goes ahead unpaired. BlueZ then asks for pairing itself, so on a host without a Bluetooth agent this connection’s reads can time out. A sensor that refused to pair isn’t asked again for 10 minutes; after any other failure, the next connection pairs again.

Source

pub async fn is_connected(&self) -> bool

Check if the device is connected (queries BLE stack state).

Returns false if the Bluetooth stack reports an error or doesn’t answer within the connection’s validation_timeout (3 s by default, set with ConnectionConfig::validation_timeout). On macOS the stack stops answering once the sensor has dropped the connection.

Note: This only checks the BLE stack’s connection state, which may be stale, especially on macOS. For a more reliable check, use Self::validate_connection.

Source

pub async fn validate_connection(&self) -> bool

Validate the connection by reading the current measurements.

This reads the characteristic that Self::read_current uses, so the check needs no pairing that a reading doesn’t need (unpaired Aranet2 and AranetRn+ sensors answer it) and never starts a pairing that a reading wouldn’t. It fails only if the read fails or takes longer than the connection’s validation_timeout (3 s by default, set with ConnectionConfig::validation_timeout).

This is more reliable than is_connected() as it actively verifies the connection is working. It detects “zombie connections”, where the BLE stack thinks it’s connected but the device is actually out of range.

§Returns

true if the connection is active and responsive, false otherwise.

Source

pub async fn is_connection_alive(&self) -> bool

Check if the connection is alive by reading the current measurements.

This is an alias for Self::validate_connection that better describes the intent when used for connection health monitoring. Like it, it needs no pairing that a reading doesn’t need, and fails only if the read fails or takes longer than the connection’s validation_timeout.

§Example
ⓘ
// In a health monitor loop
if !device.is_connection_alive().await {
    // Connection lost, need to reconnect
}
Source

pub fn config(&self) -> &ConnectionConfig

Get the current connection configuration.

Source

pub async fn signal_quality(&self) -> Option<SignalQuality>

Get the current signal quality based on RSSI.

Returns None if RSSI cannot be read.

Source

pub async fn disconnect(&self) -> Result<()>

Disconnect from the device.

This will:

  1. Abort all active notification handlers
  2. Disconnect from the BLE peripheral

Returns Error::Timeout if the Bluetooth stack doesn’t confirm the disconnect within 5 s. The disconnect runs as a background task on aranet-core’s own runtime, or on the caller’s runtime if aranet-core’s can’t be started, so it completes even if this future is dropped, for example by a caller’s timeout. Returns Error::Io if there is no runtime to run it on at all, or if the task panicked or its runtime shut down before it finished.

Important: You MUST call this method before dropping the Device to ensure proper cleanup of BLE resources.

Source

pub fn name(&self) -> Option<&str>

Get the device name.

Source

pub fn address(&self) -> &str

Get the device address or identifier.

On Linux and Windows, this returns the Bluetooth MAC address (e.g., “AA:BB:CC:DD:EE:FF”). On macOS, this returns a UUID identifier since MAC addresses are not exposed.

Source

pub fn device_type(&self) -> Option<DeviceType>

Get the detected device type.

Source

pub async fn read_rssi(&self) -> Result<i16>

Read the current RSSI (signal strength) of the connection.

Returns the RSSI in dBm. More negative values indicate weaker signals. Typical values range from -30 (strong) to -90 (weak).

Returns Error::Timeout if the Bluetooth stack doesn’t answer within the connection’s read_timeout (10 s by default, set with ConnectionConfig::read_timeout).

Source

pub async fn read_characteristic(&self, uuid: Uuid) -> Result<Vec<u8>>

Read a characteristic value by UUID.

This method includes a timeout to prevent indefinite hangs on BLE operations. The timeout is controlled by ConnectionConfig::read_timeout.

Source

pub async fn read_characteristic_with_timeout( &self, uuid: Uuid, read_timeout: Duration, ) -> Result<Vec<u8>>

Read a characteristic value with a custom timeout.

Use this when you need a different timeout than the default, for example when reading large data.

Source

pub async fn write_characteristic(&self, uuid: Uuid, data: &[u8]) -> Result<()>

Write a value to a characteristic.

This method includes a timeout to prevent indefinite hangs on BLE operations. The timeout is controlled by ConnectionConfig::write_timeout.

Source

pub async fn write_characteristic_with_timeout( &self, uuid: Uuid, data: &[u8], write_timeout: Duration, ) -> Result<()>

Write a value to a characteristic with a custom timeout.

Source

pub async fn read_current(&self) -> Result<CurrentReading>

Read current sensor measurements.

Automatically selects the correct characteristic UUID based on device type:

  • Aranet4 uses f0cd3001
  • Aranet2, Radon, Radiation use f0cd3003
  • an unknown type tries f0cd3001, then f0cd3003 if the device doesn’t have it
Source

pub async fn read_battery(&self) -> Result<u8>

Read the battery level (0-100).

Source

pub async fn read_device_info(&self) -> Result<DeviceInfo>

Read device information.

This method reads all device info characteristics in parallel for better performance.

Source

pub async fn read_device_info_essential(&self) -> Result<DeviceInfo>

Read essential device information only.

This is a faster alternative to Self::read_device_info that only reads the most critical characteristics: name, serial number, and firmware version. Use this for faster startup when full device info isn’t needed immediately.

Source

pub async fn subscribe_to_notifications<F>( &self, uuid: Uuid, callback: F, ) -> Result<()>
where F: Fn(&[u8]) + Send + Sync + 'static,

Subscribe to notifications on a characteristic.

The callback is called on a background task with the value of each notification the characteristic sends, until unsubscribe_from_notifications or disconnect is called or the device is dropped.

Subscribing to the same characteristic again replaces its callback. If subscribing fails, the previous callback keeps running.

Opening the notification stream and enabling notifications on the device each give up with Error::Timeout after the connection’s write_timeout (set with ConnectionConfig::write_timeout).

Source

pub async fn unsubscribe_from_notifications(&self, uuid: Uuid) -> Result<()>

Unsubscribe from notifications on a characteristic.

Stops the characteristic’s callback first, then tells the device to stop sending notifications. Once this returns, the callback is not called again, even if the device call fails.

Waiting for the callback to stop and the device call each give up after the connection’s write_timeout (set with ConnectionConfig::write_timeout); the device call then fails with Error::Timeout.

Source

pub async fn cached_characteristic_count(&self) -> usize

Get the number of cached characteristics.

This is useful for debugging and testing to verify service discovery worked.

Source§

impl Device

Source

pub async fn get_history_info(&self) -> Result<HistoryInfo>

Get information about the stored history.

Source

pub async fn download_history(&self) -> Result<Vec<HistoryRecord>>

Download all historical readings from the device.

Source

pub async fn download_history_with_options( &self, options: HistoryOptions, ) -> Result<Vec<HistoryRecord>>

Download historical readings with custom options.

§Device Support
  • Aranet4: Downloads CO₂, temperature, pressure, humidity
  • Aranet2: Downloads temperature, humidity
  • AranetRn+ (Radon): Downloads radon, temperature, pressure, humidity
  • Aranet Radiation: Not supported - returns an error. The device protocol for historical radiation data requires additional documentation. Use Device::read_current() to get current radiation readings.
§Adaptive Delay

If options.use_adaptive_delay is enabled, the read delay will be automatically adjusted based on the connection’s signal quality.

§Checkpointing

If a checkpoint callback is set, progress will be saved periodically to allow resuming interrupted downloads.

Source

pub async fn download_history_v1(&self) -> Result<Vec<HistoryRecord>>

Download history using V1 protocol (notification-based).

This is used for older devices that don’t support the V2 read-based protocol. V1 uses notifications on the HISTORY_V1 characteristic.

Source§

impl Device

Source

pub async fn get_interval(&self) -> Result<MeasurementInterval>

Get the current measurement interval.

Source

pub async fn set_interval(&self, interval: MeasurementInterval) -> Result<()>

Set the measurement interval.

The device will start using the new interval after the current measurement cycle completes.

Note: This method does not verify the write succeeded. For verified writes, use Self::set_interval_verified.

Source

pub async fn set_interval_verified( &self, interval: MeasurementInterval, ) -> Result<()>

Set the measurement interval with verification.

This method writes the new interval and then reads it back to verify the change was applied successfully. Use this for critical settings changes where confirmation is needed.

§Errors

Returns Error::WriteFailed if the read-back value doesn’t match the requested interval.

Source

pub async fn set_smart_home(&self, enabled: bool) -> Result<()>

Enable or disable Smart Home integration.

When enabled, the device advertises sensor data that can be read without connecting (passive scanning).

Note: This method does not verify the write succeeded. For verified writes, use Self::set_smart_home_verified.

Source

pub async fn set_smart_home_verified(&self, enabled: bool) -> Result<()>

Enable or disable Smart Home integration with verification.

This method writes the setting and then reads it back to verify the change was applied successfully.

§Errors

Returns Error::WriteFailed if the read-back value doesn’t match the requested setting.

Source

pub async fn set_bluetooth_range(&self, range: BluetoothRange) -> Result<()>

Set the Bluetooth range.

Note: This method does not verify the write succeeded. For verified writes, use Self::set_bluetooth_range_verified.

Source

pub async fn set_bluetooth_range_verified( &self, range: BluetoothRange, ) -> Result<()>

Set the Bluetooth range with verification.

This method writes the setting and then reads it back to verify the change was applied successfully.

§Errors

Returns Error::WriteFailed if the read-back value doesn’t match the requested setting.

Source

pub async fn get_calibration(&self) -> Result<CalibrationData>

Read calibration data from the device.

Source

pub async fn get_settings(&self) -> Result<DeviceSettings>

Read device settings from the SENSOR_STATE characteristic.

This reads the device configuration including:

  • Smart Home integration status
  • Bluetooth range setting
  • Temperature display unit
  • Radon display unit (for Aranet Radon devices)
  • Buzzer settings
  • Calibration settings

Trait Implementations§

Source§

impl AranetDevice for Device

Source§

async fn is_connected(&self) -> bool

Check if the device is connected.
Source§

async fn disconnect(&self) -> Result<()>

Disconnect from the device.
Source§

fn name(&self) -> Option<&str>

Get the device name, if available.
Source§

fn address(&self) -> &str

Get the device address or identifier. Read more
Source§

fn device_type(&self) -> Option<DeviceType>

Get the detected device type, if available.
Source§

async fn read_current(&self) -> Result<CurrentReading>

Read the current sensor values.
Source§

async fn read_device_info(&self) -> Result<DeviceInfo>

Read device information (model, serial, firmware version, etc.).
Source§

async fn read_rssi(&self) -> Result<i16>

Read the current RSSI (signal strength) in dBm. Read more
Source§

async fn read_battery(&self) -> Result<u8>

Read the battery level (0-100).
Source§

async fn get_history_info(&self) -> Result<HistoryInfo>

Get information about stored history.
Source§

async fn download_history(&self) -> Result<Vec<HistoryRecord>>

Download all historical readings.
Source§

async fn download_history_with_options( &self, options: HistoryOptions, ) -> Result<Vec<HistoryRecord>>

Download historical readings with custom options.
Source§

async fn get_interval(&self) -> Result<MeasurementInterval>

Get the current measurement interval.
Source§

async fn set_interval(&self, interval: MeasurementInterval) -> Result<()>

Set the measurement interval.
Source§

async fn get_calibration(&self) -> Result<CalibrationData>

Read calibration data from the device.
Source§

async fn connect(&self) -> Result<()>

Connect to the device. Read more
Source§

impl Debug for Device

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl DeviceStreamExt for Device

Source§

fn stream(self: Arc<Self>) -> ReadingStream

Create a reading stream with default options. Read more
Source§

fn stream_with_options(self: Arc<Self>, options: StreamOptions) -> ReadingStream

Create a reading stream with custom options.
Source§

impl Drop for Device

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

§

impl !Freeze for Device

§

impl !RefUnwindSafe for Device

§

impl Send for Device

§

impl Sync for Device

§

impl Unpin for Device

§

impl !UnwindSafe for Device

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,