Edited at

Raspberry Pi 3+DHT22 で気温を通知するLINEbotを作る②

More than 1 year has passed since last update.

Raspberry Pi 3+DHT22 で気温を通知するLINEbotを作る①

前記事の続き。

部屋の気温を聞いたら返ってくるLINEbotを作ります!

前記事ではオウム返しするLINEbotを作成することができました。

今回はそのLINEbotを改良し部屋の気温を通知してもらいましょう!


気温センサの設定

気温センサ(DHT22)とRaspberry Piを使って部屋の気温を計測します。


RPi.GPIOをインストール

RPi.GPIOはpythonを使ってRaspberry PiのGPIOピン制御できるモジュールです。

$ sudo apt-get install python-rpi.gpio


pythonのパッケージをインストール

$ sudo apt-get install build-essential python-dev


DHTライブラリをインストール

$ sudo git clone https://github.com/adafruit/Adafruit_Python_DHT.git

$ cd Adafruit_Python_DHT
$ sudo python setup.py install


温度センサとRaspberry Piを接続

DHT22とラズパイを以下のように接続します!

抵抗値は大幅にずれなければそこまで気にする必要はなさそうです。(測定される温度に若干誤差が生じるかもしれませんが)

DHT22.png


計測用プログラム作成

ファイル名は「dht22.py」として作成してください。保存先はAdafruit_python_DHTがあるフォルダに入れておきましょう。


dht22.py

#!/usr/bin/python

# coding: utf-8
import Adafruit_DHT as DHT

## センサーの種類
SENSOR_TYPE = DHT.DHT22

## 接続したGPIOポート
DHT_GPIO = 22

## 測定開始
h,t = DHT.read_retry(SENSOR_TYPE, DHT_GPIO)

## 結果表示
print "現在の部屋の気温は{0:0.1f} 度です。" . format(t)



プログラムを実行する

先ほどのプログラムのパーミッションを設定し、実行します。

ラズパイのターミナルを開いて以下を入力します。

$ chmod 755 dht22.py

$ ./dht22.py

実行すると、以下のような結果が表示されると思います。

現在の部屋の気温は25.0度です。

これで、温度センサから部屋の気温を所得することができました!やったね!


app.pyを改良する

ここまでで、(オウム返し)LINEbotができて温度センサから温度を計測できるようになりました。

これを組み合わせることで、気温を通知してくれるLINEbotが完成します!

app.pyから外部コマンド(dht22.py)を実行するためにsubprocessをインストールします。

$ sudo pip install subprocess

app.pyを以下のように書き換えてdht22.pyがあるフォルダに保存しておきましょう。

import subprocessをするのを忘れずに。


app.py

# -*- coding: utf-8 -*-


# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from __future__ import unicode_literals

import errno
import os
import sys
import subprocess #追加
import tempfile
from argparse import ArgumentParser

from flask import Flask, request, abort

from linebot import (
LineBotApi, WebhookHandler
)
from linebot.exceptions import (
InvalidSignatureError
)
from linebot.models import (
MessageEvent, TextMessage, TextSendMessage,
SourceUser, SourceGroup, SourceRoom,
TemplateSendMessage, ConfirmTemplate, MessageTemplateAction,
ButtonsTemplate, ImageCarouselTemplate, ImageCarouselColumn, URITemplateAction,
PostbackTemplateAction, DatetimePickerTemplateAction,
CarouselTemplate, CarouselColumn, PostbackEvent,
StickerMessage, StickerSendMessage, LocationMessage, LocationSendMessage,
ImageMessage, VideoMessage, AudioMessage, FileMessage,
UnfollowEvent, FollowEvent, JoinEvent, LeaveEvent, BeaconEvent
)

app = Flask(__name__)

# get channel_secret and channel_access_token from your environment variable
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
if channel_secret is None:
print('Specify LINE_CHANNEL_SECRET as environment variable.')
sys.exit(1)
if channel_access_token is None:
print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
sys.exit(1)

line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)

static_tmp_path = os.path.join(os.path.dirname(__file__), 'static', 'tmp')

# function for create tmp dir for download content
def make_static_tmp_dir():
try:
os.makedirs(static_tmp_path)
except OSError as exc:
if exc.errno == errno.EEXIST and os.path.isdir(static_tmp_path):
pass
else:
raise

