Nerves从Hello World到点亮LED

官网 https://nerves-project.org/

文档 https://hexdocs.pm/nerves/getting-started.html


上一篇 Nerves环境配置 中我们在 Ubuntu 虚拟机上配置好了 Nerves 的开发环境。正所谓万事开头难,又所谓事事难开头。既然开了头,那就走两步。

如果是新安装 Erlang 和 Elixir 环境的话,首先我们需要安装 Elixir 和 Erlang 的包管理器:

elixir 复制代码
mix local.hex
mix local.rebar

hex 是 Elixir 的包管理器,rebar3 是 Erlang 的包管理器。上面的命令既可以用来安装 hexrebar3,也可以用来更新它们。

然后从 hex 源安装 nerves 包:

elixir 复制代码
mix archive.install hex nerves_bootstrap

严格来说,这里只是安装 nerves 的 mix 任务,而不是 nerves 库,这和 Phoenix 是一样的。之后我们也可以通过下面的命令来更新 nerves。

elixir 复制代码
mix local.nerves

Hello World

找一个合适的地方,在命令行运行下面的命令创建 nerves 工程:

elixir 复制代码
mix nerves.new hello_nerves

进入 hello_nerves 目录,可以看到它也只是一个普通的带监督树的 Elixir 工程而已。在开始之前,我们需要先生成 SSH 公钥,如果已有公钥的话,可以跳过这一步。

bash 复制代码
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

邮箱换成你自己的,然后一直回车保持默认选项就可以了,公钥会用于树莓派和主机之间的网络通信。如果这里你修改了公钥名称或者存放路径,可能会导致 nerves 找不到公钥,那时就需要去修改 config/target.exs 配置文件了,默认配置如下:

elixir 复制代码
keys =
  System.user_home!()
  |> Path.join(".ssh/id_{rsa,ecdsa,ed25519}.pub")
  |> Path.wildcard()

下一步下载依赖。

bash 复制代码
cd hello_nerves
MIX_TARGET=rpi4 mix deps.get

Nerves 需要通过环境变量指定目标平台,我的开发板是树莓派4,所以是 rpi4,其他平台可以参考官方文档👈点击直达👈。

环境变量也可以使用 export MIX_TARGET=rpi4 来设置。如果这一步报错,提示你"需要依赖 nerves",那可能就是 Erlang 版本太低了,参考Nerves环境配置安装一个最新版的 Erlang 应该就可以了。

接下来构建镜像。

bash 复制代码
MIX_TARGET=rpi4 mix firmware

如果是首次使用的话,需要将生成的镜像写到SD卡里面。取下树莓派上的SD卡,插入电脑里面,如果电脑没有SD卡接口,需要准备一个读卡器。将SD卡插入电脑后,用下面的命令写入固件。

bash 复制代码
MIX_TARGET=rpi4 mix burn

如果使用的是虚拟机的话,插入USB后,会有弹窗提示,选择将USB设备连接到虚拟机。固件烧录时,会自动识别出SD卡。

烧录完成后,取出SD卡插回树莓派,连上电源和HDMI显示器,系统开机后,屏幕上会显示一些信息和 iex> 命令提示符,这和我们在电脑上运行 iex 时是一样的。将键盘连接到树莓派,就可以和 Elixir 交互式 shell 愉快的玩耍了。

树莓派4的 type c 接口既可以供电,也支持数据传输,将他和电脑主机相连,就得到了一个树莓派和主机之间的USB网络连接。树莓派4既可以连接WiFi,也可以连接网线,Nerves 默认还提供了基于USB的网络接口,这在开发调试阶段非常有用。

Nerves 在树莓派和主机之间使用 mDNS 做为内网主机发现服务,我们不需要知道树莓派的具体IP地址,而是可以通过 nerves.local 主机名(在config/target.exs中配置)来访问树莓派。比如我们可以在虚拟机的 shell 中通过 ssh 来连接到树莓派。

bash 复制代码
ssh nerves.local

不出意外的话,我们会看到和连接到树莓派的屏幕上相同的内容。我们既可以在这里与树莓派交互,也可以将键盘连接到树莓派,选择一种你喜欢的方式就好。

