Turning things on

Getting Started with Elixir and Nerves: Part 1

Part One: Deploying Elixir to a Raspberry Pi

Intro

Note: this is part one of a two part series on Elixir, Nerves and the Raspberry Pi:

Last year, I was fortunate enough to attend the Lone Star Elixir Conference and take one of the training classes on using Nerves and Raspberry Pi Zero. If you don’t now anything about Elixir or Nerves, Elixir is a functional programming language with a Ruby-like syntax and tooling, yet runs on Erlang’s rock solid VM. Nerves is the framework that allows you to deploy Elixir applications on IoT style devices like the Raspberry Pi and Beagle Bone. Elixir and Nerves are great tools for programming small, embedded devices. Nerves takes care of a lot of common tasks for dealing with embedded devices and includes toolkits for easy control of the electronics such as GPIO.

I decided to brush off my Raspberry Pi Zero from the nerves class I attended.

What are we doing?

In this article we will:

  • Create a firmware using Elixir and Nerves and burn it to an SD card
  • Wire up a Pi to our laptop via USB
  • SSH to the Pi and run code directly on the device
  • Control the on board LED on the device via that code

In the next part of this series, we’ll take it a step further and control an external LED on a breadboard.

Not familiar with Elixir, Nerves or the Raspberry Pi? On the off chance you’re not these links should get you started:

Parts

There aren’t too many parts needed to get started. You will need:

  • a breadboard
  • some wires
  • an LED. I’m using blue one.
  • a resistor. I’m using a 340ohm with 5% tolerance.
  • A micro-USB cable.
  • a raspberry pi. I’m using a pi zero.
  • an sd card that fits the pi
  • A 9v battery (optional)

You might invest in a [Canakit] to get everything you need in one shot if you don’t have a bunch of electronics. Since I’m new to this stuff myself, I borrowed one from Audion.

A note on the USB cable. Apparently, some cables are charging only, i.e. they won’t pass data. We need one that can transmit data as we’re going to connect it to our computer. If you get a kit you’ll probably get the right one included, but in my case, I went thru a few before I realized they were not data cables. The pi zero used USB micro, but your laptop and pi could require a different type of USB connection.

Sofware Setup

Before we do anything, we need the proper software installed. Rather than enumerate those steps here, use your preferred package management tool (homebrew, apt-get) to install this:

We’ll be using mix extensively, which is similar to Ruby’s rake, so it might be helpful if you took a peek at those docs as well if you’re entirely new to elixir.

A nerves project is really just a mix project with some IoT specific frameworks.

I also am a fan of ASDF as it lets me manage multiple versions of multiple languages such as elixir, erlang, ruby, python and more. This is entirely optional though.

Getting Started

Once you have the dependencies installed, we can try something fairly simple. Most Raspberry Pis have a built in LED that you can control with nerves and elixir. Don’t worry, we will definitely do the electronics part as well. But its useful to just see if we can get nerves up and running and do something simple.

First we’ll create a new nerves project. Type the following commands into a terminal:

mix nerves.new nerves_heartbeat

cd nerves_heartbeat

export MIX_TARGET=rpi0

These lines do the following:

  • create a new nerves project called “nerves_heartbeat”
  • change directories into the new project
  • set the MIX_TARGET environment variable to rpi0 for my pi zero.

That last part is crucial. Many commands won’t run if you don’t set MIX_TARGET and its not always obvious what is wrong. This was mentioned to me at the conference but it has bitten me several times since. I’m using a Pi Zero, so my MIX_TARGET is set to rpi0. As you probably know, exporting a variable at the linux or mac command line, will only live for that session. If you close or exist that session, you’ll have to set it again. You can also add MIX_TARRGET=rpi0 at the beginning of most commands.

If you are using a raspberry pi 2, 3 or 4, you’ll change rpi0 to whatever you’re using.

Once that is done, we’ll add some dependencies to our application. Open the mix.exs file in the root of the application and add the following to defp deps section.

  # specific to heartbeat
  {:nerves_leds, "~> 0.8", targets: @all_targets},
  {:circuits_gpio, "~> 0.4", targets: @all_targets},

Your version numbers might vary if you’re using a newer version of nerves then when this article was written. I’m using Nerves 1.4.

These two libraries import some handy tools for interacting with the Raspberry Pis circuitry. nerves_leds lets us easily talk to the built in LED on the pi, and circuits_gpio allows us to talk to the GPIO pins, which stand for “general input/output”, hence the name.

Now, to control the built in LED, we’ll need to configure one thing. Open the file config/config.exs and add the following:

config :nerves_leds, names: [green: "led0"]

This just maps an LED we’re calling “green” to a folder on the filesystem called “led0” which controls the LED (more on this in a bit.) Then we can reference this LED by name, “green”, in elixir. Once you’ve saved your file, we can try to build the nerves project to our pi. Insert your SD card into your comptuer and run the following commands on your PC:

mix nerves.release.init

mix firmware.burn

These two commands will create a release configuration file and burn the firmware on to your SD card. On my mac, the computer asked me for my touch ID or password to update the card. Your computer might require similar permissions. If you have multiple monitor setup, you should watch for this as I didn’t notice the dialog pop up on my top monitor as I was so focused on the bottom one with my terminal. It looked like the terminal locked up, but really it was just waiting patiently to give enthusiastic consent to burning the firmware to the SD card.

In any case, give consent and you’ll see a bunch of messages fly by and should end with this:

|====================================| 100% (22.97 / 22.97) MB
Success!
Elapsed time: 1.817 s

I also almost always see the following:

fwup: eject failed: 0xc010 (49168) (null))

