search
LoginSignup
1

More than 1 year has passed since last update.

posted at

updated at

Organization

Raspberry Pi 4 + Grove Base HAT for Raspberry Pi + Grove Buzzer + Grove ButtonでつくるNervesアラーム

この記事はRaspberry Pi Advent Calendar 2020 10日目です。
前日は、@s51517765さんのエアコンをSlackでスマートリモコン化の効果を温湿度センサで測定する【2020年版】でした。
10日目はまだ投稿されていなかったので投稿してみました :calendar:
はじめまして! よろしくお願いします! :rocket::rocket::rocket:


はじめに

What is Nerves ?

外観

IMG_20201214_215925.jpg

準備

mix nerves.new alarm

  • このコマンドでばーっと、プロジェクトの雛形が作られます
$ mix nerves.new alarm
$ cd alarm
$ export MIX_TARGET=rpi4
  • 今回はRaspberry Pi 4を使っているのでrpi4です
  • その他のものを使う場合は、Targetsをご参照ください

依存ライブラリの導入

mix.exs
@@ -49,7 +49,11 @@ defmodule Alarm.MixProject do
       {:nerves_system_rpi4, "~> 1.13", runtime: false, targets: :rpi4},
       {:nerves_system_bbb, "~> 2.8", runtime: false, targets: :bbb},
       {:nerves_system_osd32mp1, "~> 0.4", runtime: false, targets: :osd32mp1},
-      {:nerves_system_x86_64, "~> 1.13", runtime: false, targets: :x86_64}
+      {:nerves_system_x86_64, "~> 1.13", runtime: false, targets: :x86_64},
+
+      # add
+      {:circuits_gpio, "~> 0.4"},
+      {:quantum, "~> 3.0"}
     ]
   end
  • Alarm.MixProject.deps/0に追加します
  • circuits_gpioは、GPIOを扱えるライブラリです
  • quantumは、cronライクなジョブスケジューラです

lib/alarm/buzzer.ex

  • ブザーを鳴らしたり、止めたりします
  • D16Grove - Buzzerを挿したので16をopenしています
lib/alarm/buzzer.ex
defmodule Alarm.Buzzer do
  @pin 16

  def start do
    Circuits.GPIO.write(gpio(), 1)
  end

  def stop do
    Circuits.GPIO.write(gpio(), 0)
  end

  def pin, do: @pin

  defp gpio do
    {:ok, gpio} = Circuits.GPIO.open(@pin, :output)
    gpio
  end
end

lib/alarm/button.ex

lib/alarm/button.ex
defmodule Alarm.Button do
  @pin 5

  def pin, do: @pin

  def gpio do
    {:ok, gpio} = Circuits.GPIO.open(@pin, :input)
    gpio
  end
end

lib/alarm/set_interrupter.ex

lib/alarm/set_interrupter.ex
defmodule Alarm.SetInterrupter do
  use GenServer

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

  def init(state) do
    Circuits.GPIO.set_interrupts(Alarm.Button.gpio(), :both, receiver: Alarm.Observer)

    {:ok, state}
  end
end
  • Grove Button(P)が押されたり、離されたりすると、Alarm.Observerモジュールで処理をします

lib/alarm/observer.ex

lib/alarm/observer.ex
defmodule Alarm.Observer do
  use GenServer

  require Logger

  @button_pin Alarm.Button.pin()

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

  def init(state) do
    {:ok, state}
  end

  def handle_info({:circuits_gpio, @button_pin, _timestamp, 1}, state) do
    Logger.debug("Received rising event on pin #{@button_pin}")
    Alarm.Buzzer.State.toggle()
    {:noreply, state}
  end

  def handle_info({:circuits_gpio, @button_pin, _timestamp, 0}, state) do
    Logger.debug("Received falling event on pin #{@button_pin}")
    {:noreply, state}
  end
end
  • ボタンが押されたときに、Alarm.Buzzer.State.toggle/0を呼び出しています(後述)
  • ボタンが離されたときは特に処理はしていません

lib/alarm/buzzer/state.ex

lib/alarm/buzzer/state.ex
defmodule Alarm.Buzzer.State do
  use GenServer

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

  def init(state) do
    {:ok, state}
  end

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

  # 鳴らしていないときはこちらが実行される
  def handle_cast(:toggle, false) do
    Alarm.Buzzer.start()

    {:noreply, true}
  end

  # 鳴らしているときはこちらが実行される
  def handle_cast(:toggle, true) do
    Alarm.Buzzer.stop()

    {:noreply, false}
  end