我们在 iex shell 中输入 HelloNerves.hello() 回车,不出意外的话,会看到输出 :world,这正是 lib/hello_nerves.ex 的内容。

只要烧录过一次镜像,修改代码并运行 mix firmware 重新生成镜像之后,我们就不再需要取出SD卡 mix burn 了,而是可以直接 mix upload 上传新固件。

bash 复制代码
MIX_TARGET=rpi4 mix upload

点亮 LED

Nerves 官方文档是通过 GPIO 点亮外接 LED 灯,作为 Hello World 还有有点超纲了,所以我们先从点亮板载 LED 开始。

原理

Linux 将一切设备都抽象成了文件,板载 LED 也不例外。树莓派4有两颗板载 LED:一颗红色电源指示灯;一颗绿色SD卡活动指示灯。分别对应 /sys/class/leds/ 目录下的 PWRACT 两个目录。它俩都是软链接,链接到的是 /sys/devices/platform/leds/leds/ 目录下的 PWRACT 目录。

具体控制 LED 的是 PWRACT 目录下的文件。比如brightness 文件用来控制 LED 灯的亮度,它里面是一个数字,0 表示熄灭,1(或255) 表示点亮。trigger 文件用来控制 LED 灯的行为,比如闪烁,或者根据某些条件亮灭。打开 ACT 目录下的 trigger 文件,可以看到下面的内容:

复制代码
none rfkill-any rfkill-none timer oneshot heartbeat backlight default-on transient input panic pattern mmc1 [mmc0] rfkill0

它是一个空格分隔的 LED 行为列表,[] 表示当前选中的行为模式。

模式 说明
none 不触发任何模式,LED不会自动亮起或闪烁。
rfkill-any 当任何无线设备被禁用时,LED会亮起。
rfkill-none 当没有无线设备被禁用时,LED会亮起。
timer LED会按照设定的时间间隔闪烁。
oneshot LED会闪烁一次。
heartbeat LED会按照心跳模式闪烁。
backlight LED的亮度会根据背光设置变化。
default-on LED默认保持亮起。
transient LED会在特定事件发生时短暂亮起。
input LED会在接收到输入事件时亮起。
panic 在系统崩溃时,LED会闪烁。
pattern LED会按照预设的模式闪烁。
mmc1 当SD卡槽1有活动时,LED会亮起。
mmc0 表示当SD卡槽0有活动时,LED会亮起。
rfkill0 当无线设备0被禁用时,LED会亮起。

如果我们希望通过 brightness 文件来控制 LED 灯,需要将模式设置为 none

复制代码
echo none > brightness

brightness 文件比较特殊,对它写入 none 会自动变成 [none] ...

复制代码
[none] rfkill-any rfkill-none timer oneshot heartbeat backlight default-on transient input panic pattern mmc1 mmc0 rfkill0

LED控制

以上是板载 LED 控制的原理,对于编程,我们可以使用 nerves_leds 库,它封装了板载 LED 的控制接口。首先我们 mix.exs 文件中添加依赖:

elixir 复制代码
defp deps do
  [
    ... ...
    # Dependencies for all targets except :host
    {:nerves_leds, "~> 0.8", targets: @all_targets},
    ... ...
  ]
end

然后获取依赖:

bash 复制代码
MIX_TARGET=rpi4 mix deps.get

随后在 config.exs 文件中添加一行配置:

elixir 复制代码
config :nerves_leds, names: [green: "ACT"]

这里的配置是可选的,有这个配置,后面我们可以通过 :green 来访问 LED,不配置的话也可以直接通过 "ACT" 来访问这个 LED。

接下来我们用一个 GenServer 来控制 LED,在 lib/hello_nerves/ 目录下创建 blinker.ex 文件,输入以下内容:

