So you want to write an embedded driver in Rust
Tags: rust, embedded, electronics
Published: 2019-12-07 21:19:10


_This is the fourth post along my journey to creating a biometric password manager. For an overview of the project, [read this post](https://dev.to/minkovsky/designing-a-biometric-password-manager-26kn)._

For my sins, I decided to pick a project where I try to learn way too many different things at the same time, and choose components with little resources and lots of work that needs to be put into them in order to get them working. And so I ended up in the middle of writing a driver for an obscure part in a language I don't know very well yet, so at least I'm gonna get a blog post out of this whole process.

# Poking it with a stick

When you see a new library or piece of tech that you want to use but don't know how, you tend to want to look at the documentation. Except in the case where the documentation's quite limited, partly due to being badly put together, and partly because what little there is, is badly translated. It's like you buy a car but then you realise that the whole dashboard is missing, and what you have is a photo of a dashbord from a similar type of car and a brief guide of what all the wires do, except some of the labels are confusing or wrong. So you decide to make your own dashboard because hey, you bought the car already.

The question is, when you start working on your dashboard, what bit do you hook up first? Ideally you want something that gives you some indication that the car is working, but doesn't actually cause it to do anything. Like the indicator lights. And so the very first request I implemented was for the status of my R502.

When you're dealing with something that you don't fully understand, you tend to go a bit conservative. I wanted to make my test request something that would not alter the state of the device, something analogous to a GET request in a REST API. Many embedded components offer this sort of endpoint one way or another - it's a useful tool for validating that the device has initialised properly and is ready for operation.

# Make it work first, make it pretty later, don't skip steps

Prepare to rewrite your code. A lot. You want to create something simple first, but the moment it outlives its usefulness, you have to replace it. Other people will look at your code, do you want them to yell at you on Twitter?

Remember how I said to do the simplest, non-destructive thing first? You should also do it in a fairly simple way. For instance, having a massive match statement that contains a handful of initial commands is okay for the start - but pretty soon you'll start wanting to split up the logic somehow into separate "classes" or modules. The final design of a driver like this should ideally separate the concerns - you'd want to put all the UART interface stuff in one layer, and packet serialisation/deserialisation in another.

One more thing: Write examples. Examples are the most important piece of documentation you can ever produce. They will also help you validate your API design since you'll be forced to use the API you create. In fact, I realised that I needed to change how my API is designed after writing an example that simply authenticates with the R502 - code consuming the library ended up looking clunky and not too much to my liking. Another thing you can do in the example is cheat - while the driver I'm writing is going to target embedded devices through the embedded-hal interface, there's nothing that says you have to do development on an embedded device to begin with - just write some bridge code between your favourite serial port support crate for an x64 architecture and embedded-hal, and you'll be able to compile and run your examples directly on your machine.

# Hell is other people's code

Remember the dubiously modified Arduino driver that I mentioned in the last post on the R502? Turns out that actually it can tell you quite a lot about the device if you actually pay attention to it, and also really understand the datasheet. What took me the longest was trying to figure out how to put the individual commands together to actually enroll and verify fingerprints. I remember one day I was trying to figure out what commands to implement after the initial handshake protocol. So I looked more thoroughly at the examples given in the shady Dropbox link that you have to email the manufacturer for. And at some point, it just clicked - but that will only happen if you really take the mental effort to understand the datasheet and the examples, and that's not easy considering that the datasheet is written in bad English and the examples are written in bad C++.

# A brief guide to the R502

From what I can understand now, the theory of operation of the R502 fingerprint sensor module is thus:

The front of the device is a type of camera. It uses capacitance to create an image - likely an offshoot from capacitive touch button technology. When you place a finger on the device and tell it to capture an image, it will measure how long it takes to charge each capacitor in (presumably) a matrix of 192x192 pixels, creating an image of your fingerprint. That image is stored in the R502's _image buffer_. There exist commands to download the image from the R502 to a host, or even upload another from the host - more on that in the speculations below. The R502 can then be instructed to process the image and put the result into one of two _character buffers_ - and I suspect that _character_ here doesn't refer to text but rather the characteristics of your finger.

Two character buffers? Why?

Well, to enroll a fingerprint, you have to capture two images of it. Because the device has little in the way of RAM - although it does rock a full fake STM32 micro - it's easier to have two character buffers rather than two image buffers. It's also easier to run the matching algorithm on already processed fingerprints. And if you want to match a specific fingerprint, you can actually load a character _file_ from R502's internal flash into one of the buffers and capture a new image into another. Then the matching is a question of "do the two character buffers look similar?" There is also a function in the device to match a newly captured fingerprint against the whole _library_ - all enrolled fingerprints on the device. The R502 supports up to 200.

So, what are the common workflows on this device then?

## Enrolling a fingerprint

This workflow registers a new fingerprint in the R502.

1. Capture a fingerprint image
2. Process it into character buffer 1
3. Capture another image of the same finger
4. Process it into character buffer 2
5. Generate a _template_ from both character buffers
6. Store the template into the _library_ at the given index

## Matching a specific fingerprint

This workflow matches a new fingerprint against a specific,
previously enrolled one.

1. Capture a fingerprint image
2. Process it into character buffer 1
3. Load the requested finger from flash into character buffer 2
4. Perform a match

## Searching for a fingerprint

This workflow matches a new fingerprint against the whole list of
previously enrolled fingers, and tells which one it is if there is
a match.

1. Capture a fingerprint image
2. Process it into character buffer 1
3. Perform a search

# The order of doing things

The principal rule of getting things to just work is to do the simplest thing first. However, after implementing a few commands, they are all roughly the same level of simple. Sure, some of them return their data in multiple packets, but we're not doing those yet. The second thing to consider is, which piece of work unblocks the biggest proportion of further development - and in this case, capturing a fingerprint image features in all three core workflows.

I am using github's projects functionaliy here to keep track of what I'm doing in an approximation of a kanban process. Each workflow has a project attached and issues are linked together - at the very least, the final workflow ticket will have a list of dependencies. It should help me keep track of what I need to do next - with the downside of making the project fully project-managed. I mean how am I supposed to just do the things in an order?

# What is the R502 for?

So here's the speculation section. The R502 is not an inherently secure device - I would love it to be able to do signed exchanges or even just basic encryption of packets being sent back and forth.

However, that's not quite what the device is designed for - apparently, you can upload fingerprint images to it from a host machine, which in theory means you can network the things and enroll someone on one and have them be enrolled on all of your readers. One of the use cases listed on the manufacturer's website is attendance tracking - for schools or perhaps shift work. Certainly the TSA has been using fingerprint-based shift clocks (which were [hilariously vulnerable](https://youtu.be/hbqVNlwfjxo?t=1043)), and I hear that this method of clocking in and out is popular in various places in Asia.

Some of the other accessories sold by HZ Grow include a control box for a solenoid. You plug your fingerprint reader and lock into it, and it'll lock and unlock stuff for you based on whether the fingerprint is correct. In fact there are gun safes that implement similar mechanisms, again [hilariously vulnerable](https://www.youtube.com/watch?v=rg9k12aTR5o) to attacks on anything other than the electronics.

# Call the forklift person

Right so I have this driver now. It doesn't do much but you are able to perform one of the main workflows outlined in a previous section, so I decided that it's a good time as any to release the bloody thing to the world and see what comes back. Hopefully nothing nasty. I picked the MIT licence for it because that's the default in Cargo and I don't hold any trademarks I wish to protect or patents I need to grant so there's no point of using the Apache 2.0 licence. Then I made sure I was happy with the documentation and finally did `cargo publish`.

It's there now. On the internet. Here: https://crates.io/crates/hzgrow-r502 (oh and the docs are here: https://docs.rs/hzgrow-r502/0.1.1/)

I should probably go add it to some list or something, in case someone wants to buy one of those fingerprint modules and start using it for something. There's probably fun to be had. Somehow.

Next time I hope to have more of the functionality done and have a demo running on an actual microcontroller. That should be an amount of fun as well.