Nerves で空気質センサ SGP40 を使う


[Nerves]で空気質センサ[SGP40][SGP40 - Datasheet]を使います。

## TL;DR

- SGP40搭載 空気質センサボードを入手
- ご自身の[Nerves]のプロジェクトに[sgp40][sgp40 - hex]パッケージをインストール

## SGP40 とは

- [Sensirion]製
- 屋内の空気質を測定するために適したセンサ
- [混合酸化化合物（MOx）][MOX]センサと温度コントロール用小型ホットプレートを搭載
- 濃度値やVOCガス種のデータを出力するものではなく、屋内に存在する一般的な[燃料蒸発ガス（VOC）][VOC]をもとにデジタル値を出力
- 出力された値はSensirionのVOCインデックスアルゴリズムで利用可能
- 関連する[VOC]の変化にともなう相対濃度を検知し、24時間あたりの平均値として出力
- I2Cアドレス：`0x59`

## SGP40 搭載 空気質センサボードを入手

対象デバイスとセンサーとの間は[I2C]で通信するのですが、[I2C]の接続方法はメーカーにより様々ですので注意が必要です。

https://www.google.com/search?q=SGP40搭載+空気質センサボード&tbm=isch

例えば、[Qwiic接続システム]で接続する場合は以下のボードが使えます。オンラインまたはお近くの電子部品屋さんでお求めください。

https://www.switch-science.com/products/7287

https://www.power-of-tech.com/parts-map/index-map

## sgp40（Elixirパッケージ）

[メーカー提供のCで書かれたプログラム][Sensirion/embedded-i2c-sgp40]を[Elixir]から使えるようにしたものです。僕が色々試した結果できた産物です。よかったら使ってみてください。

Cプログラムを[Elixir]から使えるようにする方法にはいくつか選択肢がありますし、場合によってはプログラム全体を[Elixir]プログラムに翻訳する手もあります。

https://qiita.com/zacky1972/items/b1cbac9a4f31cd60800a

https://qiita.com/hisaway/items/ea80b50fef410a2e5432

https://andrealeopardi.com/posts/using-c-from-elixir-with-nifs/

https://www.thestackcanary.com/supercharge-your-elixir-with-nifs/

https://cultivatehq.com/posts/communicating-with-c-from-elixir-using-ports/

[Sensirion/embedded-i2c-sgp40]や[SGP40のデータシート][SGP40 - Datasheet]を眺めた結果、頻繁に複雑な計算処理が走るので[Elixir]プログラムに書き換えるのは得策ではないと思い、[Port]を介してメーカーのCプログラムと対話することにしました。

