Is there a better way to send tweaks to elixir source to RPi than burning firmware?

I’m new to both Elixir and RPi, so forgive me if I am missing something totally obvious.

After burning the firmware on the SD card, say I want to simply tweak one source file. Is there a simple way to do it? I don’t want to re-burn the SD card … that’s so 1980’s!

How does one do dynamic deployment at dev time?

1 Like

The simplest is to ssh into the device and copy-paste. You can also use sftp to upload the file then use Code.compile on the uploaded file. Both of those only work with elixir code, not nif’s.

Welcome to the forum :wave:!

Assuming you created your project with nerves.new then nerves_ssh should be enabled by default: GitHub - nerves-project/nerves_ssh: Nerves SSH support

The gist of it is to run mix firmware, then mix firmware.push (possibly providing the nerve’s device ip address). So that will upload the new firmware to the device and then it will automatically restart. If want to revert to the previous firmware you can ssh to the device and run Nerves.Runtime.revert.

3 Likes

Thanks, I’ll try this.

Maybe it is my mental model of “firmware”. I assume firmware to be the low level operating system stuff that needs the system to reboot. I don’t think of any elixir code as firmware. I imagined something like using a local vi to edit an elixiir file and compile it. I wasn’t thinking of burning the firmware again and pushing it to SD or via HTTP; is that an incremental process with rapid turnaround?

You’ll probably use both methods.

Nerves builds a “firmware” in that it contains everything to run the RPi, including the Linux kernel and your elixir code. Assuming you’re using Nerves for this. That firmware image is readonly.

However, since Elixir is an interpreted language you can upload code into a running Elixir VM (called the BEAM) via the Elixir shell (basically IEx). You can paste a module and it’ll then recompile the code and run it. Super handy for trying out i2c devices, html tweaks. It only takes a few seconds usually.

The copy/paste method gets tedious for big changes or lots of modules. Also any changes will be lost on reboot since it’s read-only. So you’ll want to make an updated firmware and push it regularly. It’s a bit slower to build the firmware since it’s rebuilding the entire rpi system, but not too bad (only a minute or two).

I tend too try several small changes via ssh and then upload a new firmware once I’m done. It’s sort of like connecting to a running docker container, tweaking code, and then building a new docker container with the changes and re-booting the container instance.

2 Likes

Thank you much for the detailed response.

I had assumed a hobby-mode of sorts, where not everything readonly, where you can send updates of files individually. Turnaround of even a few minutes is a bit distracting … that is too long a compile-edit-run-debug cycle. I had assumed that because it is a linux distribution, I would be able to scp or ftp over changes file by file and have the changes be permanent.

It looks like micropython too works the way nerves does … you have to flash the firmware to get the changes over.

Is it too much to expect for this state of affairs to change?

Nerves is not meant for “compile-edit-run-debug” (on the hardware).
Build the system on your workstation (write tests) and only occasionally do an integration test on the hardware.
Even if you are heavily using hardware peripherals, its normally not too hard to write tests.
If you don’t want to / can’t do that, you could just not use nerves for development and set up sth like this:

This could also be worth a look:

1 Like

Thank you much for your response.

Yes, I’d read about the incremental firmware patch, but it is still not a rapid turnaround. I would hazard to guess that a large fraction (>90%?) of nerves usage currently is instructional in nature, not deployment in production. For this reason, I think that there should be a dev mode that is oriented towards rapid iteration. The best examples are in ruby-on-rails or in the node ecosystem where any changes to a file is automatically applied. You’d not have this in production.

It seems to me that there is no reason in dev mode to have the entire image be mounted as read-only. Surely the elixir path for the app source files can be on a rw partition, no?

I don’t know what the implications would be.
But I sure know, that it would make the whole system more complex. When building a large system like nerves the best features are those that you do not implement. Keep the code clean and the team focused on the core of the problem. And nerves does a great job with that. Most of the benefits of nerves you only see if you want to build a robust product. Including a way to assemble the final product and feed it with updates. I’m happy the team is focused on that.

1 Like

I don’t see any increase in complexity. Erlang and Elixir have search paths to locate modules. All you need to do is to put a rw partition in that path, and have it automatically deploy on the next call … standard hot redeploy. The simplest would be to ssh into the device and edit it in-situ (for local debugging). Of course, when deployed in production mode, this facility would not be available.

Also I am not talking of the code that belongs to nerves … only the application under development. There is no reason to assume that every project is going to be huge and in production. I like the Perl motto: “Make simple things easy, and the hard things possible”.

It depends on your use case(s). SSH’ing into a Nerves device and iterating quickly is really nice for debugging new hardware or just learning. To be clear though, the “compiling” I mentioned before is just the standard Elixir code compilation, not an entire Nerves system compilation.

Nerves isn’t really a Linux distribution, precisely. It’s a very slimmed down Linux distribution, so many things you take for granted won’t be there. The reason for this is that it’s really targeted for IoT deployments. Having a “mutable” version would require a lot of re-engineering of the system and I don’t think it’d buy much relative to the effort.

However, see the next bit.

Most of what you talk about is possible, and pretty easy to do. Most Nerves images setup a writeable data ext4 partition mounted at /root/ by convention.

Assuming you have SFTP running as part of nerves_ssh (I believe sftp is enabled by default nowadays). Then you can copy files over to the data partition and use Code.compile_file/1 or the IEx “c” function to load the code.

Here’s the workflow:

  1. SFTP “lib/my_code/my_module.ex” to “/root/my_module.ex”
  • note: only /root/ is writeable by default
  • /root/ is created and formatted during first time you run the device
  1. ssh into the devices and (re)compile the code with: c("/root/my_module.ex")
  • the alternate way is Code.compile_file("/root/my_module.ex")
  1. repeat as desired
  • some things like, Phoenix changing routes or some Ecto changes may require a full mix firmware.upload
  • if some code stops “updating”, just do a mix firmware.upload

You can setup a script to automate that. The best way to setup a “hobby” mode would be to add some code that automatically checks for, and then loads any code found in /root/code-updates/. Really wouldn’t recommend it for deployments, but it’d only be a dozen lines or so to do.

1 Like

Excellent! This is what I was looking for.

Really wouldn’t recommend it for deployments …

Oh yes, absolutely. As I said, this is purely for rapid iteration during development.

Many many thanks. I’ll try it out.

1 Like

You may be interested in this post as well: Embedded Elixir

And if I recall correctly, @ConnorRigby was working on a VSCode extension to hot-reload the files that you edit, but I’m not sure if he’s still working on that.

1 Like

Thanks. Having the RPii and the host in the same network is nice, and as the post says, it needs to be written in a certain way for it to hot-deploy. Ultimately it relies on IEx.helpers.nl to “beam code over” (!). That’s v nice. But it only lasts until a reboot. Having the modified code installed on the RPi is a simpler choice, at least for beginners, I think. Simple file orientation, and use mix+scp to replicate only what’s changed.

To adapt one more time, Nerves just includes the Linux kernel and not the whole distribution. It’s primarily directive is running straight to the BEAM via an OTP release.

That aside, there are a few experimental pieces that might be of interest to your development use-case:

  • this explains a bit of the RO case and options to change that. Specifically, you can use the experimental --x-pivot-root-on-overlayfs option in erlinit.conf to make the fs writable and thus open the options for hot code reloading more.
  • you can get files to and from device for this reloading via SFTP as others have stated. You could even use SSHFS to mount the remote file system locally and work directly from host. See this post for more details
1 Like

This looks very juicy indeed. Thank you for your information.

And in general, thank you all for your prompt and courteous help to a newcomer!