あらまし
前回の記事でRaspberry Piにリモートログインができるようになったところから、
本格的にRaspberry Piらしいことをしていこうと思います。
今回は、Raspberry PiでI2C接続のセンサから測定値を読み取る方法と、
センサから読み取った測定値をチャットサービス(Discord)に投稿するボットを作る方法を説明します。
I2C環境センサについて
I2Cとは、Wikipediaの記事にあるように1つのコントローラ(マイコンなど)と、1個または多数の周辺機器を接続する
シリアル式のバス接続規格です。
I2Cは「アイ・スクェアド・シー」または「アイ・トゥ・シー」と読まれることが多いです。
今回使うI2C接続のセンサとして、Boshe BME680環境センサを購入しました。
このセンサでは温度と湿度、気圧の測定が行えます。
秋月電子の通販で買ったものではセンサ素子の乗った基板とブレッドボードに挿せるピンヘッダが付属していましたが、
自分でピンヘッダを基板にはんだ付けする必要がありました。
(はんだ付けの技術については、本記事では割愛します。)
Raspberry PiでI2Cを有効化する
Raspberry PiのI2Cはデフォルトで無効になっていますので、有効化の設定を行います。
まず、aptパッケージマネージャでi2c-toolsパッケージをインストールします。
sudo apt install i2c-tools
次に、raspi-configツールからi2cの設定を有効にします。
sudo raspi-config
raspi-configのメニュー画面から、矢印キーで「5 Interfacing Options」を選択してEnterキーを押します。
Interfacing Optionsのメニューに入るので、「P5 I2C」を選択してEnterキーを押します。
「はい」を選択します。
I2Cが有効化された旨が表示されます。raspi-configのメニューから「Finish」でメニューを閉じて次に進みます。
Raspberry Piにセンサを接続
まず、Raspberry Piの電源を切ります。
sudo shutdown -h now
コマンドを実行し、しばらくしたら電源用USBケーブルをRaspberry Pi本体から引き抜きます。
Raspberry Piのピンヘッダに、環境センサの各端子をジャンパワイヤで接続します。
Raspberry Pi 環境センサ
+5V (2) --- VIN (1) (※ カッコ内はピン番号)
SCL (5) --- SCL (2)
SDA (3) --- SDA (3)
GND (6) --- GND (4)
接続ができたら、Raspberry Piに電源用USBを挿入して起動します。
Raspberry Piが起動したら、PCからSSHでRaspberry Piにログインします。
センサの動作確認
再起動とログインができたら、i2cdetectコマンドを実行します。
以下のように「77」(BME680のI2Cアドレス) が表示されればセンサが接続できています。
$ sudo i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- 77
続いて、Pythonプログラムからセンサの値を読み取ってみましょう。
git clone https://github.com/pimoroni/bme680-python.git
cd bme680-python/examples
sudo pip3 install bme680
python3 read-all.py
以下のようにセンサから取得した値が表示されれば成功です。
(出力は一例です。数値はセンサの物理的環境によって異なります。)
$ python3 read-all.py
read-all.py - Displays temperature, pressure, humidity, and gas.
Press Ctrl+C to exit!
(中略)
Polling:
25.25 C,1014.70 hPa,70.11 %RH
25.26 C,1014.71 hPa,70.07 %RH,3712.5851520667234 Ohms
25.30 C,1014.73 hPa,70.00 %RH,6040.203089121114 Ohms
25.33 C,1014.71 hPa,69.91 %RH,8579.990372223523 Ohms
25.37 C,1014.70 hPa,69.83 %RH,11141.141109863216 Ohms
キー割り込み(Ctrl-c)操作でスクリプトの実行を停止します。
ここまで出来たら、いよいよI2Cセンサの測定値をつぶやくDiscordボットの作成に入ります。
Discordボットの作成 : 自分専用サーバーの作成とボットの登録
Discordボットを動かすための、自分専用のサーバーを作ります。
まず、Discordアプリを開いて「サーバーを追加」を選択します。
「サーバーを作成」画面で「Create my own」を選びます。
サーバー名を入力し、「新規作成」を押します。
Discordサーバーにボットを招待する
あとは、新しく作ったサーバーにボットを招待します。
discord.pyの公式サイトにボットのアカウント作成方法がありますので、ページに示された手順に従って
ボットのOAuth設定と、ボットのサーバーに対する権限の設定を行います。
権限を適切に設定しないと、ボットがサーバーに投稿しない、もしくはボットがユーザーの投稿に反応できないことがありますので注意しましょう。
Discordのサイトの、ボットの設定ページ内にあるボットのトークンを控えておきます。
また、ボットが投稿するためのチャンネルを作成し、作成したチャンネルのIDを控えておきます。
チャンネルのIDはこちらに記された手順で確認できます。
DiscordボットのためのPythonの設定
venvによるPythonの仮想環境を作成し、その中でPIPパッケージのインストールを行います。
まず、作業用ディレクトリを作成します。
mkdir ~/work/discordbot
cd ~/work/discordbot
作業用ディレクトリに入ったら、venvコマンドでPythonの仮想環境を作ります。
python3 -m venv .
source bin/activate
activateスクリプトを呼び出すとプロンプトの形が変わります。
この状態で、pipコマンドでパッケージを導入します。
導入するパッケージはbme680 (I2C環境センサの制御)と、discord.py (Discordボット用ライブラリ)です。
pip install bme680
pip install discord.py
「.env」隠しファイルを作成し、その中にボットのトークンと投稿先チャネルのIDを含めます。
touch .env
echo BOT_TOKEN=*********************** >> .env
echo SANDBOX_CHANNEL_ID=******** >> .env
.envファイルはくれぐれもリポジトリにコミットしないようにしましょう。
Gitを使っていれば、.gitignoreファイルに「.env」を追加します。
echo .env >> .gitignore
ボットの実装
ボットをPythonプログラム (example_bot.py)で実装します。
import discord
from discord.ext import tasks
import bme680tph
# 環境設定 (.env ファイル) 用
import os
from os.path import join, dirname
from dotenv import load_dotenv
@client.event
async def on_ready():
print ('We have logged in as {0.user}'.format(client))
await client.get_channel(sandbox_channel_id).send('こんにちは')
@client.event
async def on_message(message):
print('on_message:{0}'.format(message.content));
if message.author.bot:
return
if message.content.startswith('$hello'):
await message.channel.send('よぅ!')
if message.content.startswith('$室温'):
await message.channel.send(bme680tph.get_sensor())
if __name__ == "__main__":
load_dotenv(verbose=True)
sandbox_channel_id = int(os.environ.get("SANDBOX_CHANNEL_ID"))
bot_token = os.environ.get("BOT_TOKEN")
client = discord.Client()
client.run(bot_token)
見ての通り、プログラムは、「@client.event」デコレータがつけられた2つの関数と
「if __name__=="__main__":」ブロックからなります。
on_ready関数はdiscord.pyで作成したclient(ボット)がDiscordサーバーに正常に接続できた時に呼び出され、
on_message関数はclientが接続しているサーバーに誰かが投稿した時に呼び出されるコールバック関数です。
「if __name__=="__main__":」直後の3行は.envファイルから設定(投稿先のチャンネルIDとボットのトークン)
を読み出す処理です。
最後の2行でclientを作成し、ボットの動作を開始しています。
センサーから気温、湿度と気圧を取得する手続きを記した
bme680tph.py ファイルを作成し、example_bot.pyと同じディレクトリに置きます。
このbme680tph.pyはpimoroni氏の提供によるライブラリ(PyPI bme680モジュール) を使用して作成しています。
import bme680
try:
sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY)
except IOError:
sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY)
# These oversampling settings can be tweaked to
# change the balance between accuracy and noise in
# the data.
sensor.set_humidity_oversample(bme680.OS_2X)
sensor.set_pressure_oversample(bme680.OS_4X)
sensor.set_temperature_oversample(bme680.OS_8X)
sensor.set_filter(bme680.FILTER_SIZE_3)
def get_sensor():
if sensor.get_sensor_data():
output = """温度 : {0:.2f} [°C],
気圧 : {1:.2f} [hPa],
湿度 : {2:.3f} [%RH]""".format(
sensor.data.temperature,
sensor.data.pressure,
sensor.data.humidity)
return(output)
このプログラムを実行し、Discordアプリからボットの投稿用チャネルを開いて
「こんにちは」と投稿されていることを確認します。
python3 example_bot.py
また、「$hello」と投稿して、ボットが「よぅ!」と返事すること、
「$室温」を投稿して、ボットが気温と湿度、気圧を応答することを確認します。
もしボットによる投稿が確認できなかったら、ボットを起動したターミナルにエラー出力がないか確認します。
(なお私はon_message関数のスペルを1文字間違えたためにボットが応答せず、数時間もの間苦悶の表情を浮かべて「どおしてだよおぉぉ」と唸っていました。)
動作確認ができたら一回ボットを停止します。ボットはキーボード割り込み(Ctrl-c)で停止します。
一定時間ごとに定期投稿する
次に、ボットに手続きを追加して、1時間ごとに気温と湿度、気圧を投稿するようにします。
example_bot.pyに新しく関数loopを追加します。
@tasks.loop(seconds=60)
async def loop():
# 現在の時刻 : 00分のときのみセンサの値を送信
print(datetime.now().strftime('%H:%M'))
if datetime.now().strftime('%M') == '00':
await client.get_channel(sandbox_channel_id).send('定期送信\n'+bme680tph.get_sensor())
関数loopに付けられたtasks.loopデコレータは、loop関数を一定時間ごとに実行するために付けています。
毎時00分に、bme680tph.pyの関数を呼び出して気温と湿度、気圧を投稿します。
続いて、clientの開始直後にloop関数の周期実行を開始させます。
example_bot.pyの末尾にloop.start()を追加します。
client = discord.Client()
client.run(bot_token)
loop.start()
定期実行が正しく行われれば、1時間ごとに「定期送信」で始まる投稿がされるようになります。
まとめ
第1回と本記事で、以下の内容を取り上げました。
第1回
- Raspberry Pi OSについて
- Raspberry Pi OSイメージのダウンロードと検証
- Raspberry Pi OSイメージのmicroSDへの書き込み
- Raspberry Piの起動
- Raspberry Piのネットワークログイン設定
- マルチキャストDNSの導入
- SSHの有効化
第2回
- I2C接続のセンサについて
- I2C接続のセンサをPythonプログラムから使う
- Discord.pyによるボットの作成
- ボットが定期投稿を行うようにする
- Discordサーバーの新規作成とボットの招待
次回はやるかどうか未定ですが、出来たらHeroku上のカスタムサーバに
センサの測定値を定期的に送信して、気温・湿度と気圧のグラフをWebブラウザから確認できるようにしたい
などと考えております。