Raspberry Piで照度を取得する TSL2572 TSL2561

Raspberry Pi

照度を取得したい

これまでで、気温、湿度、気圧が取得できるようになりました。

次は何をセンシングするか・・・それは光、つまり照度です。

照度を取得することで、簡単に以下のようなことができるようになります。

照度センサーとスマートホーム一例
  • 夜間に部屋に人がいることの判断
  • 深夜電気を消し忘れている際に自動で消す
  • 夜間の行動ログ
  • 寝室で明るさから睡眠時刻の判断、記録

主に夜間に部屋が明るいか暗いかを判断できます。
昼間は太陽光があるので人の行動はあまり検知できません。

使用するセンサーについて

購入時期の関係で、私の手元にある照度センサーが2種類あります。

TSL25721 (TSL2572)
・TSL2561

両方とも秋月電子で購入しましたが、現在はTSL25721 (TSL2572)しか販売していないようです。

今回はTSL25721はRaspberry Pi3、TSL2561はRaspberry Pi Zeroに接続して使用しようと思います。

TSL25721

接続する

秋月電子のサイトにRaspberry Piとの接続例とサンプルプログラムがあるので、そのまま利用してみます。

TSL25721使用 照度センサーモジュール: センサ一般 秋月電子通商-電子部品・ネット通販
電子部品,通販,販売,半導体,IC,マイコン,電子工作TSL25721使用 照度センサーモジュール秋月電子通商 電子部品通信販売
TSL25721Raspberry Pi
Vin3.3v (1番ピン)
GNDGND (6番ピン)
3v33.3v (1番ピン)
0E繋がない
INT繋がない
SDAGPIO2 (SDA) 3番ピン
SCLGPIO3 (SCL) 5番ピン

接続したら、コマンドでI2Cで認識されているか確認します。

pi@raspberrypi3:/opt/TSL2572 $ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- 39 -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --

39番に認識されています。76番はBME280です。

サンプルプログラムを実行する

上記のように接続したら秋月のサンプルプログラムを実行してみます。
途中でiPhoneのライトを当ててみました。
光量の変化が反映されています。

pi@raspberrypi3:/opt/TSL2572 $ python3 TSL2572.py
init....
0 Lx
81.3 Lx
81.3 Lx
81.3 Lx
81.3 Lx
81.3 Lx
81.3 Lx
80.9 Lx
389.8 Lx
195.5 Lx
204.9 Lx
1114.4 Lx
2056.6 Lx
1730.9 Lx
2063.0 Lx
1615.8 Lx
1954.5 Lx

このままでは、光量を1分ごとにロギングするといった用途には不向きなプログラムなので、少し修正します。

TSL2572_2.py
#Sensor <-> RasPiHeader
#Vin    <-> 01
#3V3    <-> 01
#GND    <-> 09
#SCL    <-> 05
#SDA    <-> 03

#Python 2.7.16

import time
import smbus
i2c = smbus.SMBus(1)

#TSL2572 Register Set
TSL2572_ADR      = 0x39
TSL2572_COMMAND  = 0x80
TSL2572_TYPE_REP = 0x00
TSL2572_TYPE_INC = 0x20
TSL2572_ALSIFC   = 0x66

TSL2572_SAI   = 0x40
TSL2572_AIEN  = 0x10
TSL2572_WEN   = 0x80
TSL2572_AEN   = 0x02
TSL2572_PON   = 0x01

TSL2572_ENABLE   = 0x00
TSL2572_ATIME    = 0x01
TSL2572_WTIME    = 0x03
TSL2572_AILTL    = 0x04
TSL2572_AILTH    = 0x05
TSL2572_AIHTL    = 0x06
TSL2572_AIHTH    = 0x07
TSL2572_PRES     = 0x0C
TSL2572_CONFIG   = 0x0D
TSL2572_CONTROL  = 0x0F
TSL2572_ID       = 0x12
TSL2572_STATUS   = 0x13
TSL2572_C0DATA   = 0x14
TSL2572_C0DATAH  = 0x15
TSL2572_C1DATA   = 0x16
TSL2572_C1DATAH  = 0x17