@app.route("/callback", methods=['POST'])
def callback():
# get X-Line-Signature header value
signature = request.headers['X-Line-Signature']

# get request body as text
body = request.get_data(as_text=True)
app.logger.info("Request body: " + body)

# handle webhook body
try:
handler.handle(body, signature)
except InvalidSignatureError:
abort(400)

return 'OK'

@handler.add(MessageEvent, message=TextMessage)
def handle_text_message(event):
text = event.message.text

if text == 'profile':
if isinstance(event.source, SourceUser):
profile = line_bot_api.get_profile(event.source.user_id)
line_bot_api.reply_message(
event.reply_token, [
TextSendMessage(
text='Display name: ' + profile.display_name
),
TextSendMessage(
text='Status message: ' + profile.status_message
)
]
)
else:
line_bot_api.reply_message(
event.reply_token,
TextMessage(text="Bot can't use profile API without user ID"))
elif text == 'bye':
if isinstance(event.source, SourceGroup):
line_bot_api.reply_message(
event.reply_token, TextMessage(text='Leaving group'))
line_bot_api.leave_group(event.source.group_id)
elif isinstance(event.source, SourceRoom):
line_bot_api.reply_message(
event.reply_token, TextMessage(text='Leaving group'))
line_bot_api.leave_room(event.source.room_id)
else:
line_bot_api.reply_message(
event.reply_token,
TextMessage(text="Bot can't leave from 1:1 chat"))
elif text == 'confirm':
confirm_template = ConfirmTemplate(text='Do it?', actions=[
MessageTemplateAction(label='Yes', text='Yes!'),
MessageTemplateAction(label='No', text='No!'),
])
template_message = TemplateSendMessage(
alt_text='Confirm alt text', template=confirm_template)
line_bot_api.reply_message(event.reply_token, template_message)
elif text == 'buttons':
buttons_template = ButtonsTemplate(
title='My buttons sample', text='Hello, my buttons', actions=[
URITemplateAction(
label='Go to line.me', uri='https://line.me'),
PostbackTemplateAction(label='ping', data='ping'),
PostbackTemplateAction(
label='ping with text', data='ping',
text='ping'),
MessageTemplateAction(label='Translate Rice', text='米')
])
template_message = TemplateSendMessage(
alt_text='Buttons alt text', template=buttons_template)
line_bot_api.reply_message(event.reply_token, template_message)
elif text == 'carousel':
carousel_template = CarouselTemplate(columns=[
CarouselColumn(text='hoge1', title='fuga1', actions=[
URITemplateAction(
label='Go to line.me', uri='https://line.me'),
PostbackTemplateAction(label='ping', data='ping')
]),
CarouselColumn(text='hoge2', title='fuga2', actions=[
PostbackTemplateAction(
label='ping with text', data='ping',
text='ping'),
MessageTemplateAction(label='Translate Rice', text='米')
]),
])
template_message = TemplateSendMessage(
alt_text='Carousel alt text', template=carousel_template)
line_bot_api.reply_message(event.reply_token, template_message)
elif text == 'image_carousel':
image_carousel_template = ImageCarouselTemplate(columns=[
ImageCarouselColumn(image_url='https://via.placeholder.com/1024x1024',
action=DatetimePickerTemplateAction(label='datetime',
data='datetime_postback',
mode='datetime')),
ImageCarouselColumn(image_url='https://via.placeholder.com/1024x1024',
action=DatetimePickerTemplateAction(label='date',
data='date_postback',
mode='date'))
])
template_message = TemplateSendMessage(
alt_text='ImageCarousel alt text', template=image_carousel_template)
line_bot_api.reply_message(event.reply_token, template_message)
############################################################################################
#ここから
############################################################################################
elif text == '気温':
proc = subprocess.Popen(['python','dht22.py'],stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
temp = proc.stdout.readline()
line_bot_api.reply_message(
event.reply_token, TextSendMessage(text=temp))
############################################################################################
#ここを追加
############################################################################################
elif text == 'imagemap':
pass
else:
line_bot_api.reply_message(
event.reply_token, TextSendMessage(text=event.message.text))

@handler.add(MessageEvent, message=LocationMessage)
def handle_location_message(event):
line_bot_api.reply_message(
event.reply_token,
LocationSendMessage(
title=event.message.title, address=event.message.address,
latitude=event.message.latitude, longitude=event.message.longitude
)
)

@handler.add(MessageEvent, message=StickerMessage)
def handle_sticker_message(event):
line_bot_api.reply_message(
event.reply_token,
StickerSendMessage(
package_id=event.message.package_id,
sticker_id=event.message.sticker_id)
)

# Other Message Type
@handler.add(MessageEvent, message=(ImageMessage, VideoMessage, AudioMessage))
def handle_content_message(event):
if isinstance(event.message, ImageMessage):
ext = 'jpg'
elif isinstance(event.message, VideoMessage):
ext = 'mp4'
elif isinstance(event.message, AudioMessage):
ext = 'm4a'
else:
return

message_content = line_bot_api.get_message_content(event.message.id)
with tempfile.NamedTemporaryFile(dir=static_tmp_path, prefix=ext + '-', delete=False) as tf:
for chunk in message_content.iter_content():
tf.write(chunk)
tempfile_path = tf.name

dist_path = tempfile_path + '.' + ext
dist_name = os.path.basename(dist_path)
os.rename(tempfile_path, dist_path)

line_bot_api.reply_message(
event.reply_token, [
TextSendMessage(text='Save content.'),
TextSendMessage(text=request.host_url + os.path.join('static', 'tmp', dist_name))
])

@handler.add(MessageEvent, message=FileMessage)
def handle_file_message(event):
message_content = line_bot_api.get_message_content(event.message.id)
with tempfile.NamedTemporaryFile(dir=static_tmp_path, prefix='file-', delete=False) as tf:
for chunk in message_content.iter_content():
tf.write(chunk)
tempfile_path = tf.name

dist_path = tempfile_path + '-' + event.message.file_name
dist_name = os.path.basename(dist_path)
os.rename(tempfile_path, dist_path)

line_bot_api.reply_message(
event.reply_token, [
TextSendMessage(text='Save file.'),
TextSendMessage(text=request.host_url + os.path.join('static', 'tmp', dist_name))
])

@handler.add(FollowEvent)
def handle_follow(event):
line_bot_api.reply_message(
event.reply_token, TextSendMessage(text='Got follow event'))

@handler.add(UnfollowEvent)
def handle_unfollow():
app.logger.info("Got Unfollow event")

@handler.add(JoinEvent)
def handle_join(event):
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text='Joined this ' + event.source.type))

