Deploying software to Microcontrollers (brain storming)

I’m looking more for a brain-storming session than to find specific answers.
I think there’s more than one “right way” to solve this.

A bunch of context

I’m working on a Nix based robotics platform. It’s going really well! I took a Roomba, slapped a Pi on it, enabled the Bluetooth, and started driving it around with a PS4 controller. I wanted to make it do more but the Roomba’s power limitations are stopping me (can’t use more than 25% of the CPU or attach sensors)
Here is the project for that robot

There’s a lot more work to be done before I think these tools are ready for public use but currently it can cross compile, build disk images (with unattended installers), push updates over ssh (supports automatic ssh jumping), and tools to help manage the NixOS firewall for ROS users since DDS tends to be a bit insanitary.

The next objective

This project has always required hardware. It started by just deploying software to a computer on a bench. From there I taped a Raspberry Pi to the top of a Roomba. Now it is time to build my own robot hardware.

Microcontrollers are necessary in robotics for low-level hardware control. Their crude IO, fast reaction times, and simpler (more reliable) software makes them ideal for managing batteries, sensors, and motors. This will be necessary for developing custom hardware for robots.

My current plan

Let’s call the computer on the robot running NixOS “the SoC”, and the Microcontroller we want to flash a binary to “the MCU”.

In prior research I discovered that Nix can be used to build Arduino sketches. The output of that can be passed to a flashing tool. There is no reason alternative platforms can’t be used, as long as a compatible binary is produced. With this, my current “deploy over ssh” solution can be used to get the binary onto the SoC.

The hard part is getting the binary from the SoC onto the MCU.

Putting together a simple tool to flash the binary to the MCU should be pretty simple. The probe-rs project already supports a lot of boards. A shell script wrapping Arduino CLI is probably another good option.

The complex part is determining if we should flash the MCU. Flashing the MCU every time we boot up will significantly reduce the lifespan of its memory. I’ve thought through a few ways to do this, but these two seemed the most practical.

Tracking the software version ourselves

Write the hash of the binary under /var/lib/flash_utility/last_install after we’ve installed it to the MCU. We then use that to check if we need to update it whenever the system powers up or rebuilds. The only downside I can think of to this is if you were to move the SoC to a new robot body where the firmware is out of sync with /var/lib/flash_utility/last_install. In that situation, I think it is reasonable for the user to just delete that file and run the flash utility again. As far as I know, the flash utility will need to run as root to access /var/lib. If it wasn’t for that, we would only need to run the flash utility under a user with dialout permissions or access to the necessary USB probes.

Asking the MCU for the software version

If the developer adds a way for the flash utility to ask the MCU for the hash of its current firmware, we could use that to check if its up to date. This depends on:

  1. The developer having implemented that feature
  2. The MCU being in a working state (actually having firmware on it to begin with)

I’ve chosen not to go with this approach because it felt like too much work to adapt already existing embedded software to. For example, we’d have to make sure they support the flash utilities communication protocol, and that a matching hash algorithm is implemented in whatever is running on that MCU (could be C, C++, Arduino C, Some proprietary fork of C, Rust, Micro Python, lots of others)

Known caveats with my current plan

This only works for boards with some kind of flashing tool attached. Most Arduino compatible devkits will fit this just fine. Some devices that expect to be flashed over Ethernet, I2C, SPI, UART, etc, will not work and will require a custom flash tool. That’s something I think is worth researching into later but I think it’s currently unnecessary.

The directory in /var/lib can just be owned by that special user.

It sounds like a very handy setup to me!

1 Like

I’m guessing a setup like that would require a systemd unit? As far as I understand, a Nix derivation (nix package) can only create files under its own derivation in the store. I still consider myself new to Nix so I apologize if I’m confusing things.

A package always resides in the store, yes, but your configuration can set that up as part of the environment. The simplest thing to do is

  users.user.myservice = {
    home = "/var/lib/myservice";
    group = "myservice";
    uid = NNN;
  };

Where you choose a uid that won’t conflict. But, you probably want a NixOS module that makes systemd service, anyway. When you have the systemd service, an even simpler approach is DynamicUser with StateDirectory (which takes a relative path under /var/lib). A simple example of this would be something like: nixpkgs/nixos/modules/services/search/tika.nix at b4346092ca78dff067983a5b285e1fdad8754d1a · NixOS/nixpkgs · GitHub

State directories! I had been looking for something like that but Googling didn’t find it. I was at least able to find it in the documentation after manually searching a bit.
I can even find services on my own system using that feature already. It’s perfect and you just made me really glad I asked.

2 Likes