Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Diving tables

Slide 4

Slide 4 text

Dive data

Slide 5

Slide 5 text

Dive data

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

A data sample 0000005AD4051100 32.22 28.42 ndl

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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 } });

Slide 12

Slide 12 text

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“

Slide 13

Slide 13 text

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.

Slide 14

Slide 14 text

Memory Segments Devicve info Location pointers (1 page) Logbook ringbuffer Dive Profile ringbuffer

Slide 15

Slide 15 text

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, };

Slide 16

Slide 16 text

Reading pointers fn read_pointers(port: &mut dyn SerialPort) - > Result, Error> { let mut pointers = vec![0; PAGESIZE]; device_read(port, I300_LAYOUT.cf_pointers, &mut pointers)?; Ok(pointers) }

Slide 17

Slide 17 text

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::().unwrap(); let rb_logbook_last = rdr.read_u16::().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.

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

My Rust version let command: &mut [u8] = &mut [0; 4]; let mut cursor = Cursor::new(command); cursor.write_u8(read_cmd)?; cursor.write_u16::(pageno as u16)?; cursor.write_u8(0)?;

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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!

Slide 24

Slide 24 text

Next up • Write a binding to libdivecomputer • Implement common calculation algorithms myself