end
  • 初期状態をfalseにしておいて、Grove - Buzzerを鳴らしているときには、状態をtrueにしています

日本時間07:00に鳴動するようにする

lib/alarm/scheduler.ex
defmodule Alarm.Scheduler do
  use Quantum, otp_app: :alarm
end
config/config.exs
config :alarm, Alarm.Scheduler,
  jobs: [
    {"0 22 * * *", {Alarm.Buzzer.State, :toggle, []}}
  ]
  • UTC時刻で指定しています

Wi-Fiで接続する(オプション)

  • LANケーブルでネットワーク接続する場合には必要ありません
    • 今回のアプリケーションではネットワークにつながなくてもよいのですが、ネットワークにつながっていると、sshで接続できてさらに後述する良いことがあるので、ネットワークにつなげておくことはオススメです
  • VintageNet Cookbook — WiFi
  • 環境変数NERVES_NETWORK_SSIDNERVES_NETWORK_PSKを開発マシンにセットしておいてください
config/target.exs
config :vintage_net,
  regulatory_domain: "US",
  config: [
    {"usb0", %{type: VintageNetDirect}},
    {"eth0",
     %{
       type: VintageNetEthernet,
       ipv4: %{method: :dhcp}
     }},
    {"wlan0",
     %{
       type: VintageNetWiFi,
       vintage_net_wifi: %{
         networks: [
           %{
             key_mgmt: :wpa_psk,
             ssid: System.get_env("NERVES_NETWORK_SSID"),
             psk: System.get_env("NERVES_NETWORK_PSK")
           }
         ]
       },
       ipv4: %{method: :dhcp}
     }}
  ]

Alarm.Application

  • mix nerves.new alarmしたときにほとんどできあがっています
  • やることは、今回use GenServerしているモジュールを追加するのみです
    • 「#add」をつけた4行のみです
lib/alarm/application.ex
defmodule Alarm.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  def start(_type, _args) do
    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: Alarm.Supervisor]

    children =
      [
        # Children for all targets
        # Starts a worker by calling: Alarm.Worker.start_link(arg)
        # {Alarm.Worker, arg},
        Alarm.Scheduler #add
      ] ++ children(target())

    Supervisor.start_link(children, opts)
  end

  # List all child processes to be supervised
  def children(:host) do
    [
      # Children that only run on the host
      # Starts a worker by calling: Alarm.Worker.start_link(arg)
      # {Alarm.Worker, arg},
    ]
  end

  def children(_target) do
    [
      # Children for all targets except host
      # Starts a worker by calling: Alarm.Worker.start_link(arg)
      # {Alarm.Worker, arg},
      Alarm.Observer, #add
      Alarm.SetInterrupter, #add
      Alarm.Buzzer.State #add
    ]
  end

  def target() do
    Application.get_env(:alarm, :target)
  end
end

ファームウェアビルド

$ mix firmware

microSDカードに焼く

  • microSDカードを開発マシンに挿して焼き込みます
$ mix burn
  • こんがり焼けたらRaspberry Pi 4に挿して電源ON!!!
  • 朝7時に「フ、ピェ〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜」ってけっこう大きい音が鳴り続けますので、ボタンを押して止めてください!

おまけ

ssh 接続

$ ssh nerves.local

iex>
  • IEx(Elixir's interactive shell)が立ち上がります

ファームウェアアップデート

  • ソースコードを修正する
$ mix firmware
$ mix upload

Wrapping Up :christmas_tree::santa::santa_tone1::santa_tone2::santa_tone3::santa_tone4::santa_tone5::christmas_tree:

  • Nervesを使うと、Elixirだけで、Raspberry Piで動くアプリケーションを作ることができます
    • (circuits_gpioはよ〜く探検していくと、C言語のコードが入っています)
  • ソースコード全体はTORIFUKUKaiou/alarmです
  • Seeed株式会社様のモノを利用することで、不器用ですからな自分でも、簡単に工作できます!
  • 最後に紹介したネットワーク越しにファームウェアをアップデートできるのはとても便利です
    • いちいちmicroSDカードを抜き差ししなくて済むのはvery very :thumbsup::thumbsup::thumbsup:です
  • この記事で少しでもNervesに興味を持っていただけましたら、ぜひNervesJPにご参加ください
  • Enjoy Elixir !!! :rocket::rocket::rocket:

https___qiita-user-contents.imgix.net_https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F240349%2F5ef22bb9-f357-778c-1bff-b018cce54948.png_ixlib=rb-1.2.png

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1