#TSL2572 setings
atime = 0xC0
gain = 1.0

def initTSL2572() :
  if (getTSL2572reg(TSL2572_ID)!=[0x34]) :
    #check TSL2572 ID
    return -1
  setTSL2572reg(TSL2572_COMMAND | TSL2572_TYPE_INC | TSL2572_CONTROL,0x00)
  setTSL2572reg(TSL2572_COMMAND | TSL2572_TYPE_INC | TSL2572_CONFIG,0x00)
  setTSL2572reg(TSL2572_COMMAND | TSL2572_TYPE_INC | TSL2572_ATIME,atime)
  setTSL2572reg(TSL2572_COMMAND | TSL2572_TYPE_INC | TSL2572_ENABLE,TSL2572_AEN | TSL2572_PON)
  return 0


def setTSL2572reg(reg,dat) :
  i2c.write_byte_data(TSL2572_ADR,reg,dat)


def getTSL2572reg(reg) :
  dat = i2c.read_i2c_block_data(TSL2572_ADR,TSL2572_COMMAND | TSL2572_TYPE_INC | reg,1)
  return dat


def getTSL2572adc() :
  dat = i2c.read_i2c_block_data(TSL2572_ADR,TSL2572_COMMAND | TSL2572_TYPE_INC | TSL2572_C0DATA,4)
  adc0 = (dat[1] << 8) | dat[0]
  adc1 = (dat[3] << 8) | dat[2]
  return[adc0,adc1]


#main
#print("init....")
if (initTSL2572()!=0) :
 print("Failed. Check connection!!")
 sys.exit()

measure_num=3    # Number of iterations
total=0

for sample_i in range(measure_num):
  adc = getTSL2572adc()

  cpl = 0.0
  lux1 = 0.0
  lux2 = 0.0
  cpl = (2.73 * (256 - atime) * gain)/(60.0)
  lux1 = ((adc[0] * 1.00) - (adc[1] * 1.87)) / cpl
  lux2 = ((adc[0] * 0.63) - (adc[1] * 1.00)) / cpl
  if ((lux1 <= 0) and (lux2 <= 0)) :
    #print("0 Lx")
    total = total + 0
  elif (lux1 > lux2) :
    #print("{:.1f} Lx".format(lux1))
    total = total + lux1
  elif (lux1 < lux2) :
    #print("{:.1f} Lx".format(lux2))
    total = total + lux2
  time.sleep(0.2)

print("{:.1f} Lx".format(total / measure_num))

変更点としては、「init...」や各print文のコメントアウトと3回測定して平均値を出力するようにしています。
measure_numを変更すれば測定回数を変えられます。
他の部分はサンプルと同じです。

TSL2561

次はRaspberry Pi ZeroにTSL2561を接続して照度を取得してみます。

接続する

ピン配置は同じでした。

TSL2561Raspberry Pi
Vin3.3v (1番ピン)
GNDGND (6番ピン)
3v33.3v (1番ピン)
0E繋がない
INT繋がない
SDAGPIO2 (SDA) 3番ピン
SCLGPIO3 (SCL) 5番ピン

I2Cで認識されているか確認します。

pi@raspberrypizero:~ $ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- 39 -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --

同様に39番で認識していました。こちらも76番はBME280です。

プログラムを実行する

秋月のTSL2572用のスクリプトが使えないか実験してみます。

pi@raspberrypizero:/opt/tsl2561 $ python3 tsl2561.py
Failed. Check connection!!
Traceback (most recent call last):
  File "tsl2561.py", line 79, in <module>
    sys.exit()
NameError: name 'sys' is not defined

だめでした。接続確認で失敗しているようです。

先人の知恵をお借りしてサンプルコードを拝借します。
下記サイトを参考にさせていただきました。

Raspberry pi 3でストロベリー・リナックス社製「TSL2561 照度センサ・モジュール メーカー品番:TSL2561」を使う(試行錯誤編) - Qiita
Rasberry pi 3でストロベリー・リナックス社製の「TSL2561 照度センサ・モジュール (メーカー品番:TSL2561)」を使うための奮闘記録です。TSL2561チップを使ったセンサーの記事はいくつかありますが、うまく動か...
pi@raspberrypizero:/opt/tsl2561 $ python tsl2561_3.py
Lux : 136.94852521
Lux : 136.663190584

