Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

libdivecomputer

Build Status Crates.io Docs

Safe, idiomatic Rust bindings for libdivecomputer -- a cross-platform C library for communicating with dive computers from various manufacturers.

See also the repository containing the low-level libdivecomputer-sys FFI bindings.

Features

  • Context with builder pattern and configurable logging
  • Device descriptor enumeration and search
  • Device scanning across all transports (Serial, USB, USB HID, IrDA, Bluetooth, BLE, USB Storage)
  • One-call dive downloading with download_dives(), or low-level foreach for advanced control
  • Dive data parsing (depth, temperature, gas mixes, tank pressure, deco stops, etc.)
  • Auto-dispatching IoStream::open() -- no manual transport matching
  • Device memory read/write/dump and clock sync
  • Vendor-specific APIs (Heinrichs Weikamp, Atomics, Suunto, Oceanic, etc.)
  • BLE support via btleplug (feature-gated)

Usage

Add to your Cargo.toml:

[dependencies]
libdivecomputer = "0.2"

To disable BLE support (removes btleplug/tokio dependencies):

[dependencies]
libdivecomputer = { version = "0.2", default-features = false }

Examples

List supported dive computers

use libdivecomputer::{Context, Descriptor, LogLevel};

fn main() -> libdivecomputer::Result<()> {
    let ctx = Context::builder()
        .log_level(LogLevel::Warning)
        .build()?;

    for desc in Descriptor::iter(&ctx)? {
        println!("{desc} (family: {})", desc.family());
    }

    Ok(())
}

Find a specific device descriptor

use libdivecomputer::{Context, Descriptor, LogLevel};

fn main() -> libdivecomputer::Result<()> {
    let ctx = Context::builder()
        .log_level(LogLevel::Warning)
        .build()?;

    if let Some(desc) = Descriptor::find_by_name(&ctx, "Shearwater Petrel 3")? {
        println!("Found: {desc} (family: {})", desc.family());
        println!("Transports: {}", desc.transports());
    }

    Ok(())
}

Scan for connected devices

use libdivecomputer::{Context, LogLevel, scan};

fn main() -> libdivecomputer::Result<()> {
    let ctx = Context::builder()
        .log_level(LogLevel::Warning)
        .build()?;

    for transport in &ctx.get_transports() {
        println!("Scanning {transport}...");
        match scan(&ctx, transport).execute() {
            Ok(devices) => {
                for device in &devices {
                    println!("  Found: {} ({})", device.name, device.connection);
                }
            }
            Err(e) => eprintln!("  Error: {e}"),
        }
    }

    Ok(())
}

Download dives from a device

use libdivecomputer::{
    Context, Descriptor, Device, DeviceEvent, DownloadOptions, IoStream, LogLevel, Transport, scan,
};

fn main() -> libdivecomputer::Result<()> {
    let ctx = Context::builder()
        .log_level(LogLevel::Warning)
        .build()?;

    let desc = Descriptor::find_by_name(&ctx, "Shearwater Petrel 3")?
        .expect("device not found in descriptor database");

    // Scan and connect.
    let devices = scan(&ctx, Transport::Ble).execute()?;
    let device_info = devices.into_iter().next().expect("no device found");

    let iostream = IoStream::open(&ctx, &device_info.connection)?;
    let dev = Device::open(&ctx, &desc, iostream)?;

    // Download and parse all dives.
    let dives = dev.download_dives(&mut DownloadOptions {
        on_event: Some(Box::new(|event| {
            if let DeviceEvent::Progress { current, maximum } = event {
                println!("Progress: {:.0}%", 100.0 * current as f64 / maximum as f64);
            }
        })),
        ..Default::default()
    })?;

    for dive in &dives {
        println!(
            "Dive: {:.1}m, {} min, {}",
            dive.max_depth,
            dive.duration.as_secs() / 60,
            dive.start,
        );
    }

    Ok(())
}

Parse previously saved dive data

use libdivecomputer::{Context, Descriptor, LogLevel, Parser};

fn main() -> libdivecomputer::Result<()> {
    let ctx = Context::builder()
        .log_level(LogLevel::Warning)
        .build()?;

    let desc = Descriptor::find_by_name(&ctx, "Suunto EON Steel")?
        .expect("device not found");

    let data = std::fs::read("dive.bin").expect("failed to read dive file");
    let fingerprint = &data[12..16]; // device-specific fingerprint location

    let parser = Parser::from_descriptor(&ctx, &desc, &data)?;
    let dive = parser.parse(fingerprint)?;

    println!("Date: {}", dive.start);
    println!("Max depth: {:.1} m", dive.max_depth);
    println!("Duration: {} min", dive.duration.as_secs() / 60);
    println!("Gas mixes: {:?}", dive.gasmixes);
    println!("Samples: {}", dive.samples.len());

    Ok(())
}

Running the included examples

cargo run --example device_scanner                                      # scan for devices
cargo run --example device_download -- -d "Shearwater Petrel 3" -t BLE  # download dives
cargo run --example dive_parser -- -d "Suunto EON Steel" dives/*.bin    # parse saved data

License

Licensed under either of

at your option.

Note that libdivecomputer has its own LGPL-2.1 license.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.