3
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RaspberryPi3で温湿度を取得してSlackに通知する

Posted at

##0.はじめに
ラズパイで何か役に立つものを作りたいと思い、これをやってみました。
Slackで毎日AM11時に、部屋の温湿度の通知が来ることを目標にして作っていきます。

尚、基本的には下記の記事を参考にしています。ありがとうございました。
【30分IoT】Raspberry PiとSlackを連携させて温湿度センサーの値(AM2302)を投稿してみよう

##1.必要なもの
以下を使って作成しました。

  • RaspberryPi3
  • 温湿度センサモジュール(DHT11)
  • ブレッドボード
  • 抵抗10kΩ 1本
  • ジャンパーケーブル(オスXメス) 3本

##2.環境
次のことが準備できている前提で進みます。

  • ラズパイのOSインストール、初期設定が完了している。
  • 必要なものが揃っている。
  • Slackのサインアップが終わっている。

##3.配線する
ラズパイの電源を落として下図のように配線をします。
今回GPIOの接続は4番ピンにしています。
この後、温度を取得する時に指定するので、4番でない場合は何番にしたか覚えておきましょう。

DHT11
スクリーンショット 2018-03-11 15.20.14.png

スクリーンショット 2018-03-11 12.17.35.png

##4.Slackの準備をする
次にSlackに受信するためのAPIの導入をしていきます。
以下のページから設定を行います。
https://my.slack.com/services/new/incoming-webhook/

-1.チャンネルを選択して、「着信Webフックインテグレーションの追加」をクリックします。
スクリーンショット 2018-03-11 12.43.30.png

-2.このURLはPythonのプログラムで使用します。
スクリーンショット 2018-03-11 12.46.16.png

-3.設定を保存する。名前やアイコンは自由に変更することができます。
スクリーンショット 2018-03-11 14.14.38.png

##5.温湿度を取得する
次にDHT11を使って、温度や湿度を取得するプログラムを用意するのですが、
これはすでに先人の方が用意をしてくれております。(本当にありがとうございます。)

そこで今回はそのプログラムをgitから取得する手順を載せます。

-1.Slackwebをインストールします。
まずはラズパイからSlackに通知するための準備として、Slackwebをインストールします。
ラズパイからターミナルを開いてください。

terminal
$ sudo pip install slackweb
Collecting slackweb
  Downloading slackweb-1.0.5.tar.gz
Building wheels for collected packages: slackweb
  Running setup.py bdist_wheel for slackweb ... done
  Stored in directory: /root/.cache/pip/wheels/3e/40/3e/787721594a76a65d717864ed83b21b5dfc56cc93598d872157
Successfully built slackweb
Installing collected packages: slackweb
Successfully installed slackweb-1.0.5

-2.温度を取得するプログラムをgitから取得します。
gitをインストールしていない方は、gitをインストールします。

terminal
$ sudo apt-get install git

任意のディレクトリで下記を実行します。

terminal
$ git clone https://github.com/szazo/DHT11_Python.git
Cloning into 'DHT11_Python'...
remote: Counting objects: 48, done.
remote: Total 48 (delta 0), reused 0 (delta 0), pack-reused 48
Unpacking objects: 100% (48/48), done.

このような構成になっていると思います。

terminal
/DHT11_Python $ ls -l
合計 24
-rw-r--r-- 1 pi pi 1071  2月  4 13:39 LICENSE.md
-rw-r--r-- 1 pi pi  899  2月  4 13:39 README.md
-rw-r--r-- 1 pi pi    1  2月  4 13:39 __init__.py
-rw-r--r-- 1 pi pi 5969  2月  4 13:39 dht11.py
-rw-r--r-- 1 pi pi  464  2月  4 13:39 dht11_example.py
dht11.py
import time
import RPi


class DHT11Result:
    'DHT11 sensor result returned by DHT11.read() method'

    ERR_NO_ERROR = 0
    ERR_MISSING_DATA = 1
    ERR_CRC = 2

    error_code = ERR_NO_ERROR
    temperature = -1
    humidity = -1

    def __init__(self, error_code, temperature, humidity):
        self.error_code = error_code
        self.temperature = temperature
        self.humidity = humidity

    def is_valid(self):
        return self.error_code == DHT11Result.ERR_NO_ERROR