値が取得できました。

同じような条件で比較する TSL2561 TSL2572

TSL2561とTSL2572でセンサーの値を比較してみます。

両方センサー部を上に向けてみました。

しかし、同じ部屋で同じような条件で取得しているのに、tsl2561とtsl2572ではlxの値が異なっています。

そこで、以下のサイトでtsl2561とtsl2572でそれぞれサンプルを公開されているので実行してみます。

RPZ-IR-Sensor (Raspberry Pi用 温度/湿度/気圧/明るさ/赤外線 ホームIoT拡張ボード) – Indoor Corgi
pi@raspberrypi3:/opt/TSL2572 $ python TSL2572_2.py
 ADC Time : 200ms
 ADC Gain : 8
 ch0 : 0x150D
 ch1 : 0x7AE
 Lux : 65.9lux
pi@raspberrypizero:/opt/tsl2561 $ python tsl2561_2.py
 ADC Time : 101ms
 ADC Gain : High
 ch0 : 0x829
 ch1 : 0x2EB
 Lux : 252.8lux

センサーが異なるので同じ値にはならないのかもしれませんが、なんか気分が良くないですね。。。

今度秋月を利用することがあれば、TSL2572をもう一つ購入しようかと思います。

同じセンサーで同じプログラムを使わないと良くないですね。

今回は最初のサイトのスクリプトをメインで使用させていただきたいと思います。
https://qiita.com/boyaki_machine/items/a238e9d03455a2eea26e

TSL2572のときと同様に、3回計測して平均を出すようにスクリプトを修正します。

TSL2561.py
#!/usr/bin/python -u
# -*- coding: utf-8 -*-

import smbus
import time