elixir 复制代码
defmodule HelloNerves.Blinker do
  use GenServer

  require Logger
  alias Nerves.Leds

  def start_link(state \\ []) do
    GenServer.start_link(__MODULE__, state, name: __MODULE__)
  end

  def init(state) do
    enable()

    {:ok, state}
  end

  def handle_cast(:enable, state) do
    Logger.info("Enabling LED")
    Leds.set(green: true)

    {:noreply, state}
  end

  def handle_cast(:disable, state) do
    Logger.info("Disabling LED")
    Leds.set(green: false)

    {:noreply, state}
  end

  def enable() do
    GenServer.cast(__MODULE__, :enable)
  end

  def disable() do
    GenServer.cast(__MODULE__, :disable)
  end
end

这里我们通过 Leds.set 来控制 LED 灯的亮灭,true 表示点亮,false 表示熄灭。:green 就是前面我们配置的 LED 灯的名字。如果没配置,我们也可以直接使用 "ACT" 来访问它。最后我们在 lib/hello_nerves/application.ex 中将 HelloNerves.Blinker 添加到监督树。

elixir 复制代码
defp target_children() do
  [
    # Children for all targets except host
    # Starts a worker by calling: Target.Worker.start_link(arg)
    # {Target.Worker, arg},
    {HelloNerves.Blinker, name: HelloNerves.Blinker}
  ]
end

注意,Nerves 有两个监督树,我们要将它添加到设备端的监督树中。将树莓派链接到电脑,我们就可以重新编译并上传新固件了。

bash 复制代码
MIX_TARGET=rpi4 mix firmware
MIX_TARGET=rpi4 mix upload

等待上传完成,树莓派会重新启动,运行 ssh nerves.local 连接到树莓派,然后我们就可以通过 HelloNerves.Blinker.enable()HelloNerves.Blinker.disable() 来控制 LED 灯的亮灭了。

网络接口

既然 Nerves 也只是一个普通的 Elixir 工程而已,那么添加一个网络接口,通过网络远程遥控 LED 灯想必不是一件难事。编写 HTTP 接口需要用到 cowboy 库,首先还是添加依赖:

elixir 复制代码
defp deps do
  [
    # Dependencies for all targets
    {:plug_cowboy, "~> 2.0"},
    ... ...
  ]
end

然后获取依赖:

bash 复制代码
MIX_TARGET=rpi4 mix deps.get

lib/hello_nerves/ 下添加 http.ex 文件,输入以下内容:

elixir 复制代码
defmodule HelloNerves.Http do
  use Plug.Router

  plug(:match)
  plug(:dispatch)

  get("/", do: send_resp(conn, 200, "Feel free to use API endpoints!"))

  get "/enable" do
    HelloNerves.Blinker.enable()
    send_resp(conn, 200, "LED enabled")
  end

  get "/disable" do
    HelloNerves.Blinker.disable()
    send_resp(conn, 200, "LED disabled")
  end

  match(_, do: send_resp(conn, 404, "Oops!"))
end

同样,它也需要添加到监督树中:

elixir 复制代码
defp target_children() do
[
    # Children for all targets except host
    # Starts a worker by calling: Target.Worker.start_link(arg)
    # {Target.Worker, arg},
    {HelloNerves.Blinker, name: HelloNerves.Blinker},
    {Plug.Cowboy, scheme: :http, plug: HelloNerves.Http, options: [port: 80]}
  ]
end

最后编译并上传固件:

bash 复制代码
MIX_TARGET=rpi4 mix firmware
MIX_TARGET=rpi4 mix upload

上传完成后,打开浏览器,输入 nerves.local 回车,应该能看到 Feel free to use API endpoints! 的响应。

访问 nerves.local/enablenerves.local/disable 可以点亮或熄灭 LED。

既然 HTTP 接口可以,那么 Phoenix 和 Live View 呢?自然也不在话下,但那已经不是本期的主题了。

Nerves工程 vs Elixir工程

Nerves 工程就是一个带监督树的 Elixir 工程,所以从结构和语法来说,它和 Elixir 工程是没有区别的,只是内容上和我们常看到的 Elixir 工程不太一样。

