Elixir Phoenix framework embedded image for Raspberry Pi 2

Phoenix is a great framework and I think it will do for soft-realtime applications what Rubby on Rails did for typical CRUD applications i.e. make it really easy to create such kind of applications.

With all the different devices we interact with, Phoenix is also aiming towards becoming the framework for the backend of all those devices and Elixir is a great language for such kind of distributed systems.

I think that apart from this Phoenix can also shine in the IoT (Internet of Things) world due to it's ease of writing applications that can interact with all such connected devices using the concept of Channels, which is just a mechanism of sending and receiving messages over web (using Web Sockets, long polling or other mechanism as well).

In this post we are going to see how to create a Phoenix framework embedded image for Raspberry Pi 2. Why would you do that? Here are some use cases

  1. As the number of connected devices are going to grow, people will need a unified way to control various things in home using a web portal and iOS/Android applications. There can be a single hub device in home that connects with all those device and that hub can be powered by Phoenix, the mobile application can also directly communicate with it.
  2. It can act as a toy server for your Phoenix application which you can even take to some event and show it off.
  3. Maybe in future when we have even more powerfull & cheap SOC devices, a cluster of such devices can power a small scale application, who knows.

First thing first, here is a simple Chat application using Phoenix channel running on Raspberry Pi 2 sitting at my home. You can get the source code from here.

Create Phoenix application

Follow the Getting Started guide to install the Phoenix installer.

$ mix phoenix.new pi_hello_phoenix
$ cd pi_hello_phoenix
$ mix do deps.get, compile

Edit the mix.exs to add new packages

...
def application do
  [mod: {PiChat, []},
  applications: [:phoenix, :phoenix_html, :cowboy, :logger,
                 :phoenix_ecto, :postgrex, :ethernet]]
end
...
defp deps do
  [{:phoenix, "~> 0.13"},
   {:phoenix_ecto, "~> 0.4"},
   {:postgrex, ">= 0.0.0"},
   {:phoenix_html, "~> 1.0"},
   {:phoenix_live_reload, "~> 0.4", only: :dev},
   {:cowboy, "~> 1.0"},
   {:exrm, "~> 0.15.0" },
   {:ethernet, github: "cellulose/ethernet"}
end

We added the exrm and ethernet package and also add :ethernet in application. The exrm package is used to create a single executable that packages your applications and all the dependencies so you can deploy it. The ethernet package will use DHCP to connect to the network.

Lets set the port to 80 for prod configuration and also configure the endpoint to act as server. Edit config/prod.exs

config :pi_chat, PiChat.Endpoint,
  http: [port: 80],
  ...
  server: true

The last thing we have to do is to start the ethernet when our application starts. Edit lib/pi_hello_phoenix.ex

...
def start(_type, _args) do
  import Supervisor.Spec, warn: false

  # If this fails locally comment it out when developing
  Ethernet.start
...

Create Raspberry PI 2 image

Nerves-Project uses the Erlang/Elixir release and creates a bootable firmware image from that using Buildroot. The advantage of it is that its a shippable image which you can publish and anyone can burn that image onto the board/SD card and be done with it. This creates a bare bone image, stripping away all the useless things for embedded system such as video, UI etc. which in turn makes it bootup pretty quickly.

You can use the instructions at Nerves-Project Github to download the source code and compile it but it can take upto hour or more depending upon your machine. I have published an image that you can just pull and use, making the process a lot quicker.

Lets first pull in the docker image, run it and run few commands under it.

$ docker pull zabirauf/nerves-sdk-elixir-rpi2
$ docker run -i -t -v /path/to/pi_hello_phoenix:/opt/pi_hello_phoenix zabirauf/nerves-sdk-elixir-rpi2 /bin/bash
root@bb9f59897a2a: cd /Downloads/nerves-sdk && source ./nerves-env.sh

We have to create a Makefile in our project so that nerves can compile it and create an image.

/path/to/pi_hello_phoenix: echo 'include $(NERVES_ROOT)/scripts/nerves-elixir.mk' > Makefile

So we just include the Makefile that already comes with nerves.

Now in your docker terminal which is still running the docker container lets create the image

root@bb9f59897a2a: cd /opt/pi_hello_phoenix
root@bb9f59897a2a: MIX_ENV=prod make

This will compile the project, create an erlang release from it and then use that release to create an image. After that is done your image would be under /path/to/pi_hello_phoenix/_images.

Burning image on SD Card

I am using Mac so the instructions would be for Mac. If you are on Linux or Windows then you can get the instructions to burn .img on SD card by just searching for it.

First run diskutil list and see where is your SD card mounted (in my case its /dev/disk7). Then run the following

CAUTION: Wrong disk path can cause you to loose data for that disk. Make sure you are using the right SD card path.

$ sudo diskutil unmountDisk /dev/disk7
$ sudo dd bs=1m if=/path/to/pi_hello_phoenix/_images/pi_hello_phoenix.img of=/dev/disk7

This will take some time and then once its done you can eject the SD card. If this does not work then use the alternative method mentioned at Raspberry Pi

Now just insert the SD card in Raspberry Pi 2, connect the ethernet cable and power it up. Visit the IP of the Pi in your browser and you should be greeted with the Phoenix application.
I found the IP by checking the console output from Raspberry Pi using USB TTL Serial Cable. If you don't have that then you can find the list of connected devices from your router admin portal and try visiting them.