[Elixirの公式ドキュメント][Port]によると、[Port]は、[Erlang VM][Erlang]の外部で[オペレーティング システム プロセス](https://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9)を開始し、[メッセージ パッシング](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8_(%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF))を介してプロセスと通信するためのメカニズムを提供するとのことです。

> Functions for interacting with the external world through ports.
>
> Ports provide a mechanism to start operating system processes external to the Erlang VM and communicate with them via message passing.

[Port]利用する場合、開発者は[Erlang VM][Erlang]との通信に[標準入力・標準出力](https://qiita.com/angel_p_57/items/03582181e9f7a69f8168)を使用する実行可能ファイルを書きます。

個人的に[GregMefford/blinkchain]プロジェクトの実装例がわかりやすいのでいつも参考にしています。

### src/port_interface.h

- Cプログラムで計算した結果を`:ok`, `{:ok, "hoge"}`, `{:error, "hoge"}`の形式で[Elixir]プログラムに返すための関数群。

[GregMefford/blinkchain]の[port_interface.h](https://github.com/GregMefford/blinkchain/blob/8b7a76a3c1d0e19d533ce93f33e72f1a9fc2d599/src/port_interface.h)をインターフェースとしてそのまま使ってます。

https://github.com/elixir-sensors/sgp40/blob/c2188ccfb5452fb61997825f1fa80bb743312f18/src/port_interface.h

[GregMefford/blinkchain]: https://github.com/GregMefford/blinkchain

### src/main.c

- 無限ループで[Elixir]プログラムからコマンドが送られてくるのを待つ
- 標準入力から固定バイト数を読み取り、そこからコマンドと引数を取り出す
- 処理を実行
- 結果を`:ok`, `{:ok, "hoge"}`, `{:error, "hoge"}`の形式で[Elixir]プログラムに返す

https://github.com/elixir-sensors/sgp40/blob/c2188ccfb5452fb61997825f1fa80bb743312f18/src/main.c

バッファーの長さは、受け取りたいコマンド、引数、区切り文字を合わせた全体の最長文字数に応じてを決定します。

https://github.com/elixir-sensors/sgp40/blob/c2188ccfb5452fb61997825f1fa80bb743312f18/src/main.c#L14

不正なメッセージを誤って送信してしまった時にすぐに気がつくよう、コマンドが認識されなかった時にメッセージ全体をElixirプログラムに返すようにしておきます。

https://github.com/elixir-sensors/sgp40/blob/c2188ccfb5452fb61997825f1fa80bb743312f18/src/main.c#L104C1-L107C6

## Nervesで空気質センサSGP40を使う

使い方は簡単です。

ご自身のNervesプロジェクトの`mix.exs`の依存性リストに`{:sgp40, ~> 0.1.5}`を追加。

あとはいつも通りファームウエアをコンパイルして、ファームウエアをmicroSD焼き、microSDを対象デバイスに搭載。

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

https://qiita.com/torifukukaiou/items/6096c201fbb013e65baa

対象デバイスが起動したら、対話コンソール（IEx）を開き、SGP40モジュールの関数でセンサーを操作します。

```elixir
iex> {:ok, sgp} = SGP40.start_link(bus_name: "i2c-1")
{:ok, #PID<0.1407.0>}

iex> SGP40.measure(sgp)
{:ok, %SGP40.Measurement{voc_index: 123, timestamp_ms: 885906}}
```

:tada::tada::tada:

## Elixir Forum

Elixir Forum に続々お便りをいただいております。ありがとうございます。

https://elixirforum.com/t/build-a-weather-station-with-elixir-and-nerves-pragprog/41889/65

## さいごに

本記事は [autoracex #259](https://autoracex.connpass.com/event/300537/) の成果です。ありがとうございます。

https://autoracex.connpass.com/

https://qiita.com/torifukukaiou/items/1edb3e961acf002478fd

![toukon-qiita-macbook_20230912_091808.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82804/fd5c55ec-4fe0-8af6-59bc-bab1ef3d182b.jpeg)

<!-- begin hyperlink list -->
[Qwiic接続システム]: https://www.mouser.jp/new/sparkfun/sparkfun-qwiic-connect-system/
[Sensirion]: https://sensirion.com/jp/
[MOX]: https://ja.wikipedia.org/wiki/MOX%E7%87%83%E6%96%99
[VOC]: https://www.env.go.jp/air/osen/voc/e-as/forcaruser.html
[SGP40 - Datasheet]: https://cdn-learn.adafruit.com/assets/assets/000/097/511/original/Sensirion_Gas-Sensors_SGP40_Datasheet.pdf
[sgp40 - hex]: https://hex.pm/packages/sgp40
[SGP40 - switch-science]: https://www.switch-science.com/products/7287
[I2C]: https://ja.wikipedia.org/wiki/I2C
[Nerves]: https://github.com/nerves-project/nerves
[nerves]: https://github.com/nerves-project/nerves
[nerves_systems]: https://github.com/nerves-project/nerves_systems
[Nerves Systems Builder]: https://github.com/nerves-project/nerves_systems
[Elixir]: https://ja.wikipedia.org/wiki/Elixir_(プログラミング言語)
[Mix]: https://hexdocs.pm/mix/Mix.html
[Buildroot]: https://buildroot.org/
[x86_64]: https://ja.wikipedia.org/wiki/X64
[aarch64]: https://ja.wikipedia.org/wiki/AArch64
[Linux]: https://ja.wikipedia.org/wiki/Linux
[仮想機械]: https://ja.wikipedia.org/wiki/仮想機械
[Debian]: https://ja.wikipedia.org/wiki/Debian
[Erlang]: https://ja.wikipedia.org/wiki/Erlang
[hex]: https://hex.pm/
[rebar]: https://github.com/erlang/rebar3
[asdf]: https://asdf-vm.com/
[asdf installation]: https://asdf-vm.com/guide/getting-started.html#_3-install-asdf
[nerves_bootstrap]: https://github.com/nerves-project/nerves_bootstrap
[シェル]: https://ja.wikipedia.org/wiki/シェル
[bash]: https://ja.wikipedia.org/wiki/Bash
[アーカイブ]: https://ja.wikipedia.org/wiki/アーカイブ_(コンピュータ)
[インクリメンタルビルド]: https://ja.wikipedia.org/wiki/ビルド_(ソフトウェア)
[対象ボード]: https://hexdocs.pm/nerves/targets.html
[Sensirion/embedded-i2c-sgp40]: https://github.com/Sensirion/embedded-i2c-sgp40
[Port]: https://hexdocs.pm/elixir/Port.html
<!-- end hyperlink list -->