Skip to content

Latest commit

 

History

History
206 lines (152 loc) · 6.05 KB

File metadata and controls

206 lines (152 loc) · 6.05 KB

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.