# Strawberry Linux社の「TSL2561 照度センサ・モジュール」から
# I2Cでデータを取得するクラス
# https://strawberry-linux.com/catalog/items?code=12561
# 2016-05-03 Boyaki Machine
class SL_TSL2561:
    def __init__(self, address, channel):
        self.address    = address
        self.channel    = channel
        self.bus        = smbus.SMBus(self.channel)
        self.gain       = 0x00          # 0x00=normal, 0x10=×16
        self.integrationTime    = 0x02  # 0x02=402ms, 0x01=101ms, 0x00=13.7ms
        self.scale      = 1.0

        # センサ設定の初期化
        self.setLowGain()
        self.setIntegrationTime('default')

    def powerOn(self):
        self.bus.write_i2c_block_data(self.address, 0x80, [0x03])
        time.sleep(0.5)

    def powerOff(self):
        self.bus.write_i2c_block_data(self.address, 0x80, [0x00])

    # High Gainにセットする(16倍の感度?)
    def setHighGain(self):
        # High Gainにするとうまくrawデータが取れないことがある。
        # 要原因調査 ( 5047固定値になる )
        self.gain   = 0x10
        data        = self.integrationTime | self.gain
        self.bus.write_i2c_block_data(self.address, 0x81, [data])
        self.calcScale()

    # Low Gain(default) にセットする
    def setLowGain(self):
        self.gain   = 0x00
        data        = self.integrationTime | self.gain
        self.bus.write_i2c_block_data(self.address, 0x81, [data])
        self.calcScale()

    # 積分する時間の設定(1回のセンシングにかける時間?)
    # val = shor, middle, logn(default)
    def setIntegrationTime(self, val):
        if val=='short':
            self.integrationTime    = 0x00  # 13.7ms scale=0.034
        elif val=='middle':
            self.integrationTime    = 0x01  # 101ms  scale=0.252
        else:
            self.integrationTime    = 0x02  # defaultVal 402ms  scale=1.0
        data = self.integrationTime | self.gain
        self.bus.write_i2c_block_data(self.address, 0x81, [data])
        self.calcScale()

    def getVisibleLightRawData(self):
        data    = self.bus.read_i2c_block_data(self.address, 0xAC ,2)
        raw     = data[1] << 8 | data[0]    # 16bitで下位バイトが先
        return raw

    def getInfraredRawData(self):
        data    = self.bus.read_i2c_block_data(self.address, 0xAE ,2)
        raw     = data[1] << 8 | data[0]    # 16bitで下位バイトが先
        return raw

    def getRawData(self):
        data    = self.bus.read_i2c_block_data(self.address, 0xAC ,4)
        VL      = data[1] << 8 | data[0]    # 可視光 16bitで下位バイトが先
        IR      = data[3] << 8 | data[2]    # 赤外線 16bitで下位バイトが先
        return (VL,IR)

    def calcScale(self):
        _scale = 1.0
        # integrationTimeによるスケール
        if self.integrationTime == 0x01:    # middle
            _scale = _scale / 0.252
        elif self.integrationTime == 0x00:  # short
            _scale = _scale / 0.034

        # gainによるスケール
        if self.gain == 0x00 :              # gain 1
            _scale = _scale * 16.0

        self.scale = _scale

    def getLux(self):
        # センサ生データの取得
        raw  = self.getRawData()

        # 65535の時はエラー出力にする実装
        if raw[0] == 65535 or raw[1] == 65535:
            return "Range Over"

        # センサ設定により生データをスケールする
        VLRD = raw[0] * self.scale
        IRRD = raw[1] * self.scale

        # 0の除算にならないように
        if (float(VLRD) == 0):
            ratio = 9999
        else:
            ratio = (IRRD / float(VLRD))

        # Luxの算出
        if ((ratio >= 0) & (ratio <= 0.52)):
            lux = (0.0315 * VLRD) - (0.0593 * VLRD * (ratio**1.4))
        elif (ratio <= 0.65):
            lux = (0.0229 * VLRD) - (0.0291 * IRRD)
        elif (ratio <= 0.80):
            lux = (0.0157 * VLRD) - (0.018 * IRRD)
        elif (ratio <= 1.3):
            lux = (0.00338 * VLRD) - (0.0026 * IRRD)
        elif (ratio > 1.3):
            lux = 0

        return lux


if __name__ == "__main__":
    sensor  = SL_TSL2561(0x39,1)
    sensor.powerOn()
    # sensor.setHighGain()
    sensor.setIntegrationTime('default')

    measure_num = 3
    total = 0

    for sample_i in range(measure_num):
        #lux = str("{:.1f}".format(sensor.getLux()))
        total = total + sensor.getLux()
        #print str(sensor.getLux()) + "lx"
        #print lux + " lx"
        time.sleep(0.2)

    print "{:.1f}".format(total / measure_num) + " lx"

最後の方を少しだけ修正しています。
※python2系で実行してください。python3だとエラーになります。

Mackerelに送信する

Mackerelにデータを送るようにしてみます。

Raspberry Pi Zero WHにMackerelを導入して死活監視と気温などのグラフ化を行う
Mackerelを導入してできることMackerelは簡単に言うとSaaS型の監視サービスです。疎通確認やCPU使用率などを監視して問題があるとSlackやメールにアラートを飛ばしてくれます。クラウド型なので、...

Mackerelの形式に変換するシェルスクリプトを書きます。

2561と2572は適宜読み替えてください。

lumi.sh
#!/bin/bash

data=$(python /opt/tsl2561/tsl2561.py)
array=(${data//,/ })
VALUE=${array[0]}
SECONDS=`date '+%s'`
NAME='luminosity.room'

echo -e "${NAME}\t${VALUE}\t${SECONDS}"

実行権限も忘れずに。

$ chmod +x lumi.sh

あとはMackerelのコンフィグに登録します。

/etc/mackerel-agent/mackerel-agent.conf
[plugin.metrics.roomluminosity]
command = "/opt/mackerel/lumi.sh"

最終行に追加しました。

あとはMackerelエージェントの再起動を行います。

$ sudo systemctl restart mackerel-agent.service

グラフが取得できるようになれば完成です。

コメント

タイトルとURLをコピーしました