Skip to content

BLOG

Avoid These Early Pitfalls of Networking on Nerves and Elixir

Here at Very we love Elixir and in particular the Nerves framework. Recently I’ve been working on a Slack controlled LED panel that is controlled with Nerves. The Slack hex package uses HTTPoison under the hood. So I decided to perform a simple test by just building a firmware image that had HTTPoison and running a few test commands. 

Unfortunately after building the firmware and flashing it to a Raspberry Pi, I encountered an error trying to make a call with HTTPoison:

Since I’m reasonably sure that the above URL is not malformed, I was able to reason pretty confidently that the culprit must be internet connectivity issues. Sure enough, Nerves does not come with network management out of the box. Fortunately, the Nerves Project does maintain a networking lib that I was able to easily plug into the build. (As a bonus I recommend checking out Init Gadget for a bunch of great default utils including Nerves Network!)

So I was ready to try my code again but once more, failure. Using Nerves.Network.status("eth0") I was able to see that the network had not been configured yet. After waiting a few moments, and trying a few more status check saw that a connection was eventually established. Since my project was going to be using a Supervisor to manage a GenServer that would be attempting to automatically make network requests I needed to be sure that my network was configured so that the GenServer wouldn’t crash when it tried to boot up:

defmodule TimingExample.Launcher do
  require Logger
  use GenServer

  def start_link(opts) do
    opts = Keyword.merge([name: __MODULE__], opts)
    GenServer.start_link(__MODULE__, :ok, opts)
  end

  def init(_) do
    check_network(Nerves.Network.status("eth0"))
    HTTPoison.get!("https://www.verytechnology.com/")
    {:ok, {}}
  end

  defp check_network(%{ipv4_address: ip}) do
    Logger.info("Connected")
  end

  defp check_network(_) do
    Process.sleep(1000)
    check_network(Nerves.Network.status("eth0"))
  end
end

As you can see above, checking the network status for an IP address is a simple, if inelegant, solution that allowed me to move forward. I was not able to go very far, however:

This error was a bit trickier to debug for me. Suffice to say that I spent a few cycles on this problem which turned out to be a timing issue. Calling DateTime.utc_now().year returned the value 1970! My first instinct was to turn to NTP:

def init(_) do
  update_time()
  HTTPoison.get!("https://www.verytechnology.com/")

  {:ok, {}}
end

defp update_time do
  if DateTime.utc_now().year == 1970 do
    System.cmd("ntpd", ["-q", "-p", "pool.ntp.org"])
    Process.sleep(1000)
    update_time()
  end
end

This has the added benefit of no longer needing to check the network since NTP will fail to synchronize until the network connection has been established. However, after some further research, I found another great add-on for Nerves: Nerves Time. This package will handle the nitty-gritty of managing NTP and will actually make sure your device time is closely estimated until is able to synchronize with NTP. So I have one more adjustment to make to my code:

defp check_time do
  if !Nerves.Time.synchronized?() do
    Process.sleep(1000)
    check_time()
  end
end

Great! Now for a final flash and test:

Thanks for reading and I hope that this post helps you avoid some early pitfalls of Nerves development. If you have any comments or suggestions or angry rants please feel free to leave feedback on Heartbeat.