7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

導入

自宅のRaspberry Pi 4で動かしているNervesアプリが、10秒に1度、温度・湿度を送信しています。
それが8日間、誰にも受け取られずに黙々と送り続けていました。

Docker composeで動かしているPhoenix APIが止まっていたため、受け取ってくれる相手はいなかったはずなのです。(おそらくWindows Update)
Phoenix APIを再開すると、Nervesアプリは何事もなかったようにデータを送り続けていました。
…健気でした。学ばされました。

Grafanaのグラフ

Grafanaのグラフを示します。

aht20-grafana.jpg

6/21からの8日間ほど、線になっている部分がPhoenix APIサーバのほうが止まっていた期間です。

システム構成

システム構成を示します。

image.png

Raspberry Pi 4には、「ナウでヤングなcoolなすごいヤツ」であるElixir製のIoTフレームワークNervesで作ったアプリケーションが動いています。AHT20から取得した温度・湿度のデータを10秒に一回、Phoenixで作ったAPIサーバに送っています。Phoenixは、Elixir製のWebアプリケーションフレームワークです。

Windowsでは、Docker composeを使ってPhoenixアプリコンテナ、PostgreSQL(timescaledb)コンテナ、グラフ表示のGrafanaコンテナが動いています。

技術的ポイント

技術的ポイントをいくつか説明します。

Nervesの定期送信の仕組み

GenServerで定期送信をしています。

defmodule Publisher do
  use GenServer
  require Logger

  def start_link(options \\ %{}) do
    GenServer.start_link(__MODULE__, options, name: __MODULE__)
  end

  @impl true
  def init(options) do
    state = %{
      interval: options[:interval] || 10_000,
      aht20_tracker_url: options[:aht20_tracker_url],
      measurements: :no_measurements
    }

    schedule_next_publish(state.interval)
    {:ok, state}
  end

  defp schedule_next_publish(interval) do
    Process.send_after(self(), :publish_data, interval)
  end

  @impl true
  def handle_info(:publish_data, state) do
    {:noreply, state |> measure() |> publish()}
  end

  defp measure(state) do
    AHT20.read()
    |> measure(state)
  end

  defp measure({:ok, measurements}, state) do
    Map.merge(state, %{measurements: measurements})
  end

  defp measure(_, state) do
    state
  end

  defp publish(state) do
    result =
      Req.post(state.aht20_tracker_url, json: state.measurements)

    Logger.debug("Server response: #{inspect(result)}")
    schedule_next_publish(state.interval)
    state
  end
end

AHT20からの温度・湿度データの読み込み

AHT20からの温度・湿度データの読み込みは次のプログラムで行っています。

defmodule AHT20 do
  @i2c_bus "i2c-1"
  @i2c_addr 0x38
  @initialization_command <<0xBE, 0x08, 0x00>>
  @trigger_measurement_command <<0xAC, 0x33, 0x00>>
  @two_pow_20 2 ** 20

  def read do
    {:ok, ref} = Circuits.I2C.open(@i2c_bus)

    Circuits.I2C.write(ref, @i2c_addr, @initialization_command)
    Process.sleep(10)

    Circuits.I2C.write(ref, @i2c_addr, @trigger_measurement_command)
    Process.sleep(80)

    result =
      Circuits.I2C.read(ref, @i2c_addr, 7)
      |> convert()

    Circuits.I2C.close(ref)

    result
  end

  def convert({:ok, <<_state, raw_humidity::20, raw_temperature::20, _crc>>}) do
    humidity = (raw_humidity / @two_pow_20 * 100.0) |> Float.round(1)

    temperature =
      (raw_temperature / @two_pow_20 * 200.0 - 50.0)
      |> Float.round(1)

    {:ok, %{humidity: humidity, temperature: temperature}}
  end

  def convert({:error, error}), do: {:error, error}
  def convert(_), do: {:error, "An error occurred"}
end

気づき・教訓

人も、サービスも、組織も、「受け手がいない時間」に何をしているかが重要なのかもしれない。
Nervesアプリは、文句も言わず、ただやるべきことをやり続けていました。
自分も腐らず、たゆまず、積み重ねていこうと思いました。

ただQiitaへの記事投稿(闘魂)を続けていこうと思いました。

7
0
0

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
  3. You can use dark theme
What you can do with signing up
7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?