IoTを使って畑を監視する
sparkgene
この記事は RECRUIT MARKETING PARTNERS Advent Calendar 2015 の投稿記事です。
畑を監視する?
こんにちはsparkgeneです。
最近は趣味で畑を借りて作物を育てている人が増えてきているようです。しかし趣味で畑仕事をしていると、面倒を見れるのが週末だけだったりしてちょっと予定があって畑に行けない事も出てきます。そして水が足りなくてせっかく育ててきた作物をダメにしてしまうことも多々あるようです。
こんな悩みをIotを使って畑の状態を監視して、少しでも被害を減らせるようにする仕組みを考えてみました。
この仕組みで使うモノ・サービス
- Raspberry Piとセンサー類
- AWS IoT、Amazon DynamoDB、AWS Lambda、Amazon CloudWatch
- SORACOM Air
監視に使うセンサー類
今回は以下の3種類のセンサーを利用して畑を監視してみることにします。
- 温度・湿度センサー(DHT11)
- デジタル光センサー(GY30/BH1750FVI)
- 土壌水分センサー(YL-69)
センサー類から値を収集するために利用するのは、手元にあったRaspberry Pi 1 Model Bを利用します。このRaspberry PiはOSにRaspbianの最新版(2015/12現在)であるjessieを利用しています。
pi@raspberrypi ~ $ uname -a
Linux raspberrypi 4.1.7+ #817 PREEMPT Sat Sep 19 15:25:36 BST 2015 armv6l GNU/Linux
温度と湿度の取得(DHT11)
DHT11はデジタルの温度&湿度センサーです。
adafruitからDHT系センサーのライブラリが提供されているのでこれを使います。
sudo apt-get update
sudo apt-get install build-essential python-dev
git clone https://github.com/adafruit/Adafruit_Python_DHT.git
cd Adafruit_Python_DHT
sudo python setup.py install
Adafruit_Python_DHT/example/simpletest.py
を参考に、自分の配線に合わせてPINを4
に指定したソースを書きます。
#!/usr/bin/python
import sys
import Adafruit_DHT
humidity, temperature = Adafruit_DHT.read_retry(Adafruit_DHT.DHT11, 4)
if humidity is not None and temperature is not None:
print 'Temp={0:0.1f}*C Humidity={1:0.1f}%'.format(temperature, humidity)
else:
print 'Failed to get reading. Try again!'
試しに実行してみます。
pi@raspberrypi ~ $ sudo python temp.py
Temp=19.0*C Humidity=32.0%
気温と湿度が取れました!
明るさを取得(GY30/BH1750FVI)
GY30はデジタルの光センサーです。
GY30はI2Cを使うので、Raspberry PiでI2Cが使えるように設定を変更します。
# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
snd-bcm2835
i2c-bcm2708 ←追加
i2c-dev ←追加
起動時にモジュールを自動で読み込む設定をしたので、sudo reboot
で一度再起動します。
I2Cにつながっているデバイスが簡単に見れるツールをインストールします。
sudo i2cdetect -y 1
とコマンドを打つと、つながっているIC2デバイスを確認することが出来ます。
pi@raspberrypi ~ $ sudo i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- 23 -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
光センサーのアドレスは0x23
でした。
I2Cのデバイスをpythonから使う時は、smbus
ライブラリを使うことで簡単に扱うことが出来ます。
テストプログラムを書いて実行してみます。読みだすI2Cデバイスのアドレスは先ほど調べた0x23
を指定します。
#!/usr/bin/python
import smbus
bus = smbus.SMBus(1)
addr = 0x23
luxRead = bus.read_i2c_block_data(addr,0x11)
print("Lux: "+str(luxRead[1]* 10))
pi@raspberrypi ~ $ sudo python light.py
Lux: 10.00
明るさが取れました!部屋で実行したところ、明るさは10ルクス(lux)とわかりました。
畑の水分を調べる
土壌センサー(YL-69)は2つの電極みたいのを使って、電気の通り具合でどれだけ水分を含んでいるかを判断します。どれぐらい含んでいるかは、アナログ値で取得するか、水分量が閾値を超えたら出力がINになるデジタル出力の2種類の方法で取得することが出来ます。
今回はアナログ値で取得してみたいのですが、Raspberry Piはアナログの入出力に対応していません。そこで、A/Dコンバータ(ADC)を使ってアナログ値をRaspberry Piでも取得できるようにします。
MCP3008(A/Dコンバータ)
MCP3008は10bitのADCですので、アナログ値を0〜1023までのデジタルな値に変換してくれます。今回は電源として3.3vを利用しておりVREFに3.3vを印加しているので、1023の時は電極同士が未通電(=カラカラに乾燥)と判断でき、0の時は電極同士が短絡状態(=水分が増えることで抵抗がなくなる状態)と判断することが出来ます。
MCP3008はアナログの入力をデジタル値として取得できるようになるのですが、Raspberry PiとはSPI(Serial Peripheral Interface)を使ってデジタル変換されたアナログ値を取得します。MCP3008とRaspberry Piの配線とソースはadafruitの記事を参考にしました。
配線が終わったら実際に値を取得するために必要なセットアップを行います。
adafruitのサンプルソースを参考に土壌センサーから値を取得するソースを書きます。
#!/usr/bin/env python
# Written by Limor "Ladyada" Fried for Adafruit Industries, (c) 2015
# This code is released into the public domain
import time
import os
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
# read SPI data from MCP3008 chip, 8 possible adc's (0 thru 7)
def readadc(adcnum, clockpin, mosipin, misopin, cspin):
if ((adcnum > 7) or (adcnum < 0)):
return -1
GPIO.output(cspin, True)
GPIO.output(clockpin, False) # start clock low
GPIO.output(cspin, False) # bring CS low
commandout = adcnum
commandout |= 0x18 # start bit + single-ended bit
commandout <<= 3 # we only need to send 5 bits here
for i in range(5):
if (commandout & 0x80):
GPIO.output(mosipin, True)
else:
GPIO.output(mosipin, False)
commandout <<= 1
GPIO.output(clockpin, True)
GPIO.output(clockpin, False)
adcout = 0
# read in one empty bit, one null bit and 10 ADC bits
for i in range(12):
GPIO.output(clockpin, True)
GPIO.output(clockpin, False)
adcout <<= 1
if (GPIO.input(misopin)):
adcout |= 0x1
GPIO.output(cspin, True)
adcout >>= 1 # first bit is 'null' so drop it
return adcout
# change these as desired - they're the pins connected from the
# SPI port on the ADC to the Cobbler
SPICLK = 18
SPIMISO = 23
SPIMOSI = 24
SPICS = 25
# set up the SPI interface pins
GPIO.setup(SPIMOSI, GPIO.OUT)
GPIO.setup(SPIMISO, GPIO.IN)
GPIO.setup(SPICLK, GPIO.OUT)
GPIO.setup(SPICS, GPIO.OUT)
# adc #0
adc_pin = 0;
while True:
# read the analog pin
moisture = readadc(adc_pin, SPICLK, SPIMOSI, SPIMISO, SPICS)
print("moisture: {0}".format(moisture))
time.sleep(1)
adc_pin = 0;
としているのはMCP3008のチャンネル0を利用しているからです。チャンネルは0〜7まであるので1つのチップで8個のアナログ入力に対応できます。
では、実行してみましょう。
pi@raspberrypi ~ $ sudo python moisture.py
moisture: 1023
moisture: 1023
moisture: 984 ←ウエットティッシュで電極同士を触ってみる
moisture: 760
moisture: 697
moisture: 659
moisture: 651
moisture: 647
moisture: 655
moisture: 657
moisture: 661
moisture: 1022 ←ウエットティッシュを離した
moisture: 1023
moisture: 1023
水分の状態で、数値が変化するのが確認できました!
取得したデータをサーバに上げる
リモートから監視するのでセンサーから取得したデータをサーバに保存する必要があるのですが、10月のre:Inventで発表されたAWS Iotを利用して各センサーから取得したデータを処理したいと思います。
デバイス(Thing)の作成
画面上部のCreate resource
をクリックすると、作成できるリソースの一覧が表示されますので、create a thing
を選択します。
名称を付けてCreate
ボタンをクリックすると、リソースの一覧に作成したデバイスが表示されます。
証明書を作成
AWS Iotでは証明書をデバイスにインストールしておくことで、デバイスの認証を行います。
作成するとダウンロードリンクが表示されるので、ダウンロードします。
デバイスと同じく作成すると、リソースのところに証明書が表示されます。今の状態だとinactive
になっているので、証明書を選択して右上のメニューからActivate
を選んで、有効にします。
ステータスがActive
になりました。
ポリシーを作成
デバイスに対して、AWS IoTの各種操作を許可するためのポリシーを作成します。
Action | ポリシーで許可する操作はiot関連全て |
Resource | 許可対象のリソースは全て |
各種リソースを紐付ける
証明書とポリシーのひも付けをするので、証明書を選択し、右上のメニューからAttach a Policy
を選びます。
確認のダイアログが表示されるので、先ほど作成したポリシーの名前を入力します。
次にデバイスと証明書のひも付けを行います。先ほどと同じように証明書を選択した状態にして右上のメニューからAttach a thing
を選びます。
確認のダイアログで最初に追加したデバイスの名前を入力します。
ルールを作成
画面上部のCreate a resource
をクリックし、リソースの中からCreate a Rule
をクリックします。
RuleではIoTデバイスから送られてくるデータをSQLライクな言語で抽出することが出来ます。これにより、色んな種類のデバイスから色んなデータが送られてきても、必要なデータのみを取り出しすことが出来ます。
Attribute | 取り出すデータ |
Topic Filter | デバイスで指定されたトピックを指定 |
Choose an action
でinsert message into database table(DynamoDB)
を選択すると、DynamoDBへのマッピング情報の入力が求められるので、入力します。
Hash Key Value | ${deviceid} |
Range Key Value | ${timestamp} |
${deviceid}
はJSONデータのキーを意味していて、送信されてくるデータから値を取得してDynamoDBに格納します。AWS IoT SQL、JSONの参照についてはこちらを参照してください。
Role Name
は無ければCreate a new role
から新しくRoleを作成します。入力が全て終わったらAdd a action
でアクションを追加します。
データを送信する
AWS IoTのクライアントライブラリはC、nodejs、Arduinoが用意されていますので、今回はnodejsを使います。
必要なライブラリをインストール。
sudo apt-get install nodejs
sudo apt-get install npm
mkdir /home/pi/pi_farm
cd /home/pi/pi_farm
npm install aws-iot-device-sdk
AWS IoTで作成した証明書をRaspberry Piに保存します。
pi@raspberrypi ~/ $ cd /home/pi/pi_farm
pi@raspberrypi ~/pi_farm $ mkdir certs
pi@raspberrypi ~/pi_farm $ ll certs/
total 12
-rw-r--r-- 1 pi pi 1221 Dec 6 15:01 cert.pem
-rw-r--r-- 1 pi pi 1675 Dec 6 14:59 private.pem
-rw-r--r-- 1 pi pi 1732 Dec 6 14:17 rootca.crt
rootca.crt
は、こちらのページ内にあるリンクからダウンロードしたものを利用します。デバイスからAWS IoTに接続できない場合は、このページに書かれている方法で検証すると、証明書が正しいかを確認することが出来ます。
サンプルソースからcollector.py
とiot_client.js
をダウンロードします。
センサーの値を取得するスクリプトを実行
pi@raspberrypi ~/pi_farm $ sudo python collector.py
2015-12-06 07:46:42,27.0,21.0,52.50,1023
タイムスタンプ、気温、湿度、明るさ、水分
をカンマ区切りで取得します。
iot_client.js
を実行してAWS Iotにデータを送ります。
pi@raspberrypi ~/pi_farm $ sudo nodejs iot_client.js
Connected to Message Broker.
Publish: {"deviceid":"pi_01","timestamp":"2015-12-06 07:48:26","temp":"27.0","hum":"20.0","lx":"52.50","moi":"1023"}
Publish: {"deviceid":"pi_01","timestamp":"2015-12-06 07:48:36","temp":"28.0","hum":"20.0","lx":"51.67","moi":"1023"}
Publish: {"deviceid":"pi_01","timestamp":"2015-12-06 07:48:49","temp":"28.0","hum":"20.0","lx":"52.50","moi":"1023"}
Publish: {"deviceid":"pi_01","timestamp":"2015-12-06 07:48:56","temp":"27.0","hum":"20.0","lx":"51.67","moi":"1023"}
Publish: {"deviceid":"pi_01","timestamp":"2015-12-06 07:49:06","temp":"28.0","hum":"20.0","lx":"51.67","moi":"1023"}
無事に送れました。AWS側を見てみます。
AWS Iotにデータが送られるとCloudWatch Logsにログが吐かれます。もしデータが登録されない場合は、このログを見て原因を調査します。
DynamoDBを確認すると、正しくデータが登録されていることが確認できました。
ネットワーク環境を整える
会社や家ではWi-Fiなどネットワーク環境が整っていますが、当然畑にはWi-Fi環境はありません。その為、Raspberry Piからデータを送るために、ポケットWi-Fiや3Gのモデムを必要とします。今回はSORACOM Airのsimと3G USBドングル FS01BUを使って、Raspberry Piをインターネットに繋ぎます。
こちらを参考にRaspberry PiでSF01BUが使えるようにする為の設定を行いました。設定が終わったら、動作確認を行います。
pi@raspberrypi ~/pi_farm $ sudo wvdial
--> WvDial: Internet dialer version 1.61
--> Initializing modem.
--> Sending: ATZ
ATZ
OK
--> Sending: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
OK
--> Sending: AT+CGDCONT=1,"IP","soracom.io"
AT+CGDCONT=1,"IP","soracom.io"
OK
--> Modem initialized.
--> Sending: ATD*99***1#
--> Waiting for carrier.
ATD*99***1#
CONNECT 14400000
--> Carrier detected. Starting PPP immediately.
--> Starting pppd at Sun Dec 6 18:33:30 2015
--> Pid of pppd: 26727
--> Using interface ppp0
--> pppd: �Q��*�[01]�*�[01]
--> pppd: �Q��*�[01]�*�[01]
繋がりました!
自動起動の設定
センサーから値を取得しAWSへ送信するプログラムと、モデムを起動する準備ができたので、SSHでログインしてコマンドを叩いて実行するのではなくそれぞれを自動起動できるように設定します。自動起動にはsupervisordを使います。
センサー取得スクリプトの自動実行を設定します。
[program:pi_farm]
directory=/home/pi/pi_farm
command=/usr/bin/nodejs /home/pi/pi_farm/iot_client.js
redirect_stderr=true
stdout_logfile=/var/log/pi_farm.log
user=root
soracomへの接続はこちらで公開されているスクリプトを利用します。
git clone https://gist.github.com/j3tm0t0/65367f971c3d770557f3 soracom
mv soracom /opt/
ダイアルアップの自動接続
[program:soracom]
directory=/opt/soracom
command=sh /opt/soracom/connect_air.sh
redirect_stderr=true
stdout_logfile=/var/log/soracom.log
user=root
両方の設定でlogをファイルに出力する設定としていますが、実際に使う場合は長期間稼働することを考えると、ストレージがいっぱいになってしまう可能性があるので、最低限の内容に抑えるか、出力させない様にした方がいいと思います。
supervisordでそれぞれを有効にします。
pi@raspberrypi ~/pi_farm $ sudo supervisorctl reread
pi_farm: available
soracom: available
pi@raspberrypi ~/pi_farm $ sudo supervisorctl add pi_farm
pi_farm: added process group
pi@raspberrypi ~/pi_farm $ sudo supervisorctl add soracom
soracom: added process group
pi@raspberrypi ~/pi_farm $ sudo supervisorctl status
pi_farm RUNNING pid 1574, uptime 0:00:38
soracom RUNNING pid 1593, uptime 0:00:31
pi@raspberrypi ~/pi_farm $ ps axufww
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 760 0.1 2.6 16296 10168 ? Ss 20:42 0:03 /usr/bin/python /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
root 1574 10.6 4.9 82620 18704 ? Sl 21:16 0:05 \_ /usr/bin/nodejs /home/pi/pi_farm/iot_client.js
root 1593 0.3 0.3 1884 1148 ? S 21:16 0:00 \_ sh /opt/soracom/connect_air.sh
root 1615 0.4 1.1 9772 4212 ? S 21:16 0:00 \_ wvdial
root 1616 0.0 0.5 3792 2144 ? S 21:16 0:00 \_ /usr/sbin/pppd 460800 modem crtscts defaultroute usehostname -detach user so
これで、Raspberry Piの電源をいれるだけで、ネットワークへ接続し収集したデータをアップロードします。
実際に監視してみる
一通り動くものが出来上がったので、実際にプランター畑を監視してみましょう。
先程はDynamoDBにデータが登録されるのを確認しましたが、それだと視認性が悪いのでCloudWatchを使ってビジュアライズします。やり方としてはDynamoDB StreamsのイベントでLambdaが実行できるので、これを使ってDynamoDBに登録されたデータをLambda経由でCloudWatchに登録するようにします。
AWS IoTにLambdaと連携するactionを追加することで同じことが実現できますが、DynamoDB streamを使ってみたかったので上記の方法になっています。データとして蓄積する必要が無い場合は、DynamoDBに入れずにLambda経由でCloudWatchに登録するほうが楽かもしれない。
DynamoDBのStreamを編集
DynamoDBのテーブルを選択するとStreamの設定ができるのでManage Stream
をクリックします。
Lambdaファンクションを作成
テンプレートの中からdynamodb-process-stream-python
を選択します。
DynamoDB table
には作成したテーブル名を指定し、Starting position
では時系列順に処理するので古いものから順に読み取る「Trim horizon」を指定します。
Lambdaファンクションのソースにはサンプルソースのlambda_function.py
を貼り付けて保存します。
しばらく待ってると…
センサーから読み取った数値がグラフとして見るようになりました。CloudWatchのDashboardを作ると、見たいグラフだけを綺麗に並べることが出来ます。
ちなみにこのグラフは朝の6時頃から昼ぐらいまでのデータなので、tempture(気温)とlux(明るさ)の数値が上がっていくに連れて、土の水分量が変わっていくのが確認できると思います。
まとめ
AWSのマネージドサービスを利用することでサーバを立てることなく、センサーから取得したデータをグラフするとこまで出来ました。サーバ立てないで済むということはそれだけ運用コストも削減できるので非常に便利ですね。CloudWatchを使っているのでセンサーから取得した値に対してアラームも設定することが出来、乾燥していると判断したらSNS経由でメールを送って通知することも可能です。
また、LambdaでCloudWatchに書き込むとともにS3にデータを保存しておくと、Amazon Machine Learningで機械学習を行うことも可能で、乾き過ぎ
とか水多すぎて根腐れをしてしまう
と言った事もデータの傾向で知ることも出来ると思います。
SORACOMのsim+USBドングルもすごく便利でした。Raspberry Piの3Gシールドは結構高いのですが、この組み合わせだと他でも使えそうですし、費用も抑えられます。SORACOMの画面で通信料が見られるのですが、1時間毎のグラフもあったりして分かりやすいです。
見えてきた課題
実際に作り込むことで見えてきた課題としては、以下のものがありました。
電源問題
畑にはWi-Fiも無いですが、そもそも電源も無いことが多いです。今回作ったものを実際の畑で使う為には、電源を用意する必要があります。今考えているのはソーラーパネルとバッテリーを組み合わせたシステムの導入です(1万円ぐらいで揃いそう)。また、今回はスクリプトを実行しっぱなしにし、3G回線も繋ぎっぱなしのスクリプトを書きましたが、消費電力や通信費を考えると、1時間に1回取得したデータを一気にアップロードするといった、やり方も必要かと思います。
ちなみに、全部動作している状態の消費電力は2.2Wぐらい
畑は広い
実際の畑は広いです。土壌センサーを複数の場所に設置しないと、全体的な状態を見ることが出来ません。今回のA/Dコンバータはあと7つのセンサーを接続することが出来ますが、遠くのセンサーまで線を張り巡らすのは現実的ではありません。なぜなら、畑は耕したりするので配線がじゃまになります。とはいえセンサー毎に今回のセットを用意するとなると、コストがスゴイ掛かってしまいます。何らかの方法でセンサーとゲートウェイ(Raspberry Pi)を繋ぐ必要が出てきます。そして、ここでも電源の問題が出てきます。
遠隔地からの監視
畑が遠くにあった場合、何かあったとしても簡単にはSSHで入って何かするといった事ができません。このような場合は、スクリプトを自動でアップデートする仕組みとか、再起動させる仕組みとかを入れる必要があります。幸いなことに、AWS IoTにはDevice Shadowというものがあり、たとえデバイスがネットワークから一時的に切り離されたとしても(例えば電源切れとか)、こちらから送ったステータスを保持しておいて、デバイスが再度繋がった時にステータスを見てgit cloneして再起動させるといった事もできます。
付録
参考までに、今回使った物の値段は以下のようになっています。
商品 | 値段 |
Raspberry Pi Type B | 4,800 |
ブレッドボードミニ | 200 |
温度湿度センサー(DHT11) | 400 |
光センサー(GY30) | 300 |
土壌センサー(YL-69) | 800 |
A/Dコンバータ(MCP3008) | 500 |
3G USBドングル(FS01BU) | 9,000 |
SORACOM sim | 900 |
合計 | 16,900 |
---|