Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Using Rust to interface with my dive computer

Using Rust to interface with my dive computer

Florian Gilcher

May 26, 2020

More Decks by Florian Gilcher

Other Decks in Programming


  1. Using Rust to talk to/about my dive computer Florian Gilcher

    / @skade / @argorak https://ferrous-systems.com/ https://cfp.oxidizeconf.com/events/oxidize-global-2020
  2. What is a dive computer? • Dive computers are the

    most crucial security equipment you can have while diving • Constantly tracks your depth and your (calculated) absorption of Nitrogen • Absorbed Nitrogen is the cause of divers' sickness • Continues tracking the release of Nitrogen during surface times • Calculates the time you can spend at depth • Checks a safe ascent speed • Usually provides a tracking function for later
  3. How a dive computer works on the surface • The

    dive computer is always in waiting mode • It provides a simple configuration interface • Gas mixture • Timer and warning settings • It does not allow to switch off safety features • After a dive, stays in “No Fly mode” for 24 hours • Cannot be switched off • Provides dive history • Marks dives where you ascended too quickly
  4. How a dive computer works at depth • Dive mode

    is activated on first contact with water • Tracks every tick • Depth • Temperature • Remaining “No Deco(mpression)”-Time • If “0”, decompression depth • Saves those data points regularly • Calculates your modelled Nitrogen absorption on every tick • Presents you with: • Remaining time at current depth • Ascent/Descent speed • Separate “stop” mode
  5. Interfacing with the dive computer • Purely for reading •

    Dive computers are security equipment, don’t mess with it • Buy a cable (60 EUR for a 200 EUR computer) • Figure out it doesn’t connect properly • Try to figure out what the dive computer actually exposes • Hint: it’s rarely documented • Use libdivecomputer as a reference
  6. libdivecomputer • Natural layering • Hardware type • Maker type

    • Memory layout type • Works mostly by listing • Page sizes on the computer • Address models • USB/Serial differentiation • Fully Iterator-Based • Fully dynamic dispatch based
  7. Connecting to the computer let computer = serialport::available_ports()? .into_iter() .find(|info|

    { if let SerialPortInfo { port_type: SerialPortType::UsbPort(info), .. } = info { if info.vid == 1027 && info.pid == 62560 { true } else { false } } else { false } });
  8. Sending a Message let i300 = computer.unwrap(); let mut port

    = serialport::open(&computer.port_name) ?; let cmd: &[u8] = &[0x84, 0x00]; let res = port.write(cmd); This will trigger the computer to respond with its name. "AQUAI300 \0\0 512K“
  9. Direct Memory Access: Page Based Addressing Dive page pointers 16

    bit 1 ... 64 65 66 67 Each page (on an i300) is 16 bytes big.
  10. static I300_LAYOUT: Layout = Layout { memsize: 0x10000, highmem: 0,

    cf_devinfo: 0x0000, cf_pointers: 0x0040, rb_logbook_begin: 0x0240, rb_logbook_end: 0x0A40, rb_logbook_entry_size: 8, rb_profile_begin: 0x0A40, rb_profile_end: 0xFE00, };
  11. Reading pointers fn read_pointers(port: &mut dyn SerialPort) - > Result<Vec<u8>,

    Error> { let mut pointers = vec![0; PAGESIZE]; device_read(port, I300_LAYOUT.cf_pointers, &mut pointers)?; Ok(pointers) }
  12. Finding out places use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt}; let pointers

    = read_cf_pointers(port)?; let mut rdr = Cursor::new(&pointers[4..]); let rb_logbook_first = rdr.read_u16::<LittleEndian>().unwrap(); let rb_logbook_last = rdr.read_u16::<LittleEndian>().unwrap(); These two are pointers into a ringbuffer, giving us the first and last logbook locations. The logbook just contains general dive data and is of fixed size. The ringbuffer size is known.
  13. Reading a page: libdivecomputer • You need to send the

    computer: [0xB1, page as u16, 0] unsigned char command[] = {0xB1, (number >> 8) & 0xFF, // high (number ) & 0xFF, // low }; // number is the page number, retrieved from the divelog pointers
  14. My Rust version let command: &mut [u8] = &mut [0;

    4]; let mut cursor = Cursor::new(command); cursor.write_u8(read_cmd)?; cursor.write_u16::<BigEndian>(pageno as u16)?; cursor.write_u8(0)?;
  15. Rust may be from Mars, C from Venus • Differences

    in idiomatic encoding • Pointers vs. Values • Differences in Algorithm abstraction • Cursor type for reading instead of a function • Rust favors indexing, C favours pointer calculations • Dynamic dispatch is common in C, avoided in Rust
  16. Problems porting old, good libraries • Ported library has optimisations

    • Example: Dives are read backwards • Complete libraries are vast • Example: Support for 3 different page models of just my computers make
  17. Takeaways • Dive computers are KISS, applied thoroughly • And

    have awesome users documentation! • Lingo is the hardest to learn • A good C codebase is good to read up on, but a mediocre architecture reference • Implementing off a data sheet would be easier • The page based memory model is actually nice to learn on • \0\0 seems to be a separator people use
  18. Why I stopped working on the project • No way

    to compete with libdivecomputer • At least without hundreds of computer models • Iterator model is easy to interface with using Rust • Dynamic dispatch model is easy to interface with using Rust • I got my fun out of it!