class DHT11:
    'DHT11 sensor reader class for Raspberry'

    __pin = 0

    def __init__(self, pin):
        self.__pin = pin

    def read(self):
        RPi.GPIO.setup(self.__pin, RPi.GPIO.OUT)

        # send initial high
        self.__send_and_sleep(RPi.GPIO.HIGH, 0.05)

        # pull down to low
        self.__send_and_sleep(RPi.GPIO.LOW, 0.02)

        # change to input using pull up
        RPi.GPIO.setup(self.__pin, RPi.GPIO.IN, RPi.GPIO.PUD_UP)

        # collect data into an array
        data = self.__collect_input()

        # parse lengths of all data pull up periods
        pull_up_lengths = self.__parse_data_pull_up_lengths(data)

        # if bit count mismatch, return error (4 byte data + 1 byte checksum)
        if len(pull_up_lengths) != 40:
            return DHT11Result(DHT11Result.ERR_MISSING_DATA, 0, 0)

        # calculate bits from lengths of the pull up periods
        bits = self.__calculate_bits(pull_up_lengths)

        # we have the bits, calculate bytes
        the_bytes = self.__bits_to_bytes(bits)

        # calculate checksum and check
        checksum = self.__calculate_checksum(the_bytes)
        if the_bytes[4] != checksum:
            return DHT11Result(DHT11Result.ERR_CRC, 0, 0)

        # ok, we have valid data, return it
        return DHT11Result(DHT11Result.ERR_NO_ERROR, the_bytes[2], the_bytes[0])

    def __send_and_sleep(self, output, sleep):
        RPi.GPIO.output(self.__pin, output)
        time.sleep(sleep)

    def __collect_input(self):
        # collect the data while unchanged found
        unchanged_count = 0

        # this is used to determine where is the end of the data
        max_unchanged_count = 100

        last = -1
        data = []
        while True:
            current = RPi.GPIO.input(self.__pin)
            data.append(current)
            if last != current:
                unchanged_count = 0
                last = current
            else:
                unchanged_count += 1
                if unchanged_count > max_unchanged_count:
                    break

        return data

    def __parse_data_pull_up_lengths(self, data):
        STATE_INIT_PULL_DOWN = 1
        STATE_INIT_PULL_UP = 2
        STATE_DATA_FIRST_PULL_DOWN = 3
        STATE_DATA_PULL_UP = 4
        STATE_DATA_PULL_DOWN = 5

        state = STATE_INIT_PULL_DOWN

        lengths = [] # will contain the lengths of data pull up periods
        current_length = 0 # will contain the length of the previous period

        for i in range(len(data)):

            current = data[i]
            current_length += 1

            if state == STATE_INIT_PULL_DOWN:
                if current == RPi.GPIO.LOW:
                    # ok, we got the initial pull down
                    state = STATE_INIT_PULL_UP
                    continue
                else:
                    continue
            if state == STATE_INIT_PULL_UP:
                if current == RPi.GPIO.HIGH:
                    # ok, we got the initial pull up
                    state = STATE_DATA_FIRST_PULL_DOWN
                    continue
                else:
                    continue
            if state == STATE_DATA_FIRST_PULL_DOWN:
                if current == RPi.GPIO.LOW:
                    # we have the initial pull down, the next will be the data pull up
                    state = STATE_DATA_PULL_UP
                    continue
                else:
                    continue
            if state == STATE_DATA_PULL_UP:
                if current == RPi.GPIO.HIGH:
                    # data pulled up, the length of this pull up will determine whether it is 0 or 1
                    current_length = 0
                    state = STATE_DATA_PULL_DOWN
                    continue
                else:
                    continue
            if state == STATE_DATA_PULL_DOWN:
                if current == RPi.GPIO.LOW:
                    # pulled down, we store the length of the previous pull up period
                    lengths.append(current_length)
                    state = STATE_DATA_PULL_UP
                    continue
                else:
                    continue

        return lengths

    def __calculate_bits(self, pull_up_lengths):
        # find shortest and longest period
        shortest_pull_up = 1000
        longest_pull_up = 0

        for i in range(0, len(pull_up_lengths)):
            length = pull_up_lengths[i]
            if length < shortest_pull_up:
                shortest_pull_up = length
            if length > longest_pull_up:
                longest_pull_up = length

        # use the halfway to determine whether the period it is long or short
        halfway = shortest_pull_up + (longest_pull_up - shortest_pull_up) / 2
        bits = []

        for i in range(0, len(pull_up_lengths)):
            bit = False
            if pull_up_lengths[i] > halfway:
                bit = True
            bits.append(bit)

        return bits

    def __bits_to_bytes(self, bits):
        the_bytes = []
        byte = 0

        for i in range(0, len(bits)):
            byte = byte << 1
            if (bits[i]):
                byte = byte | 1
            else:
                byte = byte | 0
            if ((i + 1) % 8 == 0):
                the_bytes.append(byte)
                byte = 0

        return the_bytes

    def __calculate_checksum(self, the_bytes):
        return the_bytes[0] + the_bytes[1] + the_bytes[2] + the_bytes[3] & 255
