Putting code on a Nerves device
2024-09-16Underjord is a tiny, wholesome team doing Elixir consulting and contract work. If you like the writing you should really try the code. See our services for more information.
The Nerves project is a way of building embedded Linux devices where the BEAM virtual machine takes care of running things. This does not constrain what you can run in any significant way.
The easiest way to think of it is as a replacement for systemd. Though it is not only that. I will attempt to explain the different ways you can run things via the BEAM and consequently, on Nerves.
1. BEAM code (Elixir, Erlang & friends)
If you write your darned application the it was intended. In a Elixir, or Erlang, or probably Gleam, potentially even LfE. Then you’ll have the loveliest time. Not because that’s given preferential treatment, rather just because these languages are capably of utilizing the whole runtime.
You get light-weight processes that communicate through message passing. Resilient pre-emptive scheduling. Consistently low-latency. Hot code updates which we use heavily in Nerves because it is really nice to just paste code into the device during development instead of reflashing.
And you get easy access to libraries, the Phoenix web framework and the parts just fit together. Beyond that the Nerves runtime is built in this same manner and you can interact with things like your network configuration, firmware update and plenty of hardware via message passing and function calls. This makes it very easy to write systems that react live to changes.
In my world-view this is where you want to spend the bulk of your development time because you get quick iteration speed, great capabilities and a lovely language that does a decent job of keeping you from making a mess.
2. Native Implemented Functions or C + BEAM
This is number two in terms of how close to the BEAM it is, not for how recommended it is. The risk of NIFs must be stated. You segfault in a NIF and the BEAM cannot protect you and your undermine the resilience of it. The Nerves variant of the Erlang heart module might still save you. But a lot of the safeties of the higher level of abstraction go out the window. You are running code directly in the BEAM.
The various kinds of NIFs are a bit much to get into here. If you can slice the work in thin timeslices the old-school NIFs are rad because the scheduler can treat them much like it treats any BEAM process. But there are also dirty NIFs that run on a different life-cycle with “worse” guarantees but a much more manageable programming model.
C is the basic way of doing this. Then you have Zig via the tool Zigler. And of course Rust via Rustler. Both Zig and Rust to some extent reduce the risk of you segfaulting the BEAM so they are potentially preferable.
3. Ports
There are several ways of running Ports. This is an Erlang concept where a Port is an external process that you start from the BEAM and communicate with via stdin and stdout. In Nerves we typically use a Linux-specific and more featureful Muontrap library. It supports cgroup stuff to control memory and CPU usage of the processes. This can be plugged right into your BEAM supervision tree and have it’s lifecycle managed like a proper gentleprocess.
So if you have a vendor-provided binary you have to run, some Python script, some nice statically compiled Go binary. This is a good path. With tools like beam_notify you can even pass reasonable BEAM messages from shell scripts.
That’s it?
These three cover everything you need. Especially number 3, the Ports. You can run processes on Linux. What else do you need?
But there are several additional interesting things you can do.
4. C Node
You can build an application that pretends it is an Erlang node and.. I would say that this is a stretch for most Nerves usage. Let me know if you have different ideas. But it is one of the Erlang mechanisms for interop.
5. Containers
Apparently containers are cool.
Actually, they are cool. I don’t like most of the tools around them but it is very practical sometimes to just pack up and ship the developer machine to not figure out what exact combo of deps will make node happy. By default Nerves does not ship a container runtime because they are a lot of extra stuff when not needed and in most embedded projects you can just ship a firmware update when you want to ship new code. But it can make sense either because you get containers from somewhere or if you have a need for very isolated and selective updates. Or you just really like it architecturally.
Several people have run Docker and variants on Nerves for this. Steffen Deusch put together a few systems using Balena Engine which I hadn’t heard of. It is a container runtime that keeps things quite lean in the service of embedded use. I’ve tried it and had it work. It is not an officially maintained set of systems. If enough people request containers we might consider it.
6. Special language implementations
If you want a sandboxed language to run on device I would look at luerl which is an implementation of Lua in Erlang which has some really cool facets to it. Every run of Lua code returns a new state of the Lua program which means you can fork off the execution or save, load and so on. Quite neat.
The highly experimental pythonx library also offers and embedded language but this time by putting Python in a NIF. Quite wild, very early. But damn if that is not a fast path to AI/ML work.
Conclusion
You can ship your machine learning as Python if you want to. Either include Python in your system and run via a Port or run it in a container. Nerves doesn’t care. There are a lot of nice libraries for improved interop between Python and Erlang programs actually.
You can run you cool Go binaries and your uncool hardware vendor software.
For most of the work I do recommend using the Elixir that is right there at your fingertips.
Regardless the Nerves project provides you what you need.
If you have any questions about Nerves or this topic. Don’t hesitate to reach out, I am @lawik on the Fediverse and my email is lars@underjord.io. Hope you can run your code in a satisfactory way.
Underjord is a 4 people team doing Elixir consulting and contract work. If you like the writing you should really try the code. See our services for more information.
Note: Or try the videos on the YouTube channel.