闘魂Elixir ーー 照度センサーで明るさを測ることを楽しむ

<b><font color="red">$\huge{元氣ですかーーーーッ！！！}$</font></b>
<b><font color="red">$\huge{元氣があればなんでもできる！}$</font></b>

<b><font color="red">$\huge{闘魂とは己に打ち克つこと。}$</font></b>
<b><font color="red">$\huge{そして闘いを通じて己の魂を磨いていく}$</font></b>
<b><font color="red">$\huge{ことだと思います}$</font></b>


# はじめに

この記事は、2023/03/19(日)に行われた「[NervesJP #36 Build a Weather Station を読む会 4回目](https://nerves-jp.connpass.com/event/277885/)」で行った照度センサーでの明るさ測定をまとめました。

この記事のメインは「[照度センサーで明るさを測る](https://qiita.com/torifukukaiou/items/cba33c4d39d6f776f99e#%E7%85%A7%E5%BA%A6%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC%E3%81%A7%E6%98%8E%E3%82%8B%E3%81%95%E3%82%92%E6%B8%AC%E3%82%8B)」です。
以下、周辺知識をまとめています。


## [Nerves](https://nerves-project.org/)?

[Nerves](https://nerves-project.org/)は、[Elixir](https://elixir-lang.org/)製のナウでヤングでクールなすごいIoTフレームワークです。
[Elixir](https://elixir-lang.org/)専用OSと言っても過言ではありません。

伝統的な組み込みエンジニアのあなたに[Nerves](https://nerves-project.org/)をぜひ使って欲しいです。
Jon Thacker 氏の言葉を引用します。

https://elixir-lang.org/blog/2023/03/09/embedded-and-cloud-elixir-at-sparkmeter/

> I’m traditionally an embedded engineer, and I only learned Elixir as part of this project. However, transferring my mental model was so easy that I do believe that we would be capable of training other embedded engineers as well.
> - Jon Thacker, Senior VP of Engineering

https://elixir--lang-org.translate.goog/blog/2023/03/09/embedded-and-cloud-elixir-at-sparkmeter/?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=wapp

> 私は伝統的に組み込みエンジニアであり、このプロジェクトの一環として Elixir を学んだだけです。しかし、自分のメンタル モデルを移植するのはとても簡単だったので、他の組み込みエンジニアも同様にトレーニングできると確信しています。
> - Jon Thacker 氏、エンジニアリング担当上級副社長 




## [Elixir](https://elixir-lang.org/)?

[Elixir](https://elixir-lang.org/)は、プログラミング言語です。
パイプ演算子[|>](https://hexdocs.pm/elixir/Kernel.html#%7C%3E/2)でつなげて処理を書くのが気持ちいいプログラミング言語です。

```elixir
Mix.install [{:req, "~> 0.3.6"}]

"https://qiita.com/api/v2/items?query=tag:Elixir&per_page=100"
|> Req.get!()
|> Map.get(:body)
|> Enum.filter(fn %{"likes_count" => likes_count} -> likes_count >= 1 end)
|> Enum.map(fn %{"title" => title, "user" => %{"id" => id}, "likes_count" => likes_count} -> {title, likes_count, id} end)
```

上記は、[Qiita API v2](https://qiita.com/api/v2/docs)を利用させていただいて、[Elixir](https://elixir-lang.org/)に関する記事を取得しています。
最新の100件を取得し、いいねが1以上付いた記事でフィルタリングし、各記事の情報を丸めています。

ここでは、[Elixir](https://elixir-lang.org/)を未インストールの方のために、[Docker](https://www.docker.com/)で動かす例を示します。

```
$ docker run --rm -it hexpm/elixir:1.14.3-erlang-25.2.3-ubuntu-jammy-20221130 iex
```

IExと呼ばれるREPL(Read-Eval-Print Loop)が立ち上がりますので、上記のプログラムを迷わず実行してみてください。
途中、`Shall I install Hex? (if running non-interactively, use "mix local.hex --force") [Yn]`や`Shall I install rebar3? (if running non-interactively, use "mix local.rebar --force") [Yn]`を訊かれたら、迷わず元氣よく`Y`を答えてください。

これであなたもアルケミスト(Alchemist)です。
[Elixir](https://elixir-lang.org/)を日本語訳すると「不老不死の霊薬」です。
プログラミング言語[Elixir](https://elixir-lang.org/)の使い手は、アルケミスト(Alchemist) ーー 錬金術師と尊称されます。

前置きが長くなりました。
そろそろ本題です。

## 対象読者

次の方におすすめです。

- [Elixir](https://elixir-lang.org/)をはじめてみたものの[Nerves](https://nerves-project.org/)になかなか最初の一歩が踏み出せずに危ぶんでいる人
- 伝統的な組み込みエンジニアのあなた

つまりは全人類です


# Build a Weather Station を読む会について

「そろそろ本題です」と言いつつ、「Build a Weather Station を読む会」の説明をします。

書籍があります。
『[Build a Weather Station with Elixir and Nerves](https://pragprog.com/titles/passweather/build-a-weather-station-with-elixir-and-nerves/)』という名の書籍です。
この本を読みましょう、ウェザーステーションを作りましょう、値を[Phoenix](https://www.phoenixframework.org/)で作ったAPIサーバーに放り込んで、[Grafana](https://grafana.com/)を使ってグラフを書いてみましょうという内容です。
[Elixir](https://elixir-lang.org/)、[Nerves](https://nerves-project.org/)を使って話が進行します。

![](https://pragprog.com/titles/passweather/build-a-weather-station-with-elixir-and-nerves/passweather.jpg)

英語の本です。
ご安心ください。
[Google翻訳](https://translate.google.com/?hl=ja)という文明の利器があります。

書籍の通りに進めるのが詰まるところがなくてよいとおもわれますが、日本ではなかなか手に入りにくいデバイスがあります。

ご安心ください。
日本の[Nerves](https://nerves-project.org/)界のお歴々が、日本で入手しやすいデバイスを選定してくれています。

ただ書籍から外れることをするので、デバイスの制御部分は自分たちで作らないといけません。
ご安心ください。
[NervesJP](https://nerves-jp.connpass.com/)がいっしょに開発を進めてくれます。

いい加減、本題に移ります。

---

# [Nerves](https://nerves-project.org/)の始め方

「いい加減、本題に移ります」と言いつつ、前置きの最後です。
[Nerves](https://nerves-project.org/)の始め方を紹介しておきます。

## [Elixir](https://elixir-lang.org/)でIoT!?

https://www.slideshare.net/takasehideki/elixiriotcoolnerves-236780506

## Nerves超入門 ーー Nervesを使った開発の日常風景（景色）、ElixirでIoTを楽しむ

https://qiita.com/torifukukaiou/items/91441a14dcf66472af39

[Elixir](https://elixir-lang.org/)、[Nerves](https://nerves-project.org/)のセットアップからmicroSDカードへのファームウェア焼き込みを解説しています。

もう本当に今度こそ本題に入ります。

---

# 照度センサーで明るさを測る

照度センサーでのルクスを測ります。
照度センサーは、[GROVE - I2C デジタル光センサ](https://www.switch-science.com/products/1174)を使います。

## 組み立て

安心してください。組み立ては難しくありません。
はめ込み式ですので、手先が[不器用ですから](https://www.youtube.com/watch?v=c4e7jGkbDDw&t=40)の私でも組み立てられます。

![IMG_20230323_201619.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131808/0658a7dd-79b9-a1d6-a25d-8ce92e98e174.jpeg)

I2Cのところに、[GROVE - I2C デジタル光センサ](https://www.switch-science.com/products/1174)を差し込んでいるのがポイントです。


使用デバイスの一覧は[コチラ](https://docs.google.com/spreadsheets/d/11b-yxD7ne-fYmr6ksc0hEvYLf14ZiMilTjIyyXybCMM/edit#gid=0)です。

https://www.youtube.com/watch?v=c4e7jGkbDDw&t=40

## データシート

https://raw.githubusercontent.com/SeeedDocument/Grove-Digital_Light_Sensor/master/res/TSL2561T.pdf

日本語訳をしてくださっている方がいらっしゃいました。ありがとうございます。

http://www.ne.jp/asahi/shared/o-family/ElecRoom/AVRMCOM/TSL2561/TSL2561.html

伝統的な組み込みエンジニアのあなたはすらすら読めるのだとおもいます。
私は門外漢でさっぱり読めません。こういったドキュメントのことを総じて「データシート」と呼ばれることもあまり馴染みがありません。
@pojiro さんのスペシャルサンクスでなんとかルクスを計算できました。

## 実装

[データシート](https://raw.githubusercontent.com/SeeedDocument/Grove-Digital_Light_Sensor/master/res/TSL2561T.pdf)の[日本語訳](http://www.ne.jp/asahi/o-family/extdisk/TSL2561/TSL2561_DJP.pdf)を見ながら、実装していくわけです。

この記事では、[Nerves](https://nerves-project.org/)にsshで入ってIEx上で動かしてみることにします。

まず、バンとプログラムを全掲載します。

```elixir
Circuits.I2C.detect_devices

{:ok, bus} = Circuits.I2C.open("i2c-1")
{:ok, <<_::4, 3::4>>} = Circuits.I2C.write_read(bus, 0x29, <<0xA0, 0x03>>, 1) # Power on
{:ok, <<partno::4, revno::4>>} = Circuits.I2C.write_read(bus, 0x29, <<0xAA>>, 1) # 部品番号とシリコン改訂番号の読み出し

f = fn ->
  {:ok, <<channel0::little-16, channel1::little-16>>} = Circuits.I2C.write_read(bus, 0x29, <<0xAC>>, 4)

  # 以下、部品番号が0 or 1のCSパッケージの場合の計算式
  channel0 = channel0 * 16
  channel1 = channel1 * 16
  ratio = channel1 / channel0
  cond do
    0 < ratio and ratio <= 0.52 -> 0.0315 * channel0 - 0.0593 * channel0 * (ratio ** 1.4)
    0.52 < ratio and ratio <= 0.65 -> 0.0229 * channel0 - 0.0291 * channel1
    0.65 < ratio and ratio <= 0.80 -> 0.0157 * channel0 - 0.0180 * channel1
    0.80 < ratio and ratio <= 1.30 -> 0.00338 * channel0 - 0.00260 * channel1
    1.30 < ratio -> 0
    true -> 0
  end
end
```

私一人では一生かかってもできなかったかもしれません。正直に言うと、[データシート](https://raw.githubusercontent.com/SeeedDocument/Grove-Digital_Light_Sensor/master/res/TSL2561T.pdf
)の[日本語訳](http://www.ne.jp/asahi/o-family/extdisk/TSL2561/TSL2561_DJP.pdf)をにらめっこしても電源ONで手が止まりました。
@pojiro さんのスペシャルサンクスのおかげです。

### 解説

以下、解説します。

#### Circuits.I2C.detect_devices

```elixir
iex> Circuits.I2C.detect_devices
Devices on I2C bus "i2c-1":
 * 4  (0x4)
 * 41  (0x29)

Devices on I2C bus "i2c-20":

Devices on I2C bus "i2c-21":

2 devices detected on 3 I2C buses
```

この結果から、デバイスのアドレスは、`0x29`です。（`0x4`はなに？）

#### Circuits.I2C.open

まずデバイスをオープンします。

```elixir
{:ok, bus} = Circuits.I2C.open("i2c-1")
```

#### 電源ON

電源を入れます。

```elixir
{:ok, <<_::4, 3::4>>} = Circuits.I2C.write_read(bus, 0x29, <<0xA0, 0x03>>, 1) # Power on
```

2バイトのデータ`<<0xA0, 0x03>>`を書き込んで1バイト読み込んでいます。

`0xA0`は、「COMMANDレジスター」の節をご参照ください。

- 上位4bit(つまり`A`)は、COMMANDレジスターの選択とSMBのワード書き込み／ワード読み出しプロトコルの選択を意味します。
- 下位4bit(つまり`0`)は、CONTROL レジスター(0h)を指定しています。

`0x03`は、CONTROL レジスター(0h)に書き込む値です。
「CONTROL レジスター」の節をご参照ください。
下位2bitが1つまり、0x03を書き込むと電源ONになります。
0x00を書き込むと電源OFFとなり、照度センサーから値は取れなくなります。

左辺の`{:ok, <<_::4, 3::4>>}`を説明します。
電源ONすると、下位4bitからは3が返ってくるとのことです。上位4bitも3のような気もしますが、「何らかの情報」とのことなので無視することにします。
[Elixir](https://elixir-lang.org/)の場合、`::bit数`でビット指定で値を取り出すことができます。
詳しくは[<<>>](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#%3C%3C%3E%3E/1)をご参照ください。

こちらの記事（「[組込みに欠かせない Elixir でのビットの扱い方](https://qiita.com/kikuyuta/items/e200a6208013f38333de)」、「[ビット操作関数 Bitwise を使ってみる](https://qiita.com/kikuyuta/items/7212ea7c190b303aa606)」 両方とも@kikuyuta先生の記事）もあわせてお読みください。

https://qiita.com/kikuyuta/items/e200a6208013f38333de

https://qiita.com/kikuyuta/items/7212ea7c190b303aa606


#### 部品番号とシリコン改訂番号の読み出し

部品番号とシリコン改訂番号を読み出します。
さきほどと同じような要領で4bitのデータを取り出します。
「IDレジスター （Ah）」の節をご参照ください。

部品番号(partno)は4種類の値があります。
0, 1: CSパッケージ
4, 5: T/FN/CLパッケージ

[GROVE - I2C デジタル光センサ](https://www.switch-science.com/products/1174)は、2種類のパッケージがあります。
CSパッケージとT/FN/CLパッケージの2つです。
取得した値をルクスに変換する際の計算式がパッケージによって異なります。

```elixir
{:ok, <<partno::4, revno::4>>} = Circuits.I2C.write_read(bus, 0x29, <<0xAA>>, 1)
```

上位4bitに部品番号、下位4bitにシリコン改訂番号が格納されています。
シリコン改訂番号(revno)は特に今回は使いません。

#### ルクス計算

```elixir
f = fn ->
  {:ok, <<channel0::little-16, channel1::little-16>>} = Circuits.I2C.write_read(bus, 0x29, <<0xAC>>, 4)

  # 以下、部品番号が0 or 1のCSパッケージの場合の計算式
  channel0 = channel0 * 16
  channel1 = channel1 * 16
  ratio = channel1 / channel0
  cond do
    0 < ratio and ratio <= 0.52 -> 0.0315 * channel0 - 0.0593 * channel0 * (ratio ** 1.4)
    0.52 < ratio and ratio <= 0.65 -> 0.0229 * channel0 - 0.0291 * channel1
    0.65 < ratio and ratio <= 0.80 -> 0.0157 * channel0 - 0.0180 * channel1
    0.80 < ratio and ratio <= 1.30 -> 0.00338 * channel0 - 0.00260 * channel1
    1.30 < ratio -> 0
    true -> 0
  end
end
```

何回も実行できるように無名関数を`f`に束縛しています。
実行は、`f.()`です。

まず`Circuits.I2C.write_read(bus, 0x29, <<0xAC>>, 4)`は、Cレジスターを選択して4バイトのデータを読み出しています。
2バイトずつ意味があり、リトルエンディアンです。`channel0`と`channel1`という変数に束縛しています。なぜ`channel`なのかはピンときていませんが、データシートにそう書いてあります。

値が取れればあとは計算式に当てはめるだけです。
データシートにルクスの計算式は書いてあります。

ルクスの計算は「Lux（ルクス）の計算」の節をご参照ください。
さきほど触れた通り、CSパッケージとT/FN/CLパッケージとでは計算式が異なります。
私が所持しているものはCSパッケージでした。
ここでのプログラム例は、CSパッケージの計算式を書いています。

16倍しているところは、Cのサンプルに書いてある「もし、利得が16Xでない場合はスケールする。」に対応します。
「TIMING レジスター （1h）」の設定次第です。
今回は設定を変更していないので、初期値の`0x02`となり上記の形になります。

疑り深い方は以下のプログラムで値を取得して、「TIMING レジスター （1h）」の節と見比べてみてください。

```elixir
{:ok, <<0::3, gain::1, manual::1, 0::1, integ::2>>} = Circuits.I2C.write_read(bus, 0x29, <<0xA1>>, 1)
```

## 実行

`f.()`でルクスを測り放題です。

```elixir
f.()
```

`238.3050122220337`が返ってきました。夜ではありますが、パソコン作業するのにはちょっと暗いのかも。

https://www.10kai.co.jp/contents/code/column106

このコラムによると、「●勉強・読書：500～1000lx」とのことです。

## お手本

@pojiro さんのお手本は以下にあります。

https://github.com/NervesJP/sensor_hub_poncho/blob/main/sensor_hub/lib/sensor_hub/tsl2561.ex

ありがとうございます！！！


---

# さいごに

まとめます。
照度センサーで明るさを測ることを楽しみました。

この記事で言いたいことをもう一度書いておきます。

[Nerves](https://nerves-project.org/)は、[Elixir](https://elixir-lang.org/)製のナウでヤングでクールなすごいIoTフレームワークです。
伝統的な組み込みエンジニアのあなたにぜひ[Nerves](https://nerves-project.org/)を触ってもらいたいです。
ビットの演算が超楽チンです。

https://qiita.com/kikuyuta/items/e200a6208013f38333de


**闘魂**とは、  **「己に打ち克つこと。そして闘いを通じて己の魂を磨いていくことである」** との猪木さんの言葉をそのまま胸に刻み込んでいます。
知っているだけで終わらせることなく、実行する、断行する、一歩を踏み出すことを自らの行動で示していきたいとおもいます。
**アントニオ猪木さんのメッセージから元氣をもらったものとして、それを次代に語り継ぎ、自分自身が「闘魂」を体現するものでありたいとおもいます。**

---

<b><font color="red">$\huge{元氣ですかーーーーッ！！！}$</font></b>
<b><font color="red">$\huge{元氣があればなんでもできる！}$</font></b>
<b><font color="red">$\huge{１、２、３ ぁっダァー！}$</font></b>