dht11_example.py
import RPi.GPIO as GPIO
import dht11
import time
import datetime

# initialize GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

# read data using pin 14
instance = dht11.DHT11(pin=14)

while True:
    result = instance.read()
    if result.is_valid():
        print("Last valid input: " + str(datetime.datetime.now()))
        print("Temperature: %d C" % result.temperature)
        print("Humidity: %d %%" % result.humidity)

    time.sleep(1)

-3.取得したpythonプログラムを修正します。
dht11_example.pyを以下のように修正します。

dht11_example.py
# coding:utf-8
import RPi.GPIO as GPIO
import dht11
import time
import datetime
import slackweb #←追加

# initialize GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

# read data using pin 4
instance = dht11.DHT11(pin=4) #←配線したGPIOピンの番号を指定

#取得した情報をslackwebに通知する
result = instance.read()
temp = result.temperature
humidity = result.humidity
nowtime = "{0:%Y/%m/%d %H:%M:%S}".format(datetime.datetime.now())
if humidity is not None and temp is not None:
    msg = u"{0}現在の温度は{1:0.1f}度、湿度は{2:0.1f}% です".format(nowtime,temp, humidity)
else:
    msg = u"温湿度を測定できませんでした"

slack = slackweb.Slack(url="https://hooks.slack.com/services/...")#手順4-2のURLを記載する
slack.notify(text=msg)
print msg

ファイル名を変更します。(任意)

terminal
$ mv dht11_example.py notify_temperature.py

実行してこのように出力されればOKです。

terminal
$ python ./notify_temperature.py
2018/02/04 14:27:57現在の温度は18.0度、湿度は19.0% です 

##6.cronを設定する
ラズパイで定期的に起動して、温湿度を取得する場合はcronに登録します。

-1.cronを起動します。

terminal
$ sudo /etc/init.d/cron start
[ ok ] Starting cron (via systemctl): cron.service.
$ sudo apt-get install chkconfig
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
以下の追加パッケージがインストールされます:
  insserv
提案パッケージ:
  bootchart2
以下のパッケージが新たにインストールされます:
  chkconfig insserv
アップグレード: 0 個、新規インストール: 2 個、削除: 0 個、保留: 94 個。
67.5 kB のアーカイブを取得する必要があります。
この操作後に追加で 196 kB のディスク容量が消費されます。
続行しますか? [Y/n] Y
取得:1 http://ftp.tsukuba.wide.ad.jp/Linux/raspbian/raspbian stretch/main armhf chkconfig all 11.4.54.60.1debian1 [9,766 B]
取得:2 http://ftp.tsukuba.wide.ad.jp/Linux/raspbian/raspbian stretch/main armhf insserv armhf 1.14.0-5.4 [57.7 kB]
67.5 kB を 11秒 で取得しました (6,014 B/s)
以前に未選択のパッケージ chkconfig を選択しています。
(データベースを読み込んでいます ... 現在 125206 個のファイルとディレクトリがインストールされています。)
.../chkconfig_11.4.54.60.1debian1_all.deb を展開する準備をしています ...
chkconfig (11.4.54.60.1debian1) を展開しています...
以前に未選択のパッケージ insserv を選択しています。
.../insserv_1.14.0-5.4_armhf.deb を展開する準備をしています ...
insserv (1.14.0-5.4) を展開しています...
chkconfig (11.4.54.60.1debian1) を設定しています ...
man-db (2.7.6.1-2) のトリガを処理しています ...
insserv (1.14.0-5.4) を設定しています ...
$ sudo chkconfig cron
cron  on

-2.cronに登録する。
cronを編集し、以下を追記する。

terminal
$ crontab -e

毎日AM11時に起動する場合
00 11 * * * python [ディレクトリパス]/notify_temperature.py

実行するとSlackで通知がきます。
スクリーンショット 2018-03-11 15.30.35.png

##7.おわりに
無事完成できたでしょうか。私は配線のところで間違えてしまい、苦戦しました。

以上、ありがとうございました。

3
9
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
3
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?