View Source IEx with Nerves

Nerves greets you with a prompt for Elixir's interactive shell (IEx). This prompt is your main entry point to interacting with Elixir, your program, and hardware. This chapter focuses on Nerves-specific use of the IEx prompt.

Viewing log messages

Attaching to the logger

The Elixir console logger is almost always not included with Nerves so log messages don't print to the terminal. Instead, run log_attach to see log messages:

iex> log_attach
{:ok, #PID<0.30684.4>}
iex> Logger.info("hello")

02:23:34.863 [info]  hello
:ok

To stop log messages from being printed, run log_detach.

undefined function log_attach/0

log_attach is a function of Toolshed and might not be imported by default. If you get an undefined function error, run use Toolshed in your IEx session and try again. See Toolshed for more details.

RingLogger

You'll frequently want to see log messages that occurred in the past. The Nerves new project generator creates projects with RingLogger to support this. RingLogger is an Elixir logger backend that stores logs completely in memory. This is nice for embedded systems where you don't want to wear out Flash storage by writing to it. The drawbacks are RingLogger discards old messages and doesn't save them across reboots.

To view log messages, run RingLogger.next at the IEx prompt. Repeated calls print newly received log messages. RingLogger.reset lets you start at the oldest message again.

See the RingLogger docs for more information on tuning log levels, filtering by module, and grep'ing for keywords.

Dmesg

Nerves routes Linux kernel log messages and syslog messages to the Elixir Logger. This means Elixir logger backends have a complete picture of the log messages sent by the kernel, C, and BEAM programs. Sometimes, though, it's useful to focus on the kernel messages in isolation. The dmesg helper lets you do this:

iex> dmesg
[    0.000000] Booting Linux on physical CPU 0x0
[    0.000000] Linux version 5.10.41 (buildroot@buildroot) (armv7-nerves-linux-gnueabihf-gcc (crosstool-NG 1.24.0.299_6729a76) 10.2.0, GNU ld (crosstool-NG 1.24.0.299_6729a76) 2.36.1) #1 PREEMPT Fri Aug 20 01:26:27 UTC 2021
[    0.000000] CPU: ARMv7 Processor [413fc082] revision 2 (ARMv7), cr=10c5387d
[    0.000000] CPU: PIPT / VIPT nonaliasing data cache, VIPT aliasing instruction cache

undefined function dmesg/0

dmesg is a function of Toolshed and might not be imported by default. If you get an undefined function error, run use Toolshed in your IEx session and try again. See Toolshed for more details.

RamoopsLogger

The RamoopsLogger is an Elixir logger backend that records messages to a special memory region using Linux's pstore driver. This memory region survives reboots so it's useful for capturing log messages that happen just before an unexpected reboot. Even if you've configured a file-backed logger backend, the RamoopsLogger can sometimes capture messages that would have been lost to disk caching.

This driver is enabled in most official Nerves systems. However, :ramoops_logger is not added to Nerves projects by default. See the documentation for registering it with the Elixir Logger.

Other loggers

Pretty much any logger backend in Elixir can be used with Nerves. The caveat is that Nerves does not guarantee the following:

  1. Networking always works
  • If you're using a network-based logger, check that it handles network outages gracefully.
  1. The application data partition (/data) is mounted
  • The application data partition is almost always available. However, on the first boot and if severely corrupted, it will be reformatted. This partition is also technically optional and a system can choose to omit it. Since the Elixir Logger starts very early in the boot process, it's possible for log messages to be received before /data is ready. This is a temporary situation, but it is important that the Logger backend not give up.

Networking

Most Nerves projects use the VintageNet library for configuring the network. To get a quick overview of network configuration and status, run VintageNet.info:

iex> VintageNet.info
All interfaces:       ["eth0", "lo", "wlan0", "wwan0"]
Available interfaces: ["wlan0", "wwan0"]

Interface eth0
  Type: VintageNetEthernet
  Present: true
  State: :configured (1 days, 14:59:09)
  Connection: :disconnected (1 days, 14:59:09)
  Configuration:
    %{type: VintageNetEthernet, ipv4: %{method: :dhcp}}

Interface wlan0
  Type: VintageNetWiFi
  Present: true
  State: :configured (1 days, 14:59:05)
  Connection: :internet (5:02:35)
  Addresses: 192.168.99.81/24, fe80::9a48:27ff:fedd:a10e/64
  Configuration:
    %{
      type: VintageNetWiFi,
      ipv4: %{method: :dhcp},
      vintage_net_wifi: ...
    }

Interface wwan0
  Type: VintageNetQMI
  Power: On (watchdog timeout in 59969 ms)
  Present: true
  State: :configured (14:44:50)
  Connection: :internet (14:43:51)
  Addresses: 100.101.32.76/29, fe80::8eb:885f:3fce:d37d/64
  Configuration:
    %{ ...
    }

If your muscle memory types ifconfig, that works too:

iex(2)> ifconfig
lo: flags=[:up, :loopback, :running]
    inet 127.0.0.1  netmask 255.0.0.0
    inet ::1  netmask ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
    hwaddr 00:00:00:00:00:00

eth0: flags=[:up, :broadcast, :running, :multicast]
    hwaddr 60:64:05:4e:fb:ef

wlan0: flags=[:up, :broadcast, :running, :multicast]
    inet 192.168.99.81  netmask 255.255.255.0  broadcast 192.168.99.255
    inet fe80::9a48:27ff:fedd:a10e  netmask ffff:ffff:ffff:ffff::
    hwaddr 98:48:27:dd:a1:0e

wwan0: flags=[:up, :pointtopoint, :running, :multicast]
    inet 100.101.32.76  netmask 255.255.255.248
    inet fe80::8eb:885f:3fce:d37d  netmask ffff:ffff:ffff:ffff::

undefined function ifconfig/0

ifconfig is a function of Toolshed and might not be imported by default. If you get an undefined function error, run use Toolshed in your IEx session and try again. See Toolshed for more details.

Another option is to run ip(8). Nerves provides a trimmed down Busybox version of ip. For example:

iex> cmd("ip route")
default via 192.168.99.1 dev wlan0  metric 20
default via 100.101.32.77 dev wwan0  metric 30
100.101.32.72/29 dev wwan0 scope link  src 100.101.32.76  metric 30
192.168.99.0/24 dev wlan0 scope link  src 192.168.99.81  metric 20

See the VintageNet documentation for more tips on debugging and adjusting network configurations.

Toolshed

Toolshed is a library of IEx helpers that augments the ones that Elixir provides. It's included by the Nerves new project generator (see Customizing the IEx session section for more details).

The helpers should be available by default, but if not, run:

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

If you're used to the Linux commandline, many Toolshed helpers will seem familiar except with an Elixir twist. One difference is that you need to add double quotes around filenames and IP addresses. The names are similar, though, like uname, ping, uptime, date, lsof and more.

Toolshed also simplifies running shell commands. Keeping in mind that Nerves provides a limited Linux userland, you can still run simple shell scripts and commandline applications using cmd. For example,

iex> cmd("ls -las /")
     0 drwxr-xr-x    3 root     root            97 Mar 12  2020 var
     0 drwxr-xr-x    7 root     root            88 Mar 12  2020 usr
     0 drwxrwxrwt    3 root     root           180 Sep  2 21:05 tmp
     0 dr-xr-xr-x   12 root     root             0 Jan  1  1970 sys
...

Another useful command for checking Internet-connectivity is weather. This sends an HTTP request to Igor Chubin's super useful wttr.in service.

Linux shell commands

Maybe Erlang?

Erlang contains an amazing amount of functionality, so before reaching for Linux utilities, we highly recommend checking the Erlang documentation.

Nerves includes a minimal version of busybox to support running simple shell scripts and access network configuration utilities that do not have analogs in Erlang/OTP.

To see what's available, run busybox without arguments:

iex> cmd("busybox")
BusyBox v1.33.1 () multi-call binary.
BusyBox is copyrighted by many authors between 1998-2015.
Licensed under GPLv2. See source distribution for detailed
copyright notices.

Usage: busybox [function [arguments]...]
   or: busybox --list
   or: busybox --show SCRIPT
   or: function [arguments]...

	BusyBox is a multi-call binary that combines many common Unix
	utilities into a single executable.  Most people will create a
	link to busybox for each function they wish to use and BusyBox
	will act like whatever it was invoked as.

Currently defined functions:
	[, [[, ash, base32, basename, brctl, cat, cp, cut, date, dd, devmem,
	df, dirname, dmesg, dnsd, expr, find, free, grep, halt, id, ifconfig,
	install, ip, ipaddr, iplink, ipneigh, iproute, iprule, iptunnel, kill,
	killall, ls, lsmod, mim, mkdir, mknod, mktemp, modinfo, modprobe,
	mount, mv, ntpd, pidof, ping, ping6, poweroff, ps, pwd, reboot, rm,
	rmdir, rmmod, sed, sh, sha256sum, sleep, sysctl, tail, touch, udhcpc,
	udhcpd, uevent, umount, unzip

Where's Bash?

Everyone asks this and it's come up since almost day one. It is probably the most visible distinction of what it means that Nerves uses the Linux kernel but very little of the standard Linux userland.

Since Nerves provides only a few Linux utilities, the shell prompt is not as useful as you would expect. The projects that once provided a shell prompt have been abandoned due to this.

Our recommendation is to spend some time working at the iex> prompt and if you're missing a utility, check if Elixir or Erlang/OTP provide it. If they do and it just needs an IEx helper to make it ergonomic, then please consider contributing a new helper to Toolshed.

If having a proper Unix shell and Linux userland is critical to your application, it may be better not to use Nerves. Buildroot, Yocto, Raspberry Pi OS, and other embedded Linux projects run Erlang and Elixir too and many Nerves-related libraries also work well outside of Nerves.

Customizing the IEx session

The Nerves new project generator creates a default iex.exs for setting up the prompt. You can find it in rootfs_overlay/etc/iex.exs.

The default iex.exs prints a message of the day (from NervesMOTD) and loads the Toolshed helpers. See the IEx .iex.exs docs for more information on what can be done.

Keep the following in mind:

  1. Elixir evaluates the iex.exs file for the console very early in the boot process. It's likely that networking and your OTP applications have not started, so you may get runtime exceptions
  2. A common sign that a typo broke the iex.exs is that the Toolshed helpers are not available. You can still run use Toolshed at the prompt.
  3. The iex.exs is stored in a read-only location so you can't update it on the device. You can create /root/.iex.exs and customize it. Use sftp to update or erase it if you mess it up.

Changing the IEx console output

Depending on the platform, Nerves sends the IEx console to an attached display or UART. If you find yourself taking pictures of the display to capture error log messages, you probably want to start using the UART. That requires a USB-to-UART cable (often called an FTDI cable) and you'll need a serial communications program on your computer.

erlinit sets up the console before starting Erlang. The /etc/erlinit.config files in the official Nerves systems have comments about where the console output goes. Here's an example:

 Specify where erlinit should send the IEx prompt. Only one may be enabled at
# a time.
-c ttyAMA0     # UART pins on the GPIO connector
# -c tty1      # HDMI output

The easiest way of changing the console location is in your config.exs. For example, specify the following to use tty1:

config :nerves, :erlinit,
  ctty: "tty1"

When you ship a Nerves device for production, you may want to disable the console completely. To disable, set the ctty to null:

config :nerves, :erlinit,
  ctty: "null",
  alternate_exec: "/usr/bin/run_erl /tmp/ /tmp exec"

The :alternate_exec key is optional here. It calls run_erl to log console output to a file in /tmp. This is useful if code calls IO.puts rather than Logger.

Remote console access

The Nerves new generator sets up NervesSSH by default allowing you to remotely connect with ssh nerves.local (or via the IP address or another hostname you may have set)

If NervesSSH is not an option, the extty library may be useful for connecting an IEx prompt to the transport of your choice.

Exiting SSH sessions

If you're using Toolshed, type exit at the IEx prompt. Otherwise, use ssh's magic exit sequence: <enter>~.. Run <enter>? to see all the available SSH magic sequences

Erlang and LFE prompts

While Nerves definitely has a lot of Elixir in it now, it has always been the intention to support other BEAM languages.

The boot console is configured using your project's vm.args. The console supplied over SSH connections is set though the application environment for :nerves_ssh:

config :nerves_ssh,
  shell: :lfe

See the Nerves Examples for small Erlang and LFE programs.