@handler.add(LeaveEvent)
def handle_leave():
app.logger.info("Got leave event")

@handler.add(PostbackEvent)
def handle_postback(event):
if event.postback.data == 'ping':
line_bot_api.reply_message(
event.reply_token, TextSendMessage(text='pong'))
elif event.postback.data == 'datetime_postback':
line_bot_api.reply_message(
event.reply_token, TextSendMessage(text=event.postback.params['datetime']))
elif event.postback.data == 'date_postback':
line_bot_api.reply_message(
event.reply_token, TextSendMessage(text=event.postback.params['date']))

@handler.add(BeaconEvent)
def handle_beacon(event):
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(
text='Got beacon event. hwid={}, device_message(hex string)={}'.format(
event.beacon.hwid, event.beacon.dm)))

if __name__ == "__main__":
arg_parser = ArgumentParser(
usage='Usage: python ' + __file__ + ' [--port <port>] [--help]'
)
arg_parser.add_argument('-p', '--port', type=int, default=8000, help='port')
arg_parser.add_argument('-d', '--debug', default=False, help='debug')
options = arg_parser.parse_args()

# create tmp dir for download content
make_static_tmp_dir()

app.run(debug=options.debug, port=options.port)



app.pyを実行

前回のLINEbotと同様にapp.pyを実行してLINEbotを立ち上げます。

$ python app.py


最終動作確認

LINEbotに「気温」と送ってみましょう!

返ってきました!!!!

ラズパイをずっとつけておけば、いつでもどこでもLINEbotから部屋の気温を聞くことができます。


最後に

IoTイイネ!

app.pyを改良すれば色んなことができそうですね('ω')!

気温を聞くだけじゃ物足りないので次はエアコンをつけるLINEbotを作成しようと思います!