【M5StackMP135 】Livebook でエアコンの働を可視化してみた

# はじめに

[この記事](https://qiita.com/GeekMasahiro/items/1987ed321baa43724039)では、M5StackMP135でLivebookを動作させてGPIOを使ってみました。
M5Stackには、コネクターで接続するだけで使える豊富なデバイスが魅力です。加速度、3軸地磁気センサ、気圧・温度センサを一つのパッケージにした[M5STACK-U171](ssci.to/9426)（以下、U171と呼ぶことにします。）を[コアジェニック](https://coregenik.com/ja/index)さんにいただいたので、Livebookで遊んでみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/02670712-5b49-cf87-25d8-7bdbc173f3ff.png)

# i2cを使ってみる

U171には、３つのセンサーが搭載されています。いずれも一つのi2cバスに接続されていて、M5Stack本体からはi2cバスを介して制御します。
スイッチサイエンスの[説明ページ](https://www.switch-science.com/products/9426)によると搭載されているセンサーとアドレスは以下の通り

|センサーの種類|チップ名|アドレス|
|---|----|---|
|IMU|BMI270|0x68|
|地磁気センサ|BMM150|0x10|
|気圧・温度センサ|BMP280|0x76|

まずは、i2cバスの状態を確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/8488d3c0-dea4-b9de-efcb-07e1fda5112f.png)

３つのバス認識されていました。

[ドキュメント](https://docs.m5stack.com/en/core/M5CoreMP135)によると、Port Aのi2cバスは、```/dev/i2c-1```となっています。
detect_device/1を使って、接続されているデバイスを調べてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/6ebc2bda-f036-561f-f6a2-25f274a889a9.png)

0x68と0x76のデバイスを検知しました。
0x10の磁気センサーが見つかりません（謎）。

# 温度を測定してみる

温度センサーのBMP280はhexdocsに[モジュール](https://hexdocs.pm/bmp280/readme.html)がありました。
これを使って値を読み取ってみます。



```Elixir:setupのセル
Mix.install([
  {:circuits_i2c, "~> 2.0"},
  {:bmp280, "~> 0.2.13"}
])
```

```Elixir
{:ok, bmp} = BMP280.start_link(bus_name: "i2c-1", bus_address: 0x76)
BMP280.measure(bmp)
```

```Elixir:Output
{:ok,
 %BMP280.Measurement{
   temperature_c: 34.753966059455706,
   pressure_pa: 100672.16892826237,
   altitude_m: -56.548979627198875,
   humidity_rh: :unknown,
   dew_point_c: :unknown,
   gas_resistance_ohms: :unknown,
   timestamp_ms: 132942
 }}
 ```

室温34℃！！！
取得できました。

# グラフ表示してみる

動作を確認できたので、温度を定期的に測定してグラフ表示してみます。
グラフ表示まで簡単にできるところがLivebookのありがたい所ですね。

温度変化がないとつまらないので、エアコンのON/OFFをして、エアコンの働を可視化してみます。

15:27分エアコンON
15:47分エアコンOFF
です。
ちなみに、エアコンの設定温度は30℃です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/2432b002-2f2b-9ca9-e59d-fb3d87a99c1b.png)

ちゃんと測定できてそう。

# グラフ表示のプログラム

```Elixir
defmodule TemperatureServer do
  use GenServer

  # サーバーの開始
  def start_link() do
    {:ok, bmp} = BMP280.start_link(bus_name: "i2c-1", bus_address: 0x76)
    GenServer.start_link(__MODULE__, %{bmp: bmp, measurements: []}, name: __MODULE__)
  end

  # 初期化
  def init(state) do
    # 定期的に :measure_temperature メッセージを自身に送信する
    schedule_measurement()
    {:ok, state}
  end

  # 定期的に測定するためのスケジューリング関数
  defp schedule_measurement do
    # 5000ミリ秒 (5秒後) に :measure_temperature メッセージを送る
    Process.send_after(self(), :measure_temperature, 5000)
  end

  # 温度を測定して保存するハンドル
  def handle_info(:measure_temperature, %{bmp: bmp} = state) do
    # BMP280から温度を測定
    {:ok, measurement} = BMP280.measure(bmp)

    # 時刻を取得
    timestamp = DateTime.utc_now()

    # 結果をリストに追加
    new_measurement = {timestamp, measurement.temperature_c}
    new_state = %{state | measurements: [new_measurement | state.measurements]}

    # 次の測定をスケジューリング
    schedule_measurement()

    {:noreply, new_state}
  end

  # 測定結果を取得するための関数
  def handle_call(:get_measurements, _from, state) do
    {:reply, Enum.reverse(state.measurements), state}
  end

  # 外部API：測定結果を取得する
  def get_measurements do
    GenServer.call(__MODULE__, :get_measurements)
  end
end
```

```Elixir
defmodule TemperatureGraph do
  @moduledoc """
  Temperature measurements visualization using VegaLite.
  """

  alias VegaLite, as: Vl

  # 温度データをグラフ表示
  def plot_measurements(measurements) do
    # VegaLiteで必要なデータを生成
    data =
      measurements
      |> Enum.map(fn {timestamp, temperature} ->
        %{
          "timestamp" => DateTime.to_string(timestamp),
          "temperature" => temperature
        }
      end)

    Vl.new(width: 600, height: 400)
    |> Vl.data_from_values(data)
    |> Vl.mark(:line)
    |> Vl.encode_field(:x, "timestamp", type: :temporal)
    |> Vl.encode_field(:y, "temperature", type: :quantitative, scale: [domain: [30, 36]])
    |> Kino.VegaLite.new()

  end
end
```

```Elixir
TemperatureServer.get_measurements()
|> TemperatureGraph.plot_measurements()
```

# まとめ

- BMP280での温度計測がLivebookから行えた
- エアコンの働を可視化できた
- 暑いので熱中症には気をつけましょう