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

1c88d7906e3ffa450aedff2f5f1d1299?s=128

Florian Gilcher

May 26, 2020
Tweet

Transcript

  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. Diving tables

  4. Dive data

  5. Dive data

  6. 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
  7. 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
  8. A data sample <sample> <time>01:00</time> <vendor type="5" size="8">0000005AD4051100</vendor> <temperature>32.22</temperature> <depth>28.42</depth>

    <deco time="1020" depth="0.00">ndl</deco> </sample>
  9. 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
  10. 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
  11. 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 } });
  12. 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“
  13. 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.
  14. Memory Segments Devicve info Location pointers (1 page) Logbook ringbuffer

    Dive Profile ringbuffer
  15. 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, };
  16. 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) }
  17. 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.
  18. 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
  19. 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)?;
  20. 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
  21. 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
  22. 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
  23. 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!
  24. Next up • Write a binding to libdivecomputer • Implement

    common calculation algorithms myself