This just means nerves couldn’t eject the SD card. You can do so manually in Finder or your file explorer. Remove the SD card from your computer and plug it into the Pi. You’ll then want to plug the pi into your computer. This should power on the Pi.

Here’s where things may get tricky.

Connecting to the Pi via USB.

On the Pi Zero, the micro USB port in the middle is for data. If you use the other one, you’ll get power but you won’t be able to connect to it from our desktop or laptop computer. Also keep in mind the aforentioned issue of using a USB cable that supports data.

Since I have a 2017 Macbook Pro, I had use a stupid dongle to hook up to the other end (Micro) USB cable to my USB-C on my Mac.

Wait a while, and the Pi’s LED should start blinking all on its own. This is part of the normal power on process–we aren’t controlling it thru code yet.

If you have a Pi Zero, One, or Two, this will let you connect to the Pi over USB as if its an ethernet connection. This uses something called “Linux USB gadget mode” which is a fancy way of saying your USB connection acts like an ethernet network. Unfortunately for Pi3 users, you would have to use standard ethernet or wifi.

From the Getting Started Nerves Docs:

The RPi3B/B+ does not have USB gadget mode capability, but you can make a network connection using either wired or wireless Ethernet.

The Nerves docs in the link above have information on how to connect using wifi/wired ethernet. I like the USB cable method, but for some it may be easier to just use the ethernet.

If your computer successfully connects to the Pi, then in your network settings, you should see something like a “CDC Composite Gadget” connection and you should be assigned an IP address. On my Macbook Pro, it looks like this:

Note: If you see the device, but it says “not connected” or has a self-assigned IP, you might try using the “Renew DHCP lease” to resolve the issue. On the screen above, I just hit “Advanced” to find the Renew button. Keep in mind that your computer is considered the DHCP client in this configuration, so you are requesting an IP from the Pi. I’ve had to do this more than once, when reconnecting my USB cable to the Pi or switching between projects.

And of course, you can always ping it. Nerves will setup a sort of “virtual host” specific to your application for the IP address of the Pi. The default host is nerves.local so we can just ping that:

ping nerves.local
PING nerves.local (172.31.18.193): 56 data bytes
64 bytes from 172.31.18.193: icmp_seq=0 ttl=64 time=0.673 ms
64 bytes from 172.31.18.193: icmp_seq=1 ttl=64 time=0.675 ms
^C
--- nerves.local ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss

Congratulations! You are connected to the Pi.

SSH to the Raspberry Pi

Whether you are connecting via ethernet, wifi or USB cable as I am, you should be able to SSH over to the Pi. You just have to know its address. With USB, this is as simple as:

ssh nerves.local

To which you’ll be greeted by an iex session on the Pi:

Warning: Permanently added 'nerves.local,172.31.18.193' (RSA) to the list of known hosts.
Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
Toolshed imported. Run h(Toolshed) for more info
RingLogger is collecting log messages from Elixir and Linux. To see the
messages, either attach the current IEx session to the logger:

  RingLogger.attach

or print the next messages in the log:

  RingLogger.next

iex(nerves_heartbeat@nerves.local)1>

At this point, you are not only SSH’d to the Pi, you are also in an elixir shell. You can run elixir code directly at the command line.

What exactly is this witchcraft? Well, its accomplished using one of the tools nerves installs by default. You can see the settings autogenerated by nerves in config/config.exs:

config :nerves_init_gadget,
  ifname: "usb0",
  address_method: :dhcpd,
  mdns_domain: "nerves.local",
  node_name: node_name,
  node_host: :mdns_domain

This is what configures your USB to virtual ethernet connection. You could even change the host if you want, but I don’t recommend it. It took me a full reboot before nerves picked up the new host and I also had to flash the SD card.

Back in your elixir interactive shell on the Pi, let’s turn on that built-in green LED we configured earlier by typing:

Nerves.Leds.set(green: true)

If your LED was blinking or off, it should now be staying on. We can switch it off again:

Nerves.Leds.set(green: false)

Remember, earlier we changed the configuration in config/config.exs to map the name :green to led0.

NOTE: I believe most Pis have a “led0”. However, you should check the documentation of your Pi if you’re not using a Pi Zero like I am. It could have different names for the LEDs.

All Nerves is doing for us is writing to the filesystem to control the LED. led0 happens to be a folder on the Pi that is used to write settings for that specific LED. This may seem strange but is an old Unix convention. You could easily write to this file in python or bash to control the LED. Don’t believe me? While nerves doesn’t easily allow us to exit the Elixir shell to poke around on the Linux file system directly, we can use a library called “toolshed” to run similar commands. Run the following in your connected terminal:

use Toolshed

You’ll see:

Toolshed imported. Run h(Toolshed) for more info.
:ok

We can then look at that folder structure for the LED:

cmd("ls /sys/class/leds/led0")

uevent
trigger
brightness
max_brightness
power
subsystem
device

Elixir is showing us the files on the underlying linux file system. As I said, all the nerves_leds library does is read and write to that folder structure.

To exit out of the connect nerves/elixir shell, you must type:

exit

or you can use ~.

This will bring you back to the command prompt on your host machine. It disconnects you from the Pi rather than sending you to the Pi’s command line. It also important to note that CTRL+D will NOT work. You must use one of the two exit mappings above.

End of Line

So we have the Raspberry Pi taking Elixir/Nerves code, and we were able to talk to it just enough to make the on board LED blink. In the next part of this series, we’ll hook the Pi up to an actual breadboard, we’ll wire up the components and wires and make an external LED blink.

Continue to Part Two ->