首先是 mix.exs 的依赖部分,有些依赖多了 :target 选项,而且整个依赖库也被清晰的分成了三大部分:

  1. 通用依赖:平台和主机都需要的依赖;比如 plug_cowboy
  2. 平台依赖:除主机以外所有平台需要的依赖;比如 nerves_leds
  3. 特定平台依赖:特定于目标平台的依赖;比如目标平台预编译镜像 nerves_system_xxx

其次配置文件也分成了主机配置 host.exs 和目标平台配置 target.exs,由 config.exs 根据平台加载。

最后是 application.ex 中的监督树也分成了主机端 target_children 和目标平台端 target_children

这些只是在 Elixir 工程上做的一些小小的变化,整体来说,它还是一个带有监督树的 Elixir 工程。

iex vs bash

与普通 Linux 系统不同,Nerves 系统启动后,提供的用户交互接口并不是 bash,而是 iex。Nerves 定制的 Linux 系统并没有将 bash 包含进去,因此在 Nerves 系统中,bash 确实是无法使用的,Nerves 官网也说了,如果你一定需要 bash,那么 Nerves 将不会是你的最佳选择。虽然 bash 用不了,但是对于熟悉 Erlang 和 Elixir 的观众来说,iex 也未尝不利。

首先是 Erlang 的标准库 c 提供了一些命令行接口函数,这里 "c" 是 "command" 的缩写。比如我们熟知的 cdlspwd 等都是来自这个库。需要注意的是,这些都是 Erlang 函数,由于 Erlang 函数调用是可以省略括号的,因此对于无参函数,如 pwdls 我们可以像在 bash 中一样使用。但是对于有参数的情况就有所不同了,比如 cd "/bin"ls "/bin" 等,他们的参数是字符串,需要加上 ""

其次是 Toolshed 库也提供了一些常用的命令,使用之前需要先 use Toolshed 导入这个库,但是 Nerves 启动 iex 之前已经帮我们导入好了。它为我们提供了 unamepinguptimedatelsof 等常用命令,我们也可以通过 h Toolshed 来查看帮助文档。如果提示函数不存在,可以手动导入一下 Toolshed

最后还有兜底的 cmd 函数,Erlang 的 os 库和 Toolshed 都提供了这个函数,后者调用的是 Elixir 的 System.cmd/3cmd 也是函数,我们可以将想要调用的命令作为参数传递给它,但是要加上 "",因为它的参数是一个字符串。比如 cmd "ls -l"cmd "echo 255 > brightness" 等。

命令总结

命令 说明
mix nerves.new 生成 nerves 工程
mix firmware 构建镜像
mix firmware.gen.script 生成固件上传脚本 upload.sh
mix burn 烧录固件
mix upload 上传固件
相关推荐
爱吃肉的鹏5 天前
树莓派上部署YOLOv5:从零实现实时目标检测
深度学习·yolo·树莓派
爱吃肉的鹏7 天前
树莓派4B连接无线
人工智能·树莓派
爱吃肉的鹏8 天前
使用Flask在本地调用树莓派摄像头
人工智能·后端·python·flask·树莓派
无垠的广袤12 天前
【工业树莓派 CM0 NANO 单板计算机】YOLO26 部署方案
linux·python·opencv·yolo·树莓派·目标识别
kida_yuan15 天前
【Linux】在树莓派上搭建自建 Git 服务(基于 GitLab)- 实战笔记与运维清单
运维·gitlab·树莓派
MIXLLRED15 天前
树莓派4B(ARM架构)的Ubuntu 22.04(Jammy)上安装Intel RealSense SDK和ROS2驱动
arm开发·ubuntu·树莓派·深度相机
无垠的广袤17 天前
【工业树莓派 CM0 NANO 单板计算机】基于舵机和人脸识别的智能门禁系统
linux·python·opencv·yolo·ai·树莓派
创思通信1 个月前
通用树莓派串口调试工具(Python开发的),类似与电脑串口助手
树莓派·串口工具
无心水1 个月前
【神经风格迁移:性能】23、边缘艺术革命:树莓派+ONNX实现本地神经风格迁移,单张<2秒
pytorch·边缘计算·树莓派·onnx·int8·神经风格迁移:性能·神经风格