Nerves en 2026: Elixir para sistemas embebidos e IoT con el BEAM en hardware real

La mayoría de plataformas IoT usan C, MicroPython o, en el mejor caso, Rust. Nerves plantea algo diferente: llevar la BEAM completa, con OTP, supervisores y actualizaciones en caliente, a hardware embebido real. En 2026, la plataforma lleva años madura y hay proyectos en producción que lo demuestran.

Qué es Nerves y cómo funciona

Nerves es una plataforma para construir firmware con Elixir. No es Elixir corriendo sobre un sistema operativo de propósito general: es un sistema mínimo basado en buildroot que arranca directamente en la BEAM. El kernel de Linux sigue ahí (la BEAM lo necesita), pero el sistema de archivos es de solo lectura, no hay shell por defecto, y el único proceso de usuario que corre es la aplicación Elixir.

Esto tiene consecuencias importantes:

  • El tiempo de arranque se mide en segundos, no en minutos.
  • La huella de memoria es mucho menor que una distribución Linux completa.
  • No hay partes móviles que actualizar: el firmware es un blob atómico.
  • La superficie de ataque es mínima: no hay servidor SSH abierto por defecto, no hay paquetes del sistema que mantener.

Targets soportados

Nerves soporta una lista de hardware oficial mantenida por el proyecto:

  • Raspberry Pi (todas las versiones desde Pi 0 hasta Pi 5)
  • BeagleBone Black y Green
  • Grisp2
  • MangoPi MQ-Pro
  • Targets genéricos x86_64 para máquinas virtuales o hardware industrial

Crear un proyecto Nerves

# Instalar el generador
mix archive.install hex nerves_bootstrap

# Crear un proyecto nuevo
mix nerves.new mi_dispositivo --target rpi4

# La estructura del proyecto
mi_dispositivo/
  config/
    target.exs      # configuración específica del hardware
  lib/
    mi_dispositivo.ex
    mi_dispositivo/
      application.ex  # árbol de supervisión normal de OTP
  mix.exs

# Compilar el firmware para Raspberry Pi 4
export MIX_TARGET=rpi4
mix deps.get
mix firmware

# Grabar en una tarjeta SD
mix burn

El resultado de mix firmware es un archivo .fw que contiene el firmware completo: kernel, sistema de archivos raíz y la aplicación Elixir. Puedes grabarlo en una SD con mix burn o distribuirlo para actualizaciones remotas.

La aplicación Elixir en el dispositivo

Desde el punto de vista del código Elixir, no hay diferencia entre una aplicación para servidor y una aplicación Nerves. Tienes el mismo árbol de supervisión OTP, los mismos GenServers, el mismo Ecto si necesitas SQLite, el mismo LiveView si quieres una interfaz web local.

defmodule MiDispositivo.Application do
  use Application

  def start(_type, _args) do
    children = [
      # Leer sensores cada segundo
      {MiDispositivo.SensorWorker, intervalo: 1_000},
      # Enviar datos al servidor central
      {MiDispositivo.TelemetriaUploader, endpoint: "https://mi-servidor.com/api"},
      # Interfaz web local en el puerto 80
      {Plug.Cowboy, scheme: :http, plug: MiDispositivo.Router, port: 80}
    ]

    Supervisor.start_link(children, strategy: :one_for_one)
  end
end

defmodule MiDispositivo.SensorWorker do
  use GenServer

  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  def init(intervalo: intervalo) do
    Process.send_after(self(), :leer, intervalo)
    {:ok, %{intervalo: intervalo, lecturas: []}}
  end

  def handle_info(:leer, %{intervalo: intervalo} = estado) do
    lectura = leer_sensor_temperatura()
    Process.send_after(self(), :leer, intervalo)
    {:noreply, Map.update!(estado, :lecturas, &[lectura | &1])}
  end

  defp leer_sensor_temperatura do
    # Acceso al hardware vía Circuits.I2C, Circuits.GPIO, etc.
    {:ok, ref} = Circuits.I2C.open("i2c-1")
    {:ok, datos} = Circuits.I2C.write_read(ref, 0x48, <<0x00>>, 2)
    Circuits.I2C.close(ref)
    parse_temperatura(datos)
  end
end

Circuits: acceso al hardware

Nerves se apoya en la familia de librerías Circuits para acceder al hardware:

  • Circuits.GPIO: pines GPIO digitales (encender LEDs, leer botones)
  • Circuits.I2C: bus I2C para sensores (temperatura, humedad, acelerómetros)
  • Circuits.SPI: bus SPI para pantallas, tarjetas SD, módulos de radio
  • Circuits.UART: comunicación serie
# Encender un LED en el pin GPIO 18
{:ok, gpio} = Circuits.GPIO.open(18, :output)
Circuits.GPIO.write(gpio, 1)   # encender
:timer.sleep(1000)
Circuits.GPIO.write(gpio, 0)   # apagar
Circuits.GPIO.close(gpio)

# Leer un botón en el pin GPIO 23
{:ok, boton} = Circuits.GPIO.open(23, :input)
estado = Circuits.GPIO.read(boton)
IO.puts("Botón: #{estado}")

Actualizaciones OTA atómicas

Una de las ventajas más prácticas de Nerves en producción son las actualizaciones over-the-air. El sistema usa dos particiones de firmware: la activa y la de reserva. Cuando aplicas una actualización, se escribe en la partición de reserva. Si el dispositivo arranca con el nuevo firmware y funciona, lo marca como activo. Si falla en el arranque, vuelve automáticamente a la versión anterior.

# NervesHub: el servicio de gestión de actualizaciones OTA
# En mix.exs
{:nerves_hub_link, "~> 2.0"}

# Verificar si hay actualización disponible
NervesHub.update()

# El dispositivo descarga, verifica la firma y aplica la actualización
# de forma atómica, sin riesgo de dejarlo en estado inconsistente

Por qué la BEAM tiene sentido en IoT

Los dispositivos IoT tienen exactamente los requisitos donde OTP brilla: deben funcionar durante meses sin reiniciarse, manejar múltiples sensores y conexiones en paralelo, y recuperarse de errores de red o hardware sin intervención humana. El modelo de supervisión de OTP, que en un servidor maneja fallos de procesos de usuario, en un dispositivo embebido maneja fallos de lecturas de sensores, timeouts de red o errores de protocolo.

La alternativa habitual, C o MicroPython, obliga a gestionar la tolerancia a fallos manualmente, con watchdogs del hardware y reintentos escritos a mano. Con Nerves, el árbol de supervisión OTP hace ese trabajo de forma declarativa.

Para entender la base de supervisión OTP que hace esto posible, el artículo sobre supervisores en OTP de esta serie cubre todos los detalles. Y si te interesa comparar con Rust, que es la otra opción popular para sistemas embebidos, el artículo sobre Rust 2024 muestra un enfoque donde la seguridad de memoria es la prioridad.

Imagen: Pexels / Alessandro Oliverio

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP